jpayne@68: from __future__ import annotations jpayne@68: jpayne@68: import socket jpayne@68: import typing jpayne@68: import warnings jpayne@68: from email.errors import MessageDefect jpayne@68: from http.client import IncompleteRead as httplib_IncompleteRead jpayne@68: jpayne@68: if typing.TYPE_CHECKING: jpayne@68: from .connection import HTTPConnection jpayne@68: from .connectionpool import ConnectionPool jpayne@68: from .response import HTTPResponse jpayne@68: from .util.retry import Retry jpayne@68: jpayne@68: # Base Exceptions jpayne@68: jpayne@68: jpayne@68: class HTTPError(Exception): jpayne@68: """Base exception used by this module.""" jpayne@68: jpayne@68: jpayne@68: class HTTPWarning(Warning): jpayne@68: """Base warning used by this module.""" jpayne@68: jpayne@68: jpayne@68: _TYPE_REDUCE_RESULT = typing.Tuple[ jpayne@68: typing.Callable[..., object], typing.Tuple[object, ...] jpayne@68: ] jpayne@68: jpayne@68: jpayne@68: class PoolError(HTTPError): jpayne@68: """Base exception for errors caused within a pool.""" jpayne@68: jpayne@68: def __init__(self, pool: ConnectionPool, message: str) -> None: jpayne@68: self.pool = pool jpayne@68: super().__init__(f"{pool}: {message}") jpayne@68: jpayne@68: def __reduce__(self) -> _TYPE_REDUCE_RESULT: jpayne@68: # For pickling purposes. jpayne@68: return self.__class__, (None, None) jpayne@68: jpayne@68: jpayne@68: class RequestError(PoolError): jpayne@68: """Base exception for PoolErrors that have associated URLs.""" jpayne@68: jpayne@68: def __init__(self, pool: ConnectionPool, url: str, message: str) -> None: jpayne@68: self.url = url jpayne@68: super().__init__(pool, message) jpayne@68: jpayne@68: def __reduce__(self) -> _TYPE_REDUCE_RESULT: jpayne@68: # For pickling purposes. jpayne@68: return self.__class__, (None, self.url, None) jpayne@68: jpayne@68: jpayne@68: class SSLError(HTTPError): jpayne@68: """Raised when SSL certificate fails in an HTTPS connection.""" jpayne@68: jpayne@68: jpayne@68: class ProxyError(HTTPError): jpayne@68: """Raised when the connection to a proxy fails.""" jpayne@68: jpayne@68: # The original error is also available as __cause__. jpayne@68: original_error: Exception jpayne@68: jpayne@68: def __init__(self, message: str, error: Exception) -> None: jpayne@68: super().__init__(message, error) jpayne@68: self.original_error = error jpayne@68: jpayne@68: jpayne@68: class DecodeError(HTTPError): jpayne@68: """Raised when automatic decoding based on Content-Type fails.""" jpayne@68: jpayne@68: jpayne@68: class ProtocolError(HTTPError): jpayne@68: """Raised when something unexpected happens mid-request/response.""" jpayne@68: jpayne@68: jpayne@68: #: Renamed to ProtocolError but aliased for backwards compatibility. jpayne@68: ConnectionError = ProtocolError jpayne@68: jpayne@68: jpayne@68: # Leaf Exceptions jpayne@68: jpayne@68: jpayne@68: class MaxRetryError(RequestError): jpayne@68: """Raised when the maximum number of retries is exceeded. jpayne@68: jpayne@68: :param pool: The connection pool jpayne@68: :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` jpayne@68: :param str url: The requested Url jpayne@68: :param reason: The underlying error jpayne@68: :type reason: :class:`Exception` jpayne@68: jpayne@68: """ jpayne@68: jpayne@68: def __init__( jpayne@68: self, pool: ConnectionPool, url: str, reason: Exception | None = None jpayne@68: ) -> None: jpayne@68: self.reason = reason jpayne@68: jpayne@68: message = f"Max retries exceeded with url: {url} (Caused by {reason!r})" jpayne@68: jpayne@68: super().__init__(pool, url, message) jpayne@68: jpayne@68: jpayne@68: class HostChangedError(RequestError): jpayne@68: """Raised when an existing pool gets a request for a foreign host.""" jpayne@68: jpayne@68: def __init__( jpayne@68: self, pool: ConnectionPool, url: str, retries: Retry | int = 3 jpayne@68: ) -> None: jpayne@68: message = f"Tried to open a foreign host with url: {url}" jpayne@68: super().__init__(pool, url, message) jpayne@68: self.retries = retries jpayne@68: jpayne@68: jpayne@68: class TimeoutStateError(HTTPError): jpayne@68: """Raised when passing an invalid state to a timeout""" jpayne@68: jpayne@68: jpayne@68: class TimeoutError(HTTPError): jpayne@68: """Raised when a socket timeout error occurs. jpayne@68: jpayne@68: Catching this error will catch both :exc:`ReadTimeoutErrors jpayne@68: ` and :exc:`ConnectTimeoutErrors `. jpayne@68: """ jpayne@68: jpayne@68: jpayne@68: class ReadTimeoutError(TimeoutError, RequestError): jpayne@68: """Raised when a socket timeout occurs while receiving data from a server""" jpayne@68: jpayne@68: jpayne@68: # This timeout error does not have a URL attached and needs to inherit from the jpayne@68: # base HTTPError jpayne@68: class ConnectTimeoutError(TimeoutError): jpayne@68: """Raised when a socket timeout occurs while connecting to a server""" jpayne@68: jpayne@68: jpayne@68: class NewConnectionError(ConnectTimeoutError, HTTPError): jpayne@68: """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" jpayne@68: jpayne@68: def __init__(self, conn: HTTPConnection, message: str) -> None: jpayne@68: self.conn = conn jpayne@68: super().__init__(f"{conn}: {message}") jpayne@68: jpayne@68: @property jpayne@68: def pool(self) -> HTTPConnection: jpayne@68: warnings.warn( jpayne@68: "The 'pool' property is deprecated and will be removed " jpayne@68: "in urllib3 v2.1.0. Use 'conn' instead.", jpayne@68: DeprecationWarning, jpayne@68: stacklevel=2, jpayne@68: ) jpayne@68: jpayne@68: return self.conn jpayne@68: jpayne@68: jpayne@68: class NameResolutionError(NewConnectionError): jpayne@68: """Raised when host name resolution fails.""" jpayne@68: jpayne@68: def __init__(self, host: str, conn: HTTPConnection, reason: socket.gaierror): jpayne@68: message = f"Failed to resolve '{host}' ({reason})" jpayne@68: super().__init__(conn, message) jpayne@68: jpayne@68: jpayne@68: class EmptyPoolError(PoolError): jpayne@68: """Raised when a pool runs out of connections and no more are allowed.""" jpayne@68: jpayne@68: jpayne@68: class FullPoolError(PoolError): jpayne@68: """Raised when we try to add a connection to a full pool in blocking mode.""" jpayne@68: jpayne@68: jpayne@68: class ClosedPoolError(PoolError): jpayne@68: """Raised when a request enters a pool after the pool has been closed.""" jpayne@68: jpayne@68: jpayne@68: class LocationValueError(ValueError, HTTPError): jpayne@68: """Raised when there is something wrong with a given URL input.""" jpayne@68: jpayne@68: jpayne@68: class LocationParseError(LocationValueError): jpayne@68: """Raised when get_host or similar fails to parse the URL input.""" jpayne@68: jpayne@68: def __init__(self, location: str) -> None: jpayne@68: message = f"Failed to parse: {location}" jpayne@68: super().__init__(message) jpayne@68: jpayne@68: self.location = location jpayne@68: jpayne@68: jpayne@68: class URLSchemeUnknown(LocationValueError): jpayne@68: """Raised when a URL input has an unsupported scheme.""" jpayne@68: jpayne@68: def __init__(self, scheme: str): jpayne@68: message = f"Not supported URL scheme {scheme}" jpayne@68: super().__init__(message) jpayne@68: jpayne@68: self.scheme = scheme jpayne@68: jpayne@68: jpayne@68: class ResponseError(HTTPError): jpayne@68: """Used as a container for an error reason supplied in a MaxRetryError.""" jpayne@68: jpayne@68: GENERIC_ERROR = "too many error responses" jpayne@68: SPECIFIC_ERROR = "too many {status_code} error responses" jpayne@68: jpayne@68: jpayne@68: class SecurityWarning(HTTPWarning): jpayne@68: """Warned when performing security reducing actions""" jpayne@68: jpayne@68: jpayne@68: class InsecureRequestWarning(SecurityWarning): jpayne@68: """Warned when making an unverified HTTPS request.""" jpayne@68: jpayne@68: jpayne@68: class NotOpenSSLWarning(SecurityWarning): jpayne@68: """Warned when using unsupported SSL library""" jpayne@68: jpayne@68: jpayne@68: class SystemTimeWarning(SecurityWarning): jpayne@68: """Warned when system time is suspected to be wrong""" jpayne@68: jpayne@68: jpayne@68: class InsecurePlatformWarning(SecurityWarning): jpayne@68: """Warned when certain TLS/SSL configuration is not available on a platform.""" jpayne@68: jpayne@68: jpayne@68: class DependencyWarning(HTTPWarning): jpayne@68: """ jpayne@68: Warned when an attempt is made to import a module with missing optional jpayne@68: dependencies. jpayne@68: """ jpayne@68: jpayne@68: jpayne@68: class ResponseNotChunked(ProtocolError, ValueError): jpayne@68: """Response needs to be chunked in order to read it as chunks.""" jpayne@68: jpayne@68: jpayne@68: class BodyNotHttplibCompatible(HTTPError): jpayne@68: """ jpayne@68: Body should be :class:`http.client.HTTPResponse` like jpayne@68: (have an fp attribute which returns raw chunks) for read_chunked(). jpayne@68: """ jpayne@68: jpayne@68: jpayne@68: class IncompleteRead(HTTPError, httplib_IncompleteRead): jpayne@68: """ jpayne@68: Response length doesn't match expected Content-Length jpayne@68: jpayne@68: Subclass of :class:`http.client.IncompleteRead` to allow int value jpayne@68: for ``partial`` to avoid creating large objects on streamed reads. jpayne@68: """ jpayne@68: jpayne@68: partial: int # type: ignore[assignment] jpayne@68: expected: int jpayne@68: jpayne@68: def __init__(self, partial: int, expected: int) -> None: jpayne@68: self.partial = partial jpayne@68: self.expected = expected jpayne@68: jpayne@68: def __repr__(self) -> str: jpayne@68: return "IncompleteRead(%i bytes read, %i more expected)" % ( jpayne@68: self.partial, jpayne@68: self.expected, jpayne@68: ) jpayne@68: jpayne@68: jpayne@68: class InvalidChunkLength(HTTPError, httplib_IncompleteRead): jpayne@68: """Invalid chunk length in a chunked response.""" jpayne@68: jpayne@68: def __init__(self, response: HTTPResponse, length: bytes) -> None: jpayne@68: self.partial: int = response.tell() # type: ignore[assignment] jpayne@68: self.expected: int | None = response.length_remaining jpayne@68: self.response = response jpayne@68: self.length = length jpayne@68: jpayne@68: def __repr__(self) -> str: jpayne@68: return "InvalidChunkLength(got length %r, %i bytes read)" % ( jpayne@68: self.length, jpayne@68: self.partial, jpayne@68: ) jpayne@68: jpayne@68: jpayne@68: class InvalidHeader(HTTPError): jpayne@68: """The header provided was somehow invalid.""" jpayne@68: jpayne@68: jpayne@68: class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): jpayne@68: """ProxyManager does not support the supplied scheme""" jpayne@68: jpayne@68: # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. jpayne@68: jpayne@68: def __init__(self, scheme: str | None) -> None: jpayne@68: # 'localhost' is here because our URL parser parses jpayne@68: # localhost:8080 -> scheme=localhost, remove if we fix this. jpayne@68: if scheme == "localhost": jpayne@68: scheme = None jpayne@68: if scheme is None: jpayne@68: message = "Proxy URL had no scheme, should start with http:// or https://" jpayne@68: else: jpayne@68: message = f"Proxy URL had unsupported scheme {scheme}, should use http:// or https://" jpayne@68: super().__init__(message) jpayne@68: jpayne@68: jpayne@68: class ProxySchemeUnsupported(ValueError): jpayne@68: """Fetching HTTPS resources through HTTPS proxies is unsupported""" jpayne@68: jpayne@68: jpayne@68: class HeaderParsingError(HTTPError): jpayne@68: """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" jpayne@68: jpayne@68: def __init__( jpayne@68: self, defects: list[MessageDefect], unparsed_data: bytes | str | None jpayne@68: ) -> None: jpayne@68: message = f"{defects or 'Unknown'}, unparsed data: {unparsed_data!r}" jpayne@68: super().__init__(message) jpayne@68: jpayne@68: jpayne@68: class UnrewindableBodyError(HTTPError): jpayne@68: """urllib3 encountered an error when trying to rewind a body"""