101 lines
5.1 KiB
Python
101 lines
5.1 KiB
Python
"""Extract the accepted package producer result once; verify exact extracted bytes."""
|
|
from pathlib import Path, PurePosixPath
|
|
import datetime
|
|
import hashlib
|
|
import json
|
|
import re
|
|
import shutil
|
|
import zipfile
|
|
|
|
ROOT = Path(__file__).resolve().parent / 'native-repair-union-33e30c1'
|
|
NAME = 'ArcadiaStudio-0.2.0-preview.canvas-repair.20260912-x86_64-pc-windows-gnullvm'
|
|
SOURCE = '33e30c1cd7a6c99513a0ebd02ba68fccbad8f58d'
|
|
|
|
|
|
def require(condition, message):
|
|
if not condition:
|
|
raise RuntimeError(message)
|
|
|
|
|
|
def sha(path):
|
|
with path.open('rb') as stream:
|
|
return hashlib.file_digest(stream, 'sha256').hexdigest()
|
|
|
|
|
|
def write_new(path, data):
|
|
with path.open('x', encoding='utf-8', newline='\n') as stream:
|
|
json.dump(data, stream, indent=2)
|
|
stream.write('\n')
|
|
|
|
|
|
def main():
|
|
package = json.loads((ROOT / 'package-result.json').read_text(encoding='utf-8-sig'))
|
|
require(package['exit'] == 0 and package['source_commit'] == SOURCE, 'No accepted producer result')
|
|
result = package['result']
|
|
archive = Path(result['Archive'])
|
|
require(archive.resolve() == (ROOT / 'dist' / (NAME + '.zip')).resolve(), 'Unexpected archive path')
|
|
expected = result['SHA256']
|
|
require(re.fullmatch('[0-9a-f]{64}', expected) is not None, 'Invalid archive hash')
|
|
checksum = Path(str(archive) + '.sha256').read_text(encoding='utf-8-sig')
|
|
require(checksum == f'{expected} {NAME}.zip\n', 'Checksum file disagrees with producer')
|
|
require(sha(archive) == expected, 'Archive hash mismatch')
|
|
destination = ROOT / 'extracted'
|
|
receipt = ROOT / 'extraction-readback.json'
|
|
require(not destination.exists() and not receipt.exists(), 'Use a fresh extraction location')
|
|
started = datetime.datetime.now(datetime.timezone.utc).isoformat()
|
|
# Exclusive claim prevents concurrent extraction attempts; preserved on failure.
|
|
with (ROOT / 'extraction.claim').open('x') as claim:
|
|
claim.write(started + '\n')
|
|
try:
|
|
with zipfile.ZipFile(archive) as bundle:
|
|
entries = bundle.infolist()
|
|
require(len(entries) == result['Files'], 'Producer archive count mismatch')
|
|
names = set()
|
|
for entry in entries:
|
|
path = PurePosixPath(entry.filename)
|
|
require(path.parts[0] == NAME and len(path.parts) > 1 and not path.is_absolute()
|
|
and '..' not in path.parts and '\\' not in entry.filename
|
|
and ':' not in entry.filename and not entry.is_dir(), 'Unsafe archive member')
|
|
require(all(part == part.rstrip(' .') for part in path.parts), 'Ambiguous Windows path')
|
|
require(entry.filename.casefold() not in names, 'Case-insensitive duplicate member')
|
|
names.add(entry.filename.casefold())
|
|
destination.mkdir()
|
|
records = []
|
|
for entry in entries:
|
|
target = destination.joinpath(*PurePosixPath(entry.filename).parts)
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
with bundle.open(entry) as source, target.open('xb') as output:
|
|
shutil.copyfileobj(source, output)
|
|
with bundle.open(entry) as source:
|
|
expected_file = hashlib.file_digest(source, 'sha256').hexdigest()
|
|
actual = sha(target)
|
|
require(actual == expected_file and target.stat().st_size == entry.file_size,
|
|
f'Extraction changed file: {entry.filename}')
|
|
records.append({'path': entry.filename, 'bytes': entry.file_size, 'sha256': actual})
|
|
actual_names = {p.relative_to(destination).as_posix().casefold() for p in destination.rglob('*') if p.is_file()}
|
|
require(actual_names == names, 'Extracted inventory differs')
|
|
package_dir = destination / NAME
|
|
sums = {}
|
|
for line in (package_dir / 'SHA256SUMS.txt').read_text(encoding='utf-8-sig').splitlines():
|
|
expected_file, relative = line.split(' ', 1)
|
|
require(relative not in sums, 'Duplicate checksum member')
|
|
sums[relative] = expected_file
|
|
actual_sums = {r['path'][len(NAME) + 1:]: r['sha256'] for r in records
|
|
if r['path'] != f'{NAME}/SHA256SUMS.txt'}
|
|
require(sums == actual_sums, 'Extracted checksum inventory disagrees with actual files')
|
|
require(sha(archive) == expected, 'Archive changed during extraction')
|
|
record = {'started_utc': started, 'completed_utc': datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
'exit': 0, 'source_commit': SOURCE, 'archive': str(archive), 'archive_sha256': expected,
|
|
'package_directory': str(package_dir), 'files': records}
|
|
write_new(receipt, record)
|
|
print(json.dumps({'exit': 0, 'files': len(records), 'archive_sha256': expected,
|
|
'package_directory': str(package_dir)}))
|
|
except Exception as error:
|
|
write_new(ROOT / 'extraction-failure.json', {'started_utc': started, 'exit': 1,
|
|
'error': str(error), 'type': type(error).__name__})
|
|
raise
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|