#!/usr/bin/env python3 """Create an UNPUBLISHED one-class Qortal fixture JAR; never edit original data.""" import argparse import hashlib import io import json from pathlib import Path import zipfile PIN = 'a5fbe763645a53995565c9cb93491e024384431ee29eee38a4146b6a13ed2e87' CLASS = 'org/qortal/controller/arbitrary/ArbitraryDataCacheManager.class' MAX_CLASS_BYTES = 1024 * 1024 def sha(data): return hashlib.sha256(data).hexdigest() def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('original', type=Path) parser.add_argument('compiled_class', type=Path) parser.add_argument('output', type=Path) args = parser.parse_args() if args.original.is_symlink() or args.compiled_class.is_symlink(): raise ValueError('Symlink inputs refused') original = args.original.read_bytes() if sha(original) != PIN: raise ValueError('Original is not the qualified official JAR') with args.compiled_class.open('rb') as stream: replacement = stream.read(MAX_CLASS_BYTES + 1) if replacement[:4] != b'\xca\xfe\xba\xbe' or len(replacement) > MAX_CLASS_BYTES: raise ValueError('Invalid or excessive class input') # Build from the bytes whose pin was checked, never a reopened input path. with zipfile.ZipFile(io.BytesIO(original)) as src: names = src.namelist() if len(set(names)) != len(names) or names.count(CLASS) != 1: raise ValueError('Ambiguous original entries') if any(n.upper().startswith('META-INF/') and n.upper().endswith(('.SF', '.RSA', '.DSA', '.EC')) for n in names): raise ValueError('Refuse to invalidate an existing JAR signature') with args.output.open('xb') as file, zipfile.ZipFile(file, 'w') as dst: for item in src.infolist(): dst.writestr(item, replacement if item.filename == CLASS else src.read(item.filename)) with zipfile.ZipFile(io.BytesIO(original)) as src, zipfile.ZipFile(args.output) as dst: changed = [name for name in names if src.read(name) != dst.read(name)] if changed != [CLASS]: raise ValueError('Overlay did not change exactly the expected class') print(json.dumps({'status':'unpublished synthetic fixture, not released source or installed acceptance', 'original_sha256': PIN, 'class_sha256': sha(replacement), 'fixture_sha256': sha(args.output.read_bytes()), 'changed_entries': changed}, indent=2)) if __name__ == '__main__': main()