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