diff --git a/src/installer/records.py b/src/installer/records.py index a25611a..959df7d 100644 --- a/src/installer/records.py +++ b/src/installer/records.py @@ -192,13 +192,15 @@ def from_elements(cls, path: str, hash_: str, size: str) -> "RecordEntry": if not path: issues.append("`path` cannot be empty") + hash_value: Optional[Hash] = None if hash_: try: - hash_value: Optional[Hash] = Hash.parse(hash_) + hash_value = Hash.parse(hash_) + if hash_value.name not in hashlib.algorithms_available: + issues.append(f"invalid hash algorithm '{hash_value.name}'") + hash_value = None except ValueError: issues.append("`hash` does not follow the required format") - else: - hash_value = None if size: try: diff --git a/src/installer/sources.py b/src/installer/sources.py index 71644f6..c7b5088 100644 --- a/src/installer/sources.py +++ b/src/installer/sources.py @@ -16,7 +16,7 @@ ) from installer.exceptions import InstallerError -from installer.records import RecordEntry, parse_record_file +from installer.records import InvalidRecordEntry, RecordEntry, parse_record_file from installer.utils import canonicalize_name, parse_wheel_filename if TYPE_CHECKING: @@ -277,7 +277,18 @@ def validate_record(self, *, validate_contents: bool = True) -> None: ) continue - record = RecordEntry.from_elements(*record_args) + try: + record = RecordEntry.from_elements(*record_args) + except InvalidRecordEntry as e: + for issue in e.issues: + issues.append( + f"In {self._zipfile.filename}, entry in RECORD file for " + f"{item.filename} is invalid: {issue}" + ) + + # coverage on Windows and python < 3.10 claims that the next line is not + # reached, pragma to deal with this false positive. + continue # pragma: no cover if item.filename == f"{self.dist_info_dir}/RECORD": # Assert that RECORD doesn't have size and hash. diff --git a/tests/test_sources.py b/tests/test_sources.py index a3f7f5e..a78fcf0 100644 --- a/tests/test_sources.py +++ b/tests/test_sources.py @@ -338,3 +338,26 @@ def test_rejects_record_validation_failed(self, fancy_wheel): ), ): source.validate_record() + + def test_rejects_record_containing_unknown_hash(self, fancy_wheel): + with WheelFile.open(fancy_wheel) as source: + record_file_contents = source.read_dist_info("RECORD") + + new_record_file_contents = record_file_contents.replace("sha256=", "sha=") + replace_file_in_zip( + fancy_wheel, + filename="fancy-1.0.0.dist-info/RECORD", + content=new_record_file_contents, + ) + + with ( + WheelFile.open(fancy_wheel) as source, + pytest.raises( + WheelFile.validation_error, + match=( + "In .+, entry in RECORD file for .+ is invalid: " + "invalid hash algorithm 'sha'" + ), + ), + ): + source.validate_record(validate_contents=True)