hashes.py 2.9 KB

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