1"""
2:mod:`websockets.exceptions` defines the following exception hierarchy:
3
4* :exc:`WebSocketException`
5    * :exc:`ConnectionClosed`
6        * :exc:`ConnectionClosedError`
7        * :exc:`ConnectionClosedOK`
8    * :exc:`InvalidHandshake`
9        * :exc:`SecurityError`
10        * :exc:`InvalidMessage`
11        * :exc:`InvalidHeader`
12            * :exc:`InvalidHeaderFormat`
13            * :exc:`InvalidHeaderValue`
14            * :exc:`InvalidOrigin`
15            * :exc:`InvalidUpgrade`
16        * :exc:`InvalidStatus`
17        * :exc:`InvalidStatusCode` (legacy)
18        * :exc:`NegotiationError`
19            * :exc:`DuplicateParameter`
20            * :exc:`InvalidParameterName`
21            * :exc:`InvalidParameterValue`
22        * :exc:`AbortHandshake`
23        * :exc:`RedirectHandshake`
24    * :exc:`InvalidState`
25    * :exc:`InvalidURI`
26    * :exc:`PayloadTooBig`
27    * :exc:`ProtocolError`
28
29"""
30
31from __future__ import annotations
32
33import http
34from typing import Optional
35
36from . import datastructures, frames, http11
37
38
39__all__ = [
40    "WebSocketException",
41    "ConnectionClosed",
42    "ConnectionClosedError",
43    "ConnectionClosedOK",
44    "InvalidHandshake",
45    "SecurityError",
46    "InvalidMessage",
47    "InvalidHeader",
48    "InvalidHeaderFormat",
49    "InvalidHeaderValue",
50    "InvalidOrigin",
51    "InvalidUpgrade",
52    "InvalidStatus",
53    "InvalidStatusCode",
54    "NegotiationError",
55    "DuplicateParameter",
56    "InvalidParameterName",
57    "InvalidParameterValue",
58    "AbortHandshake",
59    "RedirectHandshake",
60    "InvalidState",
61    "InvalidURI",
62    "PayloadTooBig",
63    "ProtocolError",
64    "WebSocketProtocolError",
65]
66
67
68class WebSocketException(Exception):
69    """
70    Base class for all exceptions defined by websockets.
71
72    """
73
74
75class ConnectionClosed(WebSocketException):
76    """
77    Raised when trying to interact with a closed connection.
78
79    Attributes:
80        rcvd (Optional[Close]): if a close frame was received, its code and
81            reason are available in ``rcvd.code`` and ``rcvd.reason``.
82        sent (Optional[Close]): if a close frame was sent, its code and reason
83            are available in ``sent.code`` and ``sent.reason``.
84        rcvd_then_sent (Optional[bool]): if close frames were received and
85            sent, this attribute tells in which order this happened, from the
86            perspective of this side of the connection.
87
88    """
89
90    def __init__(
91        self,
92        rcvd: Optional[frames.Close],
93        sent: Optional[frames.Close],
94        rcvd_then_sent: Optional[bool] = None,
95    ) -> None:
96        self.rcvd = rcvd
97        self.sent = sent
98        self.rcvd_then_sent = rcvd_then_sent
99
100    def __str__(self) -> str:
101        if self.rcvd is None:
102            if self.sent is None:
103                assert self.rcvd_then_sent is None
104                return "no close frame received or sent"
105            else:
106                assert self.rcvd_then_sent is None
107                return f"sent {self.sent}; no close frame received"
108        else:
109            if self.sent is None:
110                assert self.rcvd_then_sent is None
111                return f"received {self.rcvd}; no close frame sent"
112            else:
113                assert self.rcvd_then_sent is not None
114                if self.rcvd_then_sent:
115                    return f"received {self.rcvd}; then sent {self.sent}"
116                else:
117                    return f"sent {self.sent}; then received {self.rcvd}"
118
119    # code and reason attributes are provided for backwards-compatibility
120
121    @property
122    def code(self) -> int:
123        return 1006 if self.rcvd is None else self.rcvd.code
124
125    @property
126    def reason(self) -> str:
127        return "" if self.rcvd is None else self.rcvd.reason
128
129
130class ConnectionClosedError(ConnectionClosed):
131    """
132    Like :exc:`ConnectionClosed`, when the connection terminated with an error.
133
134    A close code other than 1000 (OK) or 1001 (going away) was received or
135    sent, or the closing handshake didn't complete properly.
136
137    """
138
139
140class ConnectionClosedOK(ConnectionClosed):
141    """
142    Like :exc:`ConnectionClosed`, when the connection terminated properly.
143
144    A close code 1000 (OK) or 1001 (going away) was received and sent.
145
146    """
147
148
149class InvalidHandshake(WebSocketException):
150    """
151    Raised during the handshake when the WebSocket connection fails.
152
153    """
154
155
156class SecurityError(InvalidHandshake):
157    """
158    Raised when a handshake request or response breaks a security rule.
159
160    Security limits are hard coded.
161
162    """
163
164
165class InvalidMessage(InvalidHandshake):
166    """
167    Raised when a handshake request or response is malformed.
168
169    """
170
171
172class InvalidHeader(InvalidHandshake):
173    """
174    Raised when a HTTP header doesn't have a valid format or value.
175
176    """
177
178    def __init__(self, name: str, value: Optional[str] = None) -> None:
179        self.name = name
180        self.value = value
181
182    def __str__(self) -> str:
183        if self.value is None:
184            return f"missing {self.name} header"
185        elif self.value == "":
186            return f"empty {self.name} header"
187        else:
188            return f"invalid {self.name} header: {self.value}"
189
190
191class InvalidHeaderFormat(InvalidHeader):
192    """
193    Raised when a HTTP header cannot be parsed.
194
195    The format of the header doesn't match the grammar for that header.
196
197    """
198
199    def __init__(self, name: str, error: str, header: str, pos: int) -> None:
200        super().__init__(name, f"{error} at {pos} in {header}")
201
202
203class InvalidHeaderValue(InvalidHeader):
204    """
205    Raised when a HTTP header has a wrong value.
206
207    The format of the header is correct but a value isn't acceptable.
208
209    """
210
211
212class InvalidOrigin(InvalidHeader):
213    """
214    Raised when the Origin header in a request isn't allowed.
215
216    """
217
218    def __init__(self, origin: Optional[str]) -> None:
219        super().__init__("Origin", origin)
220
221
222class InvalidUpgrade(InvalidHeader):
223    """
224    Raised when the Upgrade or Connection header isn't correct.
225
226    """
227
228
229class InvalidStatus(InvalidHandshake):
230    """
231    Raised when a handshake response rejects the WebSocket upgrade.
232
233    """
234
235    def __init__(self, response: http11.Response) -> None:
236        self.response = response
237
238    def __str__(self) -> str:
239        return (
240            "server rejected WebSocket connection: "
241            f"HTTP {self.response.status_code:d}"
242        )
243
244
245class InvalidStatusCode(InvalidHandshake):
246    """
247    Raised when a handshake response status code is invalid.
248
249    """
250
251    def __init__(self, status_code: int, headers: datastructures.Headers) -> None:
252        self.status_code = status_code
253        self.headers = headers
254
255    def __str__(self) -> str:
256        return f"server rejected WebSocket connection: HTTP {self.status_code}"
257
258
259class NegotiationError(InvalidHandshake):
260    """
261    Raised when negotiating an extension fails.
262
263    """
264
265
266class DuplicateParameter(NegotiationError):
267    """
268    Raised when a parameter name is repeated in an extension header.
269
270    """
271
272    def __init__(self, name: str) -> None:
273        self.name = name
274
275    def __str__(self) -> str:
276        return f"duplicate parameter: {self.name}"
277
278
279class InvalidParameterName(NegotiationError):
280    """
281    Raised when a parameter name in an extension header is invalid.
282
283    """
284
285    def __init__(self, name: str) -> None:
286        self.name = name
287
288    def __str__(self) -> str:
289        return f"invalid parameter name: {self.name}"
290
291
292class InvalidParameterValue(NegotiationError):
293    """
294    Raised when a parameter value in an extension header is invalid.
295
296    """
297
298    def __init__(self, name: str, value: Optional[str]) -> None:
299        self.name = name
300        self.value = value
301
302    def __str__(self) -> str:
303        if self.value is None:
304            return f"missing value for parameter {self.name}"
305        elif self.value == "":
306            return f"empty value for parameter {self.name}"
307        else:
308            return f"invalid value for parameter {self.name}: {self.value}"
309
310
311class AbortHandshake(InvalidHandshake):
312    """
313    Raised to abort the handshake on purpose and return a HTTP response.
314
315    This exception is an implementation detail.
316
317    The public API
318    is :meth:`~websockets.server.WebSocketServerProtocol.process_request`.
319
320    Attributes:
321        status (~http.HTTPStatus): HTTP status code.
322        headers (Headers): HTTP response headers.
323        body (bytes): HTTP response body.
324    """
325
326    def __init__(
327        self,
328        status: http.HTTPStatus,
329        headers: datastructures.HeadersLike,
330        body: bytes = b"",
331    ) -> None:
332        self.status = status
333        self.headers = datastructures.Headers(headers)
334        self.body = body
335
336    def __str__(self) -> str:
337        return (
338            f"HTTP {self.status:d}, "
339            f"{len(self.headers)} headers, "
340            f"{len(self.body)} bytes"
341        )
342
343
344class RedirectHandshake(InvalidHandshake):
345    """
346    Raised when a handshake gets redirected.
347
348    This exception is an implementation detail.
349
350    """
351
352    def __init__(self, uri: str) -> None:
353        self.uri = uri
354
355    def __str__(self) -> str:
356        return f"redirect to {self.uri}"
357
358
359class InvalidState(WebSocketException, AssertionError):
360    """
361    Raised when an operation is forbidden in the current state.
362
363    This exception is an implementation detail.
364
365    It should never be raised in normal circumstances.
366
367    """
368
369
370class InvalidURI(WebSocketException):
371    """
372    Raised when connecting to an URI that isn't a valid WebSocket URI.
373
374    """
375
376    def __init__(self, uri: str, msg: str) -> None:
377        self.uri = uri
378        self.msg = msg
379
380    def __str__(self) -> str:
381        return f"{self.uri} isn't a valid URI: {self.msg}"
382
383
384class PayloadTooBig(WebSocketException):
385    """
386    Raised when receiving a frame with a payload exceeding the maximum size.
387
388    """
389
390
391class ProtocolError(WebSocketException):
392    """
393    Raised when a frame breaks the protocol.
394
395    """
396
397
398WebSocketProtocolError = ProtocolError  # for backwards compatibility
399