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.
 
 
 
 

251 linhas
8.4 KiB

  1. # Code to read HTTP data
  2. #
  3. # Strategy: each reader is a callable which takes a ReceiveBuffer object, and
  4. # either:
  5. # 1) consumes some of it and returns an Event
  6. # 2) raises a LocalProtocolError (for consistency -- e.g. we call validate()
  7. # and it might raise a LocalProtocolError, so simpler just to always use
  8. # this)
  9. # 3) returns None, meaning "I need more data"
  10. #
  11. # If they have a .read_eof attribute, then this will be called if an EOF is
  12. # received -- but this is optional. Either way, the actual ConnectionClosed
  13. # event will be generated afterwards.
  14. #
  15. # READERS is a dict describing how to pick a reader. It maps states to either:
  16. # - a reader
  17. # - or, for body readers, a dict of per-framing reader factories
  18. import re
  19. from typing import Any, Callable, Dict, Iterable, NoReturn, Optional, Tuple, Type, Union
  20. from ._abnf import chunk_header, header_field, request_line, status_line
  21. from ._events import Data, EndOfMessage, InformationalResponse, Request, Response
  22. from ._receivebuffer import ReceiveBuffer
  23. from ._state import (
  24. CLIENT,
  25. CLOSED,
  26. DONE,
  27. IDLE,
  28. MUST_CLOSE,
  29. SEND_BODY,
  30. SEND_RESPONSE,
  31. SERVER,
  32. )
  33. from ._util import LocalProtocolError, RemoteProtocolError, Sentinel, validate
  34. __all__ = ["READERS"]
  35. header_field_re = re.compile(header_field.encode("ascii"))
  36. obs_fold_re = re.compile(rb"[ \t]+")
  37. def _obsolete_line_fold(lines: Iterable[bytes]) -> Iterable[bytes]:
  38. it = iter(lines)
  39. last: Optional[bytes] = None
  40. for line in it:
  41. match = obs_fold_re.match(line)
  42. if match:
  43. if last is None:
  44. raise LocalProtocolError("continuation line at start of headers")
  45. if not isinstance(last, bytearray):
  46. # Cast to a mutable type, avoiding copy on append to ensure O(n) time
  47. last = bytearray(last)
  48. last += b" "
  49. last += line[match.end() :]
  50. else:
  51. if last is not None:
  52. yield last
  53. last = line
  54. if last is not None:
  55. yield last
  56. def _decode_header_lines(
  57. lines: Iterable[bytes],
  58. ) -> Iterable[Tuple[bytes, bytes]]:
  59. for line in _obsolete_line_fold(lines):
  60. matches = validate(header_field_re, line, "illegal header line: {!r}", line)
  61. yield (matches["field_name"], matches["field_value"])
  62. request_line_re = re.compile(request_line.encode("ascii"))
  63. def maybe_read_from_IDLE_client(buf: ReceiveBuffer) -> Optional[Request]:
  64. lines = buf.maybe_extract_lines()
  65. if lines is None:
  66. if buf.is_next_line_obviously_invalid_request_line():
  67. raise LocalProtocolError("illegal request line")
  68. return None
  69. if not lines:
  70. raise LocalProtocolError("no request line received")
  71. matches = validate(
  72. request_line_re, lines[0], "illegal request line: {!r}", lines[0]
  73. )
  74. return Request(
  75. headers=list(_decode_header_lines(lines[1:])), _parsed=True, **matches
  76. )
  77. status_line_re = re.compile(status_line.encode("ascii"))
  78. def maybe_read_from_SEND_RESPONSE_server(
  79. buf: ReceiveBuffer,
  80. ) -> Union[InformationalResponse, Response, None]:
  81. lines = buf.maybe_extract_lines()
  82. if lines is None:
  83. if buf.is_next_line_obviously_invalid_request_line():
  84. raise LocalProtocolError("illegal request line")
  85. return None
  86. if not lines:
  87. raise LocalProtocolError("no response line received")
  88. matches = validate(status_line_re, lines[0], "illegal status line: {!r}", lines[0])
  89. http_version = (
  90. b"1.1" if matches["http_version"] is None else matches["http_version"]
  91. )
  92. reason = b"" if matches["reason"] is None else matches["reason"]
  93. status_code = int(matches["status_code"])
  94. class_: Union[Type[InformationalResponse], Type[Response]] = (
  95. InformationalResponse if status_code < 200 else Response
  96. )
  97. return class_(
  98. headers=list(_decode_header_lines(lines[1:])),
  99. _parsed=True,
  100. status_code=status_code,
  101. reason=reason,
  102. http_version=http_version,
  103. )
  104. class ContentLengthReader:
  105. def __init__(self, length: int) -> None:
  106. self._length = length
  107. self._remaining = length
  108. def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]:
  109. if self._remaining == 0:
  110. return EndOfMessage()
  111. data = buf.maybe_extract_at_most(self._remaining)
  112. if data is None:
  113. return None
  114. self._remaining -= len(data)
  115. return Data(data=data)
  116. def read_eof(self) -> NoReturn:
  117. raise RemoteProtocolError(
  118. "peer closed connection without sending complete message body "
  119. "(received {} bytes, expected {})".format(
  120. self._length - self._remaining, self._length
  121. )
  122. )
  123. chunk_header_re = re.compile(chunk_header.encode("ascii"))
  124. class ChunkedReader:
  125. def __init__(self) -> None:
  126. self._bytes_in_chunk = 0
  127. # After reading a chunk, we have to throw away the trailing \r\n.
  128. # This tracks the bytes that we need to match and throw away.
  129. self._bytes_to_discard = b""
  130. self._reading_trailer = False
  131. def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]:
  132. if self._reading_trailer:
  133. lines = buf.maybe_extract_lines()
  134. if lines is None:
  135. return None
  136. return EndOfMessage(headers=list(_decode_header_lines(lines)))
  137. if self._bytes_to_discard:
  138. data = buf.maybe_extract_at_most(len(self._bytes_to_discard))
  139. if data is None:
  140. return None
  141. if data != self._bytes_to_discard[: len(data)]:
  142. raise LocalProtocolError(
  143. f"malformed chunk footer: {data!r} (expected {self._bytes_to_discard!r})"
  144. )
  145. self._bytes_to_discard = self._bytes_to_discard[len(data) :]
  146. if self._bytes_to_discard:
  147. return None
  148. # else, fall through and read some more
  149. assert self._bytes_to_discard == b""
  150. if self._bytes_in_chunk == 0:
  151. # We need to refill our chunk count
  152. chunk_header = buf.maybe_extract_next_line()
  153. if chunk_header is None:
  154. return None
  155. matches = validate(
  156. chunk_header_re,
  157. chunk_header,
  158. "illegal chunk header: {!r}",
  159. chunk_header,
  160. )
  161. # XX FIXME: we discard chunk extensions. Does anyone care?
  162. self._bytes_in_chunk = int(matches["chunk_size"], base=16)
  163. if self._bytes_in_chunk == 0:
  164. self._reading_trailer = True
  165. return self(buf)
  166. chunk_start = True
  167. else:
  168. chunk_start = False
  169. assert self._bytes_in_chunk > 0
  170. data = buf.maybe_extract_at_most(self._bytes_in_chunk)
  171. if data is None:
  172. return None
  173. self._bytes_in_chunk -= len(data)
  174. if self._bytes_in_chunk == 0:
  175. self._bytes_to_discard = b"\r\n"
  176. chunk_end = True
  177. else:
  178. chunk_end = False
  179. return Data(data=data, chunk_start=chunk_start, chunk_end=chunk_end)
  180. def read_eof(self) -> NoReturn:
  181. raise RemoteProtocolError(
  182. "peer closed connection without sending complete message body "
  183. "(incomplete chunked read)"
  184. )
  185. class Http10Reader:
  186. def __call__(self, buf: ReceiveBuffer) -> Optional[Data]:
  187. data = buf.maybe_extract_at_most(999999999)
  188. if data is None:
  189. return None
  190. return Data(data=data)
  191. def read_eof(self) -> EndOfMessage:
  192. return EndOfMessage()
  193. def expect_nothing(buf: ReceiveBuffer) -> None:
  194. if buf:
  195. raise LocalProtocolError("Got data when expecting EOF")
  196. return None
  197. ReadersType = Dict[
  198. Union[Type[Sentinel], Tuple[Type[Sentinel], Type[Sentinel]]],
  199. Union[Callable[..., Any], Dict[str, Callable[..., Any]]],
  200. ]
  201. READERS: ReadersType = {
  202. (CLIENT, IDLE): maybe_read_from_IDLE_client,
  203. (SERVER, IDLE): maybe_read_from_SEND_RESPONSE_server,
  204. (SERVER, SEND_RESPONSE): maybe_read_from_SEND_RESPONSE_server,
  205. (CLIENT, DONE): expect_nothing,
  206. (CLIENT, MUST_CLOSE): expect_nothing,
  207. (CLIENT, CLOSED): expect_nothing,
  208. (SERVER, DONE): expect_nothing,
  209. (SERVER, MUST_CLOSE): expect_nothing,
  210. (SERVER, CLOSED): expect_nothing,
  211. SEND_BODY: {
  212. "chunked": ChunkedReader,
  213. "content-length": ContentLengthReader,
  214. "http/1.0": Http10Reader,
  215. },
  216. }