Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion src/installer/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
__all__ = ["install"]


# Hash algorithms considered secure enough for RECORD verification (PEP 427).
_COMPLIANT_HASH_ALGORITHMS = frozenset({"sha256", "sha384", "sha512"})


def _process_WHEEL_file(source: WheelSource) -> Scheme: # noqa: N802
"""Process the WHEEL file, from ``source``.

Expand Down Expand Up @@ -67,20 +71,30 @@ def install(
source: WheelSource,
destination: WheelDestination,
additional_metadata: dict[str, bytes],
) -> None:
) -> list[tuple[RecordEntry, RecordEntry]]:
"""Install wheel described by ``source`` into ``destination``.

:param source: wheel to install.
:param destination: where to write the wheel.
:param additional_metadata: additional metadata files to generate, usually
generated by the caller.

:returns: A list of record mismatches between the wheel's ``RECORD``
and the actually installed files. Each entry is a
``(source_record, installed_record)`` pair. An empty list means
all files matched. Files in the ``scripts`` scheme are excluded
because their content may be modified by shebang rewriting.
"""
root_scheme = _process_WHEEL_file(source)

# RECORD handling
record_file_path = posixpath.join(source.dist_info_dir, "RECORD")
written_records = []
mismatches: list[tuple[RecordEntry, RecordEntry]] = []

# Remember the destination's default hash algorithm so we can use it as a
# fallback and restore it when we are done.
_orig_hash_algorithm = destination.hash_algorithm

# Write the entry_points based scripts.
if "entry_points.txt" in source.dist_info_filenames:
Expand Down Expand Up @@ -121,12 +135,42 @@ def install(
source=source,
root_scheme=root_scheme,
)
# Prefer the source's hash algorithm, if compliant. This enables mismatch
# detection even when the wheel uses an algorithm other than the destination's
# default.
if (
source_record.hash_ is not None
and source_record.hash_.name in _COMPLIANT_HASH_ALGORITHMS
):
destination.hash_algorithm = source_record.hash_.name
else:
destination.hash_algorithm = _orig_hash_algorithm

record = destination.write_file(
scheme=scheme,
path=destination_path,
stream=stream,
is_executable=is_executable,
)

# Compare the installed record against the wheel's RECORD.
# Scripts-scheme files are excluded because shebang rewriting
# may legitimately change content.
if scheme != "scripts":
hash_mismatch = (
source_record.hash_ is not None
and record.hash_ is not None
and source_record.hash_.name == record.hash_.name
and source_record.hash_.value != record.hash_.value
)
size_mismatch = (
source_record.size is not None
and record.size is not None
and record.size != source_record.size
)
if hash_mismatch or size_mismatch:
mismatches.append((source_record, record))

written_records.append((scheme, record))

# Write all the installation-specific metadata
Expand All @@ -148,3 +192,7 @@ def install(
record_file_path=record_file_path,
records=written_records,
)

destination.hash_algorithm = _orig_hash_algorithm

return mismatches
2 changes: 2 additions & 0 deletions src/installer/destinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class WheelDestination:
(re)writing.
"""

hash_algorithm: str = "sha256"

def write_script(
self, name: str, module: str, attr: str, section: "ScriptSection"
) -> RecordEntry:
Expand Down
Loading
Loading