52 lines
2.5 KiB
Python
52 lines
2.5 KiB
Python
#!/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()
|