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