Загрузить файлы в «venv/Lib/site-packages/websockets»
This commit is contained in:
151
venv/Lib/site-packages/websockets/streams.py
Normal file
151
venv/Lib/site-packages/websockets/streams.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
|
||||
|
||||
class StreamReader:
|
||||
"""
|
||||
Generator-based stream reader.
|
||||
|
||||
This class doesn't support concurrent calls to :meth:`read_line`,
|
||||
:meth:`read_exact`, or :meth:`read_to_eof`. Make sure calls are
|
||||
serialized.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.buffer = bytearray()
|
||||
self.eof = False
|
||||
|
||||
def read_line(self, m: int) -> Generator[None, None, bytearray]:
|
||||
"""
|
||||
Read a LF-terminated line from the stream.
|
||||
|
||||
This is a generator-based coroutine.
|
||||
|
||||
The return value includes the LF character.
|
||||
|
||||
Args:
|
||||
m: Maximum number bytes to read; this is a security limit.
|
||||
|
||||
Raises:
|
||||
EOFError: If the stream ends without a LF.
|
||||
RuntimeError: If the stream ends in more than ``m`` bytes.
|
||||
|
||||
"""
|
||||
n = 0 # number of bytes to read
|
||||
p = 0 # number of bytes without a newline
|
||||
while True:
|
||||
n = self.buffer.find(b"\n", p) + 1
|
||||
if n > 0:
|
||||
break
|
||||
p = len(self.buffer)
|
||||
if p > m:
|
||||
raise RuntimeError(f"read {p} bytes, expected no more than {m} bytes")
|
||||
if self.eof:
|
||||
raise EOFError(f"stream ends after {p} bytes, before end of line")
|
||||
yield
|
||||
if n > m:
|
||||
raise RuntimeError(f"read {n} bytes, expected no more than {m} bytes")
|
||||
r = self.buffer[:n]
|
||||
del self.buffer[:n]
|
||||
return r
|
||||
|
||||
def read_exact(self, n: int) -> Generator[None, None, bytearray]:
|
||||
"""
|
||||
Read a given number of bytes from the stream.
|
||||
|
||||
This is a generator-based coroutine.
|
||||
|
||||
Args:
|
||||
n: How many bytes to read.
|
||||
|
||||
Raises:
|
||||
EOFError: If the stream ends in less than ``n`` bytes.
|
||||
|
||||
"""
|
||||
assert n >= 0
|
||||
while len(self.buffer) < n:
|
||||
if self.eof:
|
||||
p = len(self.buffer)
|
||||
raise EOFError(f"stream ends after {p} bytes, expected {n} bytes")
|
||||
yield
|
||||
r = self.buffer[:n]
|
||||
del self.buffer[:n]
|
||||
return r
|
||||
|
||||
def read_to_eof(self, m: int) -> Generator[None, None, bytearray]:
|
||||
"""
|
||||
Read all bytes from the stream.
|
||||
|
||||
This is a generator-based coroutine.
|
||||
|
||||
Args:
|
||||
m: Maximum number bytes to read; this is a security limit.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the stream ends in more than ``m`` bytes.
|
||||
|
||||
"""
|
||||
while not self.eof:
|
||||
p = len(self.buffer)
|
||||
if p > m:
|
||||
raise RuntimeError(f"read {p} bytes, expected no more than {m} bytes")
|
||||
yield
|
||||
r = self.buffer[:]
|
||||
del self.buffer[:]
|
||||
return r
|
||||
|
||||
def at_eof(self) -> Generator[None, None, bool]:
|
||||
"""
|
||||
Tell whether the stream has ended and all data was read.
|
||||
|
||||
This is a generator-based coroutine.
|
||||
|
||||
"""
|
||||
while True:
|
||||
if self.buffer:
|
||||
return False
|
||||
if self.eof:
|
||||
return True
|
||||
# When all data was read but the stream hasn't ended, we can't
|
||||
# tell if until either feed_data() or feed_eof() is called.
|
||||
yield
|
||||
|
||||
def feed_data(self, data: bytes | bytearray) -> None:
|
||||
"""
|
||||
Write data to the stream.
|
||||
|
||||
:meth:`feed_data` cannot be called after :meth:`feed_eof`.
|
||||
|
||||
Args:
|
||||
data: Data to write.
|
||||
|
||||
Raises:
|
||||
EOFError: If the stream has ended.
|
||||
|
||||
"""
|
||||
if self.eof:
|
||||
raise EOFError("stream ended")
|
||||
self.buffer += data
|
||||
|
||||
def feed_eof(self) -> None:
|
||||
"""
|
||||
End the stream.
|
||||
|
||||
:meth:`feed_eof` cannot be called more than once.
|
||||
|
||||
Raises:
|
||||
EOFError: If the stream has ended.
|
||||
|
||||
"""
|
||||
if self.eof:
|
||||
raise EOFError("stream ended")
|
||||
self.eof = True
|
||||
|
||||
def discard(self) -> None:
|
||||
"""
|
||||
Discard all buffered data, but don't end the stream.
|
||||
|
||||
"""
|
||||
del self.buffer[:]
|
||||
75
venv/Lib/site-packages/websockets/typing.py
Normal file
75
venv/Lib/site-packages/websockets/typing.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import http
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, NewType, Sequence
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Data",
|
||||
"LoggerLike",
|
||||
"StatusLike",
|
||||
"Origin",
|
||||
"Subprotocol",
|
||||
"ExtensionName",
|
||||
"ExtensionParameter",
|
||||
]
|
||||
|
||||
|
||||
# Public types used in the signature of public APIs
|
||||
|
||||
Data = str | bytes
|
||||
"""Types supported in a WebSocket message:
|
||||
:class:`str` for a Text_ frame, :class:`bytes` for a Binary_ frame.
|
||||
|
||||
.. _Text: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
|
||||
.. _Binary : https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
|
||||
|
||||
"""
|
||||
|
||||
BytesLike = bytes | bytearray | memoryview
|
||||
"""Types accepted where :class:`bytes` is expected."""
|
||||
|
||||
DataLike = str | bytes | bytearray | memoryview
|
||||
"""Types accepted where :class:`Data` is expected."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
LoggerLike = logging.Logger | logging.LoggerAdapter[Any]
|
||||
"""Types accepted where a :class:`~logging.Logger` is expected."""
|
||||
else: # remove this branch when dropping support for Python < 3.11
|
||||
LoggerLike = logging.Logger | logging.LoggerAdapter
|
||||
"""Types accepted where a :class:`~logging.Logger` is expected."""
|
||||
|
||||
|
||||
StatusLike = http.HTTPStatus | int
|
||||
"""
|
||||
Types accepted where an :class:`~http.HTTPStatus` is expected."""
|
||||
|
||||
|
||||
Origin = NewType("Origin", str)
|
||||
"""Value of a ``Origin`` header."""
|
||||
|
||||
|
||||
Subprotocol = NewType("Subprotocol", str)
|
||||
"""Subprotocol in a ``Sec-WebSocket-Protocol`` header."""
|
||||
|
||||
|
||||
ExtensionName = NewType("ExtensionName", str)
|
||||
"""Name of a WebSocket extension."""
|
||||
|
||||
ExtensionParameter = tuple[str, str | None]
|
||||
"""Parameter of a WebSocket extension."""
|
||||
|
||||
|
||||
# Private types
|
||||
|
||||
ExtensionHeader = tuple[ExtensionName, Sequence[ExtensionParameter]]
|
||||
"""Extension in a ``Sec-WebSocket-Extensions`` header."""
|
||||
|
||||
|
||||
ConnectionOption = NewType("ConnectionOption", str)
|
||||
"""Connection option in a ``Connection`` header."""
|
||||
|
||||
|
||||
UpgradeProtocol = NewType("UpgradeProtocol", str)
|
||||
"""Upgrade protocol in an ``Upgrade`` header."""
|
||||
107
venv/Lib/site-packages/websockets/uri.py
Normal file
107
venv/Lib/site-packages/websockets/uri.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import urllib.parse
|
||||
|
||||
from .exceptions import InvalidURI
|
||||
|
||||
|
||||
__all__ = ["parse_uri", "WebSocketURI"]
|
||||
|
||||
|
||||
# All characters from the gen-delims and sub-delims sets in RFC 3987.
|
||||
DELIMS = ":/?#[]@!$&'()*+,;="
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class WebSocketURI:
|
||||
"""
|
||||
WebSocket URI.
|
||||
|
||||
Attributes:
|
||||
secure: :obj:`True` for a ``wss`` URI, :obj:`False` for a ``ws`` URI.
|
||||
host: Normalized to lower case.
|
||||
port: Always set even if it's the default.
|
||||
path: May be empty.
|
||||
query: May be empty if the URI doesn't include a query component.
|
||||
username: Available when the URI contains `User Information`_.
|
||||
password: Available when the URI contains `User Information`_.
|
||||
|
||||
.. _User Information: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1
|
||||
|
||||
"""
|
||||
|
||||
secure: bool
|
||||
host: str
|
||||
port: int
|
||||
path: str
|
||||
query: str
|
||||
username: str | None = None
|
||||
password: str | None = None
|
||||
|
||||
@property
|
||||
def resource_name(self) -> str:
|
||||
if self.path:
|
||||
resource_name = self.path
|
||||
else:
|
||||
resource_name = "/"
|
||||
if self.query:
|
||||
resource_name += "?" + self.query
|
||||
return resource_name
|
||||
|
||||
@property
|
||||
def user_info(self) -> tuple[str, str] | None:
|
||||
if self.username is None:
|
||||
return None
|
||||
assert self.password is not None
|
||||
return (self.username, self.password)
|
||||
|
||||
|
||||
def parse_uri(uri: str) -> WebSocketURI:
|
||||
"""
|
||||
Parse and validate a WebSocket URI.
|
||||
|
||||
Args:
|
||||
uri: WebSocket URI.
|
||||
|
||||
Returns:
|
||||
Parsed WebSocket URI.
|
||||
|
||||
Raises:
|
||||
InvalidURI: If ``uri`` isn't a valid WebSocket URI.
|
||||
|
||||
"""
|
||||
parsed = urllib.parse.urlparse(uri)
|
||||
if parsed.scheme not in ["ws", "wss"]:
|
||||
raise InvalidURI(uri, "scheme isn't ws or wss")
|
||||
if parsed.hostname is None:
|
||||
raise InvalidURI(uri, "hostname isn't provided")
|
||||
if parsed.fragment != "":
|
||||
raise InvalidURI(uri, "fragment identifier is meaningless")
|
||||
|
||||
secure = parsed.scheme == "wss"
|
||||
host = parsed.hostname
|
||||
port = parsed.port or (443 if secure else 80)
|
||||
path = parsed.path
|
||||
query = parsed.query
|
||||
username = parsed.username
|
||||
password = parsed.password
|
||||
# urllib.parse.urlparse accepts URLs with a username but without a
|
||||
# password. This doesn't make sense for HTTP Basic Auth credentials.
|
||||
if username is not None and password is None:
|
||||
raise InvalidURI(uri, "username provided without password")
|
||||
|
||||
try:
|
||||
uri.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
# Input contains non-ASCII characters.
|
||||
# It must be an IRI. Convert it to a URI.
|
||||
host = host.encode("idna").decode()
|
||||
path = urllib.parse.quote(path, safe=DELIMS)
|
||||
query = urllib.parse.quote(query, safe=DELIMS)
|
||||
if username is not None:
|
||||
assert password is not None
|
||||
username = urllib.parse.quote(username, safe=DELIMS)
|
||||
password = urllib.parse.quote(password, safe=DELIMS)
|
||||
|
||||
return WebSocketURI(secure, host, port, path, query, username, password)
|
||||
53
venv/Lib/site-packages/websockets/utils.py
Normal file
53
venv/Lib/site-packages/websockets/utils.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import secrets
|
||||
import sys
|
||||
|
||||
from .typing import BytesLike
|
||||
|
||||
|
||||
__all__ = ["accept_key", "apply_mask"]
|
||||
|
||||
|
||||
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
|
||||
def generate_key() -> str:
|
||||
"""
|
||||
Generate a random key for the Sec-WebSocket-Key header.
|
||||
|
||||
"""
|
||||
key = secrets.token_bytes(16)
|
||||
return base64.b64encode(key).decode()
|
||||
|
||||
|
||||
def accept_key(key: str) -> str:
|
||||
"""
|
||||
Compute the value of the Sec-WebSocket-Accept header.
|
||||
|
||||
Args:
|
||||
key: Value of the Sec-WebSocket-Key header.
|
||||
|
||||
"""
|
||||
sha1 = hashlib.sha1((key + GUID).encode()).digest()
|
||||
return base64.b64encode(sha1).decode()
|
||||
|
||||
|
||||
def apply_mask(data: BytesLike, mask: bytes | bytearray) -> bytes:
|
||||
"""
|
||||
Apply masking to the data of a WebSocket message.
|
||||
|
||||
Args:
|
||||
data: Data to mask.
|
||||
mask: 4-bytes mask.
|
||||
|
||||
"""
|
||||
if len(mask) != 4:
|
||||
raise ValueError("mask must contain 4 bytes")
|
||||
|
||||
data_int = int.from_bytes(data, sys.byteorder)
|
||||
mask_repeated = mask * (len(data) // 4) + mask[: len(data) % 4]
|
||||
mask_int = int.from_bytes(mask_repeated, sys.byteorder)
|
||||
return (data_int ^ mask_int).to_bytes(len(data), sys.byteorder)
|
||||
92
venv/Lib/site-packages/websockets/version.py
Normal file
92
venv/Lib/site-packages/websockets/version.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.metadata
|
||||
|
||||
|
||||
__all__ = ["tag", "version", "commit"]
|
||||
|
||||
|
||||
# ========= =========== ===================
|
||||
# release development
|
||||
# ========= =========== ===================
|
||||
# tag X.Y X.Y (upcoming)
|
||||
# version X.Y X.Y.dev1+g5678cde
|
||||
# commit X.Y 5678cde
|
||||
# ========= =========== ===================
|
||||
|
||||
|
||||
# When tagging a release, set `released = True`.
|
||||
# After tagging a release, set `released = False` and increment `tag`.
|
||||
|
||||
released = True
|
||||
|
||||
tag = version = commit = "16.0"
|
||||
|
||||
|
||||
if not released: # pragma: no cover
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
def get_version(tag: str) -> str:
|
||||
# Since setup.py executes the contents of src/websockets/version.py,
|
||||
# __file__ can point to either of these two files.
|
||||
file_path = pathlib.Path(__file__)
|
||||
root_dir = file_path.parents[0 if file_path.name == "setup.py" else 2]
|
||||
|
||||
# Read version from package metadata if it is installed.
|
||||
try:
|
||||
version = importlib.metadata.version("websockets")
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
# Check that this file belongs to the installed package.
|
||||
files = importlib.metadata.files("websockets")
|
||||
if files:
|
||||
version_files = [f for f in files if f.name == file_path.name]
|
||||
if version_files:
|
||||
version_file = version_files[0]
|
||||
if version_file.locate() == file_path:
|
||||
return version
|
||||
|
||||
# Read version from git if available.
|
||||
try:
|
||||
description = subprocess.run(
|
||||
["git", "describe", "--dirty", "--tags", "--long"],
|
||||
capture_output=True,
|
||||
cwd=root_dir,
|
||||
timeout=1,
|
||||
check=True,
|
||||
text=True,
|
||||
).stdout.strip()
|
||||
# subprocess.run raises FileNotFoundError if git isn't on $PATH.
|
||||
except (
|
||||
FileNotFoundError,
|
||||
subprocess.CalledProcessError,
|
||||
subprocess.TimeoutExpired,
|
||||
):
|
||||
pass
|
||||
else:
|
||||
description_re = r"[0-9.]+-([0-9]+)-(g[0-9a-f]{7,}(?:-dirty)?)"
|
||||
match = re.fullmatch(description_re, description)
|
||||
if match is None:
|
||||
raise ValueError(f"Unexpected git description: {description}")
|
||||
distance, remainder = match.groups()
|
||||
remainder = remainder.replace("-", ".") # required by PEP 440
|
||||
return f"{tag}.dev{distance}+{remainder}"
|
||||
|
||||
# Avoid crashing if the development version cannot be determined.
|
||||
return f"{tag}.dev0+gunknown"
|
||||
|
||||
version = get_version(tag)
|
||||
|
||||
def get_commit(tag: str, version: str) -> str:
|
||||
# Extract commit from version, falling back to tag if not available.
|
||||
version_re = r"[0-9.]+\.dev[0-9]+\+g([0-9a-f]{7,}|unknown)(?:\.dirty)?"
|
||||
match = re.fullmatch(version_re, version)
|
||||
if match is None:
|
||||
raise ValueError(f"Unexpected version: {version}")
|
||||
(commit,) = match.groups()
|
||||
return tag if commit == "unknown" else commit
|
||||
|
||||
commit = get_commit(tag, version)
|
||||
Reference in New Issue
Block a user