"""Ekstraksi ZIP aman: tolak path traversal, symlink, file terlalu banyak/besar, rasio kompresi ekstrem."""
import sys, zipfile, os, stat, json
MAX_FILES = 20000
MAX_TOTAL = 400 * 1024 * 1024
MAX_RATIO = 200
def extract(src, dest):
    report = {"zip": os.path.basename(src), "files": 0, "bytes": 0, "rejected": []}
    os.makedirs(dest, exist_ok=False)
    dest_real = os.path.realpath(dest)
    with zipfile.ZipFile(src) as z:
        infos = z.infolist()
        if len(infos) > MAX_FILES:
            raise SystemExit(f"terlalu banyak file: {len(infos)}")
        total = sum(i.file_size for i in infos)
        if total > MAX_TOTAL:
            raise SystemExit(f"ukuran ekstraksi terlalu besar: {total}")
        for i in infos:
            name = i.filename
            mode = (i.external_attr >> 16) & 0xFFFF
            if stat.S_ISLNK(mode):
                report["rejected"].append([name, "symlink"]); continue
            if name.startswith("/") or ".." in name.replace("\\", "/").split("/") or ":" in name:
                report["rejected"].append([name, "path traversal"]); continue
            if i.compress_size and i.file_size / max(i.compress_size, 1) > MAX_RATIO and i.file_size > 1024 * 1024:
                report["rejected"].append([name, "rasio kompresi ekstrem"]); continue
            target = os.path.realpath(os.path.join(dest, name))
            if not target.startswith(dest_real + os.sep):
                report["rejected"].append([name, "di luar folder tujuan"]); continue
            if name.endswith("/"):
                os.makedirs(target, exist_ok=True); continue
            os.makedirs(os.path.dirname(target), exist_ok=True)
            with z.open(i) as fsrc, open(target, "wb") as fdst:
                n = 0
                while True:
                    b = fsrc.read(1 << 20)
                    if not b: break
                    n += len(b)
                    if n > i.file_size + 1024:
                        raise SystemExit(f"ukuran tidak sesuai header: {name}")
                    fdst.write(b)
            os.chmod(target, 0o644)
            report["files"] += 1; report["bytes"] += n
    print(json.dumps(report, ensure_ascii=False))
if __name__ == "__main__":
    extract(sys.argv[1], sys.argv[2])
