about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/h11/_util.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/h11/_util.py')
-rw-r--r--.venv/lib/python3.12/site-packages/h11/_util.py135
1 files changed, 135 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/h11/_util.py b/.venv/lib/python3.12/site-packages/h11/_util.py
new file mode 100644
index 00000000..67184452
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/h11/_util.py
@@ -0,0 +1,135 @@
+from typing import Any, Dict, NoReturn, Pattern, Tuple, Type, TypeVar, Union
+
+__all__ = [
+    "ProtocolError",
+    "LocalProtocolError",
+    "RemoteProtocolError",
+    "validate",
+    "bytesify",
+]
+
+
+class ProtocolError(Exception):
+    """Exception indicating a violation of the HTTP/1.1 protocol.
+
+    This as an abstract base class, with two concrete base classes:
+    :exc:`LocalProtocolError`, which indicates that you tried to do something
+    that HTTP/1.1 says is illegal, and :exc:`RemoteProtocolError`, which
+    indicates that the remote peer tried to do something that HTTP/1.1 says is
+    illegal. See :ref:`error-handling` for details.
+
+    In addition to the normal :exc:`Exception` features, it has one attribute:
+
+    .. attribute:: error_status_hint
+
+       This gives a suggestion as to what status code a server might use if
+       this error occurred as part of a request.
+
+       For a :exc:`RemoteProtocolError`, this is useful as a suggestion for
+       how you might want to respond to a misbehaving peer, if you're
+       implementing a server.
+
+       For a :exc:`LocalProtocolError`, this can be taken as a suggestion for
+       how your peer might have responded to *you* if h11 had allowed you to
+       continue.
+
+       The default is 400 Bad Request, a generic catch-all for protocol
+       violations.
+
+    """
+
+    def __init__(self, msg: str, error_status_hint: int = 400) -> None:
+        if type(self) is ProtocolError:
+            raise TypeError("tried to directly instantiate ProtocolError")
+        Exception.__init__(self, msg)
+        self.error_status_hint = error_status_hint
+
+
+# Strategy: there are a number of public APIs where a LocalProtocolError can
+# be raised (send(), all the different event constructors, ...), and only one
+# public API where RemoteProtocolError can be raised
+# (receive_data()). Therefore we always raise LocalProtocolError internally,
+# and then receive_data will translate this into a RemoteProtocolError.
+#
+# Internally:
+#   LocalProtocolError is the generic "ProtocolError".
+# Externally:
+#   LocalProtocolError is for local errors and RemoteProtocolError is for
+#   remote errors.
+class LocalProtocolError(ProtocolError):
+    def _reraise_as_remote_protocol_error(self) -> NoReturn:
+        # After catching a LocalProtocolError, use this method to re-raise it
+        # as a RemoteProtocolError. This method must be called from inside an
+        # except: block.
+        #
+        # An easy way to get an equivalent RemoteProtocolError is just to
+        # modify 'self' in place.
+        self.__class__ = RemoteProtocolError  # type: ignore
+        # But the re-raising is somewhat non-trivial -- you might think that
+        # now that we've modified the in-flight exception object, that just
+        # doing 'raise' to re-raise it would be enough. But it turns out that
+        # this doesn't work, because Python tracks the exception type
+        # (exc_info[0]) separately from the exception object (exc_info[1]),
+        # and we only modified the latter. So we really do need to re-raise
+        # the new type explicitly.
+        # On py3, the traceback is part of the exception object, so our
+        # in-place modification preserved it and we can just re-raise:
+        raise self
+
+
+class RemoteProtocolError(ProtocolError):
+    pass
+
+
+def validate(
+    regex: Pattern[bytes], data: bytes, msg: str = "malformed data", *format_args: Any
+) -> Dict[str, bytes]:
+    match = regex.fullmatch(data)
+    if not match:
+        if format_args:
+            msg = msg.format(*format_args)
+        raise LocalProtocolError(msg)
+    return match.groupdict()
+
+
+# Sentinel values
+#
+# - Inherit identity-based comparison and hashing from object
+# - Have a nice repr
+# - Have a *bonus property*: type(sentinel) is sentinel
+#
+# The bonus property is useful if you want to take the return value from
+# next_event() and do some sort of dispatch based on type(event).
+
+_T_Sentinel = TypeVar("_T_Sentinel", bound="Sentinel")
+
+
+class Sentinel(type):
+    def __new__(
+        cls: Type[_T_Sentinel],
+        name: str,
+        bases: Tuple[type, ...],
+        namespace: Dict[str, Any],
+        **kwds: Any
+    ) -> _T_Sentinel:
+        assert bases == (Sentinel,)
+        v = super().__new__(cls, name, bases, namespace, **kwds)
+        v.__class__ = v  # type: ignore
+        return v
+
+    def __repr__(self) -> str:
+        return self.__name__
+
+
+# Used for methods, request targets, HTTP versions, header names, and header
+# values. Accepts ascii-strings, or bytes/bytearray/memoryview/..., and always
+# returns bytes.
+def bytesify(s: Union[bytes, bytearray, memoryview, int, str]) -> bytes:
+    # Fast-path:
+    if type(s) is bytes:
+        return s
+    if isinstance(s, str):
+        s = s.encode("ascii")
+    if isinstance(s, int):
+        raise TypeError("expected bytes-like object, not int")
+    return bytes(s)