about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/h11/_events.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/h11/_events.py')
-rw-r--r--.venv/lib/python3.12/site-packages/h11/_events.py369
1 files changed, 369 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/h11/_events.py b/.venv/lib/python3.12/site-packages/h11/_events.py
new file mode 100644
index 00000000..075bf8a4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/h11/_events.py
@@ -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