#!/usr/bin/env python3
"""Dump every HTTP request/response pair of challenge.pcapng as JSON lines.

Usage: python3 -I extract_http.py <capture.pcapng> <out.jsonl>
Each line: {"frame", "method", "uri", "req_headers", "req_body", "status", "resp_frame", "resp_body"}
"""
import json
import subprocess
import sys


def first(layer, key, default=""):
    v = layer.get(key, default)
    return v[0] if isinstance(v, list) else v


def main():
    pcap, out = sys.argv[1], sys.argv[2]
    raw = subprocess.run(["tshark", "-r", pcap, "-Y", "http", "-T", "json",
                          "-e", "frame.number", "-e", "http.request.method", "-e", "http.request.uri",
                          "-e", "http.request.line", "-e", "http.response.code",
                          "-e", "http.request_in", "-e", "http.file_data"],
                         capture_output=True, text=True, check=True).stdout
    reqs, pairs = {}, []
    for pkt in json.loads(raw):
        L = pkt["_source"]["layers"]
        fr = first(L, "frame.number")
        data = first(L, "http.file_data")
        body = bytes.fromhex(data.replace(":", "")) if data else b""
        if "http.request.method" in L:
            reqs[fr] = dict(frame=int(fr), method=first(L, "http.request.method"),
                            uri=first(L, "http.request.uri"),
                            req_headers=[h.strip() for h in L.get("http.request.line", [])],
                            req_body=body.decode("latin1"))
        else:
            rin = first(L, "http.request_in")
            if rin in reqs:
                p = reqs.pop(rin)
                p.update(status=first(L, "http.response.code"), resp_frame=int(fr),
                         resp_body=body.decode("latin1"))
                pairs.append(p)
    with open(out, "w") as f:
        for p in sorted(pairs, key=lambda x: x["frame"]):
            f.write(json.dumps(p) + "\n")
    print(f"{len(pairs)} pairs -> {out}")


if __name__ == "__main__":
    main()
