Path: blob/main/test/lib/python3.9/site-packages/pip/_internal/utils/hashes.py
4804 views
import hashlib1from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, List23from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError4from pip._internal.utils.misc import read_chunks56if TYPE_CHECKING:7from hashlib import _Hash89# NoReturn introduced in 3.6.2; imported only for type checking to maintain10# pip compatibility with older patch versions of Python 3.611from typing import NoReturn121314# The recommended hash algo of the moment. Change this whenever the state of15# the art changes; it won't hurt backward compatibility.16FAVORITE_HASH = "sha256"171819# Names of hashlib algorithms allowed by the --hash option and ``pip hash``20# Currently, those are the ones at least as collision-resistant as sha256.21STRONG_HASHES = ["sha256", "sha384", "sha512"]222324class Hashes:25"""A wrapper that builds multiple hashes at once and checks them against26known-good values2728"""2930def __init__(self, hashes: Dict[str, List[str]] = None) -> None:31"""32:param hashes: A dict of algorithm names pointing to lists of allowed33hex digests34"""35allowed = {}36if hashes is not None:37for alg, keys in hashes.items():38# Make sure values are always sorted (to ease equality checks)39allowed[alg] = sorted(keys)40self._allowed = allowed4142def __and__(self, other: "Hashes") -> "Hashes":43if not isinstance(other, Hashes):44return NotImplemented4546# If either of the Hashes object is entirely empty (i.e. no hash47# specified at all), all hashes from the other object are allowed.48if not other:49return self50if not self:51return other5253# Otherwise only hashes that present in both objects are allowed.54new = {}55for alg, values in other._allowed.items():56if alg not in self._allowed:57continue58new[alg] = [v for v in values if v in self._allowed[alg]]59return Hashes(new)6061@property62def digest_count(self) -> int:63return sum(len(digests) for digests in self._allowed.values())6465def is_hash_allowed(self, hash_name: str, hex_digest: str) -> bool:66"""Return whether the given hex digest is allowed."""67return hex_digest in self._allowed.get(hash_name, [])6869def check_against_chunks(self, chunks: Iterable[bytes]) -> None:70"""Check good hashes against ones built from iterable of chunks of71data.7273Raise HashMismatch if none match.7475"""76gots = {}77for hash_name in self._allowed.keys():78try:79gots[hash_name] = hashlib.new(hash_name)80except (ValueError, TypeError):81raise InstallationError(f"Unknown hash name: {hash_name}")8283for chunk in chunks:84for hash in gots.values():85hash.update(chunk)8687for hash_name, got in gots.items():88if got.hexdigest() in self._allowed[hash_name]:89return90self._raise(gots)9192def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":93raise HashMismatch(self._allowed, gots)9495def check_against_file(self, file: BinaryIO) -> None:96"""Check good hashes against a file-like object9798Raise HashMismatch if none match.99100"""101return self.check_against_chunks(read_chunks(file))102103def check_against_path(self, path: str) -> None:104with open(path, "rb") as file:105return self.check_against_file(file)106107def __bool__(self) -> bool:108"""Return whether I know any known-good hashes."""109return bool(self._allowed)110111def __eq__(self, other: object) -> bool:112if not isinstance(other, Hashes):113return NotImplemented114return self._allowed == other._allowed115116def __hash__(self) -> int:117return hash(118",".join(119sorted(120":".join((alg, digest))121for alg, digest_list in self._allowed.items()122for digest in digest_list123)124)125)126127128class MissingHashes(Hashes):129"""A workalike for Hashes used when we're missing a hash for a requirement130131It computes the actual hash of the requirement and raises a HashMissing132exception showing it to the user.133134"""135136def __init__(self) -> None:137"""Don't offer the ``hashes`` kwarg."""138# Pass our favorite hash in to generate a "gotten hash". With the139# empty list, it will never match, so an error will always raise.140super().__init__(hashes={FAVORITE_HASH: []})141142def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":143raise HashMissing(gots[FAVORITE_HASH].hexdigest())144145146