diff --git a/packaging/whl.py b/packaging/whl.py index 35856a759c..f30575ae1b 100644 --- a/packaging/whl.py +++ b/packaging/whl.py @@ -18,9 +18,25 @@ import os import pkg_resources import re +import stat import zipfile +def current_umask(): + """Get the current umask which involves having to set it temporarily.""" + mask = os.umask(0) + os.umask(mask) + return mask + + +def set_extracted_file_to_default_mode_plus_executable(path): + """ + Make file present at path have execute for user/group/world + (chmod +x) is no-op on windows per python docs + """ + os.chmod(path, (0o777 & ~current_umask() | 0o111)) + + class Wheel(object): def __init__(self, path): @@ -107,8 +123,21 @@ def extras(self): return self.metadata().get('extras', []) def expand(self, directory): - with zipfile.ZipFile(self.path(), 'r') as whl: - whl.extractall(directory) + with zipfile.ZipFile(self.path(), "r", allowZip64=True) as whl: + whl.extractall(directory) + # The following logic is borrowed from Pip: + # https://github.com/pypa/pip/blob/cc48c07b64f338ac5e347d90f6cb4efc22ed0d0b/src/pip/_internal/utils/unpacking.py#L240 + for info in whl.infolist(): + name = info.filename + # Do not attempt to modify directories. + if name.endswith("/") or name.endswith("\\"): + continue + mode = info.external_attr >> 16 + # if mode and regular file and any execute permissions for + # user/group/world? + if mode and stat.S_ISREG(mode) and mode & 0o111: + name = os.path.join(directory, name) + set_extracted_file_to_default_mode_plus_executable(name) # _parse_metadata parses METADATA files according to https://www.python.org/dev/peps/pep-0314/ def _parse_metadata(self, content):