import argparse
import hashlib
import json
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parent
INCOMING = ROOT / "incoming"
ROOT_REPO = ROOT.parent.parent
def parse_har(path: Path) -> dict | None:
har = json.loads(path.read_text())
for entry in har.get("log", {}).get("entries", []):
url = entry.get("request", {}).get("url", "")
if any(s in url for s in ("turnstile", "recaptcha", "hcaptcha", "/cdn-cgi/challenge-platform/")):
resp = entry.get("response", {})
content = resp.get("content", {})
body = content.get("text", "")
return {
"url": url,
"status": resp.get("status", 0),
"headers": {h["name"].lower(): h["value"] for h in resp.get("headers", [])},
"body": body,
}
return None
def parse_curl_trace(path: Path) -> dict | None:
text = path.read_text()
if "<= Recv data" not in text:
return None
body_chunks = []
for line in text.splitlines():
if line.startswith("0000:"):
ascii_part = line.split(" ", 1)
if len(ascii_part) > 1:
body_chunks.append(ascii_part[1])
body = "".join(body_chunks)
if not body:
return None
return {
"url": "unknown",
"status": 0,
"headers": {},
"body": body,
}
def parse_pcap(_path: Path) -> dict | None:
print("[pcap2fixture] PCAP parsing not yet implemented; "
"use HAR or curl --trace-ascii.", file=sys.stderr)
return None
def to_adversarial(captured: dict, vendor: str, out_root: Path) -> Path:
body = captured["body"] or ""
cid = hashlib.sha256(body.encode("utf-8", "replace")).hexdigest()[:16]
payload = {
"id": cid,
"vendor": vendor,
"status": captured.get("status", 0),
"headers": captured.get("headers", {}),
"body": body,
"cookie_names": [],
"expected": "recognised",
"notes": f"pcap2fixture import from {captured.get('url', 'unknown')}",
"captured_at_unix": int(time.time()),
}
dest_dir = out_root / vendor
dest_dir.mkdir(parents=True, exist_ok=True)
dest = dest_dir / f"{cid}.json"
dest.write_text(json.dumps(payload, indent=2))
return dest
def infer_vendor(captured: dict) -> str:
url = captured.get("url", "").lower()
body = (captured.get("body") or "").lower()
if "turnstile" in url or "cf-turnstile" in body:
return "cloudflare-turnstile"
if "/cdn-cgi/challenge-platform/" in url:
return "cloudflare-interstitial"
if "hcaptcha" in url or "h-captcha" in body:
return "hcaptcha"
if "recaptcha" in url or "g-recaptcha" in body:
return "recaptcha-v2"
return "unknown"
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--out-mode",
choices=["bench", "adversarial"],
default="adversarial",
)
args = parser.parse_args()
if not INCOMING.is_dir():
INCOMING.mkdir(parents=True, exist_ok=True)
print(f"[pcap2fixture] created {INCOMING}, drop captures in and rerun")
return 0
files = sorted(INCOMING.iterdir())
if not files:
print(f"[pcap2fixture] {INCOMING} is empty")
return 0
out_root = ROOT_REPO / "adversarial-corpus"
count = 0
for path in files:
if path.suffix == ".har":
captured = parse_har(path)
elif path.suffix == ".txt":
captured = parse_curl_trace(path)
elif path.suffix == ".pcap":
captured = parse_pcap(path)
else:
print(f"[pcap2fixture] skipping {path.name} (unknown extension)")
continue
if not captured:
print(f"[pcap2fixture] no challenge response found in {path.name}")
continue
vendor = infer_vendor(captured)
if args.out_mode == "adversarial":
dest = to_adversarial(captured, vendor, out_root)
print(f"[pcap2fixture] {path.name} → {dest.relative_to(ROOT_REPO)}")
count += 1
else:
print(f"[pcap2fixture] bench-mode emission not yet implemented "
"(use adversarial mode)")
print(f"[pcap2fixture] {count} capture(s) imported")
return 0
if __name__ == "__main__":
sys.exit(main())