Загрузить файлы в «venv/Lib/site-packages/websockets/legacy»
This commit is contained in:
224
venv/Lib/site-packages/websockets/legacy/framing.py
Normal file
224
venv/Lib/site-packages/websockets/legacy/framing.py
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import struct
|
||||||
|
from collections.abc import Awaitable, Sequence
|
||||||
|
from typing import Any, Callable, NamedTuple
|
||||||
|
|
||||||
|
from .. import extensions, frames
|
||||||
|
from ..exceptions import PayloadTooBig, ProtocolError
|
||||||
|
from ..typing import BytesLike, DataLike
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ..speedups import apply_mask
|
||||||
|
except ImportError:
|
||||||
|
from ..utils import apply_mask
|
||||||
|
|
||||||
|
|
||||||
|
class Frame(NamedTuple):
|
||||||
|
fin: bool
|
||||||
|
opcode: frames.Opcode
|
||||||
|
data: BytesLike
|
||||||
|
rsv1: bool = False
|
||||||
|
rsv2: bool = False
|
||||||
|
rsv3: bool = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def new_frame(self) -> frames.Frame:
|
||||||
|
return frames.Frame(
|
||||||
|
self.opcode,
|
||||||
|
self.data,
|
||||||
|
self.fin,
|
||||||
|
self.rsv1,
|
||||||
|
self.rsv2,
|
||||||
|
self.rsv3,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.new_frame)
|
||||||
|
|
||||||
|
def check(self) -> None:
|
||||||
|
return self.new_frame.check()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def read(
|
||||||
|
cls,
|
||||||
|
reader: Callable[[int], Awaitable[bytes]],
|
||||||
|
*,
|
||||||
|
mask: bool,
|
||||||
|
max_size: int | None = None,
|
||||||
|
extensions: Sequence[extensions.Extension] | None = None,
|
||||||
|
) -> Frame:
|
||||||
|
"""
|
||||||
|
Read a WebSocket frame.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reader: Coroutine that reads exactly the requested number of
|
||||||
|
bytes, unless the end of file is reached.
|
||||||
|
mask: Whether the frame should be masked i.e. whether the read
|
||||||
|
happens on the server side.
|
||||||
|
max_size: Maximum payload size in bytes.
|
||||||
|
extensions: List of extensions, applied in reverse order.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
PayloadTooBig: If the frame exceeds ``max_size``.
|
||||||
|
ProtocolError: If the frame contains incorrect values.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Read the header.
|
||||||
|
data = await reader(2)
|
||||||
|
head1, head2 = struct.unpack("!BB", data)
|
||||||
|
|
||||||
|
# While not Pythonic, this is marginally faster than calling bool().
|
||||||
|
fin = True if head1 & 0b10000000 else False
|
||||||
|
rsv1 = True if head1 & 0b01000000 else False
|
||||||
|
rsv2 = True if head1 & 0b00100000 else False
|
||||||
|
rsv3 = True if head1 & 0b00010000 else False
|
||||||
|
|
||||||
|
try:
|
||||||
|
opcode = frames.Opcode(head1 & 0b00001111)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ProtocolError("invalid opcode") from exc
|
||||||
|
|
||||||
|
if (True if head2 & 0b10000000 else False) != mask:
|
||||||
|
raise ProtocolError("incorrect masking")
|
||||||
|
|
||||||
|
length = head2 & 0b01111111
|
||||||
|
if length == 126:
|
||||||
|
data = await reader(2)
|
||||||
|
(length,) = struct.unpack("!H", data)
|
||||||
|
elif length == 127:
|
||||||
|
data = await reader(8)
|
||||||
|
(length,) = struct.unpack("!Q", data)
|
||||||
|
if max_size is not None and length > max_size:
|
||||||
|
raise PayloadTooBig(length, max_size)
|
||||||
|
if mask:
|
||||||
|
mask_bits = await reader(4)
|
||||||
|
|
||||||
|
# Read the data.
|
||||||
|
data = await reader(length)
|
||||||
|
if mask:
|
||||||
|
data = apply_mask(data, mask_bits)
|
||||||
|
|
||||||
|
new_frame = frames.Frame(opcode, data, fin, rsv1, rsv2, rsv3)
|
||||||
|
|
||||||
|
if extensions is None:
|
||||||
|
extensions = []
|
||||||
|
for extension in reversed(extensions):
|
||||||
|
new_frame = extension.decode(new_frame, max_size=max_size)
|
||||||
|
|
||||||
|
new_frame.check()
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
new_frame.fin,
|
||||||
|
new_frame.opcode,
|
||||||
|
new_frame.data,
|
||||||
|
new_frame.rsv1,
|
||||||
|
new_frame.rsv2,
|
||||||
|
new_frame.rsv3,
|
||||||
|
)
|
||||||
|
|
||||||
|
def write(
|
||||||
|
self,
|
||||||
|
write: Callable[[bytes], Any],
|
||||||
|
*,
|
||||||
|
mask: bool,
|
||||||
|
extensions: Sequence[extensions.Extension] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Write a WebSocket frame.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: Frame to write.
|
||||||
|
write: Function that writes bytes.
|
||||||
|
mask: Whether the frame should be masked i.e. whether the write
|
||||||
|
happens on the client side.
|
||||||
|
extensions: List of extensions, applied in order.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ProtocolError: If the frame contains incorrect values.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# The frame is written in a single call to write in order to prevent
|
||||||
|
# TCP fragmentation. See #68 for details. This also makes it safe to
|
||||||
|
# send frames concurrently from multiple coroutines.
|
||||||
|
write(self.new_frame.serialize(mask=mask, extensions=extensions))
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_data(data: DataLike) -> tuple[int, BytesLike]:
|
||||||
|
"""
|
||||||
|
Convert a string or byte-like object to an opcode and a bytes-like object.
|
||||||
|
|
||||||
|
This function is designed for data frames.
|
||||||
|
|
||||||
|
If ``data`` is a :class:`str`, return ``OP_TEXT`` and a :class:`bytes`
|
||||||
|
object encoding ``data`` in UTF-8.
|
||||||
|
|
||||||
|
If ``data`` is a bytes-like object, return ``OP_BINARY`` and a bytes-like
|
||||||
|
object.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: If ``data`` doesn't have a supported type.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if isinstance(data, str):
|
||||||
|
return frames.Opcode.TEXT, data.encode()
|
||||||
|
elif isinstance(data, BytesLike):
|
||||||
|
return frames.Opcode.BINARY, data
|
||||||
|
else:
|
||||||
|
raise TypeError("data must be str or bytes-like")
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_ctrl(data: DataLike) -> bytes:
|
||||||
|
"""
|
||||||
|
Convert a string or byte-like object to bytes.
|
||||||
|
|
||||||
|
This function is designed for ping and pong frames.
|
||||||
|
|
||||||
|
If ``data`` is a :class:`str`, return a :class:`bytes` object encoding
|
||||||
|
``data`` in UTF-8.
|
||||||
|
|
||||||
|
If ``data`` is a bytes-like object, return a :class:`bytes` object.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: If ``data`` doesn't have a supported type.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if isinstance(data, str):
|
||||||
|
return data.encode()
|
||||||
|
elif isinstance(data, BytesLike):
|
||||||
|
return bytes(data)
|
||||||
|
else:
|
||||||
|
raise TypeError("data must be str or bytes-like")
|
||||||
|
|
||||||
|
|
||||||
|
# Backwards compatibility with previously documented public APIs
|
||||||
|
encode_data = prepare_ctrl
|
||||||
|
|
||||||
|
# Backwards compatibility with previously documented public APIs
|
||||||
|
from ..frames import Close # noqa: E402 F401, I001
|
||||||
|
|
||||||
|
|
||||||
|
def parse_close(data: bytes) -> tuple[int, str]:
|
||||||
|
"""
|
||||||
|
Parse the payload from a close frame.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Close code and reason.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ProtocolError: If data is ill-formed.
|
||||||
|
UnicodeDecodeError: If the reason isn't valid UTF-8.
|
||||||
|
|
||||||
|
"""
|
||||||
|
close = Close.parse(data)
|
||||||
|
return close.code, close.reason
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_close(code: int, reason: str) -> bytes:
|
||||||
|
"""
|
||||||
|
Serialize the payload for a close frame.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return Close(code, reason).serialize()
|
||||||
158
venv/Lib/site-packages/websockets/legacy/handshake.py
Normal file
158
venv/Lib/site-packages/websockets/legacy/handshake.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import binascii
|
||||||
|
|
||||||
|
from ..datastructures import Headers, MultipleValuesError
|
||||||
|
from ..exceptions import InvalidHeader, InvalidHeaderValue, InvalidUpgrade
|
||||||
|
from ..headers import parse_connection, parse_upgrade
|
||||||
|
from ..typing import ConnectionOption, UpgradeProtocol
|
||||||
|
from ..utils import accept_key as accept, generate_key
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["build_request", "check_request", "build_response", "check_response"]
|
||||||
|
|
||||||
|
|
||||||
|
def build_request(headers: Headers) -> str:
|
||||||
|
"""
|
||||||
|
Build a handshake request to send to the server.
|
||||||
|
|
||||||
|
Update request headers passed in argument.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
headers: Handshake request headers.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
``key`` that must be passed to :func:`check_response`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = generate_key()
|
||||||
|
headers["Upgrade"] = "websocket"
|
||||||
|
headers["Connection"] = "Upgrade"
|
||||||
|
headers["Sec-WebSocket-Key"] = key
|
||||||
|
headers["Sec-WebSocket-Version"] = "13"
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def check_request(headers: Headers) -> str:
|
||||||
|
"""
|
||||||
|
Check a handshake request received from the client.
|
||||||
|
|
||||||
|
This function doesn't verify that the request is an HTTP/1.1 or higher GET
|
||||||
|
request and doesn't perform ``Host`` and ``Origin`` checks. These controls
|
||||||
|
are usually performed earlier in the HTTP request handling code. They're
|
||||||
|
the responsibility of the caller.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
headers: Handshake request headers.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
``key`` that must be passed to :func:`build_response`.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
InvalidHandshake: If the handshake request is invalid.
|
||||||
|
Then, the server must return a 400 Bad Request error.
|
||||||
|
|
||||||
|
"""
|
||||||
|
connection: list[ConnectionOption] = sum(
|
||||||
|
[parse_connection(value) for value in headers.get_all("Connection")], []
|
||||||
|
)
|
||||||
|
|
||||||
|
if not any(value.lower() == "upgrade" for value in connection):
|
||||||
|
raise InvalidUpgrade("Connection", ", ".join(connection))
|
||||||
|
|
||||||
|
upgrade: list[UpgradeProtocol] = sum(
|
||||||
|
[parse_upgrade(value) for value in headers.get_all("Upgrade")], []
|
||||||
|
)
|
||||||
|
|
||||||
|
# For compatibility with non-strict implementations, ignore case when
|
||||||
|
# checking the Upgrade header. The RFC always uses "websocket", except
|
||||||
|
# in section 11.2. (IANA registration) where it uses "WebSocket".
|
||||||
|
if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"):
|
||||||
|
raise InvalidUpgrade("Upgrade", ", ".join(upgrade))
|
||||||
|
|
||||||
|
try:
|
||||||
|
s_w_key = headers["Sec-WebSocket-Key"]
|
||||||
|
except KeyError as exc:
|
||||||
|
raise InvalidHeader("Sec-WebSocket-Key") from exc
|
||||||
|
except MultipleValuesError as exc:
|
||||||
|
raise InvalidHeader("Sec-WebSocket-Key", "multiple values") from exc
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw_key = base64.b64decode(s_w_key.encode(), validate=True)
|
||||||
|
except binascii.Error as exc:
|
||||||
|
raise InvalidHeaderValue("Sec-WebSocket-Key", s_w_key) from exc
|
||||||
|
if len(raw_key) != 16:
|
||||||
|
raise InvalidHeaderValue("Sec-WebSocket-Key", s_w_key)
|
||||||
|
|
||||||
|
try:
|
||||||
|
s_w_version = headers["Sec-WebSocket-Version"]
|
||||||
|
except KeyError as exc:
|
||||||
|
raise InvalidHeader("Sec-WebSocket-Version") from exc
|
||||||
|
except MultipleValuesError as exc:
|
||||||
|
raise InvalidHeader("Sec-WebSocket-Version", "multiple values") from exc
|
||||||
|
|
||||||
|
if s_w_version != "13":
|
||||||
|
raise InvalidHeaderValue("Sec-WebSocket-Version", s_w_version)
|
||||||
|
|
||||||
|
return s_w_key
|
||||||
|
|
||||||
|
|
||||||
|
def build_response(headers: Headers, key: str) -> None:
|
||||||
|
"""
|
||||||
|
Build a handshake response to send to the client.
|
||||||
|
|
||||||
|
Update response headers passed in argument.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
headers: Handshake response headers.
|
||||||
|
key: Returned by :func:`check_request`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
headers["Upgrade"] = "websocket"
|
||||||
|
headers["Connection"] = "Upgrade"
|
||||||
|
headers["Sec-WebSocket-Accept"] = accept(key)
|
||||||
|
|
||||||
|
|
||||||
|
def check_response(headers: Headers, key: str) -> None:
|
||||||
|
"""
|
||||||
|
Check a handshake response received from the server.
|
||||||
|
|
||||||
|
This function doesn't verify that the response is an HTTP/1.1 or higher
|
||||||
|
response with a 101 status code. These controls are the responsibility of
|
||||||
|
the caller.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
headers: Handshake response headers.
|
||||||
|
key: Returned by :func:`build_request`.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
InvalidHandshake: If the handshake response is invalid.
|
||||||
|
|
||||||
|
"""
|
||||||
|
connection: list[ConnectionOption] = sum(
|
||||||
|
[parse_connection(value) for value in headers.get_all("Connection")], []
|
||||||
|
)
|
||||||
|
|
||||||
|
if not any(value.lower() == "upgrade" for value in connection):
|
||||||
|
raise InvalidUpgrade("Connection", " ".join(connection))
|
||||||
|
|
||||||
|
upgrade: list[UpgradeProtocol] = sum(
|
||||||
|
[parse_upgrade(value) for value in headers.get_all("Upgrade")], []
|
||||||
|
)
|
||||||
|
|
||||||
|
# For compatibility with non-strict implementations, ignore case when
|
||||||
|
# checking the Upgrade header. The RFC always uses "websocket", except
|
||||||
|
# in section 11.2. (IANA registration) where it uses "WebSocket".
|
||||||
|
if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"):
|
||||||
|
raise InvalidUpgrade("Upgrade", ", ".join(upgrade))
|
||||||
|
|
||||||
|
try:
|
||||||
|
s_w_accept = headers["Sec-WebSocket-Accept"]
|
||||||
|
except KeyError as exc:
|
||||||
|
raise InvalidHeader("Sec-WebSocket-Accept") from exc
|
||||||
|
except MultipleValuesError as exc:
|
||||||
|
raise InvalidHeader("Sec-WebSocket-Accept", "multiple values") from exc
|
||||||
|
|
||||||
|
if s_w_accept != accept(key):
|
||||||
|
raise InvalidHeaderValue("Sec-WebSocket-Accept", s_w_accept)
|
||||||
201
venv/Lib/site-packages/websockets/legacy/http.py
Normal file
201
venv/Lib/site-packages/websockets/legacy/http.py
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ..datastructures import Headers
|
||||||
|
from ..exceptions import SecurityError
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["read_request", "read_response"]
|
||||||
|
|
||||||
|
MAX_NUM_HEADERS = int(os.environ.get("WEBSOCKETS_MAX_NUM_HEADERS", "128"))
|
||||||
|
MAX_LINE_LENGTH = int(os.environ.get("WEBSOCKETS_MAX_LINE_LENGTH", "8192"))
|
||||||
|
|
||||||
|
|
||||||
|
def d(value: bytes) -> str:
|
||||||
|
"""
|
||||||
|
Decode a bytestring for interpolating into an error message.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return value.decode(errors="backslashreplace")
|
||||||
|
|
||||||
|
|
||||||
|
# See https://datatracker.ietf.org/doc/html/rfc7230#appendix-B.
|
||||||
|
|
||||||
|
# Regex for validating header names.
|
||||||
|
|
||||||
|
_token_re = re.compile(rb"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+")
|
||||||
|
|
||||||
|
# Regex for validating header values.
|
||||||
|
|
||||||
|
# We don't attempt to support obsolete line folding.
|
||||||
|
|
||||||
|
# Include HTAB (\x09), SP (\x20), VCHAR (\x21-\x7e), obs-text (\x80-\xff).
|
||||||
|
|
||||||
|
# The ABNF is complicated because it attempts to express that optional
|
||||||
|
# whitespace is ignored. We strip whitespace and don't revalidate that.
|
||||||
|
|
||||||
|
# See also https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189
|
||||||
|
|
||||||
|
_value_re = re.compile(rb"[\x09\x20-\x7e\x80-\xff]*")
|
||||||
|
|
||||||
|
|
||||||
|
async def read_request(stream: asyncio.StreamReader) -> tuple[str, Headers]:
|
||||||
|
"""
|
||||||
|
Read an HTTP/1.1 GET request and return ``(path, headers)``.
|
||||||
|
|
||||||
|
``path`` isn't URL-decoded or validated in any way.
|
||||||
|
|
||||||
|
``path`` and ``headers`` are expected to contain only ASCII characters.
|
||||||
|
Other characters are represented with surrogate escapes.
|
||||||
|
|
||||||
|
:func:`read_request` doesn't attempt to read the request body because
|
||||||
|
WebSocket handshake requests don't have one. If the request contains a
|
||||||
|
body, it may be read from ``stream`` after this coroutine returns.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stream: Input to read the request from.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
EOFError: If the connection is closed without a full HTTP request.
|
||||||
|
SecurityError: If the request exceeds a security limit.
|
||||||
|
ValueError: If the request isn't well formatted.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.1
|
||||||
|
|
||||||
|
# Parsing is simple because fixed values are expected for method and
|
||||||
|
# version and because path isn't checked. Since WebSocket software tends
|
||||||
|
# to implement HTTP/1.1 strictly, there's little need for lenient parsing.
|
||||||
|
|
||||||
|
try:
|
||||||
|
request_line = await read_line(stream)
|
||||||
|
except EOFError as exc:
|
||||||
|
raise EOFError("connection closed while reading HTTP request line") from exc
|
||||||
|
|
||||||
|
try:
|
||||||
|
method, raw_path, version = request_line.split(b" ", 2)
|
||||||
|
except ValueError: # not enough values to unpack (expected 3, got 1-2)
|
||||||
|
raise ValueError(f"invalid HTTP request line: {d(request_line)}") from None
|
||||||
|
|
||||||
|
if method != b"GET":
|
||||||
|
raise ValueError(f"unsupported HTTP method: {d(method)}")
|
||||||
|
if version != b"HTTP/1.1":
|
||||||
|
raise ValueError(f"unsupported HTTP version: {d(version)}")
|
||||||
|
path = raw_path.decode("ascii", "surrogateescape")
|
||||||
|
|
||||||
|
headers = await read_headers(stream)
|
||||||
|
|
||||||
|
return path, headers
|
||||||
|
|
||||||
|
|
||||||
|
async def read_response(stream: asyncio.StreamReader) -> tuple[int, str, Headers]:
|
||||||
|
"""
|
||||||
|
Read an HTTP/1.1 response and return ``(status_code, reason, headers)``.
|
||||||
|
|
||||||
|
``reason`` and ``headers`` are expected to contain only ASCII characters.
|
||||||
|
Other characters are represented with surrogate escapes.
|
||||||
|
|
||||||
|
:func:`read_request` doesn't attempt to read the response body because
|
||||||
|
WebSocket handshake responses don't have one. If the response contains a
|
||||||
|
body, it may be read from ``stream`` after this coroutine returns.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stream: Input to read the response from.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
EOFError: If the connection is closed without a full HTTP response.
|
||||||
|
SecurityError: If the response exceeds a security limit.
|
||||||
|
ValueError: If the response isn't well formatted.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
|
||||||
|
|
||||||
|
# As in read_request, parsing is simple because a fixed value is expected
|
||||||
|
# for version, status_code is a 3-digit number, and reason can be ignored.
|
||||||
|
|
||||||
|
try:
|
||||||
|
status_line = await read_line(stream)
|
||||||
|
except EOFError as exc:
|
||||||
|
raise EOFError("connection closed while reading HTTP status line") from exc
|
||||||
|
|
||||||
|
try:
|
||||||
|
version, raw_status_code, raw_reason = status_line.split(b" ", 2)
|
||||||
|
except ValueError: # not enough values to unpack (expected 3, got 1-2)
|
||||||
|
raise ValueError(f"invalid HTTP status line: {d(status_line)}") from None
|
||||||
|
|
||||||
|
if version != b"HTTP/1.1":
|
||||||
|
raise ValueError(f"unsupported HTTP version: {d(version)}")
|
||||||
|
try:
|
||||||
|
status_code = int(raw_status_code)
|
||||||
|
except ValueError: # invalid literal for int() with base 10
|
||||||
|
raise ValueError(f"invalid HTTP status code: {d(raw_status_code)}") from None
|
||||||
|
if not 100 <= status_code < 1000:
|
||||||
|
raise ValueError(f"unsupported HTTP status code: {d(raw_status_code)}")
|
||||||
|
if not _value_re.fullmatch(raw_reason):
|
||||||
|
raise ValueError(f"invalid HTTP reason phrase: {d(raw_reason)}")
|
||||||
|
reason = raw_reason.decode()
|
||||||
|
|
||||||
|
headers = await read_headers(stream)
|
||||||
|
|
||||||
|
return status_code, reason, headers
|
||||||
|
|
||||||
|
|
||||||
|
async def read_headers(stream: asyncio.StreamReader) -> Headers:
|
||||||
|
"""
|
||||||
|
Read HTTP headers from ``stream``.
|
||||||
|
|
||||||
|
Non-ASCII characters are represented with surrogate escapes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
|
||||||
|
|
||||||
|
# We don't attempt to support obsolete line folding.
|
||||||
|
|
||||||
|
headers = Headers()
|
||||||
|
for _ in range(MAX_NUM_HEADERS + 1):
|
||||||
|
try:
|
||||||
|
line = await read_line(stream)
|
||||||
|
except EOFError as exc:
|
||||||
|
raise EOFError("connection closed while reading HTTP headers") from exc
|
||||||
|
if line == b"":
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw_name, raw_value = line.split(b":", 1)
|
||||||
|
except ValueError: # not enough values to unpack (expected 2, got 1)
|
||||||
|
raise ValueError(f"invalid HTTP header line: {d(line)}") from None
|
||||||
|
if not _token_re.fullmatch(raw_name):
|
||||||
|
raise ValueError(f"invalid HTTP header name: {d(raw_name)}")
|
||||||
|
raw_value = raw_value.strip(b" \t")
|
||||||
|
if not _value_re.fullmatch(raw_value):
|
||||||
|
raise ValueError(f"invalid HTTP header value: {d(raw_value)}")
|
||||||
|
|
||||||
|
name = raw_name.decode("ascii") # guaranteed to be ASCII at this point
|
||||||
|
value = raw_value.decode("ascii", "surrogateescape")
|
||||||
|
headers[name] = value
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise SecurityError("too many HTTP headers")
|
||||||
|
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
async def read_line(stream: asyncio.StreamReader) -> bytes:
|
||||||
|
"""
|
||||||
|
Read a single line from ``stream``.
|
||||||
|
|
||||||
|
CRLF is stripped from the return value.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Security: this is bounded by the StreamReader's limit (default = 32 KiB).
|
||||||
|
line = await stream.readline()
|
||||||
|
# Security: this guarantees header values are small (hard-coded = 8 KiB)
|
||||||
|
if len(line) > MAX_LINE_LENGTH:
|
||||||
|
raise SecurityError("line too long")
|
||||||
|
# Not mandatory but safe - https://datatracker.ietf.org/doc/html/rfc7230#section-3.5
|
||||||
|
if not line.endswith(b"\r\n"):
|
||||||
|
raise EOFError("line without CRLF")
|
||||||
|
return line[:-2]
|
||||||
1635
venv/Lib/site-packages/websockets/legacy/protocol.py
Normal file
1635
venv/Lib/site-packages/websockets/legacy/protocol.py
Normal file
File diff suppressed because it is too large
Load Diff
1191
venv/Lib/site-packages/websockets/legacy/server.py
Normal file
1191
venv/Lib/site-packages/websockets/legacy/server.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user