• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 subprocess
9import sys
10import zipfile
11
12import six
13
14from dependency_manager import exceptions
15
16
17def _WinReadOnlyHandler(func, path, execinfo):
18  if not os.access(path, os.W_OK):
19    os.chmod(path, stat.S_IWRITE)
20    func(path)
21  else:
22    six.reraise(*execinfo)
23
24
25def RemoveDir(dir_path):
26  assert os.path.isabs(dir_path)
27  if sys.platform.startswith('win'):
28    dir_path = u'\\\\?\\' + dir_path
29  if os.path.isdir(dir_path):
30    shutil.rmtree(dir_path, onerror=_WinReadOnlyHandler)
31
32
33def VerifySafeArchive(archive):
34  def ResolvePath(path_name):
35    return os.path.realpath(os.path.abspath(path_name))
36  # Must add pathsep to avoid false positives.
37  # Ex: /tmp/abc/bad_file.py starts with /tmp/a but not /tmp/a/
38  base_path = ResolvePath(os.getcwd()) + os.path.sep
39  for member in archive.namelist():
40    if not ResolvePath(os.path.join(base_path, member)).startswith(base_path):
41      raise exceptions.ArchiveError(
42          'Archive %s contains a bad member: %s.' % (archive.filename, member))
43
44
45def GetModeFromPath(file_path):
46  return stat.S_IMODE(os.stat(file_path).st_mode)
47
48
49def GetModeFromZipInfo(zip_info):
50  return zip_info.external_attr >> 16
51
52
53def SetUnzippedDirPermissions(archive, unzipped_dir):
54  """Set the file permissions in an unzipped archive.
55
56     Designed to be called right after extractall() was called on |archive|.
57     Noop on Win. Otherwise sets the executable bit on files where needed.
58
59     Args:
60         archive: A zipfile.ZipFile object opened for reading.
61         unzipped_dir: A path to a directory containing the unzipped contents
62             of |archive|.
63  """
64  if sys.platform.startswith('win'):
65    # Windows doesn't have an executable bit, so don't mess with the ACLs.
66    return
67  for zip_info in archive.infolist():
68    archive_acls = GetModeFromZipInfo(zip_info)
69    if archive_acls & stat.S_IXUSR:
70      # Only preserve owner execurable permissions.
71      unzipped_path = os.path.abspath(
72          os.path.join(unzipped_dir, zip_info.filename))
73      mode = GetModeFromPath(unzipped_path)
74      os.chmod(unzipped_path, mode | stat.S_IXUSR)
75
76
77def UnzipArchive(archive_path, unzip_path):
78  """Unzips a file if it is a zip file.
79
80  Args:
81      archive_path: The downloaded file to unzip.
82      unzip_path: The destination directory to unzip to.
83
84  Raises:
85      ValueError: If |archive_path| is not a zipfile.
86  """
87  # TODO(aiolos): Add tests once the refactor is completed. crbug.com/551158
88  if not (archive_path and zipfile.is_zipfile(archive_path)):
89    raise ValueError(
90        'Attempting to unzip a non-archive file at %s' % archive_path)
91  if not os.path.exists(unzip_path):
92    os.makedirs(unzip_path)
93  # The Python ZipFile does not support symbolic links, which makes it
94  # unsuitable for Mac builds. so use ditto instead. crbug.com/700097.
95  if sys.platform.startswith('darwin'):
96    assert os.path.isabs(unzip_path)
97    unzip_cmd = ['ditto', '-x', '-k', archive_path, unzip_path]
98    proc = subprocess.Popen(unzip_cmd, bufsize=0, stdout=subprocess.PIPE,
99                            stderr=subprocess.PIPE)
100    proc.communicate()
101    return
102  try:
103    with zipfile.ZipFile(archive_path, 'r') as archive:
104      VerifySafeArchive(archive)
105      assert os.path.isabs(unzip_path)
106      unzip_path_without_prefix = unzip_path
107      if sys.platform.startswith('win'):
108        unzip_path = u'\\\\?\\' + unzip_path
109      archive.extractall(path=unzip_path)
110      SetUnzippedDirPermissions(archive, unzip_path)
111  except:
112    # Hack necessary because isdir doesn't work with escaped paths on Windows.
113    if unzip_path_without_prefix and os.path.isdir(unzip_path_without_prefix):
114      RemoveDir(unzip_path_without_prefix)
115    raise
116