Files
llm/scripts/parse_1c_extension_zipped_info.py
T

85 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""Parse _ExtensionsInfo._ExtensionZippedInfo blobs.
Observed structure starts with a 4-byte marker, followed by a 20-byte SHA1 key
that points to a ConfigCAS file. The rest contains small extension metadata,
including a UTF-16LE serialized text fragment.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
from inspect_1c_sql_files import GUID_RE
def extract_utf16le_fragment(data: bytes) -> str:
starts = [pos for pos in (data.find(b"{\x00"), data.find(b'"\x00#\x00"\x00')) if pos >= 0]
if not starts:
return ""
start = min(starts)
fragment = data[start:]
if len(fragment) % 2:
fragment = fragment[:-1]
try:
return fragment.decode("utf-16-le", errors="ignore").strip("\x00")
except UnicodeError:
return ""
def parse_blob(path: Path, cas_dir: Path | None) -> dict[str, Any]:
data = path.read_bytes()
root_key = data[4:24].hex() if len(data) >= 24 else ""
text = extract_utf16le_fragment(data)
cas_path = cas_dir / root_key if cas_dir and root_key else None
return {
"file_name": path.name,
"path": str(path),
"bytes": len(data),
"marker_hex": data[:4].hex(),
"root_cas_key": root_key,
"root_cas_exists": bool(cas_path and cas_path.is_file()),
"root_cas_path": str(cas_path) if cas_path and cas_path.is_file() else None,
"text_fragment": text,
"guids": sorted(set(match.lower() for match in GUID_RE.findall(text))),
}
def main() -> int:
parser = argparse.ArgumentParser(description="Parse exported _ExtensionZippedInfo blobs.")
parser.add_argument("input", type=Path, help="A blob file or directory with *_ExtensionZippedInfo.bin files.")
parser.add_argument("--cas-dir", type=Path)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
files = [args.input] if args.input.is_file() else sorted(args.input.glob("*ExtensionZippedInfo.bin"))
items = [parse_blob(path, args.cas_dir) for path in files]
report = {
"schema": "onec_extension_zipped_info.v1",
"input": str(args.input),
"cas_dir": str(args.cas_dir) if args.cas_dir else None,
"file_count": len(items),
"root_cas_found_count": sum(1 for item in items if item["root_cas_exists"]),
"items": items,
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(
json.dumps(
{
"output": str(args.output),
"files": len(items),
"root_cas_found": report["root_cas_found_count"],
},
ensure_ascii=False,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())