Update 2025-04-13_16:26:34
This commit is contained in:
369
venv/lib/python3.11/site-packages/h11/_events.py
Normal file
369
venv/lib/python3.11/site-packages/h11/_events.py
Normal file
@ -0,0 +1,369 @@
|
||||
# High level events that make up HTTP/1.1 conversations. Loosely inspired by
|
||||
# the corresponding events in hyper-h2:
|
||||
#
|
||||
# http://python-hyper.org/h2/en/stable/api.html#events
|
||||
#
|
||||
# Don't subclass these. Stuff will break.
|
||||
|
||||
import re
|
||||
from abc import ABC
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, cast, Dict, List, Tuple, Union
|
||||
|
||||
from ._abnf import method, request_target
|
||||
from ._headers import Headers, normalize_and_validate
|
||||
from ._util import bytesify, LocalProtocolError, validate
|
||||
|
||||
# Everything in __all__ gets re-exported as part of the h11 public API.
|
||||
__all__ = [
|
||||
"Event",
|
||||
"Request",
|
||||
"InformationalResponse",
|
||||
"Response",
|
||||
"Data",
|
||||
"EndOfMessage",
|
||||
"ConnectionClosed",
|
||||
]
|
||||
|
||||
method_re = re.compile(method.encode("ascii"))
|
||||
request_target_re = re.compile(request_target.encode("ascii"))
|
||||
|
||||
|
||||
class Event(ABC):
|
||||
"""
|
||||
Base class for h11 events.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@dataclass(init=False, frozen=True)
|
||||
class Request(Event):
|
||||
"""The beginning of an HTTP request.
|
||||
|
||||
Fields:
|
||||
|
||||
.. attribute:: method
|
||||
|
||||
An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte
|
||||
string. :term:`Bytes-like objects <bytes-like object>` and native
|
||||
strings containing only ascii characters will be automatically
|
||||
converted to byte strings.
|
||||
|
||||
.. attribute:: target
|
||||
|
||||
The target of an HTTP request, e.g. ``b"/index.html"``, or one of the
|
||||
more exotic formats described in `RFC 7320, section 5.3
|
||||
<https://tools.ietf.org/html/rfc7230#section-5.3>`_. Always a byte
|
||||
string. :term:`Bytes-like objects <bytes-like object>` and native
|
||||
strings containing only ascii characters will be automatically
|
||||
converted to byte strings.
|
||||
|
||||
.. attribute:: headers
|
||||
|
||||
Request headers, represented as a list of (name, value) pairs. See
|
||||
:ref:`the header normalization rules <headers-format>` for details.
|
||||
|
||||
.. attribute:: http_version
|
||||
|
||||
The HTTP protocol version, represented as a byte string like
|
||||
``b"1.1"``. See :ref:`the HTTP version normalization rules
|
||||
<http_version-format>` for details.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("method", "headers", "target", "http_version")
|
||||
|
||||
method: bytes
|
||||
headers: Headers
|
||||
target: bytes
|
||||
http_version: bytes
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
method: Union[bytes, str],
|
||||
headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]],
|
||||
target: Union[bytes, str],
|
||||
http_version: Union[bytes, str] = b"1.1",
|
||||
_parsed: bool = False,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
if isinstance(headers, Headers):
|
||||
object.__setattr__(self, "headers", headers)
|
||||
else:
|
||||
object.__setattr__(
|
||||
self, "headers", normalize_and_validate(headers, _parsed=_parsed)
|
||||
)
|
||||
if not _parsed:
|
||||
object.__setattr__(self, "method", bytesify(method))
|
||||
object.__setattr__(self, "target", bytesify(target))
|
||||
object.__setattr__(self, "http_version", bytesify(http_version))
|
||||
else:
|
||||
object.__setattr__(self, "method", method)
|
||||
object.__setattr__(self, "target", target)
|
||||
object.__setattr__(self, "http_version", http_version)
|
||||
|
||||
# "A server MUST respond with a 400 (Bad Request) status code to any
|
||||
# HTTP/1.1 request message that lacks a Host header field and to any
|
||||
# request message that contains more than one Host header field or a
|
||||
# Host header field with an invalid field-value."
|
||||
# -- https://tools.ietf.org/html/rfc7230#section-5.4
|
||||
host_count = 0
|
||||
for name, value in self.headers:
|
||||
if name == b"host":
|
||||
host_count += 1
|
||||
if self.http_version == b"1.1" and host_count == 0:
|
||||
raise LocalProtocolError("Missing mandatory Host: header")
|
||||
if host_count > 1:
|
||||
raise LocalProtocolError("Found multiple Host: headers")
|
||||
|
||||
validate(method_re, self.method, "Illegal method characters")
|
||||
validate(request_target_re, self.target, "Illegal target characters")
|
||||
|
||||
# This is an unhashable type.
|
||||
__hash__ = None # type: ignore
|
||||
|
||||
|
||||
@dataclass(init=False, frozen=True)
|
||||
class _ResponseBase(Event):
|
||||
__slots__ = ("headers", "http_version", "reason", "status_code")
|
||||
|
||||
headers: Headers
|
||||
http_version: bytes
|
||||
reason: bytes
|
||||
status_code: int
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]],
|
||||
status_code: int,
|
||||
http_version: Union[bytes, str] = b"1.1",
|
||||
reason: Union[bytes, str] = b"",
|
||||
_parsed: bool = False,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
if isinstance(headers, Headers):
|
||||
object.__setattr__(self, "headers", headers)
|
||||
else:
|
||||
object.__setattr__(
|
||||
self, "headers", normalize_and_validate(headers, _parsed=_parsed)
|
||||
)
|
||||
if not _parsed:
|
||||
object.__setattr__(self, "reason", bytesify(reason))
|
||||
object.__setattr__(self, "http_version", bytesify(http_version))
|
||||
if not isinstance(status_code, int):
|
||||
raise LocalProtocolError("status code must be integer")
|
||||
# Because IntEnum objects are instances of int, but aren't
|
||||
# duck-compatible (sigh), see gh-72.
|
||||
object.__setattr__(self, "status_code", int(status_code))
|
||||
else:
|
||||
object.__setattr__(self, "reason", reason)
|
||||
object.__setattr__(self, "http_version", http_version)
|
||||
object.__setattr__(self, "status_code", status_code)
|
||||
|
||||
self.__post_init__()
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
pass
|
||||
|
||||
# This is an unhashable type.
|
||||
__hash__ = None # type: ignore
|
||||
|
||||
|
||||
@dataclass(init=False, frozen=True)
|
||||
class InformationalResponse(_ResponseBase):
|
||||
"""An HTTP informational response.
|
||||
|
||||
Fields:
|
||||
|
||||
.. attribute:: status_code
|
||||
|
||||
The status code of this response, as an integer. For an
|
||||
:class:`InformationalResponse`, this is always in the range [100,
|
||||
200).
|
||||
|
||||
.. attribute:: headers
|
||||
|
||||
Request headers, represented as a list of (name, value) pairs. See
|
||||
:ref:`the header normalization rules <headers-format>` for
|
||||
details.
|
||||
|
||||
.. attribute:: http_version
|
||||
|
||||
The HTTP protocol version, represented as a byte string like
|
||||
``b"1.1"``. See :ref:`the HTTP version normalization rules
|
||||
<http_version-format>` for details.
|
||||
|
||||
.. attribute:: reason
|
||||
|
||||
The reason phrase of this response, as a byte string. For example:
|
||||
``b"OK"``, or ``b"Not Found"``.
|
||||
|
||||
"""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not (100 <= self.status_code < 200):
|
||||
raise LocalProtocolError(
|
||||
"InformationalResponse status_code should be in range "
|
||||
"[100, 200), not {}".format(self.status_code)
|
||||
)
|
||||
|
||||
# This is an unhashable type.
|
||||
__hash__ = None # type: ignore
|
||||
|
||||
|
||||
@dataclass(init=False, frozen=True)
|
||||
class Response(_ResponseBase):
|
||||
"""The beginning of an HTTP response.
|
||||
|
||||
Fields:
|
||||
|
||||
.. attribute:: status_code
|
||||
|
||||
The status code of this response, as an integer. For an
|
||||
:class:`Response`, this is always in the range [200,
|
||||
1000).
|
||||
|
||||
.. attribute:: headers
|
||||
|
||||
Request headers, represented as a list of (name, value) pairs. See
|
||||
:ref:`the header normalization rules <headers-format>` for details.
|
||||
|
||||
.. attribute:: http_version
|
||||
|
||||
The HTTP protocol version, represented as a byte string like
|
||||
``b"1.1"``. See :ref:`the HTTP version normalization rules
|
||||
<http_version-format>` for details.
|
||||
|
||||
.. attribute:: reason
|
||||
|
||||
The reason phrase of this response, as a byte string. For example:
|
||||
``b"OK"``, or ``b"Not Found"``.
|
||||
|
||||
"""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not (200 <= self.status_code < 1000):
|
||||
raise LocalProtocolError(
|
||||
"Response status_code should be in range [200, 1000), not {}".format(
|
||||
self.status_code
|
||||
)
|
||||
)
|
||||
|
||||
# This is an unhashable type.
|
||||
__hash__ = None # type: ignore
|
||||
|
||||
|
||||
@dataclass(init=False, frozen=True)
|
||||
class Data(Event):
|
||||
"""Part of an HTTP message body.
|
||||
|
||||
Fields:
|
||||
|
||||
.. attribute:: data
|
||||
|
||||
A :term:`bytes-like object` containing part of a message body. Or, if
|
||||
using the ``combine=False`` argument to :meth:`Connection.send`, then
|
||||
any object that your socket writing code knows what to do with, and for
|
||||
which calling :func:`len` returns the number of bytes that will be
|
||||
written -- see :ref:`sendfile` for details.
|
||||
|
||||
.. attribute:: chunk_start
|
||||
|
||||
A marker that indicates whether this data object is from the start of a
|
||||
chunked transfer encoding chunk. This field is ignored when when a Data
|
||||
event is provided to :meth:`Connection.send`: it is only valid on
|
||||
events emitted from :meth:`Connection.next_event`. You probably
|
||||
shouldn't use this attribute at all; see
|
||||
:ref:`chunk-delimiters-are-bad` for details.
|
||||
|
||||
.. attribute:: chunk_end
|
||||
|
||||
A marker that indicates whether this data object is the last for a
|
||||
given chunked transfer encoding chunk. This field is ignored when when
|
||||
a Data event is provided to :meth:`Connection.send`: it is only valid
|
||||
on events emitted from :meth:`Connection.next_event`. You probably
|
||||
shouldn't use this attribute at all; see
|
||||
:ref:`chunk-delimiters-are-bad` for details.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("data", "chunk_start", "chunk_end")
|
||||
|
||||
data: bytes
|
||||
chunk_start: bool
|
||||
chunk_end: bool
|
||||
|
||||
def __init__(
|
||||
self, data: bytes, chunk_start: bool = False, chunk_end: bool = False
|
||||
) -> None:
|
||||
object.__setattr__(self, "data", data)
|
||||
object.__setattr__(self, "chunk_start", chunk_start)
|
||||
object.__setattr__(self, "chunk_end", chunk_end)
|
||||
|
||||
# This is an unhashable type.
|
||||
__hash__ = None # type: ignore
|
||||
|
||||
|
||||
# XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that
|
||||
# are forbidden to be sent in a trailer, since processing them as if they were
|
||||
# present in the header section might bypass external security filters."
|
||||
# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part
|
||||
# Unfortunately, the list of forbidden fields is long and vague :-/
|
||||
@dataclass(init=False, frozen=True)
|
||||
class EndOfMessage(Event):
|
||||
"""The end of an HTTP message.
|
||||
|
||||
Fields:
|
||||
|
||||
.. attribute:: headers
|
||||
|
||||
Default value: ``[]``
|
||||
|
||||
Any trailing headers attached to this message, represented as a list of
|
||||
(name, value) pairs. See :ref:`the header normalization rules
|
||||
<headers-format>` for details.
|
||||
|
||||
Must be empty unless ``Transfer-Encoding: chunked`` is in use.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("headers",)
|
||||
|
||||
headers: Headers
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
headers: Union[
|
||||
Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None
|
||||
] = None,
|
||||
_parsed: bool = False,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
if headers is None:
|
||||
headers = Headers([])
|
||||
elif not isinstance(headers, Headers):
|
||||
headers = normalize_and_validate(headers, _parsed=_parsed)
|
||||
|
||||
object.__setattr__(self, "headers", headers)
|
||||
|
||||
# This is an unhashable type.
|
||||
__hash__ = None # type: ignore
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConnectionClosed(Event):
|
||||
"""This event indicates that the sender has closed their outgoing
|
||||
connection.
|
||||
|
||||
Note that this does not necessarily mean that they can't *receive* further
|
||||
data, because TCP connections are composed to two one-way channels which
|
||||
can be closed independently. See :ref:`closing` for details.
|
||||
|
||||
No fields.
|
||||
"""
|
||||
|
||||
pass
|
Reference in New Issue
Block a user