1# Copyright 2015 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import os 6import shutil 7import stat 8import sys 9import zipfile_2_7_13 as zipfile 10 11from dependency_manager import exceptions 12 13 14def _WinReadOnlyHandler(func, path, execinfo): 15 if not os.access(path, os.W_OK): 16 os.chmod(path, stat.S_IWRITE) 17 func(path) 18 else: 19 raise execinfo[0], execinfo[1], execinfo[2] 20 21 22def RemoveDir(dir_path): 23 assert os.path.isabs(dir_path) 24 if sys.platform.startswith('win'): 25 dir_path = u'\\\\?\\' + dir_path 26 if os.path.isdir(dir_path): 27 shutil.rmtree(dir_path, onerror=_WinReadOnlyHandler) 28 29 30def VerifySafeArchive(archive): 31 def ResolvePath(path_name): 32 return os.path.realpath(os.path.abspath(path_name)) 33 # Must add pathsep to avoid false positives. 34 # Ex: /tmp/abc/bad_file.py starts with /tmp/a but not /tmp/a/ 35 base_path = ResolvePath(os.getcwd()) + os.path.sep 36 for member in archive.namelist(): 37 if not ResolvePath(os.path.join(base_path, member)).startswith(base_path): 38 raise exceptions.ArchiveError( 39 'Archive %s contains a bad member: %s.' % (archive.filename, member)) 40 41 42def GetModeFromPath(file_path): 43 return stat.S_IMODE(os.stat(file_path).st_mode) 44 45 46def GetModeFromZipInfo(zip_info): 47 return zip_info.external_attr >> 16 48 49 50def SetUnzippedDirPermissions(archive, unzipped_dir): 51 """Set the file permissions in an unzipped archive. 52 53 Designed to be called right after extractall() was called on |archive|. 54 Noop on Win. Otherwise sets the executable bit on files where needed. 55 56 Args: 57 archive: A zipfile.ZipFile object opened for reading. 58 unzipped_dir: A path to a directory containing the unzipped contents 59 of |archive|. 60 """ 61 if sys.platform.startswith('win'): 62 # Windows doesn't have an executable bit, so don't mess with the ACLs. 63 return 64 for zip_info in archive.infolist(): 65 archive_acls = GetModeFromZipInfo(zip_info) 66 if archive_acls & stat.S_IXUSR: 67 # Only preserve owner execurable permissions. 68 unzipped_path = os.path.abspath( 69 os.path.join(unzipped_dir, zip_info.filename)) 70 mode = GetModeFromPath(unzipped_path) 71 os.chmod(unzipped_path, mode | stat.S_IXUSR) 72 73 74def UnzipArchive(archive_path, unzip_path): 75 """Unzips a file if it is a zip file. 76 77 Args: 78 archive_path: The downloaded file to unzip. 79 unzip_path: The destination directory to unzip to. 80 81 Raises: 82 ValueError: If |archive_path| is not a zipfile. 83 """ 84 # TODO(aiolos): Add tests once the refactor is completed. crbug.com/551158 85 if not (archive_path and zipfile.is_zipfile(archive_path)): 86 raise ValueError( 87 'Attempting to unzip a non-archive file at %s' % archive_path) 88 if not os.path.exists(unzip_path): 89 os.makedirs(unzip_path) 90 try: 91 with zipfile.ZipFile(archive_path, 'r') as archive: 92 VerifySafeArchive(archive) 93 assert os.path.isabs(unzip_path) 94 unzip_path_without_prefix = unzip_path 95 if sys.platform.startswith('win'): 96 unzip_path = u'\\\\?\\' + unzip_path 97 archive.extractall(path=unzip_path) 98 SetUnzippedDirPermissions(archive, unzip_path) 99 except: 100 # Hack necessary because isdir doesn't work with escaped paths on Windows. 101 if unzip_path_without_prefix and os.path.isdir(unzip_path_without_prefix): 102 RemoveDir(unzip_path_without_prefix) 103 raise 104