Spaces:
Running
Running
Download scripts/package_evidence.py from noqt/eggcracker: direct link, hf CLI and curl.
- Browser
- Download file 5.84 kB
-
https://huggingface.co/spaces/noqt/eggcracker/resolve/main/scripts/package_evidence.py
- Command line
-
hf download hf://spaces/noqt/eggcracker/scripts/package_evidence.py
-
curl -L -o package_evidence.py https://huggingface.co/spaces/noqt/eggcracker/resolve/main/scripts/package_evidence.py
5.84 kB
| """Create a POSIX-metadata-preserving evidence archive and detached manifest.""" | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| import os | |
| import re | |
| import tarfile | |
| from collections.abc import Iterator | |
| from pathlib import Path, PurePosixPath | |
| from typing import Any | |
| SCHEMA = "lumi-eggcracker.portable-evidence.v1" | |
| ROOT_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z") | |
| def digest(path: Path) -> str: | |
| value = hashlib.sha256() | |
| with path.open("rb") as handle: | |
| for block in iter(lambda: handle.read(1024 * 1024), b""): | |
| value.update(block) | |
| return value.hexdigest() | |
| def source_entries(root: Path) -> Iterator[Path]: | |
| yield root | |
| if root.is_symlink() or not root.is_dir(): | |
| return | |
| for child in sorted(root.iterdir(), key=lambda item: item.name): | |
| yield from source_entries(child) | |
| def safe_member_name(value: str) -> PurePosixPath: | |
| path = PurePosixPath(value) | |
| if ( | |
| not value | |
| or value.startswith(("/", "\\")) | |
| or "\\" in value | |
| or str(path) != value.rstrip("/") | |
| or any(part in {"", ".", ".."} for part in path.parts) | |
| ): | |
| raise RuntimeError("archive member path is unsafe") | |
| return path | |
| def safe_link(member: tarfile.TarInfo, names: set[str], *, hardlink: bool) -> None: | |
| target = PurePosixPath(member.linkname) | |
| if target.is_absolute() or "\\" in member.linkname: | |
| raise RuntimeError("archive link target is unsafe") | |
| if hardlink: | |
| resolved = safe_member_name(member.linkname) | |
| if str(resolved) not in names: | |
| raise RuntimeError("archive hardlink target is missing") | |
| else: | |
| resolved = PurePosixPath( | |
| os.path.normpath(str(PurePosixPath(member.name).parent / target)).replace( | |
| "\\", "/" | |
| ) | |
| ) | |
| source_root = PurePosixPath(member.name).parts[0] | |
| if any(part == ".." for part in resolved.parts) or resolved.parts[:1] != ( | |
| source_root, | |
| ): | |
| raise RuntimeError("archive symlink target escapes") | |
| def archive_entries(path: Path) -> list[dict[str, Any]]: | |
| with tarfile.open(path, "r:gz") as archive: | |
| members = archive.getmembers() | |
| names = [str(safe_member_name(member.name)) for member in members] | |
| if len(names) != len(set(names)): | |
| raise RuntimeError("archive contains duplicate members") | |
| known = set(names) | |
| result: list[dict[str, Any]] = [] | |
| for member in members: | |
| common: dict[str, Any] = { | |
| "gid": member.gid, | |
| "mode": member.mode, | |
| "mtime": member.mtime, | |
| "path": member.name, | |
| "uid": member.uid, | |
| } | |
| if member.isdir(): | |
| common["type"] = "directory" | |
| elif member.isfile(): | |
| handle = archive.extractfile(member) | |
| if handle is None: | |
| raise RuntimeError("archive regular member cannot be read") | |
| value = hashlib.sha256() | |
| while block := handle.read(1024 * 1024): | |
| value.update(block) | |
| common.update( | |
| {"sha256": value.hexdigest(), "size": member.size, "type": "file"} | |
| ) | |
| elif member.issym(): | |
| safe_link(member, known, hardlink=False) | |
| common.update({"target": member.linkname, "type": "symlink"}) | |
| elif member.islnk(): | |
| safe_link(member, known, hardlink=True) | |
| common.update({"target": member.linkname, "type": "hardlink"}) | |
| else: | |
| raise RuntimeError("archive contains an unsupported member type") | |
| result.append(common) | |
| return result | |
| def main() -> int: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--evidence", required=True, type=Path) | |
| parser.add_argument("--output", required=True, type=Path) | |
| parser.add_argument("--root-name") | |
| args = parser.parse_args() | |
| supplied_root = args.evidence | |
| root = supplied_root.resolve(strict=True) | |
| root_name = args.root_name or root.name | |
| manifest_path = Path(str(args.output) + ".manifest.json") | |
| checksum_path = Path(str(args.output) + ".sha256") | |
| if ( | |
| supplied_root.is_symlink() | |
| or not root.is_dir() | |
| or not ROOT_NAME.fullmatch(root_name) | |
| or args.output.suffixes[-2:] != [".tar", ".gz"] | |
| or not args.output.parent.is_dir() | |
| or any(path.exists() or path.is_symlink() for path in (args.output, manifest_path, checksum_path)) | |
| ): | |
| raise SystemExit("evidence archive arguments are invalid or outputs already exist") | |
| try: | |
| args.output.resolve().relative_to(root) | |
| except ValueError: | |
| pass | |
| else: | |
| raise SystemExit("evidence archive cannot be written inside its source") | |
| with tarfile.open( | |
| args.output, "w:gz", format=tarfile.PAX_FORMAT, dereference=False | |
| ) as archive: | |
| for path in source_entries(root): | |
| relative = path.relative_to(root) | |
| name = root_name if not relative.parts else f"{root_name}/{relative.as_posix()}" | |
| archive.add(path, arcname=name, recursive=False) | |
| entries = archive_entries(args.output) | |
| value = { | |
| "archive": args.output.name, | |
| "archive_sha256": digest(args.output), | |
| "entries": entries, | |
| "root": root_name, | |
| "schema_version": SCHEMA, | |
| } | |
| manifest_path.write_text(json.dumps(value, sort_keys=True) + "\n", encoding="utf-8") | |
| checksum_path.write_text(f"{value['archive_sha256']} {args.output.name}\n", encoding="ascii") | |
| print(json.dumps({"entries": len(entries), "result": "PASS"}, sort_keys=True)) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |