File size: 2,455 Bytes
e1acc15 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | """Explicit relocation of immutable metadata; source documents and hashes stay unchanged."""
from __future__ import annotations
from collections.abc import Mapping, Sequence
from pathlib import Path
import os
class AssemblyPaths:
def __init__(self, root: Path, mappings: Mapping[str, str]) -> None:
if root.is_symlink():
raise ValueError('assembly root symlink refused')
self.root = root.resolve(strict=True)
self.mappings = dict(mappings)
for old, relative in self.mappings.items():
if not Path(old).is_absolute() or '..' in Path(old).parts:
raise ValueError('relocation source must be an absolute normalized path')
if Path(old) == Path('/') or self.root.is_relative_to(Path(old)):
raise ValueError('relocation source prefix contains assembly root or is /')
if Path(relative).is_absolute() or '..' in Path(relative).parts:
raise ValueError('relocation destination must be relative to assembly root')
self.inside(self.root / relative)
def inside(self, path: Path) -> Path:
path = Path(os.path.abspath(path))
resolved = path.resolve(strict=True)
if not resolved.is_relative_to(self.root):
raise ValueError('identity path escapes assembly root: ' + str(path))
# Symlinks are never identity aliases, including aliases within the root.
if any(p.is_symlink() for p in (path, *path.parents) if p.is_relative_to(self.root)):
raise ValueError('assembly identity symlink refused: ' + str(path))
return resolved
def resolve(self, value: str) -> Path:
path = Path(value)
if not path.is_absolute() or '..' in path.parts:
raise ValueError('identity path must be absolute and normalized')
matches = [old for old in self.mappings if path.is_relative_to(Path(old))]
if matches:
old = max(matches, key=len)
path = self.root / self.mappings[old] / path.relative_to(old)
return self.inside(path)
def parse_mappings(values: Sequence[str]) -> dict[str, str]:
result: dict[str, str] = {}
for value in values:
old, sep, relative = value.partition('=')
if not sep or not old or not relative or old in result:
raise ValueError('path maps must be unique OLD_ABSOLUTE=ROOT_RELATIVE pairs')
result[old] = relative
return result
|