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