frame_buffer.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. """
  2. h2/frame_buffer
  3. ~~~~~~~~~~~~~~~
  4. A data structure that provides a way to iterate over a byte buffer in terms of
  5. frames.
  6. """
  7. from __future__ import annotations
  8. from hyperframe.exceptions import InvalidDataError, InvalidFrameError
  9. from hyperframe.frame import ContinuationFrame, Frame, HeadersFrame, PushPromiseFrame
  10. from .exceptions import FrameDataMissingError, FrameTooLargeError, ProtocolError
  11. # To avoid a DOS attack based on sending loads of continuation frames, we limit
  12. # the maximum number we're perpared to receive. In this case, we'll set the
  13. # limit to 64, which means the largest encoded header block we can receive by
  14. # default is 262144 bytes long, and the largest possible *at all* is 1073741760
  15. # bytes long.
  16. #
  17. # This value seems reasonable for now, but in future we may want to evaluate
  18. # making it configurable.
  19. CONTINUATION_BACKLOG = 64
  20. class FrameBuffer:
  21. """
  22. A buffer data structure for HTTP/2 data that allows iteraton in terms of
  23. H2 frames.
  24. """
  25. def __init__(self, server: bool = False) -> None:
  26. self._data = bytearray()
  27. self.max_frame_size = 0
  28. self._preamble = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" if server else b""
  29. self._preamble_len = len(self._preamble)
  30. self._headers_buffer: list[HeadersFrame | ContinuationFrame | PushPromiseFrame] = []
  31. def add_data(self, data: bytes) -> None:
  32. """
  33. Add more data to the frame buffer.
  34. :param data: A bytestring containing the byte buffer.
  35. """
  36. if self._preamble_len:
  37. data_len = len(data)
  38. of_which_preamble = min(self._preamble_len, data_len)
  39. if self._preamble[:of_which_preamble] != data[:of_which_preamble]:
  40. msg = "Invalid HTTP/2 preamble."
  41. raise ProtocolError(msg)
  42. data = data[of_which_preamble:]
  43. self._preamble_len -= of_which_preamble
  44. self._preamble = self._preamble[of_which_preamble:]
  45. self._data += data
  46. def _validate_frame_length(self, length: int) -> None:
  47. """
  48. Confirm that the frame is an appropriate length.
  49. """
  50. if length > self.max_frame_size:
  51. msg = f"Received overlong frame: length {length}, max {self.max_frame_size}"
  52. raise FrameTooLargeError(msg)
  53. def _update_header_buffer(self, f: Frame | None) -> Frame | None:
  54. """
  55. Updates the internal header buffer. Returns a frame that should replace
  56. the current one. May throw exceptions if this frame is invalid.
  57. """
  58. # Check if we're in the middle of a headers block. If we are, this
  59. # frame *must* be a CONTINUATION frame with the same stream ID as the
  60. # leading HEADERS or PUSH_PROMISE frame. Anything else is a
  61. # ProtocolError. If the frame *is* valid, append it to the header
  62. # buffer.
  63. if self._headers_buffer:
  64. stream_id = self._headers_buffer[0].stream_id
  65. valid_frame = (
  66. f is not None and
  67. isinstance(f, ContinuationFrame) and
  68. f.stream_id == stream_id
  69. )
  70. if not valid_frame:
  71. msg = "Invalid frame during header block."
  72. raise ProtocolError(msg)
  73. assert isinstance(f, ContinuationFrame)
  74. # Append the frame to the buffer.
  75. self._headers_buffer.append(f)
  76. if len(self._headers_buffer) > CONTINUATION_BACKLOG:
  77. msg = "Too many continuation frames received."
  78. raise ProtocolError(msg)
  79. # If this is the end of the header block, then we want to build a
  80. # mutant HEADERS frame that's massive. Use the original one we got,
  81. # then set END_HEADERS and set its data appopriately. If it's not
  82. # the end of the block, lose the current frame: we can't yield it.
  83. if "END_HEADERS" in f.flags:
  84. f = self._headers_buffer[0]
  85. f.flags.add("END_HEADERS")
  86. f.data = b"".join(x.data for x in self._headers_buffer)
  87. self._headers_buffer = []
  88. else:
  89. f = None
  90. elif (isinstance(f, (HeadersFrame, PushPromiseFrame)) and
  91. "END_HEADERS" not in f.flags):
  92. # This is the start of a headers block! Save the frame off and then
  93. # act like we didn't receive one.
  94. self._headers_buffer.append(f)
  95. f = None
  96. return f
  97. # The methods below support the iterator protocol.
  98. def __iter__(self) -> FrameBuffer:
  99. return self
  100. def __next__(self) -> Frame:
  101. # First, check that we have enough data to successfully parse the
  102. # next frame header. If not, bail. Otherwise, parse it.
  103. if len(self._data) < 9:
  104. raise StopIteration
  105. try:
  106. f, length = Frame.parse_frame_header(memoryview(self._data[:9]))
  107. except (InvalidDataError, InvalidFrameError) as err: # pragma: no cover
  108. msg = f"Received frame with invalid header: {err!s}"
  109. raise ProtocolError(msg) from err
  110. # Next, check that we have enough length to parse the frame body. If
  111. # not, bail, leaving the frame header data in the buffer for next time.
  112. if len(self._data) < length + 9:
  113. raise StopIteration
  114. # Confirm the frame has an appropriate length.
  115. self._validate_frame_length(length)
  116. # Try to parse the frame body
  117. try:
  118. f.parse_body(memoryview(self._data[9:9+length]))
  119. except InvalidDataError as err:
  120. msg = "Received frame with non-compliant data"
  121. raise ProtocolError(msg) from err
  122. except InvalidFrameError as err:
  123. msg = "Frame data missing or invalid"
  124. raise FrameDataMissingError(msg) from err
  125. # At this point, as we know we'll use or discard the entire frame, we
  126. # can update the data.
  127. self._data = self._data[9+length:]
  128. # Pass the frame through the header buffer.
  129. new_frame = self._update_header_buffer(f)
  130. # If we got a frame we didn't understand or shouldn't yield, rather
  131. # than return None it'd be better if we just tried to get the next
  132. # frame in the sequence instead. Recurse back into ourselves to do
  133. # that. This is safe because the amount of work we have to do here is
  134. # strictly bounded by the length of the buffer.
  135. return new_frame if new_frame is not None else self.__next__()