| Index: tools/telemetry/catapult_base/dependency_manager/dependency_manager_util.py
|
| diff --git a/tools/telemetry/catapult_base/dependency_manager/dependency_manager_util.py b/tools/telemetry/catapult_base/dependency_manager/dependency_manager_util.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a0f6b6bbad7fe4a2a1536154384d4c35095e7e75
|
| --- /dev/null
|
| +++ b/tools/telemetry/catapult_base/dependency_manager/dependency_manager_util.py
|
| @@ -0,0 +1,95 @@
|
| +# Copyright 2015 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +import os
|
| +import shutil
|
| +import stat
|
| +import sys
|
| +import zipfile
|
| +
|
| +from . import exceptions
|
| +
|
| +
|
| +def _WinReadOnlyHandler(func, path, execinfo):
|
| + if not os.access(path, os.W_OK):
|
| + os.chmod(path, stat.S_IWRITE)
|
| + func(path)
|
| + else:
|
| + raise execinfo[0], execinfo[1], execinfo[2]
|
| +
|
| +
|
| +def RemoveDir(dir_path):
|
| + if os.path.isdir(dir_path):
|
| + shutil.rmtree(dir_path, onerror=_WinReadOnlyHandler)
|
| +
|
| +
|
| +def VerifySafeArchive(archive):
|
| + def ResolvePath(path_name):
|
| + return os.path.realpath(os.path.abspath(path_name))
|
| + # Must add pathsep to avoid false positives.
|
| + # Ex: /tmp/abc/bad_file.py starts with /tmp/a but not /tmp/a/
|
| + base_path = ResolvePath(os.getcwd()) + os.path.sep
|
| + for member in archive.namelist():
|
| + if not ResolvePath(os.path.join(base_path, member)).startswith(base_path):
|
| + raise exceptions.ArchiveError(
|
| + 'Archive %s contains a bad member: %s.' % (archive.filename, member))
|
| +
|
| +
|
| +def GetModeFromPath(file_path):
|
| + return stat.S_IMODE(os.stat(file_path).st_mode)
|
| +
|
| +
|
| +def GetModeFromZipInfo(zip_info):
|
| + return zip_info.external_attr >> 16
|
| +
|
| +
|
| +def SetUnzippedDirPermissions(archive, unzipped_dir):
|
| + """Set the file permissions in an unzipped archive.
|
| +
|
| + Designed to be called right after extractall() was called on |archive|.
|
| + Noop on Win. Otherwise sets the executable bit on files where needed.
|
| +
|
| + Args:
|
| + archive: A zipfile.ZipFile object opened for reading.
|
| + unzipped_dir: A path to a directory containing the unzipped contents
|
| + of |archive|.
|
| + """
|
| + if sys.platform.startswith('win'):
|
| + # Windows doesn't have an executable bit, so don't mess with the ACLs.
|
| + return
|
| + for zip_info in archive.infolist():
|
| + archive_acls = GetModeFromZipInfo(zip_info)
|
| + if archive_acls & stat.S_IXUSR:
|
| + # Only preserve owner execurable permissions.
|
| + unzipped_path = os.path.abspath(
|
| + os.path.join(unzipped_dir, zip_info.filename))
|
| + mode = GetModeFromPath(unzipped_path)
|
| + os.chmod(unzipped_path, mode | stat.S_IXUSR)
|
| +
|
| +
|
| +def UnzipArchive(archive_path, unzip_path):
|
| + """Unzips a file if it is a zip file.
|
| +
|
| + Args:
|
| + archive_path: The downloaded file to unzip.
|
| + unzip_path: The destination directory to unzip to.
|
| +
|
| + Raises:
|
| + ValueError: If |archive_path| is not a zipfile.
|
| + """
|
| + # TODO(aiolos): Add tests once the refactor is completed. crbug.com/551158
|
| + if not (archive_path and zipfile.is_zipfile(archive_path)):
|
| + raise ValueError(
|
| + 'Attempting to unzip a non-archive file at %s' % archive_path)
|
| + if not os.path.exists(unzip_path):
|
| + os.makedirs(unzip_path)
|
| + try:
|
| + with zipfile.ZipFile(archive_path, 'r') as archive:
|
| + VerifySafeArchive(archive)
|
| + archive.extractall(path=unzip_path)
|
| + SetUnzippedDirPermissions(archive, unzip_path)
|
| + except:
|
| + if unzip_path and os.path.isdir(unzip_path):
|
| + RemoveDir(unzip_path)
|
| + raise
|
|
|