Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
wheelfile.py197 linesDownload Raw Back to wheel
1from __future__ import annotations2 3import csv4import hashlib5import os.path6import re7import stat8import time9from io import StringIO, TextIOWrapper10from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo11 12from wheel.cli import WheelError13from wheel.util import log, urlsafe_b64decode, urlsafe_b64encode14 15# Non-greedy matching of an optional build number may be too clever (more16# invalid wheel filenames will match). Separate regex for .dist-info?17WHEEL_INFO_RE = re.compile(18    r"""^(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]+?))(-(?P<build>\d[^\s-]*))?19     -(?P<pyver>[^\s-]+?)-(?P<abi>[^\s-]+?)-(?P<plat>\S+)\.whl$""",20    re.VERBOSE,21)22MINIMUM_TIMESTAMP = 315532800  # 1980-01-01 00:00:00 UTC23 24 25def get_zipinfo_datetime(timestamp=None):26    # Some applications need reproducible .whl files, but they can't do this without27    # forcing the timestamp of the individual ZipInfo objects. See issue #143.28    timestamp = int(os.environ.get("SOURCE_DATE_EPOCH", timestamp or time.time()))29    timestamp = max(timestamp, MINIMUM_TIMESTAMP)30    return time.gmtime(timestamp)[0:6]31 32 33class WheelFile(ZipFile):34    """A ZipFile derivative class that also reads SHA-256 hashes from35    .dist-info/RECORD and checks any read files against those.36    """37 38    _default_algorithm = hashlib.sha25639 40    def __init__(self, file, mode="r", compression=ZIP_DEFLATED):41        basename = os.path.basename(file)42        self.parsed_filename = WHEEL_INFO_RE.match(basename)43        if not basename.endswith(".whl") or self.parsed_filename is None:44            raise WheelError(f"Bad wheel filename {basename!r}")45 46        ZipFile.__init__(self, file, mode, compression=compression, allowZip64=True)47 48        self.dist_info_path = "{}.dist-info".format(49            self.parsed_filename.group("namever")50        )51        self.record_path = self.dist_info_path + "/RECORD"52        self._file_hashes = {}53        self._file_sizes = {}54        if mode == "r":55            # Ignore RECORD and any embedded wheel signatures56            self._file_hashes[self.record_path] = None, None57            self._file_hashes[self.record_path + ".jws"] = None, None58            self._file_hashes[self.record_path + ".p7s"] = None, None59 60            # Fill in the expected hashes by reading them from RECORD61            try:62                record = self.open(self.record_path)63            except KeyError:64                raise WheelError(f"Missing {self.record_path} file") from None65 66            with record:67                for line in csv.reader(68                    TextIOWrapper(record, newline="", encoding="utf-8")69                ):70                    path, hash_sum, size = line71                    if not hash_sum:72                        continue73 74                    algorithm, hash_sum = hash_sum.split("=")75                    try:76                        hashlib.new(algorithm)77                    except ValueError:78                        raise WheelError(79                            f"Unsupported hash algorithm: {algorithm}"80                        ) from None81 82                    if algorithm.lower() in {"md5", "sha1"}:83                        raise WheelError(84                            f"Weak hash algorithm ({algorithm}) is not permitted by "85                            f"PEP 427"86                        )87 88                    self._file_hashes[path] = (89                        algorithm,90                        urlsafe_b64decode(hash_sum.encode("ascii")),91                    )92 93    def open(self, name_or_info, mode="r", pwd=None):94        def _update_crc(newdata):95            eof = ef._eof96            update_crc_orig(newdata)97            running_hash.update(newdata)98            if eof and running_hash.digest() != expected_hash:99                raise WheelError(f"Hash mismatch for file '{ef_name}'")100 101        ef_name = (102            name_or_info.filename if isinstance(name_or_info, ZipInfo) else name_or_info103        )104        if (105            mode == "r"106            and not ef_name.endswith("/")107            and ef_name not in self._file_hashes108        ):109            raise WheelError(f"No hash found for file '{ef_name}'")110 111        ef = ZipFile.open(self, name_or_info, mode, pwd)112        if mode == "r" and not ef_name.endswith("/"):113            algorithm, expected_hash = self._file_hashes[ef_name]114            if expected_hash is not None:115                # Monkey patch the _update_crc method to also check for the hash from116                # RECORD117                running_hash = hashlib.new(algorithm)118                update_crc_orig, ef._update_crc = ef._update_crc, _update_crc119 120        return ef121 122    def write_files(self, base_dir):123        log.info(f"creating '{self.filename}' and adding '{base_dir}' to it")124        deferred = []125        for root, dirnames, filenames in os.walk(base_dir):126            # Sort the directory names so that `os.walk` will walk them in a127            # defined order on the next iteration.128            dirnames.sort()129            for name in sorted(filenames):130                path = os.path.normpath(os.path.join(root, name))131                if os.path.isfile(path):132                    arcname = os.path.relpath(path, base_dir).replace(os.path.sep, "/")133                    if arcname == self.record_path:134                        pass135                    elif root.endswith(".dist-info"):136                        deferred.append((path, arcname))137                    else:138                        self.write(path, arcname)139 140        deferred.sort()141        for path, arcname in deferred:142            self.write(path, arcname)143 144    def write(self, filename, arcname=None, compress_type=None):145        with open(filename, "rb") as f:146            st = os.fstat(f.fileno())147            data = f.read()148 149        zinfo = ZipInfo(150            arcname or filename, date_time=get_zipinfo_datetime(st.st_mtime)151        )152        zinfo.external_attr = (stat.S_IMODE(st.st_mode) | stat.S_IFMT(st.st_mode)) << 16153        zinfo.compress_type = compress_type or self.compression154        self.writestr(zinfo, data, compress_type)155 156    def writestr(self, zinfo_or_arcname, data, compress_type=None):157        if isinstance(zinfo_or_arcname, str):158            zinfo_or_arcname = ZipInfo(159                zinfo_or_arcname, date_time=get_zipinfo_datetime()160            )161            zinfo_or_arcname.compress_type = self.compression162            zinfo_or_arcname.external_attr = (0o664 | stat.S_IFREG) << 16163 164        if isinstance(data, str):165            data = data.encode("utf-8")166 167        ZipFile.writestr(self, zinfo_or_arcname, data, compress_type)168        fname = (169            zinfo_or_arcname.filename170            if isinstance(zinfo_or_arcname, ZipInfo)171            else zinfo_or_arcname172        )173        log.info(f"adding '{fname}'")174        if fname != self.record_path:175            hash_ = self._default_algorithm(data)176            self._file_hashes[fname] = (177                hash_.name,178                urlsafe_b64encode(hash_.digest()).decode("ascii"),179            )180            self._file_sizes[fname] = len(data)181 182    def close(self):183        # Write RECORD184        if self.fp is not None and self.mode == "w" and self._file_hashes:185            data = StringIO()186            writer = csv.writer(data, delimiter=",", quotechar='"', lineterminator="\n")187            writer.writerows(188                (189                    (fname, algorithm + "=" + hash_, self._file_sizes[fname])190                    for fname, (algorithm, hash_) in self._file_hashes.items()191                )192            )193            writer.writerow((format(self.record_path), "", ""))194            self.writestr(self.record_path, data.getvalue())195 196        ZipFile.close(self)197 
codekingpro/portable-devtools · Team Ai