OLD | NEW |
(Empty) | |
| 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 |
| 5 import os |
| 6 import shutil |
| 7 import stat |
| 8 import sys |
| 9 import zipfile |
| 10 |
| 11 from . import exceptions |
| 12 |
| 13 |
| 14 def _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 |
| 22 def RemoveDir(dir_path): |
| 23 if os.path.isdir(dir_path): |
| 24 shutil.rmtree(dir_path, onerror=_WinReadOnlyHandler) |
| 25 |
| 26 |
| 27 def VerifySafeArchive(archive): |
| 28 def ResolvePath(path_name): |
| 29 return os.path.realpath(os.path.abspath(path_name)) |
| 30 # Must add pathsep to avoid false positives. |
| 31 # Ex: /tmp/abc/bad_file.py starts with /tmp/a but not /tmp/a/ |
| 32 base_path = ResolvePath(os.getcwd()) + os.path.sep |
| 33 for member in archive.namelist(): |
| 34 if not ResolvePath(os.path.join(base_path, member)).startswith(base_path): |
| 35 raise exceptions.ArchiveError( |
| 36 'Archive %s contains a bad member: %s.' % (archive.filename, member)) |
| 37 |
| 38 |
| 39 def GetModeFromPath(file_path): |
| 40 return stat.S_IMODE(os.stat(file_path).st_mode) |
| 41 |
| 42 |
| 43 def GetModeFromZipInfo(zip_info): |
| 44 return zip_info.external_attr >> 16 |
| 45 |
| 46 |
| 47 def SetUnzippedDirPermissions(archive, unzipped_dir): |
| 48 """Set the file permissions in an unzipped archive. |
| 49 |
| 50 Designed to be called right after extractall() was called on |archive|. |
| 51 Noop on Win. Otherwise sets the executable bit on files where needed. |
| 52 |
| 53 Args: |
| 54 archive: A zipfile.ZipFile object opened for reading. |
| 55 unzipped_dir: A path to a directory containing the unzipped contents |
| 56 of |archive|. |
| 57 """ |
| 58 if sys.platform.startswith('win'): |
| 59 # Windows doesn't have an executable bit, so don't mess with the ACLs. |
| 60 return |
| 61 for zip_info in archive.infolist(): |
| 62 archive_acls = GetModeFromZipInfo(zip_info) |
| 63 if archive_acls & stat.S_IXUSR: |
| 64 # Only preserve owner execurable permissions. |
| 65 unzipped_path = os.path.abspath( |
| 66 os.path.join(unzipped_dir, zip_info.filename)) |
| 67 mode = GetModeFromPath(unzipped_path) |
| 68 os.chmod(unzipped_path, mode | stat.S_IXUSR) |
| 69 |
| 70 |
| 71 def UnzipArchive(archive_path, unzip_path): |
| 72 """Unzips a file if it is a zip file. |
| 73 |
| 74 Args: |
| 75 archive_path: The downloaded file to unzip. |
| 76 unzip_path: The destination directory to unzip to. |
| 77 |
| 78 Raises: |
| 79 ValueError: If |archive_path| is not a zipfile. |
| 80 """ |
| 81 # TODO(aiolos): Add tests once the refactor is completed. crbug.com/551158 |
| 82 if not (archive_path and zipfile.is_zipfile(archive_path)): |
| 83 raise ValueError( |
| 84 'Attempting to unzip a non-archive file at %s' % archive_path) |
| 85 if not os.path.exists(unzip_path): |
| 86 os.makedirs(unzip_path) |
| 87 try: |
| 88 with zipfile.ZipFile(archive_path, 'r') as archive: |
| 89 VerifySafeArchive(archive) |
| 90 archive.extractall(path=unzip_path) |
| 91 SetUnzippedDirPermissions(archive, unzip_path) |
| 92 except: |
| 93 if unzip_path and os.path.isdir(unzip_path): |
| 94 RemoveDir(unzip_path) |
| 95 raise |
OLD | NEW |