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 |