http11.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. from __future__ import annotations
  2. import dataclasses
  3. import os
  4. import re
  5. import sys
  6. import warnings
  7. from collections.abc import Generator
  8. from typing import Callable
  9. from .datastructures import Headers
  10. from .exceptions import (
  11. HeaderLineTooLong,
  12. RequestLineTooLong,
  13. SecurityError,
  14. StatusLineTooLong,
  15. TooManyHeaders,
  16. )
  17. from .version import version as websockets_version
  18. __all__ = [
  19. "SERVER",
  20. "USER_AGENT",
  21. "Request",
  22. "Response",
  23. ]
  24. PYTHON_VERSION = "{}.{}".format(*sys.version_info)
  25. # User-Agent header for HTTP requests.
  26. USER_AGENT = os.environ.get(
  27. "WEBSOCKETS_USER_AGENT",
  28. f"Python/{PYTHON_VERSION} websockets/{websockets_version}",
  29. )
  30. # Server header for HTTP responses.
  31. SERVER = os.environ.get(
  32. "WEBSOCKETS_SERVER",
  33. f"Python/{PYTHON_VERSION} websockets/{websockets_version}",
  34. )
  35. # Maximum total size of headers is around 128 * 8 KiB = 1 MiB.
  36. MAX_NUM_HEADERS = int(os.environ.get("WEBSOCKETS_MAX_NUM_HEADERS", "128"))
  37. # Limit request line and header lines. 8KiB is the most common default
  38. # configuration of popular HTTP servers.
  39. MAX_LINE_LENGTH = int(os.environ.get("WEBSOCKETS_MAX_LINE_LENGTH", "8192"))
  40. # Support for HTTP response bodies is intended to read an error message
  41. # returned by a server. It isn't designed to perform large file transfers.
  42. MAX_BODY_SIZE = int(os.environ.get("WEBSOCKETS_MAX_BODY_SIZE", "1_048_576")) # 1 MiB
  43. def d(value: bytes | bytearray) -> str:
  44. """
  45. Decode a bytestring for interpolating into an error message.
  46. """
  47. return value.decode(errors="backslashreplace")
  48. # See https://datatracker.ietf.org/doc/html/rfc7230#appendix-B.
  49. # Regex for validating header names.
  50. _token_re = re.compile(rb"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+")
  51. # Regex for validating header values.
  52. # We don't attempt to support obsolete line folding.
  53. # Include HTAB (\x09), SP (\x20), VCHAR (\x21-\x7e), obs-text (\x80-\xff).
  54. # The ABNF is complicated because it attempts to express that optional
  55. # whitespace is ignored. We strip whitespace and don't revalidate that.
  56. # See also https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189
  57. _value_re = re.compile(rb"[\x09\x20-\x7e\x80-\xff]*")
  58. # Regexes for validating status codes and numeric header values.
  59. # Set a lower limit than default_max_str_digits; 1 EB is plenty.
  60. # Content-Length = 1*DIGIT
  61. # See https://datatracker.ietf.org/doc/html/rfc9110#section-8.6
  62. _digits_re = re.compile(rb"[0-9]{1,18}")
  63. # chunk-size = 1*HEXDIG
  64. # See https://datatracker.ietf.org/doc/html/rfc9112#section-7.1
  65. _hexdigits_re = re.compile(rb"[0-9a-fA-F]{1,15}")
  66. @dataclasses.dataclass
  67. class Request:
  68. """
  69. WebSocket handshake request.
  70. ``method`` and ``path`` must contain only ASCII characters. ``headers``
  71. should contain only ASCII characters; however, non-ASCII header values are
  72. tolerated and encoded as ISO-8859-1.
  73. Attributes:
  74. path: Request path, including optional query.
  75. headers: Request headers.
  76. method: Request method; WebSocket handshake requests use GET.
  77. protocol: Request protocol; WebSocket handshake requests use HTTP/1.1.
  78. """
  79. path: str
  80. headers: Headers
  81. # method and protocol have a default value, so they're declared after path
  82. # and headers which don't.
  83. method: str = "GET"
  84. protocol: str = "HTTP/1.1"
  85. # body isn't useful is the context of this library.
  86. _exception: Exception | None = None
  87. @property
  88. def exception(self) -> Exception | None: # pragma: no cover
  89. warnings.warn( # deprecated in 10.3 - 2022-04-17
  90. "Request.exception is deprecated; use ServerProtocol.handshake_exc instead",
  91. DeprecationWarning,
  92. )
  93. return self._exception
  94. @classmethod
  95. def parse(
  96. cls,
  97. read_line: Callable[
  98. [int, type[Exception]], Generator[None, None, bytes | bytearray]
  99. ],
  100. ) -> Generator[None, None, Request]:
  101. """
  102. Parse a WebSocket handshake request.
  103. This is a generator-based coroutine.
  104. The request method and path must contain only ASCII characters. The
  105. request path isn't URL-decoded or validated in any way. Request headers
  106. should contain only ASCII characters; however, non-ASCII header values
  107. are tolerated and decoded with ISO-8859-1.
  108. :meth:`parse` doesn't read the request body because WebSocket handshake
  109. requests don't have one. If the request contains a body, it may be read
  110. from the data stream after :meth:`parse` returns.
  111. Args:
  112. read_line: Generator-based coroutine that reads a LF-terminated
  113. line or raises an exception if there isn't enough data
  114. Raises:
  115. EOFError: If the connection is closed without a full HTTP request.
  116. RequestLineTooLong: If the request line is too long.
  117. HeaderLineTooLong: If a header line is too long.
  118. TooManyHeaders: If there are too many headers.
  119. UnicodeDecodeError: If the request method or path isn't ASCII.
  120. ValueError: If the request isn't well formatted.
  121. """
  122. # https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.1
  123. # Parsing is simple because a fixed value is expected for the version
  124. # and because path isn't checked. Since WebSocket libraries generally
  125. # implement HTTP/1.1 strictly, there's little need for lenient parsing.
  126. try:
  127. request_line = yield from parse_line(read_line, RequestLineTooLong)
  128. except EOFError as exc:
  129. raise EOFError("connection closed while reading HTTP request line") from exc
  130. try:
  131. raw_method, raw_path, raw_protocol = request_line.split(b" ", 2)
  132. except ValueError: # not enough values to unpack (expected 3, got 1-2)
  133. raise ValueError(f"invalid HTTP request line: {d(request_line)}") from None
  134. if raw_protocol not in [b"HTTP/1.1", b"HTTP/1.0"]:
  135. raise ValueError(
  136. f"unsupported protocol; expected HTTP/1.1 or HTTP/1.0: "
  137. f"{d(request_line)}"
  138. )
  139. method = raw_method.decode("ascii")
  140. protocol = raw_protocol.decode("ascii")
  141. # RFC 9110 defers the definition of URIs to RFC 3986, which allows only
  142. # a subset of ASCII. Non-ASCII IRIs must be UTF-8 then percent-encoded.
  143. path = raw_path.decode("ascii")
  144. headers = yield from parse_headers(read_line)
  145. # https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3
  146. if "Transfer-Encoding" in headers:
  147. raise NotImplementedError("transfer codings aren't supported")
  148. # Some devices send a Content-Length header with a value of 0.
  149. # Reject any other value. For technical correctness, accept
  150. # multiple Content-Length headers, all with a value of 0.
  151. if not set(headers.get_all("Content-Length")) <= {"0"}:
  152. raise ValueError("unsupported request body")
  153. return cls(path, headers, method, protocol)
  154. def serialize(self) -> bytes:
  155. """
  156. Serialize a WebSocket handshake request.
  157. """
  158. # Methods are hardcoded and always ASCII. Non-ASCII paths are converted
  159. # from URI to IRI and percent-encoded. Enforce ASCII as a safety net.
  160. request_line = f"{self.method} {self.path} {self.protocol}\r\n"
  161. request = request_line.encode("ascii")
  162. request += self.headers.serialize()
  163. return request
  164. @dataclasses.dataclass
  165. class Response:
  166. """
  167. WebSocket handshake response.
  168. ``reason_phrase`` and ``headers`` should contain only ASCII characters;
  169. however, non-ASCII reason phrases and header values are tolerated and
  170. encoded as ISO-8859-1.
  171. Attributes:
  172. status_code: Response code.
  173. reason_phrase: Response reason.
  174. headers: Response headers.
  175. body: Response body.
  176. """
  177. status_code: int
  178. reason_phrase: str
  179. headers: Headers
  180. body: bytes | bytearray = b""
  181. _exception: Exception | None = None
  182. @property
  183. def exception(self) -> Exception | None: # pragma: no cover
  184. warnings.warn( # deprecated in 10.3 - 2022-04-17
  185. "Response.exception is deprecated; "
  186. "use ClientProtocol.handshake_exc instead",
  187. DeprecationWarning,
  188. )
  189. return self._exception
  190. @classmethod
  191. def parse(
  192. cls,
  193. read_line: Callable[
  194. [int, type[Exception]], Generator[None, None, bytes | bytearray]
  195. ],
  196. read_exact: Callable[[int], Generator[None, None, bytes | bytearray]],
  197. read_to_eof: Callable[
  198. [int, type[Exception]], Generator[None, None, bytes | bytearray]
  199. ],
  200. proxy: bool = False,
  201. ) -> Generator[None, None, Response]:
  202. """
  203. Parse a WebSocket handshake response.
  204. This is a generator-based coroutine.
  205. The reason phrase and headers should contain only ASCII characters;
  206. however, non-ASCII reason phrases and header values are tolerated and
  207. decoded as ISO-8859-1.
  208. Args:
  209. read_line: Generator-based coroutine that reads a LF-terminated
  210. line or raises an exception if there isn't enough data.
  211. read_exact: Generator-based coroutine that reads the requested
  212. bytes or raises an exception if there isn't enough data.
  213. read_to_eof: Generator-based coroutine that reads until the end
  214. of the stream.
  215. Raises:
  216. EOFError: If the connection is closed without a full HTTP response.
  217. StatusLineTooLong: If the status line is too long.
  218. HeaderLineTooLong: If a header line is too long.
  219. TooManyHeaders: If there are too many headers.
  220. SecurityError: If the response body exceeds a security limit.
  221. LookupError: If the response isn't well formatted.
  222. ValueError: If the response isn't well formatted.
  223. """
  224. # https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
  225. try:
  226. status_line = yield from parse_line(read_line, StatusLineTooLong)
  227. except EOFError as exc:
  228. raise EOFError("connection closed while reading HTTP status line") from exc
  229. try:
  230. protocol, raw_status_code, raw_reason = status_line.split(b" ", 2)
  231. except ValueError: # not enough values to unpack (expected 3, got 1-2)
  232. raise ValueError(f"invalid HTTP status line: {d(status_line)}") from None
  233. if proxy: # some proxies still use HTTP/1.0
  234. if protocol not in [b"HTTP/1.1", b"HTTP/1.0"]:
  235. raise ValueError(
  236. f"unsupported protocol; expected HTTP/1.1 or HTTP/1.0: "
  237. f"{d(status_line)}"
  238. )
  239. else:
  240. if protocol != b"HTTP/1.1":
  241. raise ValueError(
  242. f"unsupported protocol; expected HTTP/1.1: {d(status_line)}"
  243. )
  244. if not _digits_re.fullmatch(raw_status_code):
  245. raise ValueError(
  246. f"invalid status code; expected integer; got {d(raw_status_code)}"
  247. )
  248. status_code = int(raw_status_code)
  249. if not 100 <= status_code < 600:
  250. raise ValueError(
  251. f"invalid status code; expected 100–599; got {d(raw_status_code)}"
  252. )
  253. if not _value_re.fullmatch(raw_reason):
  254. raise ValueError(f"invalid HTTP reason phrase: {d(raw_reason)}")
  255. # RFC 2616 implies ISO-8859-1. It's easy to reverse and cannot crash.
  256. # Non-ASCII never worked reliably and the reason isn't useful anyway.
  257. reason = raw_reason.decode("iso-8859-1")
  258. headers = yield from parse_headers(read_line)
  259. body: bytes | bytearray
  260. if proxy:
  261. body = b""
  262. else:
  263. body = yield from read_body(
  264. status_code, headers, read_line, read_exact, read_to_eof
  265. )
  266. return cls(status_code, reason, headers, body)
  267. def serialize(self) -> bytes:
  268. """
  269. Serialize a WebSocket handshake response.
  270. """
  271. # Encode the reason phrase as ISO-8859-1 to round-trip cleanly.
  272. status_line = f"HTTP/1.1 {self.status_code} {self.reason_phrase}\r\n"
  273. response = status_line.encode("iso-8859-1")
  274. response += self.headers.serialize()
  275. response += self.body
  276. return response
  277. def parse_line(
  278. read_line: Callable[
  279. [int, type[Exception]], Generator[None, None, bytes | bytearray]
  280. ],
  281. too_long_exc_type: type[Exception] = SecurityError,
  282. ) -> Generator[None, None, bytes | bytearray]:
  283. """
  284. Parse a single line.
  285. CRLF is stripped from the return value.
  286. Args:
  287. read_line: Generator-based coroutine that reads a LF-terminated line
  288. or raises an exception if there isn't enough data.
  289. too_long_exc_type: exception to raise if the line is too long;
  290. defaults to :exc:`SecurityError`.
  291. Raises:
  292. EOFError: If the connection is closed without a CRLF.
  293. SecurityError: If the response exceeds a security limit.
  294. """
  295. line = yield from read_line(MAX_LINE_LENGTH, too_long_exc_type)
  296. # Not mandatory but safe - https://datatracker.ietf.org/doc/html/rfc7230#section-3.5
  297. if not line.endswith(b"\r\n"):
  298. raise EOFError("line without CRLF")
  299. return line[:-2]
  300. def parse_headers(
  301. read_line: Callable[
  302. [int, type[Exception]], Generator[None, None, bytes | bytearray]
  303. ],
  304. ) -> Generator[None, None, Headers]:
  305. """
  306. Parse HTTP headers.
  307. Headers should contain only ASCII characters; however, non-ASCII values are
  308. tolerated and decoded as ISO-8859-1.
  309. Args:
  310. read_line: Generator-based coroutine that reads a LF-terminated line
  311. or raises an exception if there isn't enough data.
  312. Raises:
  313. EOFError: If the connection is closed without complete headers.
  314. HeaderLineTooLong: If a header line is too long.
  315. TooManyHeaders: If there are too many headers.
  316. ValueError: If the request isn't well formatted.
  317. """
  318. # https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
  319. # We don't attempt to support obsolete line folding.
  320. headers = Headers()
  321. for _ in range(MAX_NUM_HEADERS + 1):
  322. try:
  323. line = yield from parse_line(read_line, HeaderLineTooLong)
  324. except EOFError as exc:
  325. raise EOFError("connection closed while reading HTTP headers") from exc
  326. if line == b"":
  327. break
  328. try:
  329. raw_name, raw_value = line.split(b":", 1)
  330. except ValueError: # not enough values to unpack (expected 2, got 1)
  331. raise ValueError(f"invalid HTTP header line: {d(line)}") from None
  332. if not _token_re.fullmatch(raw_name):
  333. raise ValueError(f"invalid HTTP header name: {d(raw_name)}")
  334. raw_value = raw_value.strip(b" \t")
  335. if not _value_re.fullmatch(raw_value):
  336. raise ValueError(f"invalid HTTP header value: {d(raw_value)}")
  337. name = raw_name.decode("ascii") # guaranteed to be ASCII at this point
  338. # Headers should be ASCII. Section 5.5 of RFC 9110 says: "Historically,
  339. # HTTP allowed field content with text in the ISO-8859-1 charset." and
  340. # "A recipient SHOULD treat other allowed octets in field content (i.e.,
  341. # obs-text) as opaque data." ISO-8859-1 is an opaque representation of
  342. # arbitrary binary data in a str object and it is easy to reverse.
  343. value = raw_value.decode("iso-8859-1")
  344. # Since we just validated raw_value, we don't need to revalidate it.
  345. headers.set_insecure(name, value)
  346. else:
  347. raise TooManyHeaders(f"expected no more than {MAX_NUM_HEADERS} headers")
  348. return headers
  349. def read_body(
  350. status_code: int,
  351. headers: Headers,
  352. read_line: Callable[
  353. [int, type[Exception]], Generator[None, None, bytes | bytearray]
  354. ],
  355. read_exact: Callable[[int], Generator[None, None, bytes | bytearray]],
  356. read_to_eof: Callable[
  357. [int, type[Exception]], Generator[None, None, bytes | bytearray]
  358. ],
  359. ) -> Generator[None, None, bytes | bytearray]:
  360. # https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3
  361. # Since websockets only does GET requests (no HEAD, no CONNECT), all
  362. # responses except 1xx, 204, and 304 include a message body.
  363. if 100 <= status_code < 200 or status_code == 204 or status_code == 304:
  364. return b""
  365. # MultipleValuesError is sufficiently unlikely that we don't attempt to
  366. # handle it when accessing headers. Instead we document that its parent
  367. # class, LookupError, may be raised.
  368. # Conversions from str to int are protected by sys.set_int_max_str_digits..
  369. elif (coding := headers.get("Transfer-Encoding")) is not None:
  370. if coding != "chunked":
  371. raise NotImplementedError(f"transfer coding {coding} isn't supported")
  372. body = b""
  373. while True:
  374. chunk_size_line = yield from parse_line(read_line, SecurityError)
  375. raw_chunk_size = chunk_size_line.split(b";", 1)[0]
  376. if not _hexdigits_re.fullmatch(raw_chunk_size):
  377. raise SecurityError(f"invalid chunk size: {d(raw_chunk_size)}")
  378. chunk_size = int(raw_chunk_size, 16)
  379. if chunk_size == 0:
  380. break
  381. if len(body) + chunk_size > MAX_BODY_SIZE:
  382. raise SecurityError(
  383. f"chunk too large: {chunk_size} bytes after {len(body)} bytes"
  384. )
  385. body += yield from read_exact(chunk_size)
  386. if (yield from read_exact(2)) != b"\r\n":
  387. raise ValueError("chunk without CRLF")
  388. # Read the trailer.
  389. yield from parse_headers(read_line)
  390. return body
  391. elif (raw_content_length := headers.get("Content-Length")) is not None:
  392. if not _digits_re.fullmatch(raw_content_length.encode("iso-8859-1")):
  393. raise SecurityError(f"invalid content length: {raw_content_length}")
  394. content_length = int(raw_content_length)
  395. if content_length > MAX_BODY_SIZE:
  396. raise SecurityError(f"body too large: {content_length} bytes")
  397. return (yield from read_exact(content_length))
  398. else:
  399. return (yield from read_to_eof(MAX_BODY_SIZE, SecurityError))