(
status_code: int,
headers: Headers,
read_line: Callable[[int], Generator[None, None, bytes | bytearray]],
read_exact: Callable[[int], Generator[None, None, bytes | bytearray]],
read_to_eof: Callable[[int], Generator[None, None, bytes | bytearray]],
)
| 378 | |
| 379 | |
| 380 | def read_body( |
| 381 | status_code: int, |
| 382 | headers: Headers, |
| 383 | read_line: Callable[[int], Generator[None, None, bytes | bytearray]], |
| 384 | read_exact: Callable[[int], Generator[None, None, bytes | bytearray]], |
| 385 | read_to_eof: Callable[[int], Generator[None, None, bytes | bytearray]], |
| 386 | ) -> Generator[None, None, bytes | bytearray]: |
| 387 | # https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3 |
| 388 | |
| 389 | # Since websockets only does GET requests (no HEAD, no CONNECT), all |
| 390 | # responses except 1xx, 204, and 304 include a message body. |
| 391 | if 100 <= status_code < 200 or status_code == 204 or status_code == 304: |
| 392 | return b"" |
| 393 | |
| 394 | # MultipleValuesError is sufficiently unlikely that we don't attempt to |
| 395 | # handle it when accessing headers. Instead we document that its parent |
| 396 | # class, LookupError, may be raised. |
| 397 | # Conversions from str to int are protected by sys.set_int_max_str_digits.. |
| 398 | |
| 399 | elif (coding := headers.get("Transfer-Encoding")) is not None: |
| 400 | if coding != "chunked": |
| 401 | raise NotImplementedError(f"transfer coding {coding} isn't supported") |
| 402 | |
| 403 | body = b"" |
| 404 | while True: |
| 405 | chunk_size_line = yield from parse_line(read_line) |
| 406 | raw_chunk_size = chunk_size_line.split(b";", 1)[0] |
| 407 | # Set a lower limit than default_max_str_digits; 1 EB is plenty. |
| 408 | if len(raw_chunk_size) > 15: |
| 409 | str_chunk_size = raw_chunk_size.decode(errors="backslashreplace") |
| 410 | raise SecurityError(f"chunk too large: 0x{str_chunk_size} bytes") |
| 411 | chunk_size = int(raw_chunk_size, 16) |
| 412 | if chunk_size == 0: |
| 413 | break |
| 414 | if len(body) + chunk_size > MAX_BODY_SIZE: |
| 415 | raise SecurityError( |
| 416 | f"chunk too large: {chunk_size} bytes after {len(body)} bytes" |
| 417 | ) |
| 418 | body += yield from read_exact(chunk_size) |
| 419 | if (yield from read_exact(2)) != b"\r\n": |
| 420 | raise ValueError("chunk without CRLF") |
| 421 | # Read the trailer. |
| 422 | yield from parse_headers(read_line) |
| 423 | return body |
| 424 | |
| 425 | elif (raw_content_length := headers.get("Content-Length")) is not None: |
| 426 | # Set a lower limit than default_max_str_digits; 1 EiB is plenty. |
| 427 | if len(raw_content_length) > 18: |
| 428 | raise SecurityError(f"body too large: {raw_content_length} bytes") |
| 429 | content_length = int(raw_content_length) |
| 430 | if content_length > MAX_BODY_SIZE: |
| 431 | raise SecurityError(f"body too large: {content_length} bytes") |
| 432 | return (yield from read_exact(content_length)) |
| 433 | |
| 434 | else: |
| 435 | try: |
| 436 | return (yield from read_to_eof(MAX_BODY_SIZE)) |
| 437 | except RuntimeError: |
no test coverage detected
searching dependent graphs…