You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

hashes.py 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from __future__ import absolute_import
  2. import hashlib
  3. from pip._vendor.six import iteritems, iterkeys, itervalues
  4. from pip._internal.exceptions import (
  5. HashMismatch, HashMissing, InstallationError,
  6. )
  7. from pip._internal.utils.misc import read_chunks
  8. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  9. if MYPY_CHECK_RUNNING:
  10. from typing import ( # noqa: F401
  11. Dict, List, BinaryIO, NoReturn, Iterator
  12. )
  13. from pip._vendor.six import PY3
  14. if PY3:
  15. from hashlib import _Hash # noqa: F401
  16. else:
  17. from hashlib import _hash as _Hash # noqa: F401
  18. # The recommended hash algo of the moment. Change this whenever the state of
  19. # the art changes; it won't hurt backward compatibility.
  20. FAVORITE_HASH = 'sha256'
  21. # Names of hashlib algorithms allowed by the --hash option and ``pip hash``
  22. # Currently, those are the ones at least as collision-resistant as sha256.
  23. STRONG_HASHES = ['sha256', 'sha384', 'sha512']
  24. class Hashes(object):
  25. """A wrapper that builds multiple hashes at once and checks them against
  26. known-good values
  27. """
  28. def __init__(self, hashes=None):
  29. # type: (Dict[str, List[str]]) -> None
  30. """
  31. :param hashes: A dict of algorithm names pointing to lists of allowed
  32. hex digests
  33. """
  34. self._allowed = {} if hashes is None else hashes
  35. def check_against_chunks(self, chunks):
  36. # type: (Iterator[bytes]) -> None
  37. """Check good hashes against ones built from iterable of chunks of
  38. data.
  39. Raise HashMismatch if none match.
  40. """
  41. gots = {}
  42. for hash_name in iterkeys(self._allowed):
  43. try:
  44. gots[hash_name] = hashlib.new(hash_name)
  45. except (ValueError, TypeError):
  46. raise InstallationError('Unknown hash name: %s' % hash_name)
  47. for chunk in chunks:
  48. for hash in itervalues(gots):
  49. hash.update(chunk)
  50. for hash_name, got in iteritems(gots):
  51. if got.hexdigest() in self._allowed[hash_name]:
  52. return
  53. self._raise(gots)
  54. def _raise(self, gots):
  55. # type: (Dict[str, _Hash]) -> NoReturn
  56. raise HashMismatch(self._allowed, gots)
  57. def check_against_file(self, file):
  58. # type: (BinaryIO) -> None
  59. """Check good hashes against a file-like object
  60. Raise HashMismatch if none match.
  61. """
  62. return self.check_against_chunks(read_chunks(file))
  63. def check_against_path(self, path):
  64. # type: (str) -> None
  65. with open(path, 'rb') as file:
  66. return self.check_against_file(file)
  67. def __nonzero__(self):
  68. # type: () -> bool
  69. """Return whether I know any known-good hashes."""
  70. return bool(self._allowed)
  71. def __bool__(self):
  72. # type: () -> bool
  73. return self.__nonzero__()
  74. class MissingHashes(Hashes):
  75. """A workalike for Hashes used when we're missing a hash for a requirement
  76. It computes the actual hash of the requirement and raises a HashMissing
  77. exception showing it to the user.
  78. """
  79. def __init__(self):
  80. # type: () -> None
  81. """Don't offer the ``hashes`` kwarg."""
  82. # Pass our favorite hash in to generate a "gotten hash". With the
  83. # empty list, it will never match, so an error will always raise.
  84. super(MissingHashes, self).__init__(hashes={FAVORITE_HASH: []})
  85. def _raise(self, gots):
  86. # type: (Dict[str, _Hash]) -> NoReturn
  87. raise HashMissing(gots[FAVORITE_HASH].hexdigest())