""" :mod:`websockets.exceptions` defines the following exception hierarchy: * :exc:`WebSocketException` * :exc:`ConnectionClosed` * :exc:`ConnectionClosedError` * :exc:`ConnectionClosedOK` * :exc:`InvalidHandshake` * :exc:`SecurityError` * :exc:`InvalidMessage` * :exc:`InvalidHeader` * :exc:`InvalidHeaderFormat` * :exc:`InvalidHeaderValue` * :exc:`InvalidOrigin` * :exc:`InvalidUpgrade` * :exc:`InvalidStatusCode` * :exc:`NegotiationError` * :exc:`DuplicateParameter` * :exc:`InvalidParameterName` * :exc:`InvalidParameterValue` * :exc:`AbortHandshake` * :exc:`RedirectHandshake` * :exc:`InvalidState` * :exc:`InvalidURI` * :exc:`PayloadTooBig` * :exc:`ProtocolError` """ import http from typing import Optional from .http import Headers, HeadersLike __all__ = [ "WebSocketException", "ConnectionClosed", "ConnectionClosedError", "ConnectionClosedOK", "InvalidHandshake", "SecurityError", "InvalidMessage", "InvalidHeader", "InvalidHeaderFormat", "InvalidHeaderValue", "InvalidOrigin", "InvalidUpgrade", "InvalidStatusCode", "NegotiationError", "DuplicateParameter", "InvalidParameterName", "InvalidParameterValue", "AbortHandshake", "RedirectHandshake", "InvalidState", "InvalidURI", "PayloadTooBig", "ProtocolError", "WebSocketProtocolError", ] class WebSocketException(Exception): """ Base class for all exceptions defined by :mod:`websockets`. """ CLOSE_CODES = { 1000: "OK", 1001: "going away", 1002: "protocol error", 1003: "unsupported type", # 1004 is reserved 1005: "no status code [internal]", 1006: "connection closed abnormally [internal]", 1007: "invalid data", 1008: "policy violation", 1009: "message too big", 1010: "extension required", 1011: "unexpected error", 1015: "TLS failure [internal]", } def format_close(code: int, reason: str) -> str: """ Display a human-readable version of the close code and reason. """ if 3000 <= code < 4000: explanation = "registered" elif 4000 <= code < 5000: explanation = "private use" else: explanation = CLOSE_CODES.get(code, "unknown") result = f"code = {code} ({explanation}), " if reason: result += f"reason = {reason}" else: result += "no reason" return result class ConnectionClosed(WebSocketException): """ Raised when trying to interact with a closed connection. Provides the connection close code and reason in its ``code`` and ``reason`` attributes respectively. """ def __init__(self, code: int, reason: str) -> None: self.code = code self.reason = reason super().__init__(format_close(code, reason)) class ConnectionClosedError(ConnectionClosed): """ Like :exc:`ConnectionClosed`, when the connection terminated with an error. This means the close code is different from 1000 (OK) and 1001 (going away). """ def __init__(self, code: int, reason: str) -> None: assert code != 1000 and code != 1001 super().__init__(code, reason) class ConnectionClosedOK(ConnectionClosed): """ Like :exc:`ConnectionClosed`, when the connection terminated properly. This means the close code is 1000 (OK) or 1001 (going away). """ def __init__(self, code: int, reason: str) -> None: assert code == 1000 or code == 1001 super().__init__(code, reason) class InvalidHandshake(WebSocketException): """ Raised during the handshake when the WebSocket connection fails. """ class SecurityError(InvalidHandshake): """ Raised when a handshake request or response breaks a security rule. Security limits are hard coded. """ class InvalidMessage(InvalidHandshake): """ Raised when a handshake request or response is malformed. """ class InvalidHeader(InvalidHandshake): """ Raised when a HTTP header doesn't have a valid format or value. """ def __init__(self, name: str, value: Optional[str] = None) -> None: self.name = name self.value = value if value is None: message = f"missing {name} header" elif value == "": message = f"empty {name} header" else: message = f"invalid {name} header: {value}" super().__init__(message) class InvalidHeaderFormat(InvalidHeader): """ Raised when a HTTP header cannot be parsed. The format of the header doesn't match the grammar for that header. """ def __init__(self, name: str, error: str, header: str, pos: int) -> None: self.name = name error = f"{error} at {pos} in {header}" super().__init__(name, error) class InvalidHeaderValue(InvalidHeader): """ Raised when a HTTP header has a wrong value. The format of the header is correct but a value isn't acceptable. """ class InvalidOrigin(InvalidHeader): """ Raised when the Origin header in a request isn't allowed. """ def __init__(self, origin: Optional[str]) -> None: super().__init__("Origin", origin) class InvalidUpgrade(InvalidHeader): """ Raised when the Upgrade or Connection header isn't correct. """ class InvalidStatusCode(InvalidHandshake): """ Raised when a handshake response status code is invalid. The integer status code is available in the ``status_code`` attribute. """ def __init__(self, status_code: int) -> None: self.status_code = status_code message = f"server rejected WebSocket connection: HTTP {status_code}" super().__init__(message) class NegotiationError(InvalidHandshake): """ Raised when negotiating an extension fails. """ class DuplicateParameter(NegotiationError): """ Raised when a parameter name is repeated in an extension header. """ def __init__(self, name: str) -> None: self.name = name message = f"duplicate parameter: {name}" super().__init__(message) class InvalidParameterName(NegotiationError): """ Raised when a parameter name in an extension header is invalid. """ def __init__(self, name: str) -> None: self.name = name message = f"invalid parameter name: {name}" super().__init__(message) class InvalidParameterValue(NegotiationError): """ Raised when a parameter value in an extension header is invalid. """ def __init__(self, name: str, value: Optional[str]) -> None: self.name = name self.value = value if value is None: message = f"missing value for parameter {name}" elif value == "": message = f"empty value for parameter {name}" else: message = f"invalid value for parameter {name}: {value}" super().__init__(message) class AbortHandshake(InvalidHandshake): """ Raised to abort the handshake on purpose and return a HTTP response. This exception is an implementation detail. The public API is :meth:`~server.WebSocketServerProtocol.process_request`. """ def __init__( self, status: http.HTTPStatus, headers: HeadersLike, body: bytes = b"" ) -> None: self.status = status self.headers = Headers(headers) self.body = body message = f"HTTP {status}, {len(self.headers)} headers, {len(body)} bytes" super().__init__(message) class RedirectHandshake(InvalidHandshake): """ Raised when a handshake gets redirected. This exception is an implementation detail. """ def __init__(self, uri: str) -> None: self.uri = uri def __str__(self) -> str: return f"redirect to {self.uri}" class InvalidState(WebSocketException, AssertionError): """ Raised when an operation is forbidden in the current state. This exception is an implementation detail. It should never be raised in normal circumstances. """ class InvalidURI(WebSocketException): """ Raised when connecting to an URI that isn't a valid WebSocket URI. """ def __init__(self, uri: str) -> None: self.uri = uri message = "{} isn't a valid URI".format(uri) super().__init__(message) class PayloadTooBig(WebSocketException): """ Raised when receiving a frame with a payload exceeding the maximum size. """ class ProtocolError(WebSocketException): """ Raised when the other side breaks the protocol. """ WebSocketProtocolError = ProtocolError # for backwards compatibility