Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

361 linhas
12 KiB

  1. """
  2. :mod:`websockets.http` module provides basic HTTP/1.1 support. It is merely
  3. :adequate for WebSocket handshake messages.
  4. These APIs cannot be imported from :mod:`websockets`. They must be imported
  5. from :mod:`websockets.http`.
  6. """
  7. import asyncio
  8. import re
  9. import sys
  10. from typing import (
  11. Any,
  12. Dict,
  13. Iterable,
  14. Iterator,
  15. List,
  16. Mapping,
  17. MutableMapping,
  18. Tuple,
  19. Union,
  20. )
  21. from .version import version as websockets_version
  22. __all__ = [
  23. "read_request",
  24. "read_response",
  25. "Headers",
  26. "MultipleValuesError",
  27. "USER_AGENT",
  28. ]
  29. MAX_HEADERS = 256
  30. MAX_LINE = 4096
  31. USER_AGENT = f"Python/{sys.version[:3]} websockets/{websockets_version}"
  32. def d(value: bytes) -> str:
  33. """
  34. Decode a bytestring for interpolating into an error message.
  35. """
  36. return value.decode(errors="backslashreplace")
  37. # See https://tools.ietf.org/html/rfc7230#appendix-B.
  38. # Regex for validating header names.
  39. _token_re = re.compile(rb"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+")
  40. # Regex for validating header values.
  41. # We don't attempt to support obsolete line folding.
  42. # Include HTAB (\x09), SP (\x20), VCHAR (\x21-\x7e), obs-text (\x80-\xff).
  43. # The ABNF is complicated because it attempts to express that optional
  44. # whitespace is ignored. We strip whitespace and don't revalidate that.
  45. # See also https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189
  46. _value_re = re.compile(rb"[\x09\x20-\x7e\x80-\xff]*")
  47. async def read_request(stream: asyncio.StreamReader) -> Tuple[str, "Headers"]:
  48. """
  49. Read an HTTP/1.1 GET request and returns ``(path, headers)``.
  50. ``path`` isn't URL-decoded or validated in any way.
  51. ``path`` and ``headers`` are expected to contain only ASCII characters.
  52. Other characters are represented with surrogate escapes.
  53. :func:`read_request` doesn't attempt to read the request body because
  54. WebSocket handshake requests don't have one. If the request contains a
  55. body, it may be read from ``stream`` after this coroutine returns.
  56. :param stream: input to read the request from
  57. :raises EOFError: if the connection is closed without a full HTTP request
  58. :raises SecurityError: if the request exceeds a security limit
  59. :raises ValueError: if the request isn't well formatted
  60. """
  61. # https://tools.ietf.org/html/rfc7230#section-3.1.1
  62. # Parsing is simple because fixed values are expected for method and
  63. # version and because path isn't checked. Since WebSocket software tends
  64. # to implement HTTP/1.1 strictly, there's little need for lenient parsing.
  65. try:
  66. request_line = await read_line(stream)
  67. except EOFError as exc:
  68. raise EOFError("connection closed while reading HTTP request line") from exc
  69. try:
  70. method, raw_path, version = request_line.split(b" ", 2)
  71. except ValueError: # not enough values to unpack (expected 3, got 1-2)
  72. raise ValueError(f"invalid HTTP request line: {d(request_line)}") from None
  73. if method != b"GET":
  74. raise ValueError(f"unsupported HTTP method: {d(method)}")
  75. if version != b"HTTP/1.1":
  76. raise ValueError(f"unsupported HTTP version: {d(version)}")
  77. path = raw_path.decode("ascii", "surrogateescape")
  78. headers = await read_headers(stream)
  79. return path, headers
  80. async def read_response(stream: asyncio.StreamReader) -> Tuple[int, str, "Headers"]:
  81. """
  82. Read an HTTP/1.1 response and returns ``(status_code, reason, headers)``.
  83. ``reason`` and ``headers`` are expected to contain only ASCII characters.
  84. Other characters are represented with surrogate escapes.
  85. :func:`read_request` doesn't attempt to read the response body because
  86. WebSocket handshake responses don't have one. If the response contains a
  87. body, it may be read from ``stream`` after this coroutine returns.
  88. :param stream: input to read the response from
  89. :raises EOFError: if the connection is closed without a full HTTP response
  90. :raises SecurityError: if the response exceeds a security limit
  91. :raises ValueError: if the response isn't well formatted
  92. """
  93. # https://tools.ietf.org/html/rfc7230#section-3.1.2
  94. # As in read_request, parsing is simple because a fixed value is expected
  95. # for version, status_code is a 3-digit number, and reason can be ignored.
  96. try:
  97. status_line = await read_line(stream)
  98. except EOFError as exc:
  99. raise EOFError("connection closed while reading HTTP status line") from exc
  100. try:
  101. version, raw_status_code, raw_reason = status_line.split(b" ", 2)
  102. except ValueError: # not enough values to unpack (expected 3, got 1-2)
  103. raise ValueError(f"invalid HTTP status line: {d(status_line)}") from None
  104. if version != b"HTTP/1.1":
  105. raise ValueError(f"unsupported HTTP version: {d(version)}")
  106. try:
  107. status_code = int(raw_status_code)
  108. except ValueError: # invalid literal for int() with base 10
  109. raise ValueError(f"invalid HTTP status code: {d(raw_status_code)}") from None
  110. if not 100 <= status_code < 1000:
  111. raise ValueError(f"unsupported HTTP status code: {d(raw_status_code)}")
  112. if not _value_re.fullmatch(raw_reason):
  113. raise ValueError(f"invalid HTTP reason phrase: {d(raw_reason)}")
  114. reason = raw_reason.decode()
  115. headers = await read_headers(stream)
  116. return status_code, reason, headers
  117. async def read_headers(stream: asyncio.StreamReader) -> "Headers":
  118. """
  119. Read HTTP headers from ``stream``.
  120. Non-ASCII characters are represented with surrogate escapes.
  121. """
  122. # https://tools.ietf.org/html/rfc7230#section-3.2
  123. # We don't attempt to support obsolete line folding.
  124. headers = Headers()
  125. for _ in range(MAX_HEADERS + 1):
  126. try:
  127. line = await read_line(stream)
  128. except EOFError as exc:
  129. raise EOFError("connection closed while reading HTTP headers") from exc
  130. if line == b"":
  131. break
  132. try:
  133. raw_name, raw_value = line.split(b":", 1)
  134. except ValueError: # not enough values to unpack (expected 2, got 1)
  135. raise ValueError(f"invalid HTTP header line: {d(line)}") from None
  136. if not _token_re.fullmatch(raw_name):
  137. raise ValueError(f"invalid HTTP header name: {d(raw_name)}")
  138. raw_value = raw_value.strip(b" \t")
  139. if not _value_re.fullmatch(raw_value):
  140. raise ValueError(f"invalid HTTP header value: {d(raw_value)}")
  141. name = raw_name.decode("ascii") # guaranteed to be ASCII at this point
  142. value = raw_value.decode("ascii", "surrogateescape")
  143. headers[name] = value
  144. else:
  145. raise websockets.exceptions.SecurityError("too many HTTP headers")
  146. return headers
  147. async def read_line(stream: asyncio.StreamReader) -> bytes:
  148. """
  149. Read a single line from ``stream``.
  150. CRLF is stripped from the return value.
  151. """
  152. # Security: this is bounded by the StreamReader's limit (default = 32 KiB).
  153. line = await stream.readline()
  154. # Security: this guarantees header values are small (hard-coded = 4 KiB)
  155. if len(line) > MAX_LINE:
  156. raise websockets.exceptions.SecurityError("line too long")
  157. # Not mandatory but safe - https://tools.ietf.org/html/rfc7230#section-3.5
  158. if not line.endswith(b"\r\n"):
  159. raise EOFError("line without CRLF")
  160. return line[:-2]
  161. class MultipleValuesError(LookupError):
  162. """
  163. Exception raised when :class:`Headers` has more than one value for a key.
  164. """
  165. def __str__(self) -> str:
  166. # Implement the same logic as KeyError_str in Objects/exceptions.c.
  167. if len(self.args) == 1:
  168. return repr(self.args[0])
  169. return super().__str__()
  170. class Headers(MutableMapping[str, str]):
  171. """
  172. Efficient data structure for manipulating HTTP headers.
  173. A :class:`list` of ``(name, values)`` is inefficient for lookups.
  174. A :class:`dict` doesn't suffice because header names are case-insensitive
  175. and multiple occurrences of headers with the same name are possible.
  176. :class:`Headers` stores HTTP headers in a hybrid data structure to provide
  177. efficient insertions and lookups while preserving the original data.
  178. In order to account for multiple values with minimal hassle,
  179. :class:`Headers` follows this logic:
  180. - When getting a header with ``headers[name]``:
  181. - if there's no value, :exc:`KeyError` is raised;
  182. - if there's exactly one value, it's returned;
  183. - if there's more than one value, :exc:`MultipleValuesError` is raised.
  184. - When setting a header with ``headers[name] = value``, the value is
  185. appended to the list of values for that header.
  186. - When deleting a header with ``del headers[name]``, all values for that
  187. header are removed (this is slow).
  188. Other methods for manipulating headers are consistent with this logic.
  189. As long as no header occurs multiple times, :class:`Headers` behaves like
  190. :class:`dict`, except keys are lower-cased to provide case-insensitivity.
  191. Two methods support support manipulating multiple values explicitly:
  192. - :meth:`get_all` returns a list of all values for a header;
  193. - :meth:`raw_items` returns an iterator of ``(name, values)`` pairs.
  194. """
  195. __slots__ = ["_dict", "_list"]
  196. def __init__(self, *args: Any, **kwargs: str) -> None:
  197. self._dict: Dict[str, List[str]] = {}
  198. self._list: List[Tuple[str, str]] = []
  199. # MutableMapping.update calls __setitem__ for each (name, value) pair.
  200. self.update(*args, **kwargs)
  201. def __str__(self) -> str:
  202. return "".join(f"{key}: {value}\r\n" for key, value in self._list) + "\r\n"
  203. def __repr__(self) -> str:
  204. return f"{self.__class__.__name__}({self._list!r})"
  205. def copy(self) -> "Headers":
  206. copy = self.__class__()
  207. copy._dict = self._dict.copy()
  208. copy._list = self._list.copy()
  209. return copy
  210. # Collection methods
  211. def __contains__(self, key: object) -> bool:
  212. return isinstance(key, str) and key.lower() in self._dict
  213. def __iter__(self) -> Iterator[str]:
  214. return iter(self._dict)
  215. def __len__(self) -> int:
  216. return len(self._dict)
  217. # MutableMapping methods
  218. def __getitem__(self, key: str) -> str:
  219. value = self._dict[key.lower()]
  220. if len(value) == 1:
  221. return value[0]
  222. else:
  223. raise MultipleValuesError(key)
  224. def __setitem__(self, key: str, value: str) -> None:
  225. self._dict.setdefault(key.lower(), []).append(value)
  226. self._list.append((key, value))
  227. def __delitem__(self, key: str) -> None:
  228. key_lower = key.lower()
  229. self._dict.__delitem__(key_lower)
  230. # This is inefficent. Fortunately deleting HTTP headers is uncommon.
  231. self._list = [(k, v) for k, v in self._list if k.lower() != key_lower]
  232. def __eq__(self, other: Any) -> bool:
  233. if not isinstance(other, Headers):
  234. return NotImplemented
  235. return self._list == other._list
  236. def clear(self) -> None:
  237. """
  238. Remove all headers.
  239. """
  240. self._dict = {}
  241. self._list = []
  242. # Methods for handling multiple values
  243. def get_all(self, key: str) -> List[str]:
  244. """
  245. Return the (possibly empty) list of all values for a header.
  246. :param key: header name
  247. """
  248. return self._dict.get(key.lower(), [])
  249. def raw_items(self) -> Iterator[Tuple[str, str]]:
  250. """
  251. Return an iterator of all values as ``(name, value)`` pairs.
  252. """
  253. return iter(self._list)
  254. HeadersLike = Union[Headers, Mapping[str, str], Iterable[Tuple[str, str]]]
  255. # at the bottom to allow circular import, because AbortHandshake depends on HeadersLike
  256. import websockets.exceptions # isort:skip # noqa