Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -2,26 +2,14 @@ import asyncio
import datetime
import io
import re
import socket
import string
import tempfile
import types
import warnings
from collections.abc import Iterator, Mapping, MutableMapping
from re import Pattern
from types import MappingProxyType
from typing import (
TYPE_CHECKING,
Any,
Dict,
Final,
Iterator,
Mapping,
MutableMapping,
Optional,
Pattern,
Tuple,
Union,
cast,
)
from typing import TYPE_CHECKING, Any, Final, Optional, TypeVar, cast, overload
from urllib.parse import parse_qsl
import attr
@ -40,11 +28,13 @@ from .abc import AbstractStreamWriter
from .helpers import (
_SENTINEL,
DEBUG,
DEFAULT_CHUNK_SIZE,
ETAG_ANY,
LIST_QUOTED_ETAG_RE,
ChainMapProxy,
ETag,
HeadersMixin,
RequestKey,
parse_http_date,
reify,
sentinel,
@ -61,7 +51,7 @@ from .typedefs import (
RawHeaders,
StrOrURL,
)
from .web_exceptions import HTTPRequestEntityTooLarge
from .web_exceptions import HTTPRequestEntityTooLarge, NotAppKeyWarning
from .web_response import StreamResponse
__all__ = ("BaseRequest", "FileField", "Request")
@ -73,6 +63,9 @@ if TYPE_CHECKING:
from .web_urldispatcher import UrlMappingMatchInfo
_T = TypeVar("_T")
@attr.s(auto_attribs=True, frozen=True, slots=True)
class FileField:
name: str
@ -82,6 +75,7 @@ class FileField:
headers: CIMultiDictProxy[str]
_Post = str | bytes | bytearray | FileField
_TCHAR: Final[str] = string.digits + string.ascii_letters + r"!#$%&'*+.^_`|~-"
# '-' at the end to prevent interpretation as range in a char class
@ -95,15 +89,10 @@ _QDTEXT: Final[str] = r"[{}]".format(
_QUOTED_PAIR: Final[str] = r"\\[\t !-~]"
_QUOTED_STRING: Final[str] = r'"(?:{quoted_pair}|{qdtext})*"'.format(
qdtext=_QDTEXT, quoted_pair=_QUOTED_PAIR
)
_QUOTED_STRING: Final[str] = rf'"(?:{_QUOTED_PAIR}|{_QDTEXT})*"'
_FORWARDED_PAIR: Final[str] = (
r"({token})=({token}|{quoted_string})(:\d{{1,4}})?".format(
token=_TOKEN, quoted_string=_QUOTED_STRING
)
)
# This does not have a ReDOS/performance concern as long as it used with re.match().
_FORWARDED_PAIR: Final[str] = rf"({_TOKEN})=({_TOKEN}|{_QUOTED_STRING})(:\d{{1,4}})?"
_QUOTED_PAIR_REPLACE_RE: Final[Pattern[str]] = re.compile(r"\\([\t !-~])")
# same pattern as _QUOTED_PAIR but contains a capture group
@ -115,8 +104,7 @@ _FORWARDED_PAIR_RE: Final[Pattern[str]] = re.compile(_FORWARDED_PAIR)
############################################################
class BaseRequest(MutableMapping[str, Any], HeadersMixin):
class BaseRequest(MutableMapping[str | RequestKey[Any], Any], HeadersMixin):
POST_METHODS = {
hdrs.METH_PATCH,
hdrs.METH_POST,
@ -146,8 +134,9 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
"_transport_peername",
]
)
_post: Optional[MultiDictProxy[Union[str, bytes, FileField]]] = None
_read_bytes: Optional[bytes] = None
_post: MultiDictProxy[_Post] | None = None
_read_bytes: bytes | None = None
_seen_str_keys: set[str] = set()
def __init__(
self,
@ -159,10 +148,10 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
loop: asyncio.AbstractEventLoop,
*,
client_max_size: int = 1024**2,
state: Optional[Dict[str, Any]] = None,
scheme: Optional[str] = None,
host: Optional[str] = None,
remote: Optional[str] = None,
state: dict[RequestKey[Any] | str, Any] | None = None,
scheme: str | None = None,
host: str | None = None,
remote: str | None = None,
) -> None:
self._message = message
self._protocol = protocol
@ -172,7 +161,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
self._headers: CIMultiDictProxy[str] = message.headers
self._method = message.method
self._version = message.version
self._cache: Dict[str, Any] = {}
self._cache: dict[str, Any] = {}
url = message.url
if url.absolute:
if scheme is not None:
@ -200,6 +189,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
self._transport_sslcontext = protocol.ssl_context
self._transport_peername = protocol.peername
self._transport_sockname = protocol.sockname
if remote is not None:
self._cache["remote"] = remote
@ -207,13 +197,13 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
def clone(
self,
*,
method: Union[str, _SENTINEL] = sentinel,
rel_url: Union[StrOrURL, _SENTINEL] = sentinel,
headers: Union[LooseHeaders, _SENTINEL] = sentinel,
scheme: Union[str, _SENTINEL] = sentinel,
host: Union[str, _SENTINEL] = sentinel,
remote: Union[str, _SENTINEL] = sentinel,
client_max_size: Union[int, _SENTINEL] = sentinel,
method: str | _SENTINEL = sentinel,
rel_url: StrOrURL | _SENTINEL = sentinel,
headers: LooseHeaders | _SENTINEL = sentinel,
scheme: str | _SENTINEL = sentinel,
host: str | _SENTINEL = sentinel,
remote: str | _SENTINEL = sentinel,
client_max_size: int | _SENTINEL = sentinel,
) -> "BaseRequest":
"""Clone itself with replacement some attributes.
@ -224,7 +214,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
if self._read_bytes:
raise RuntimeError("Cannot clone request after reading its content")
dct: Dict[str, Any] = {}
dct: dict[str, Any] = {}
if method is not sentinel:
dct["method"] = method
if rel_url is not sentinel:
@ -272,7 +262,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
return self._protocol
@property
def transport(self) -> Optional[asyncio.Transport]:
def transport(self) -> asyncio.Transport | None:
if self._protocol is None:
return None
return self._protocol.transport
@ -303,19 +293,40 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
# MutableMapping API
def __getitem__(self, key: str) -> Any:
@overload # type: ignore[override]
def __getitem__(self, key: RequestKey[_T]) -> _T: ...
@overload
def __getitem__(self, key: str) -> Any: ...
def __getitem__(self, key: str | RequestKey[_T]) -> Any:
return self._state[key]
def __setitem__(self, key: str, value: Any) -> None:
@overload # type: ignore[override]
def __setitem__(self, key: RequestKey[_T], value: _T) -> None: ...
@overload
def __setitem__(self, key: str, value: Any) -> None: ...
def __setitem__(self, key: str | RequestKey[_T], value: Any) -> None:
if not isinstance(key, RequestKey) and key not in BaseRequest._seen_str_keys:
BaseRequest._seen_str_keys.add(key)
warnings.warn(
"It is recommended to use web.RequestKey instances for keys.\n"
+ "https://docs.aiohttp.org/en/stable/web_advanced.html"
+ "#request-s-storage",
category=NotAppKeyWarning,
stacklevel=2,
)
self._state[key] = value
def __delitem__(self, key: str) -> None:
def __delitem__(self, key: str | RequestKey[_T]) -> None:
del self._state[key]
def __len__(self) -> int:
return len(self._state)
def __iter__(self) -> Iterator[str]:
def __iter__(self) -> Iterator[str | RequestKey[Any]]:
return iter(self._state)
########
@ -326,7 +337,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
return self.scheme == "https"
@reify
def forwarded(self) -> Tuple[Mapping[str, str], ...]:
def forwarded(self) -> tuple[Mapping[str, str], ...]:
"""A tuple containing all parsed Forwarded header(s).
Makes an effort to parse Forwarded headers as specified by RFC 7239:
@ -350,7 +361,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
length = len(field_value)
pos = 0
need_separator = False
elem: Dict[str, str] = {}
elem: dict[str, str] = {}
elems.append(types.MappingProxyType(elem))
while 0 <= pos < length:
match = _FORWARDED_PAIR_RE.match(field_value, pos)
@ -426,7 +437,9 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
- overridden value by .clone(host=new_host) call.
- HOST HTTP header
- socket.getfqdn() value
- local socket address the request arrived on
(transport ``sockname``)
- empty string if no transport information is available
For example, 'example.com' or 'localhost:8080'.
@ -435,10 +448,20 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
host = self._message.headers.get(hdrs.HOST)
if host is not None:
return host
return socket.getfqdn()
sockname = self._transport_sockname
if sockname is None:
return ""
if isinstance(sockname, tuple):
# AF_INET6 returns a 4-tuple (host, port, flowinfo, scopeid);
# bracket the bare address so it matches the Host-header shape
# and is a valid URL authority component.
if len(sockname) == 4:
return f"[{sockname[0]}]"
return str(sockname[0])
return str(sockname)
@reify
def remote(self) -> Optional[str]:
def remote(self) -> str | None:
"""Remote IP of client initiated HTTP request.
The IP is resolved in this order:
@ -509,7 +532,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
return self._message.raw_headers
@reify
def if_modified_since(self) -> Optional[datetime.datetime]:
def if_modified_since(self) -> datetime.datetime | None:
"""The value of If-Modified-Since HTTP header, or None.
This header is represented as a `datetime` object.
@ -517,7 +540,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
return parse_http_date(self.headers.get(hdrs.IF_MODIFIED_SINCE))
@reify
def if_unmodified_since(self) -> Optional[datetime.datetime]:
def if_unmodified_since(self) -> datetime.datetime | None:
"""The value of If-Unmodified-Since HTTP header, or None.
This header is represented as a `datetime` object.
@ -547,15 +570,15 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
@classmethod
def _if_match_or_none_impl(
cls, header_value: Optional[str]
) -> Optional[Tuple[ETag, ...]]:
cls, header_value: str | None
) -> tuple[ETag, ...] | None:
if not header_value:
return None
return tuple(cls._etag_values(header_value))
@reify
def if_match(self) -> Optional[Tuple[ETag, ...]]:
def if_match(self) -> tuple[ETag, ...] | None:
"""The value of If-Match HTTP header, or None.
This header is represented as a `tuple` of `ETag` objects.
@ -563,7 +586,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
return self._if_match_or_none_impl(self.headers.get(hdrs.IF_MATCH))
@reify
def if_none_match(self) -> Optional[Tuple[ETag, ...]]:
def if_none_match(self) -> tuple[ETag, ...] | None:
"""The value of If-None-Match HTTP header, or None.
This header is represented as a `tuple` of `ETag` objects.
@ -571,7 +594,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
return self._if_match_or_none_impl(self.headers.get(hdrs.IF_NONE_MATCH))
@reify
def if_range(self) -> Optional[datetime.datetime]:
def if_range(self) -> datetime.datetime | None:
"""The value of If-Range HTTP header, or None.
This header is represented as a `datetime` object.
@ -668,16 +691,18 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
Returns bytes object with full request content.
"""
if self._read_bytes is None:
# Raise the buffer limits so compressed payloads decompress in
# larger chunks instead of many small pause/resume cycles.
if self._client_max_size:
self._payload.set_read_chunk_size(self._client_max_size)
body = bytearray()
while True:
chunk = await self._payload.readany()
body.extend(chunk)
if self._client_max_size:
body_size = len(body)
if body_size >= self._client_max_size:
raise HTTPRequestEntityTooLarge(
max_size=self._client_max_size, actual_size=body_size
)
if body_size > self._client_max_size:
raise HTTPRequestEntityTooLarge(self._client_max_size)
if not chunk:
break
self._read_bytes = bytes(body)
@ -696,9 +721,16 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
async def multipart(self) -> MultipartReader:
"""Return async iterator to process BODY as multipart."""
return MultipartReader(self._headers, self._payload)
return MultipartReader(
self._headers,
self._payload,
client_max_size=self._client_max_size,
max_field_size=self._protocol.max_field_size,
max_headers=self._protocol.max_headers,
max_size_error_cls=HTTPRequestEntityTooLarge,
)
async def post(self) -> "MultiDictProxy[Union[str, bytes, FileField]]":
async def post(self) -> "MultiDictProxy[_Post]":
"""Return POST parameters."""
if self._post is not None:
return self._post
@ -715,7 +747,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
self._post = MultiDictProxy(MultiDict())
return self._post
out: MultiDict[Union[str, bytes, FileField]] = MultiDict()
out: MultiDict[_Post] = MultiDict()
if content_type == "multipart/form-data":
multipart = await self.multipart()
@ -738,17 +770,15 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
tmp = await self._loop.run_in_executor(
None, tempfile.TemporaryFile
)
chunk = await field.read_chunk(size=2**16)
while chunk:
chunk = await field.decode(chunk)
await self._loop.run_in_executor(None, tmp.write, chunk)
size += len(chunk)
if 0 < max_size < size:
await self._loop.run_in_executor(None, tmp.close)
raise HTTPRequestEntityTooLarge(
max_size=max_size, actual_size=size
while chunk := await field.read_chunk(size=DEFAULT_CHUNK_SIZE):
async for decoded_chunk in field.decode_iter(chunk):
await self._loop.run_in_executor(
None, tmp.write, decoded_chunk
)
chunk = await field.read_chunk(size=2**16)
size += len(decoded_chunk)
if 0 < max_size < size:
await self._loop.run_in_executor(None, tmp.close)
raise HTTPRequestEntityTooLarge(max_size)
await self._loop.run_in_executor(None, tmp.seek, 0)
if field_ct is None:
@ -764,17 +794,23 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
out.add(field.name, ff)
else:
# deal with ordinary data
value = await field.read(decode=True)
raw_data = bytearray()
while chunk := await field.read_chunk():
size += len(chunk)
if 0 < max_size < size:
raise HTTPRequestEntityTooLarge(max_size)
raw_data.extend(chunk)
value = bytearray()
# form-data doesn't support compression, so don't need to check size again.
async for d in field.decode_iter(raw_data):
value.extend(d)
if field_ct is None or field_ct.startswith("text/"):
charset = field.get_charset(default="utf-8")
out.add(field.name, value.decode(charset))
else:
out.add(field.name, value)
size += len(value)
if 0 < max_size < size:
raise HTTPRequestEntityTooLarge(
max_size=max_size, actual_size=size
)
else:
raise ValueError(
"To decode nested multipart you need to use custom reader",
@ -810,9 +846,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
ascii_encodable_path = self.path.encode("ascii", "backslashreplace").decode(
"ascii"
)
return "<{} {} {} >".format(
self.__class__.__name__, self._method, ascii_encodable_path
)
return f"<{self.__class__.__name__} {self._method} {ascii_encodable_path} >"
def __eq__(self, other: object) -> bool:
return id(self) == id(other)
@ -850,8 +884,8 @@ class Request(BaseRequest):
def __setattr__(self, name: str, val: Any) -> None:
if name not in self.ATTRS:
warnings.warn(
"Setting custom {}.{} attribute "
"is discouraged".format(self.__class__.__name__, name),
f"Setting custom {self.__class__.__name__}.{name} attribute "
"is discouraged",
DeprecationWarning,
stacklevel=2,
)
@ -860,13 +894,13 @@ class Request(BaseRequest):
def clone(
self,
*,
method: Union[str, _SENTINEL] = sentinel,
rel_url: Union[StrOrURL, _SENTINEL] = sentinel,
headers: Union[LooseHeaders, _SENTINEL] = sentinel,
scheme: Union[str, _SENTINEL] = sentinel,
host: Union[str, _SENTINEL] = sentinel,
remote: Union[str, _SENTINEL] = sentinel,
client_max_size: Union[int, _SENTINEL] = sentinel,
method: str | _SENTINEL = sentinel,
rel_url: StrOrURL | _SENTINEL = sentinel,
headers: LooseHeaders | _SENTINEL = sentinel,
scheme: str | _SENTINEL = sentinel,
host: str | _SENTINEL = sentinel,
remote: str | _SENTINEL = sentinel,
client_max_size: int | _SENTINEL = sentinel,
) -> "Request":
ret = super().clone(
method=method,