hashes.py 2.8 KB

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