#!/usr/bin/env python3
"""POC server for the IndexedDB BlobReader Content-Length mismatch.

Listens on two localhost ports:
  * 28771 — static HTTP server, serves poc.html.
  * 28772 — raw socket capture. Reads the entire request from the wire,
            parses the Content-Length header, and reports declared length
            vs. actual body bytes received.

Run from the directory containing poc.html.
"""

import http.server
import os
import re
import socket
import socketserver
import sys
import threading

WEB_PORT = 28771
CAPTURE_PORT = 28772
RESULT_FILE = "capture-result.txt"


def serve_static():
    os.chdir(os.path.dirname(os.path.abspath(__file__)))

    class Handler(http.server.SimpleHTTPRequestHandler):
        def end_headers(self):
            # Allow the POC page on :8080 to POST cross-origin to :8081.
            self.send_header("Access-Control-Allow-Origin", "*")
            super().end_headers()

        def log_message(self, fmt, *args):
            sys.stderr.write("[static ] " + (fmt % args) + "\n")

    socketserver.TCPServer.allow_reuse_address = True
    httpd = socketserver.TCPServer(("127.0.0.1", WEB_PORT), Handler)
    sys.stderr.write(f"[static ] serving poc.html on http://127.0.0.1:{WEB_PORT}/poc.html\n")
    httpd.serve_forever()


def capture_loop():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(("127.0.0.1", CAPTURE_PORT))
    srv.listen(8)
    sys.stderr.write(f"[capture] listening on http://127.0.0.1:{CAPTURE_PORT}/\n")

    while True:
        conn, addr = srv.accept()
        threading.Thread(target=handle_capture, args=(conn, addr), daemon=True).start()


def handle_capture(conn, addr):
    conn.settimeout(5.0)
    data = b""
    try:
        while True:
            chunk = conn.recv(65536)
            if not chunk:
                break
            data += chunk
    except (socket.timeout, ConnectionResetError):
        pass

    # We never send a response — Chrome will eventually error the request out
    # (or RST after the upload pipe errors), which is fine for this POC.
    try:
        conn.close()
    except OSError:
        pass

    if not data:
        return

    sep = data.find(b"\r\n\r\n")
    if sep < 0:
        sys.stderr.write(f"[capture] {addr}: {len(data)} bytes, no header terminator\n")
        return

    headers_raw = data[:sep].decode("latin-1", errors="replace")
    body = data[sep + 4:]
    request_line = headers_raw.split("\r\n", 1)[0]

    m = re.search(r"(?im)^Content-Length:\s*(\d+)", headers_raw)
    declared = int(m.group(1)) if m else None

    # Handle the CORS preflight quietly.
    if request_line.startswith("OPTIONS "):
        sys.stderr.write(f"[capture] {addr}: CORS preflight (ignored)\n")
        return

    lines = []
    lines.append("=" * 72)
    lines.append(f"[capture] connection from {addr}")
    lines.append(f"  request line       : {request_line}")
    lines.append(f"  Content-Length hdr : {declared}")
    lines.append(f"  body bytes on wire : {len(body)}")

    if declared is not None and len(body) > declared:
        excess = body[declared:declared + 200]
        printable = excess.decode("latin-1", errors="replace")
        printable = printable.replace("\r", "\\r").replace("\n", "\\n")
        lines.append(
            f"  ** MISMATCH ** body ({len(body)}) > Content-Length ({declared}); "
            f"excess = {len(body) - declared} bytes"
        )
        lines.append(f"  first 200 excess bytes (would be parsed as next pipelined request):")
        lines.append(f"    {printable}")
    elif declared is not None:
        lines.append("  (no mismatch observed)")
    lines.append("=" * 72)

    out = "\n".join(lines) + "\n"
    sys.stderr.write(out)
    sys.stderr.flush()
    with open(RESULT_FILE, "a") as f:
        f.write(out)


if __name__ == "__main__":
    # Clear previous result.
    open(RESULT_FILE, "w").close()
    threading.Thread(target=serve_static, daemon=True).start()
    capture_loop()
