← Files ClaraARCHIVED FILE

scripts/public_http.py

9.01 KB · Oct 3, 2026 · 06:30 UTC

↓ Download file

"""Public GET transport adapted from the tested image-hydration client.

Connect only to addresses returned by the caller's public-target validator.
TLS authenticates the original hostname; environment proxies are not used.
"""

from __future__ import annotations

import http.client
import ipaddress
import math
import socket
import ssl
import threading
import time
import urllib.parse
import urllib.request
from collections.abc import Callable
from typing import Any

__all__ = ["open_public_url"]
MAX_REDIRECTS = 5


class _RequestDeadline:
    """Enforce a mechanical elapsed-time budget, including trickled HTTP reads.

    Socket shutdown interrupts buffered header/body reads. The caller's resolver
    is synchronous: elapsed DNS time consumes the budget but cannot be interrupted
    here. No request proceeds after a resolver returns beyond the deadline.
    """

    def __init__(self, timeout: float) -> None:
        if not math.isfinite(timeout) or timeout <= 0:
            raise ValueError("Public evidence timeout must be finite and positive")
        self._expires = time.monotonic() + timeout
        self._lock = threading.Lock()
        self._socket: socket.socket | None = None
        self._closed = False
        self._timer = threading.Timer(timeout, self._interrupt)
        self._timer.daemon = True
        self._timer.start()

    def remaining(self) -> float:
        remaining = self._expires - time.monotonic()
        if remaining <= 0:
            raise TimeoutError(
                "Public evidence request exceeded its elapsed-time budget"
            )
        return remaining

    def watch(self, connection: socket.socket) -> None:
        with self._lock:
            self.remaining()
            self._socket = connection

    def _interrupt(self) -> None:
        with self._lock:
            if not self._closed and self._socket is not None:
                try:
                    self._socket.shutdown(socket.SHUT_RDWR)
                except OSError:
                    # The owning HTTP connection may already have closed it.
                    pass

    def close(self) -> None:
        with self._lock:
            self._closed = True
            if self._socket is not None:
                self._socket.close()
            self._socket = None
            self._timer.cancel()


def _connect_pinned_socket(
    address: str, port: int, timeout: float, *, deadline: _RequestDeadline
) -> socket.socket:
    """Connect to one already-vetted numeric address without resolving again."""

    parsed = ipaddress.ip_address(address)
    family = socket.AF_INET6 if parsed.version == 6 else socket.AF_INET
    target: tuple[Any, ...] = (
        (address, port, 0, 0) if family == socket.AF_INET6 else (address, port)
    )
    connection = socket.socket(family, socket.SOCK_STREAM)
    try:
        deadline.watch(connection)
        connection.settimeout(timeout)
        connection.connect(target)
    except OSError:
        connection.close()
        raise
    return connection


class _PinnedResponse:
    """Small response wrapper that owns its pinned HTTP connection."""

    def __init__(
        self,
        response: http.client.HTTPResponse,
        connection: http.client.HTTPConnection,
        final_url: str,
        deadline: _RequestDeadline,
    ) -> None:
        self._response = response
        self._connection = connection
        self._final_url = final_url
        self._deadline = deadline
        self.headers = response.headers
        self.status = response.status

    def read(self, amount: int = -1) -> bytes:
        self._deadline.remaining()
        try:
            data = self._response.read(amount)
        finally:
            # A shutdown can look like ordinary EOF for an unframed response.
            # Never accept that partial body as a completed capture.
            self._deadline.remaining()
        return data

    def geturl(self) -> str:
        return self._final_url

    def close(self) -> None:
        self._deadline.close()
        try:
            self._response.close()
        finally:
            self._connection.close()

    def __enter__(self) -> _PinnedResponse:
        return self

    def __exit__(self, *args: object) -> None:
        self.close()


