#!/usr/bin/env python3
"""Capture a public GET's unpaid x402 challenge. Python 3 standard library.

Usage: python3 probe.py https://provider.example/api/report > evidence.json
No auth headers, payment headers, redirects or payment retries are sent.
Only run this against a public endpoint you are authorized to inspect.
"""
import base64
import datetime
import hashlib
import ipaddress
import json
import socket
import sys
import urllib.error
import urllib.parse
import urllib.request


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def capture(url):
    u = urllib.parse.urlsplit(url)
    if u.scheme != 'https' or not u.hostname or u.username or u.password or u.fragment:
        raise ValueError('Supply an HTTPS URL without credentials or fragment.')
    if u.port not in (None, 443):
        raise ValueError('Only standard HTTPS port 443 is supported.')
    if any(k.lower() in {'token','api_key','apikey','secret','password','signature'}
           for k, _ in urllib.parse.parse_qsl(u.query)):
        raise ValueError('Credential-like query parameters are excluded.')
    ips = {r[4][0] for r in socket.getaddrinfo(u.hostname, 443, type=socket.SOCK_STREAM)}
    if not ips or any(not ipaddress.ip_address(ip).is_global for ip in ips):
        raise ValueError('Only a hostname resolving to global IP addresses is supported.')
    # Not a server-side proxy. DNS may change between this precheck and connection.
    opener = urllib.request.build_opener(NoRedirect())
    request = urllib.request.Request(url, headers={'User-Agent':'QuoteCheck/1.0','Accept':'application/json'})
    try:
        response = opener.open(request, timeout=20)
    except urllib.error.HTTPError as error:
        response = error
    with response:
        body = response.read(32769)
        challenge = response.headers.get('Payment-Required')
        decoded, decode_error = None, None
        if challenge:
            try:
                if len(challenge) > 65536:
                    raise ValueError('Challenge exceeds the bounded capture size.')
                decoded = json.loads(base64.b64decode(challenge, validate=True))
            except (ValueError, UnicodeError) as error:
                decode_error = str(error)
        return {'url':url, 'method':'GET', 'checkedAt':datetime.datetime.now(datetime.timezone.utc).isoformat(),
                'httpStatus':response.code, 'paymentRequired':decoded, 'decodeError':decode_error,
                'bodyPrefixSha256':hashlib.sha256(body[:32768]).hexdigest(), 'bodyTruncated':len(body)>32768,
                'paymentSent':False, 'redirectFollowed':False,
                'limitation':'Unpaid public GET only; no settlement, paid output or recipient ownership verified.'}


if __name__ == '__main__':
    try:
        if len(sys.argv) != 2:
            raise ValueError('Usage: python3 probe.py HTTPS_URL')
        print(json.dumps(capture(sys.argv[1]), indent=2))
    except Exception as error:
        print(json.dumps({'error':str(error),'paymentSent':False}), file=sys.stderr)
        sys.exit(1)
