Add verified browser login and human approval HTTP client
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
4e103f62a0
commit
0e48355b9f
16 changed files with 1365 additions and 39 deletions
73
informed_decision/http_transport.py
Normal file
73
informed_decision/http_transport.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
"""Bounded JSON transport for fixed, deployment-owned HTTPS endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import ipaddress
|
||||
from http.client import HTTPException
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import urlsplit
|
||||
from urllib.request import HTTPRedirectHandler, ProxyHandler, Request, build_opener
|
||||
|
||||
|
||||
class TransportError(Exception):
|
||||
"""Deliberately contains no response body, URL, token or request data."""
|
||||
|
||||
|
||||
def fixed_origin(value: str, *, allow_internal_http=False) -> str:
|
||||
parsed = urlsplit(value)
|
||||
internal_http = False
|
||||
if allow_internal_http and parsed.scheme == "http" and parsed.hostname:
|
||||
try:
|
||||
internal_http = ipaddress.ip_address(parsed.hostname).is_loopback
|
||||
except ValueError:
|
||||
internal_http = parsed.hostname.endswith((".svc", ".svc.cluster.local"))
|
||||
if ((parsed.scheme != "https" and not internal_http) or not parsed.hostname or parsed.username
|
||||
or parsed.password or parsed.path or parsed.query or parsed.fragment
|
||||
or any(c.isspace() for c in value)):
|
||||
raise ValueError("a fixed HTTPS origin without path or credentials is required")
|
||||
return value
|
||||
|
||||
|
||||
def https_origin(value: str) -> str:
|
||||
return fixed_origin(value)
|
||||
|
||||
|
||||
class _NoRedirect(HTTPRedirectHandler):
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
return None
|
||||
|
||||
|
||||
class JSONTransport:
|
||||
"""No redirects or ambient proxy credentials; five-second request timeout."""
|
||||
|
||||
def __init__(self, *, allow_internal_http=False) -> None:
|
||||
self.allow_internal_http = allow_internal_http
|
||||
self._opener = build_opener(ProxyHandler({}), _NoRedirect())
|
||||
|
||||
def request(self, method: str, url: str, *, headers=None, body=None) -> tuple[int, dict]:
|
||||
parsed = urlsplit(url)
|
||||
try:
|
||||
fixed_origin(parsed.scheme + "://" + parsed.netloc,
|
||||
allow_internal_http=self.allow_internal_http)
|
||||
except ValueError:
|
||||
raise TransportError("trusted transport required") from None
|
||||
req = Request(url, method=method, data=body, headers=headers or {})
|
||||
try:
|
||||
try:
|
||||
response = self._opener.open(req, timeout=5)
|
||||
except HTTPError as exc:
|
||||
response = exc
|
||||
with response:
|
||||
status = response.code
|
||||
if 300 <= status < 400:
|
||||
raise TransportError("upstream redirect refused")
|
||||
raw = response.read(262145)
|
||||
if len(raw) > 262144:
|
||||
raise TransportError("upstream response too large")
|
||||
result = json.loads(raw)
|
||||
if not isinstance(result, dict):
|
||||
raise TransportError("upstream object required")
|
||||
return status, result
|
||||
except (URLError, OSError, ValueError, HTTPException):
|
||||
raise TransportError("upstream request failed") from None
|
||||
Loading…
Add table
Add a link
Reference in a new issue