_events.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. # High level events that make up HTTP/1.1 conversations. Loosely inspired by
  2. # the corresponding events in hyper-h2:
  3. #
  4. # http://python-hyper.org/h2/en/stable/api.html#events
  5. #
  6. # Don't subclass these. Stuff will break.
  7. import re
  8. from abc import ABC
  9. from dataclasses import dataclass, field
  10. from typing import Any, cast, Dict, List, Tuple, Union
  11. from ._abnf import method, request_target
  12. from ._headers import Headers, normalize_and_validate
  13. from ._util import bytesify, LocalProtocolError, validate
  14. # Everything in __all__ gets re-exported as part of the h11 public API.
  15. __all__ = [
  16. "Event",
  17. "Request",
  18. "InformationalResponse",
  19. "Response",
  20. "Data",
  21. "EndOfMessage",
  22. "ConnectionClosed",
  23. ]
  24. method_re = re.compile(method.encode("ascii"))
  25. request_target_re = re.compile(request_target.encode("ascii"))
  26. class Event(ABC):
  27. """
  28. Base class for h11 events.
  29. """
  30. __slots__ = ()
  31. @dataclass(init=False, frozen=True)
  32. class Request(Event):
  33. """The beginning of an HTTP request.
  34. Fields:
  35. .. attribute:: method
  36. An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte
  37. string. :term:`Bytes-like objects <bytes-like object>` and native
  38. strings containing only ascii characters will be automatically
  39. converted to byte strings.
  40. .. attribute:: target
  41. The target of an HTTP request, e.g. ``b"/index.html"``, or one of the
  42. more exotic formats described in `RFC 7320, section 5.3
  43. <https://tools.ietf.org/html/rfc7230#section-5.3>`_. Always a byte
  44. string. :term:`Bytes-like objects <bytes-like object>` and native
  45. strings containing only ascii characters will be automatically
  46. converted to byte strings.
  47. .. attribute:: headers
  48. Request headers, represented as a list of (name, value) pairs. See
  49. :ref:`the header normalization rules <headers-format>` for details.
  50. .. attribute:: http_version
  51. The HTTP protocol version, represented as a byte string like
  52. ``b"1.1"``. See :ref:`the HTTP version normalization rules
  53. <http_version-format>` for details.
  54. """
  55. __slots__ = ("method", "headers", "target", "http_version")
  56. method: bytes
  57. headers: Headers
  58. target: bytes
  59. http_version: bytes
  60. def __init__(
  61. self,
  62. *,
  63. method: Union[bytes, str],
  64. headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]],
  65. target: Union[bytes, str],
  66. http_version: Union[bytes, str] = b"1.1",
  67. _parsed: bool = False,
  68. ) -> None:
  69. super().__init__()
  70. if isinstance(headers, Headers):
  71. object.__setattr__(self, "headers", headers)
  72. else:
  73. object.__setattr__(
  74. self, "headers", normalize_and_validate(headers, _parsed=_parsed)
  75. )
  76. if not _parsed:
  77. object.__setattr__(self, "method", bytesify(method))
  78. object.__setattr__(self, "target", bytesify(target))
  79. object.__setattr__(self, "http_version", bytesify(http_version))
  80. else:
  81. object.__setattr__(self, "method", method)
  82. object.__setattr__(self, "target", target)
  83. object.__setattr__(self, "http_version", http_version)
  84. # "A server MUST respond with a 400 (Bad Request) status code to any
  85. # HTTP/1.1 request message that lacks a Host header field and to any
  86. # request message that contains more than one Host header field or a
  87. # Host header field with an invalid field-value."
  88. # -- https://tools.ietf.org/html/rfc7230#section-5.4
  89. host_count = 0
  90. for name, value in self.headers:
  91. if name == b"host":
  92. host_count += 1
  93. if self.http_version == b"1.1" and host_count == 0:
  94. raise LocalProtocolError("Missing mandatory Host: header")
  95. if host_count > 1:
  96. raise LocalProtocolError("Found multiple Host: headers")
  97. validate(method_re, self.method, "Illegal method characters")
  98. validate(request_target_re, self.target, "Illegal target characters")
  99. # This is an unhashable type.
  100. __hash__ = None # type: ignore
  101. @dataclass(init=False, frozen=True)
  102. class _ResponseBase(Event):
  103. __slots__ = ("headers", "http_version", "reason", "status_code")
  104. headers: Headers
  105. http_version: bytes
  106. reason: bytes
  107. status_code: int
  108. def __init__(
  109. self,
  110. *,
  111. headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]],
  112. status_code: int,
  113. http_version: Union[bytes, str] = b"1.1",
  114. reason: Union[bytes, str] = b"",
  115. _parsed: bool = False,
  116. ) -> None:
  117. super().__init__()
  118. if isinstance(headers, Headers):
  119. object.__setattr__(self, "headers", headers)
  120. else:
  121. object.__setattr__(
  122. self, "headers", normalize_and_validate(headers, _parsed=_parsed)
  123. )
  124. if not _parsed:
  125. object.__setattr__(self, "reason", bytesify(reason))
  126. object.__setattr__(self, "http_version", bytesify(http_version))
  127. if not isinstance(status_code, int):
  128. raise LocalProtocolError("status code must be integer")
  129. # Because IntEnum objects are instances of int, but aren't
  130. # duck-compatible (sigh), see gh-72.
  131. object.__setattr__(self, "status_code", int(status_code))
  132. else:
  133. object.__setattr__(self, "reason", reason)
  134. object.__setattr__(self, "http_version", http_version)
  135. object.__setattr__(self, "status_code", status_code)
  136. self.__post_init__()
  137. def __post_init__(self) -> None:
  138. pass
  139. # This is an unhashable type.
  140. __hash__ = None # type: ignore
  141. @dataclass(init=False, frozen=True)
  142. class InformationalResponse(_ResponseBase):
  143. """An HTTP informational response.
  144. Fields:
  145. .. attribute:: status_code
  146. The status code of this response, as an integer. For an
  147. :class:`InformationalResponse`, this is always in the range [100,
  148. 200).
  149. .. attribute:: headers
  150. Request headers, represented as a list of (name, value) pairs. See
  151. :ref:`the header normalization rules <headers-format>` for
  152. details.
  153. .. attribute:: http_version
  154. The HTTP protocol version, represented as a byte string like
  155. ``b"1.1"``. See :ref:`the HTTP version normalization rules
  156. <http_version-format>` for details.
  157. .. attribute:: reason
  158. The reason phrase of this response, as a byte string. For example:
  159. ``b"OK"``, or ``b"Not Found"``.
  160. """
  161. def __post_init__(self) -> None:
  162. if not (100 <= self.status_code < 200):
  163. raise LocalProtocolError(
  164. "InformationalResponse status_code should be in range "
  165. "[100, 200), not {}".format(self.status_code)
  166. )
  167. # This is an unhashable type.
  168. __hash__ = None # type: ignore
  169. @dataclass(init=False, frozen=True)
  170. class Response(_ResponseBase):
  171. """The beginning of an HTTP response.
  172. Fields:
  173. .. attribute:: status_code
  174. The status code of this response, as an integer. For an
  175. :class:`Response`, this is always in the range [200,
  176. 1000).
  177. .. attribute:: headers
  178. Request headers, represented as a list of (name, value) pairs. See
  179. :ref:`the header normalization rules <headers-format>` for details.
  180. .. attribute:: http_version
  181. The HTTP protocol version, represented as a byte string like
  182. ``b"1.1"``. See :ref:`the HTTP version normalization rules
  183. <http_version-format>` for details.
  184. .. attribute:: reason
  185. The reason phrase of this response, as a byte string. For example:
  186. ``b"OK"``, or ``b"Not Found"``.
  187. """
  188. def __post_init__(self) -> None:
  189. if not (200 <= self.status_code < 1000):
  190. raise LocalProtocolError(
  191. "Response status_code should be in range [200, 1000), not {}".format(
  192. self.status_code
  193. )
  194. )
  195. # This is an unhashable type.
  196. __hash__ = None # type: ignore
  197. @dataclass(init=False, frozen=True)
  198. class Data(Event):
  199. """Part of an HTTP message body.
  200. Fields:
  201. .. attribute:: data
  202. A :term:`bytes-like object` containing part of a message body. Or, if
  203. using the ``combine=False`` argument to :meth:`Connection.send`, then
  204. any object that your socket writing code knows what to do with, and for
  205. which calling :func:`len` returns the number of bytes that will be
  206. written -- see :ref:`sendfile` for details.
  207. .. attribute:: chunk_start
  208. A marker that indicates whether this data object is from the start of a
  209. chunked transfer encoding chunk. This field is ignored when when a Data
  210. event is provided to :meth:`Connection.send`: it is only valid on
  211. events emitted from :meth:`Connection.next_event`. You probably
  212. shouldn't use this attribute at all; see
  213. :ref:`chunk-delimiters-are-bad` for details.
  214. .. attribute:: chunk_end
  215. A marker that indicates whether this data object is the last for a
  216. given chunked transfer encoding chunk. This field is ignored when when
  217. a Data event is provided to :meth:`Connection.send`: it is only valid
  218. on events emitted from :meth:`Connection.next_event`. You probably
  219. shouldn't use this attribute at all; see
  220. :ref:`chunk-delimiters-are-bad` for details.
  221. """
  222. __slots__ = ("data", "chunk_start", "chunk_end")
  223. data: bytes
  224. chunk_start: bool
  225. chunk_end: bool
  226. def __init__(
  227. self, data: bytes, chunk_start: bool = False, chunk_end: bool = False
  228. ) -> None:
  229. object.__setattr__(self, "data", data)
  230. object.__setattr__(self, "chunk_start", chunk_start)
  231. object.__setattr__(self, "chunk_end", chunk_end)
  232. # This is an unhashable type.
  233. __hash__ = None # type: ignore
  234. # XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that
  235. # are forbidden to be sent in a trailer, since processing them as if they were
  236. # present in the header section might bypass external security filters."
  237. # https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part
  238. # Unfortunately, the list of forbidden fields is long and vague :-/
  239. @dataclass(init=False, frozen=True)
  240. class EndOfMessage(Event):
  241. """The end of an HTTP message.
  242. Fields:
  243. .. attribute:: headers
  244. Default value: ``[]``
  245. Any trailing headers attached to this message, represented as a list of
  246. (name, value) pairs. See :ref:`the header normalization rules
  247. <headers-format>` for details.
  248. Must be empty unless ``Transfer-Encoding: chunked`` is in use.
  249. """
  250. __slots__ = ("headers",)
  251. headers: Headers
  252. def __init__(
  253. self,
  254. *,
  255. headers: Union[
  256. Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None
  257. ] = None,
  258. _parsed: bool = False,
  259. ) -> None:
  260. super().__init__()
  261. if headers is None:
  262. headers = Headers([])
  263. elif not isinstance(headers, Headers):
  264. headers = normalize_and_validate(headers, _parsed=_parsed)
  265. object.__setattr__(self, "headers", headers)
  266. # This is an unhashable type.
  267. __hash__ = None # type: ignore
  268. @dataclass(frozen=True)
  269. class ConnectionClosed(Event):
  270. """This event indicates that the sender has closed their outgoing
  271. connection.
  272. Note that this does not necessarily mean that they can't *receive* further
  273. data, because TCP connections are composed to two one-way channels which
  274. can be closed independently. See :ref:`closing` for details.
  275. No fields.
  276. """
  277. pass