#!/usr/bin/env python3
"""
POC server for xml-rs lexer end_of_stream() state-retention bug.

Serves the SAME byte sequence two ways:
  /chunked.svg  - split across two HTTP chunks with the boundary at 'href="x/'
  /atomic.svg   - single write

A correct streaming parser must produce identical DOMs for identical bytes
regardless of chunk boundaries. The Rust XML parser used by Blink (behind
the XMLRustForNonXslt feature) does not: it doubles the '/', '-' or ']'
character that immediately precedes a chunk boundary.
"""
import http.server
import socketserver
import time
import sys

PORT = 19283

# Identical payload for both endpoints. The split point is between CHUNK_A
# and CHUNK_B. Note CHUNK_A ends in '/' inside an attribute value while the
# parser is at depth > 0.
CHUNK_A = (
    b'<svg xmlns="http://www.w3.org/2000/svg" '
    b'xmlns:h="http://www.w3.org/1999/xhtml">'
    b'<a id="t" href="x/'
)
CHUNK_B = (
    b'y"></a>'
    b'<h:script><![CDATA['
    b'var v = document.getElementById("t").getAttribute("href");'
    b'console.log("[POC] " + location.pathname + " href=" + JSON.stringify(v));'
    b'document.title = v;'
    b']]></h:script></svg>'
)
FULL = CHUNK_A + CHUNK_B

POC_HTML = b"""<!doctype html>
<meta charset="utf-8">
<title>xml-rs chunk-boundary differential</title>
<body>
<h3>xml-rs lexer end_of_stream() chunk-boundary differential</h3>
<pre id="out">loading...</pre>
<iframe id="atomic"  src="/atomic.svg"  width="300" height="60"></iframe>
<iframe id="chunked" src="/chunked.svg" width="300" height="60"></iframe>
<script>
function grab(id) {
  try {
    var d = document.getElementById(id).contentDocument;
    return d.getElementById('t').getAttribute('href');
  } catch (e) { return '<err: ' + e + '>'; }
}
window.addEventListener('load', () => {
  // give the chunked iframe time to finish (server sleeps ~1.5s mid-stream)
  setTimeout(() => {
    var a = grab('atomic');
    var c = grab('chunked');
    var bug = (a !== c);
    var msg = 'atomic  href = ' + JSON.stringify(a) + '\\n' +
              'chunked href = ' + JSON.stringify(c) + '\\n' +
              (bug ? '*** BUG CONFIRMED: identical bytes -> different DOM ***'
                   : 'no differential observed');
    document.getElementById('out').textContent = msg;
    console.log('[POC] ' + msg.replace(/\\n/g, ' | '));
    document.title = bug ? 'BUG-CONFIRMED' : 'NO-BUG';
  }, 3000);
});
</script>
</body>
"""


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

    def _hdr(self, ctype, chunked):
        self.send_response(200)
        self.send_header("Content-Type", ctype)
        self.send_header("Cache-Control", "no-store")
        if chunked:
            self.send_header("Transfer-Encoding", "chunked")
        else:
            self.send_header("Content-Length", str(len(self._body)))
        self.end_headers()

    def _chunk(self, data):
        self.wfile.write(b"%x\r\n" % len(data))
        self.wfile.write(data)
        self.wfile.write(b"\r\n")
        self.wfile.flush()

    def do_GET(self):
        if self.path.startswith("/poc.html") or self.path == "/":
            self._body = POC_HTML
            self._hdr("text/html; charset=utf-8", chunked=False)
            self.wfile.write(POC_HTML)
            return

        if self.path.startswith("/atomic.svg"):
            self._body = FULL
            self._hdr("image/svg+xml", chunked=False)
            self.wfile.write(FULL)
            return

        if self.path.startswith("/chunked.svg"):
            self._hdr("image/svg+xml", chunked=True)
            self._chunk(CHUNK_A)
            # Force the renderer to drain & parse the first chunk before
            # the second arrives, so xml_ffi::process_next_event hits
            # end_of_stream() in State::EmptyTagClosing.
            time.sleep(1.5)
            self._chunk(CHUNK_B)
            self.wfile.write(b"0\r\n\r\n")
            self.wfile.flush()
            return

        self.send_error(404)

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


if __name__ == "__main__":
    socketserver.TCPServer.allow_reuse_address = True
    with socketserver.ThreadingTCPServer(("127.0.0.1", PORT), Handler) as httpd:
        sys.stderr.write(f"[server] listening on http://127.0.0.1:{PORT}\n")
        httpd.serve_forever()
