| OLD | NEW |
| (Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 import os |
| 7 import shutil |
| 8 import ctypes |
| 9 import urllib |
| 10 import socket |
| 11 import httplib |
| 12 import _winreg |
| 13 import tempfile |
| 14 import platform |
| 15 import urlparse |
| 16 import subprocess |
| 17 |
| 18 _PLATFORM = platform.system().lower() |
| 19 _CSIDL_COMMON_APPDATA = 28 |
| 20 _CSIDL_PROGRAM_FILESX86 = 42 |
| 21 |
| 22 # Import only on Windows. On other platforms it will invoke a ValueError. |
| 23 if _PLATFORM == 'windows': |
| 24 from ctypes import wintypes, windll |
| 25 |
| 26 |
| 27 class ChromeInstaller: |
| 28 """Provides an interface to install/uninstall Chrome.""" |
| 29 |
| 30 def __init__(self, url, build, dest, opts='', clean=True): |
| 31 self._url = (lambda u: u if self._DoesUrlExist(u) else '')(url) |
| 32 self._dest = dest |
| 33 self._opts = opts |
| 34 self._clean = clean |
| 35 self._type = (lambda o: 'system' if o.find('system-level') != -1 else |
| 36 'user')(self._opts) |
| 37 self._plat = 'win' |
| 38 self._installer = 'mini_installer.exe' |
| 39 self.SetBuild(build) |
| 40 self.c_install = ChromeVersion(self._type) |
| 41 self._c_path = self.c_install.GetChromeExePath() |
| 42 |
| 43 def SetBuild(self, bld): |
| 44 """Sets the build number that is to be installed.""" |
| 45 if type(bld) == str: |
| 46 self._build = (lambda b: (all(map(lambda x: x.isdigit(), b.split('.'))) |
| 47 and len(b.split('.')) == 4) and b or '')(bld) |
| 48 else: |
| 49 self._build = '' |
| 50 |
| 51 def GetBuild(self): |
| 52 return self._build |
| 53 |
| 54 def _DoesUrlExist(self, url): |
| 55 """Checks if a url exists. |
| 56 |
| 57 Args: |
| 58 url: URL to be verified. |
| 59 |
| 60 Returns: |
| 61 Boolean - True if the URL exists, otherwise False. |
| 62 """ |
| 63 parse = urlparse.urlparse(url) |
| 64 if parse[0] == '' or parse[1] == '': |
| 65 return False |
| 66 try: |
| 67 conn = httplib.HTTPConnection(parse.netloc) |
| 68 conn.request('HEAD', parse.path) |
| 69 res = conn.getresponse() |
| 70 except socket.gaierror: |
| 71 return False |
| 72 finally: |
| 73 conn.close() |
| 74 # Follow both permanent (301) and temporary (302) redirects. |
| 75 if res.status == 302 or res.status == 301: |
| 76 return self._DoesUrlExist(res.getheader('location')) |
| 77 return res.status == 200 |
| 78 |
| 79 def _Download(self, _file, url, dest=None): |
| 80 """Downloads a file from the specified URL. |
| 81 |
| 82 Args: |
| 83 _file: Name of the file to download. |
| 84 url: URL where the file is located. |
| 85 dest: Where file will be downloaded(optional). Default location is CWD. |
| 86 |
| 87 Returns: |
| 88 Integer - zero if successful, otherwise a non-zero value. |
| 89 """ |
| 90 f_name = (lambda d, f: os.path.join(d, f) if(d and os.path.exists(d)) else |
| 91 os.path.join(tempfile.gettempdir(), f))(dest, _file) |
| 92 file_url = '%s/%s' % (url, _file) |
| 93 # Check link before downloading, urlretrieve *WILL NOT* report |
| 94 # an error if url doesn't exist. |
| 95 if not self._DoesUrlExist(file_url): |
| 96 print "Could not locate the file specified." |
| 97 return -1 |
| 98 try: |
| 99 d = urllib.urlretrieve(file_url, f_name) |
| 100 except IOError, err: |
| 101 print 'CChromeInstaller._Download: ', err |
| 102 return -1 |
| 103 if os.path.isfile(d[0]): |
| 104 return 0 |
| 105 return 1 |
| 106 |
| 107 def _Delete(self, _path): |
| 108 """Deletes a file or folder""" |
| 109 try: |
| 110 (lambda p: shutil.rmtree(p) if os.path.isdir(p) else os.remove(p))(_path) |
| 111 return 0 |
| 112 except(OSError, IOError, TypeError), err: |
| 113 print 'CChromeInstaller._Delete: ', err |
| 114 return -1 |
| 115 |
| 116 def _LaunchInstaller(self, opts=''): |
| 117 """Launches the Chrome installer using specified options. |
| 118 |
| 119 Args: |
| 120 opts: Any options that are to be passed to the installer. |
| 121 |
| 122 Returns: |
| 123 Integer - zero if successful, otherwise a non-zero value. |
| 124 """ |
| 125 print 'Downloading installer for build %s ...' % self._build |
| 126 build_url = '%s/%s/%s' % (self._url, self._build, self._plat) |
| 127 if self._Download(self._installer, build_url, self._dest) != 0: |
| 128 print 'Failed to download installer, exiting test' |
| 129 return -1 |
| 130 print 'Launching installer...' |
| 131 cmd = '%s %s' % (os.path.join(self._dest, self._installer), opts) |
| 132 try: |
| 133 return subprocess.Popen.wait(subprocess.Popen(cmd)) |
| 134 except OSError, err: |
| 135 print 'CChromeInstaller._LaunchInstaller: ', err |
| 136 return -2 |
| 137 |
| 138 def _RemoveDuplicates(self, args): |
| 139 """Removes duplicates options from a list. |
| 140 |
| 141 Args: |
| 142 args: A list containing the options. |
| 143 |
| 144 Returns: |
| 145 A string without any duplicate options. |
| 146 """ |
| 147 return ((lambda arg: arg if arg else '') |
| 148 (' '.join(map(lambda opt: opt, list(frozenset(args.split(','))))))) |
| 149 |
| 150 def _DoPreliminaryChecks(self, op='install'): |
| 151 """Checks test parameters for validity and performs version checking.""" |
| 152 if _PLATFORM != 'windows': |
| 153 print 'Unsupported platform, aborting operation...' |
| 154 return False |
| 155 if self._url == '': |
| 156 print 'URL specified is not valid. Please check the URL and try again.' |
| 157 return False |
| 158 ver = self.c_install.GetChromeVersion() |
| 159 c_type = self.c_install.GetInstallTypeString() |
| 160 if op == 'install': |
| 161 if c_type == 'system' and self._type == 'user': |
| 162 print 'System level Chrome exists, aborting user level installation.' |
| 163 return False |
| 164 # Version installed is higher than or same as the one we're installing. |
| 165 elif self._type == c_type: |
| 166 if self._build <= ver: |
| 167 print 'Please specify a version higher than the one installed.' |
| 168 return False |
| 169 elif op == 'uninstall': |
| 170 if not ver: |
| 171 print 'No version of Chrome found, aborting uninstall...' |
| 172 return False |
| 173 if self._type != c_type: |
| 174 print ('Invalid args passed. Please specify --opts=--system-level if ' |
| 175 'uninstalling system version of Chrome, or omit it otherwise.') |
| 176 return False |
| 177 return True |
| 178 |
| 179 def InstallChrome(self): |
| 180 """Installs specified chrome build.""" |
| 181 if not self._DoPreliminaryChecks('install'): |
| 182 return None |
| 183 opts = '--install --do-not-launch-chrome %s' % self._opts |
| 184 opts = self._RemoveDuplicates(opts) |
| 185 ret = self._LaunchInstaller(opts) |
| 186 self._Delete(os.path.join(self._dest, self._installer)) |
| 187 if ret == 0 and os.path.exists(self._c_path): |
| 188 print 'Installation complete...' |
| 189 return self.c_install |
| 190 else: |
| 191 print 'Installation failed.' |
| 192 return None |
| 193 |
| 194 def UninstallChrome(self): |
| 195 """Uninstalls Chrome.""" |
| 196 if not self._DoPreliminaryChecks('uninstall'): |
| 197 return False |
| 198 install_type = self.c_install.GetChromeInstallType() |
| 199 reg_opts = self.c_install.GetUninstallArgs(install_type) |
| 200 opts = '%s --force-uninstall %s' % (reg_opts, self._opts) |
| 201 opts = self._RemoveDuplicates(opts) |
| 202 if not os.path.exists(self._c_path): |
| 203 print 'Couldn\'t find Chrome. Verify Chrome is installed and try again.' |
| 204 return False |
| 205 ret = self._LaunchInstaller(opts) |
| 206 if os.path.exists(os.path.join(self._dest, self._installer)): |
| 207 self._Delete(os.path.join(self._dest, self._installer)) |
| 208 if not os.path.exists(self._c_path): |
| 209 print 'Chrome was successfully uninstalled...' |
| 210 if self._clean: |
| 211 print 'Removing registry settings...' |
| 212 self.c_install.DeleteChromeRegEntries() |
| 213 print 'Uninstall complete.' |
| 214 return True |
| 215 else: |
| 216 print 'Uninstall failed.' |
| 217 return False |
| 218 |
| 219 |
| 220 class ChromeVersion(): |
| 221 """Contains utility functions that provide information about installed |
| 222 |
| 223 version of Chrome. The type of Chrome version is passed as an argument |
| 224 to the constructor (i.e. - user or system level version). |
| 225 """ |
| 226 |
| 227 def __init__(self, install_type): |
| 228 self._type = install_type |
| 229 self.HKEY_LOCAL = (r'SOFTWARE\Wow6432Node\Google\Update\ClientState' |
| 230 '\{8A69D345-D564-463C-AFF1-A69D9E530F96}') |
| 231 self.HKEY_USER = self.HKEY_LOCAL.replace('\\Wow6432Node', '') |
| 232 |
| 233 def _OpenKey(self, key_type, key_name, key_access=_winreg.KEY_READ): |
| 234 """Opens a registry key. |
| 235 |
| 236 Args: |
| 237 key_type: HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE. |
| 238 key_name: Name of the key that is to be opened. |
| 239 key_access: Type of access required (i.e. - KEY_READ or KEY_ALL_ACCESS). |
| 240 |
| 241 Returns: |
| 242 Handle - handle to the key if successful, otherwise zero. |
| 243 """ |
| 244 try: |
| 245 key = _winreg.OpenKey(key_type, key_name, 0, key_access) |
| 246 return key |
| 247 except(OSError, TypeError), err: |
| 248 return 0 |
| 249 |
| 250 def _GetKeyValue(self, key, name): |
| 251 """Gets the value of the specified key. |
| 252 |
| 253 Args: |
| 254 key: Handle to the key. |
| 255 name: Name of the key. |
| 256 |
| 257 Returns: |
| 258 String - key value if successful, otherwise empty string. |
| 259 """ |
| 260 try: |
| 261 val = str(_winreg.QueryValueEx(key, name)[0]) |
| 262 return val |
| 263 except OSError, err: |
| 264 print 'ChromeVersion._GetKeyValue: %s' % err |
| 265 return '' |
| 266 |
| 267 def _GetKeyName(self, install_type, b_replace=True): |
| 268 """Returns Chrome registry key name. """ |
| 269 if install_type == _winreg.HKEY_CURRENT_USER: |
| 270 if b_replace: |
| 271 return self.HKEY_USER.replace('ClientState', 'Clients') |
| 272 else: |
| 273 return self.HKEY_USER |
| 274 elif install_type == _winreg.HKEY_LOCAL_MACHINE: |
| 275 if b_replace: |
| 276 return self.HKEY_LOCAL.replace('ClientState', 'Clients') |
| 277 else: |
| 278 return self.HKEY_LOCAL |
| 279 |
| 280 def _GetWinLocalFolder(self, ftype=_CSIDL_COMMON_APPDATA): |
| 281 """Returns the full path of the 'Local' folder on Windows. |
| 282 |
| 283 Args: |
| 284 ftype: Location to look up, which could vary based on installation type. |
| 285 |
| 286 Returns: |
| 287 String - folder path if successful, otherwise an empty string. |
| 288 """ |
| 289 if _PLATFORM != 'windows': |
| 290 return '' |
| 291 SHGetFolderPathW = windll.shell32.SHGetFolderPathW |
| 292 SHGetFolderPathW.argtypes = [wintypes.HWND, |
| 293 ctypes.c_int, |
| 294 wintypes.HANDLE, |
| 295 wintypes.DWORD, |
| 296 wintypes.LPCWSTR] |
| 297 path_buf = wintypes.create_unicode_buffer(wintypes.MAX_PATH) |
| 298 result = SHGetFolderPathW(0, ftype, 0, 0, path_buf) |
| 299 return str(path_buf.value) |
| 300 |
| 301 def GetInstallTypeString(self): |
| 302 """Returns a string representing the type of Chrome installation.""" |
| 303 install_type = ({_winreg.HKEY_CURRENT_USER: 'user', |
| 304 _winreg.HKEY_LOCAL_MACHINE: 'system'}).get( |
| 305 self.GetChromeInstallType()) |
| 306 return install_type |
| 307 |
| 308 def GetChromeInstallType(self): |
| 309 """Determines Chrome installation type.""" |
| 310 c_type = None |
| 311 key = self._OpenKey(_winreg.HKEY_CURRENT_USER, |
| 312 self.HKEY_USER.replace('ClientState', 'Clients')) |
| 313 if key: |
| 314 if self._GetKeyValue(key, 'pv'): |
| 315 c_type = _winreg.HKEY_CURRENT_USER |
| 316 else: |
| 317 key = self._OpenKey(_winreg.HKEY_LOCAL_MACHINE, |
| 318 self.HKEY_USER.replace('ClientState', 'Clients')) |
| 319 if key: |
| 320 if self._GetKeyValue(key, 'pv'): |
| 321 c_type = _winreg.HKEY_LOCAL_MACHINE |
| 322 if key: |
| 323 _winreg.CloseKey(key) |
| 324 return c_type |
| 325 |
| 326 def GetChromeVersion(self): |
| 327 """Returns current Chrome version.""" |
| 328 chrome_ver = '' |
| 329 key_type = ({'user': _winreg.HKEY_CURRENT_USER, |
| 330 'system' : _winreg.HKEY_LOCAL_MACHINE}).get(self._type) |
| 331 key = self._OpenKey(key_type, self._GetKeyName(key_type)) |
| 332 if key: |
| 333 chrome_ver = self._GetKeyValue(key, 'pv') |
| 334 _winreg.CloseKey(key) |
| 335 return chrome_ver |
| 336 |
| 337 def GetUninstallArgs(self, install_type=_winreg.HKEY_CURRENT_USER): |
| 338 """Gets chrome's uninstall arguments from the registry. |
| 339 |
| 340 Args: |
| 341 install_type: Type of chrome installation (user or system). |
| 342 |
| 343 Returns: |
| 344 String - uninstall arguments if successful, otherwise empty string. |
| 345 """ |
| 346 c_args = '' |
| 347 key = self._OpenKey(install_type, self._GetKeyName(install_type, False)) |
| 348 if key: |
| 349 c_args = self._GetKeyValue(key, 'UninstallArguments') |
| 350 _winreg.CloseKey(key) |
| 351 return c_args |
| 352 |
| 353 def GetChromeExePath(self): |
| 354 """Gets Chrome's location, based on install type (i.e. user or system).""" |
| 355 c_path = '' |
| 356 f_id = (lambda _type: _type == 'user' and _CSIDL_COMMON_APPDATA or |
| 357 _CSIDL_PROGRAM_FILESX86)(self._type) |
| 358 if _PLATFORM == 'windows': |
| 359 c_path = os.path.join(self._GetWinLocalFolder(f_id), |
| 360 'Google', 'Chrome', |
| 361 'Application', 'chrome.exe') |
| 362 return c_path |
| 363 |
| 364 def DeleteChromeRegEntries(self): |
| 365 """Deletes chrome registry settings.""" |
| 366 if self._type == 'user': |
| 367 p_key = self.HKEY_USER[: self.HKEY_USER.rfind('\\')] |
| 368 key_name = self.HKEY_USER[self.HKEY_USER.rfind('\\') + 1 :] |
| 369 _type = _winreg.HKEY_CURRENT_USER |
| 370 else: |
| 371 p_key = self.HKEY_LOCAL[: self.HKEY_LOCAL.rfind('\\')] |
| 372 key_name = self.HKEY_LOCAL[self.HKEY_LOCAL.rfind('\\')+1:] |
| 373 _type = _winreg.HKEY_LOCAL_MACHINE |
| 374 key = self._OpenKey(_type, p_key, _winreg.KEY_ALL_ACCESS) |
| 375 try: |
| 376 _winreg.DeleteKey(key, key_name) |
| 377 key.Close() |
| 378 return 0 |
| 379 except _winreg.error, err: |
| 380 print "Warning: could not delete registry settings - ", err |
| 381 return -1 |
| OLD | NEW |