def _open_pinned_connection(
    *,
    scheme: str,
    hostname: str,
    port: int,
    address: str,
    timeout: float,
    deadline: _RequestDeadline,
) -> http.client.HTTPConnection:
    raw_socket = _connect_pinned_socket(address, port, timeout, deadline=deadline)
    if scheme == "https":
        context = ssl.create_default_context()
        connected_socket = None
        try:
            raw_socket.settimeout(deadline.remaining())
            connected_socket = context.wrap_socket(
                raw_socket,
                server_hostname=hostname,
            )
            deadline.watch(connected_socket)
        except (OSError, ssl.SSLError):
            if connected_socket is not None:
                connected_socket.close()
            raw_socket.close()
            raise
        connection: http.client.HTTPConnection = http.client.HTTPSConnection(
            hostname,
            port,
            timeout=timeout,
            context=context,
        )
        connection.sock = connected_socket
        return connection
    connection = http.client.HTTPConnection(hostname, port, timeout=timeout)
    connection.sock = raw_socket
    return connection


def open_public_url(
    request: urllib.request.Request,
    timeout: float,
    resolver: Callable[[str], tuple[str, int, tuple[str, ...]]],
) -> _PinnedResponse:
    """Pin public targets and share one elapsed budget across HTTP operations.

    Synchronous resolver calls consume this budget but are not cancellable here.
    The returned response must be closed to release its socket/deadline timer.
    """

    deadline = _RequestDeadline(timeout)
    response = None
    try:
        response = _open_public_url(request, resolver, deadline)
        return response
    finally:
        if response is None:
            deadline.close()


def _open_public_url(
    request: urllib.request.Request,
    resolver: Callable[[str], tuple[str, int, tuple[str, ...]]],
    deadline: _RequestDeadline,
) -> _PinnedResponse:
    """Resolve and open within the response-owned deadline lifecycle."""
    if request.data is not None or request.get_method() != "GET":
        raise ValueError(
            "Public evidence hydration supports only bodyless GET requests"
        )
    current_url = request.full_url
    headers = dict(request.header_items())
    for redirect_count in range(MAX_REDIRECTS + 1):
        deadline.remaining()
        safe_url = current_url
        hostname, port, addresses = resolver(safe_url)
        deadline.remaining()
        parts = urllib.parse.urlsplit(safe_url)
        request_target = parts.path or "/"
        if parts.query:
            request_target = f"{request_target}?{parts.query}"
        response: http.client.HTTPResponse | None = None
        connection: http.client.HTTPConnection | None = None
        last_error: BaseException | None = None
        for address in addresses:
            try:
                connection = _open_pinned_connection(
                    scheme=parts.scheme,
                    hostname=hostname,
                    port=port,
                    address=address,
                    timeout=deadline.remaining(),
                    deadline=deadline,
                )
                connection.request("GET", request_target, headers=headers)
                response = connection.getresponse()
                deadline.remaining()
                break
            except (OSError, ssl.SSLError, http.client.HTTPException) as exc:
                last_error = exc
                if response is not None:
                    response.close()
                    response = None
                if connection is not None:
                    connection.close()
                connection = None
                deadline.remaining()
        if response is None or connection is None:
            raise ValueError(
                f"Public evidence host could not be reached through its vetted public addresses: "
                f"{hostname}"
            ) from last_error
        if response.status in {301, 302, 303, 307, 308}:
            location = str(response.headers.get("Location") or "").strip()
            response.close()
            connection.close()
            if not location:
                raise ValueError("Public evidence redirect has no destination")
            if redirect_count >= MAX_REDIRECTS:
                raise ValueError("Public evidence request exceeded the redirect limit")
            current_url = urllib.parse.urljoin(safe_url, location)
            continue
        if not 200 <= response.status < 300:
            status = response.status
            response.close()
            connection.close()
            raise ValueError(f"Public evidence request failed with HTTP {status}")
        return _PinnedResponse(response, connection, safe_url, deadline)
    raise ValueError("Public evidence request exceeded the redirect limit")

SHA-256: 0320cf3b5407c0805c453f2f81918c29b1eb33c32d7384855d5b39d2ae09c073