http11.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import re
  2. from typing import Callable, Generator, NamedTuple, Optional
  3. from .datastructures import Headers
  4. from .exceptions import SecurityError
  5. MAX_HEADERS = 256
  6. MAX_LINE = 4110
  7. def d(value: bytes) -> str:
  8. """
  9. Decode a bytestring for interpolating into an error message.
  10. """
  11. return value.decode(errors="backslashreplace")
  12. # See https://tools.ietf.org/html/rfc7230#appendix-B.
  13. # Regex for validating header names.
  14. _token_re = re.compile(rb"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+")
  15. # Regex for validating header values.
  16. # We don't attempt to support obsolete line folding.
  17. # Include HTAB (\x09), SP (\x20), VCHAR (\x21-\x7e), obs-text (\x80-\xff).
  18. # The ABNF is complicated because it attempts to express that optional
  19. # whitespace is ignored. We strip whitespace and don't revalidate that.
  20. # See also https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189
  21. _value_re = re.compile(rb"[\x09\x20-\x7e\x80-\xff]*")
  22. # Consider converting to dataclasses when dropping support for Python < 3.7.
  23. class Request(NamedTuple):
  24. """
  25. WebSocket handshake request.
  26. :param path: path and optional query
  27. :param headers:
  28. """
  29. path: str
  30. headers: Headers
  31. # body isn't useful is the context of this library
  32. @classmethod
  33. def parse(
  34. cls, read_line: Callable[[], Generator[None, None, bytes]]
  35. ) -> Generator[None, None, "Request"]:
  36. """
  37. Parse an HTTP/1.1 GET request and return ``(path, headers)``.
  38. ``path`` isn't URL-decoded or validated in any way.
  39. ``path`` and ``headers`` are expected to contain only ASCII characters.
  40. Other characters are represented with surrogate escapes.
  41. :func:`parse_request` doesn't attempt to read the request body because
  42. WebSocket handshake requests don't have one. If the request contains a
  43. body, it may be read from ``stream`` after this coroutine returns.
  44. :param read_line: generator-based coroutine that reads a LF-terminated
  45. line or raises an exception if there isn't enough data
  46. :raises EOFError: if the connection is closed without a full HTTP request
  47. :raises SecurityError: if the request exceeds a security limit
  48. :raises ValueError: if the request isn't well formatted
  49. """
  50. # https://tools.ietf.org/html/rfc7230#section-3.1.1
  51. # Parsing is simple because fixed values are expected for method and
  52. # version and because path isn't checked. Since WebSocket software tends
  53. # to implement HTTP/1.1 strictly, there's little need for lenient parsing.
  54. try:
  55. request_line = yield from parse_line(read_line)
  56. except EOFError as exc:
  57. raise EOFError("connection closed while reading HTTP request line") from exc
  58. try:
  59. method, raw_path, version = request_line.split(b" ", 2)
  60. except ValueError: # not enough values to unpack (expected 3, got 1-2)
  61. raise ValueError(f"invalid HTTP request line: {d(request_line)}") from None
  62. if method != b"GET":
  63. raise ValueError(f"unsupported HTTP method: {d(method)}")
  64. if version != b"HTTP/1.1":
  65. raise ValueError(f"unsupported HTTP version: {d(version)}")
  66. path = raw_path.decode("ascii", "surrogateescape")
  67. headers = yield from parse_headers(read_line)
  68. return cls(path, headers)
  69. def serialize(self) -> bytes:
  70. """
  71. Serialize an HTTP/1.1 GET request.
  72. """
  73. # Since the path and headers only contain ASCII characters,
  74. # we can keep this simple.
  75. request = f"GET {self.path} HTTP/1.1\r\n".encode()
  76. request += self.headers.serialize()
  77. return request
  78. # Consider converting to dataclasses when dropping support for Python < 3.7.
  79. class Response(NamedTuple):
  80. """
  81. WebSocket handshake response.
  82. """
  83. status_code: int
  84. reason_phrase: str
  85. headers: Headers
  86. body: Optional[bytes] = None
  87. # If processing the response triggers an exception, it's stored here.
  88. exception: Optional[Exception] = None
  89. @classmethod
  90. def parse(
  91. cls,
  92. read_line: Callable[[], Generator[None, None, bytes]],
  93. read_exact: Callable[[int], Generator[None, None, bytes]],
  94. read_to_eof: Callable[[], Generator[None, None, bytes]],
  95. ) -> Generator[None, None, "Response"]:
  96. """
  97. Parse an HTTP/1.1 response and return ``(status_code, reason, headers)``.
  98. ``reason`` and ``headers`` are expected to contain only ASCII characters.
  99. Other characters are represented with surrogate escapes.
  100. :func:`parse_request` doesn't attempt to read the response body because
  101. WebSocket handshake responses don't have one. If the response contains a
  102. body, it may be read from ``stream`` after this coroutine returns.
  103. :param read_line: generator-based coroutine that reads a LF-terminated
  104. line or raises an exception if there isn't enough data
  105. :param read_exact: generator-based coroutine that reads the requested
  106. number of bytes or raises an exception if there isn't enough data
  107. :raises EOFError: if the connection is closed without a full HTTP response
  108. :raises SecurityError: if the response exceeds a security limit
  109. :raises LookupError: if the response isn't well formatted
  110. :raises ValueError: if the response isn't well formatted
  111. """
  112. # https://tools.ietf.org/html/rfc7230#section-3.1.2
  113. # As in parse_request, parsing is simple because a fixed value is expected
  114. # for version, status_code is a 3-digit number, and reason can be ignored.
  115. try:
  116. status_line = yield from parse_line(read_line)
  117. except EOFError as exc:
  118. raise EOFError("connection closed while reading HTTP status line") from exc
  119. try:
  120. version, raw_status_code, raw_reason = status_line.split(b" ", 2)
  121. except ValueError: # not enough values to unpack (expected 3, got 1-2)
  122. raise ValueError(f"invalid HTTP status line: {d(status_line)}") from None
  123. if version != b"HTTP/1.1":
  124. raise ValueError(f"unsupported HTTP version: {d(version)}")
  125. try:
  126. status_code = int(raw_status_code)
  127. except ValueError: # invalid literal for int() with base 10
  128. raise ValueError(
  129. f"invalid HTTP status code: {d(raw_status_code)}"
  130. ) from None
  131. if not 100 <= status_code < 1000:
  132. raise ValueError(f"unsupported HTTP status code: {d(raw_status_code)}")
  133. if not _value_re.fullmatch(raw_reason):
  134. raise ValueError(f"invalid HTTP reason phrase: {d(raw_reason)}")
  135. reason = raw_reason.decode()
  136. headers = yield from parse_headers(read_line)
  137. # https://tools.ietf.org/html/rfc7230#section-3.3.3
  138. if "Transfer-Encoding" in headers:
  139. raise NotImplementedError("transfer codings aren't supported")
  140. # Since websockets only does GET requests (no HEAD, no CONNECT), all
  141. # responses except 1xx, 204, and 304 include a message body.
  142. if 100 <= status_code < 200 or status_code == 204 or status_code == 304:
  143. body = None
  144. else:
  145. content_length: Optional[int]
  146. try:
  147. # MultipleValuesError is sufficiently unlikely that we don't
  148. # attempt to handle it. Instead we document that its parent
  149. # class, LookupError, may be raised.
  150. raw_content_length = headers["Content-Length"]
  151. except KeyError:
  152. content_length = None
  153. else:
  154. content_length = int(raw_content_length)
  155. if content_length is None:
  156. body = yield from read_to_eof()
  157. else:
  158. body = yield from read_exact(content_length)
  159. return cls(status_code, reason, headers, body)
  160. def serialize(self) -> bytes:
  161. """
  162. Serialize an HTTP/1.1 GET response.
  163. """
  164. # Since the status line and headers only contain ASCII characters,
  165. # we can keep this simple.
  166. response = f"HTTP/1.1 {self.status_code} {self.reason_phrase}\r\n".encode()
  167. response += self.headers.serialize()
  168. if self.body is not None:
  169. response += self.body
  170. return response
  171. def parse_headers(
  172. read_line: Callable[[], Generator[None, None, bytes]]
  173. ) -> Generator[None, None, Headers]:
  174. """
  175. Parse HTTP headers.
  176. Non-ASCII characters are represented with surrogate escapes.
  177. :param read_line: generator-based coroutine that reads a LF-terminated
  178. line or raises an exception if there isn't enough data
  179. """
  180. # https://tools.ietf.org/html/rfc7230#section-3.2
  181. # We don't attempt to support obsolete line folding.
  182. headers = Headers()
  183. for _ in range(MAX_HEADERS + 1):
  184. try:
  185. line = yield from parse_line(read_line)
  186. except EOFError as exc:
  187. raise EOFError("connection closed while reading HTTP headers") from exc
  188. if line == b"":
  189. break
  190. try:
  191. raw_name, raw_value = line.split(b":", 1)
  192. except ValueError: # not enough values to unpack (expected 2, got 1)
  193. raise ValueError(f"invalid HTTP header line: {d(line)}") from None
  194. if not _token_re.fullmatch(raw_name):
  195. raise ValueError(f"invalid HTTP header name: {d(raw_name)}")
  196. raw_value = raw_value.strip(b" \t")
  197. if not _value_re.fullmatch(raw_value):
  198. raise ValueError(f"invalid HTTP header value: {d(raw_value)}")
  199. name = raw_name.decode("ascii") # guaranteed to be ASCII at this point
  200. value = raw_value.decode("ascii", "surrogateescape")
  201. headers[name] = value
  202. else:
  203. raise SecurityError("too many HTTP headers")
  204. return headers
  205. def parse_line(
  206. read_line: Callable[[], Generator[None, None, bytes]]
  207. ) -> Generator[None, None, bytes]:
  208. """
  209. Parse a single line.
  210. CRLF is stripped from the return value.
  211. :param read_line: generator-based coroutine that reads a LF-terminated
  212. line or raises an exception if there isn't enough data
  213. """
  214. # Security: TODO: add a limit here
  215. line = yield from read_line()
  216. # Security: this guarantees header values are small (hard-coded = 4 KiB)
  217. if len(line) > MAX_LINE:
  218. raise SecurityError("line too long")
  219. # Not mandatory but safe - https://tools.ietf.org/html/rfc7230#section-3.5
  220. if not line.endswith(b"\r\n"):
  221. raise EOFError("line without CRLF")
  222. return line[:-2]