http11.py 18 KB

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