Parse HTTP headers. Non-ASCII characters are represented with surrogate escapes. Args: read_line: Generator-based coroutine that reads a LF-terminated line or raises an exception if there isn't enough data. Raises: EOFError: If the connection is closed
(
read_line: Callable[[int], Generator[None, None, bytes | bytearray]],
)
| 327 | |
| 328 | |
| 329 | def parse_headers( |
| 330 | read_line: Callable[[int], Generator[None, None, bytes | bytearray]], |
| 331 | ) -> Generator[None, None, Headers]: |
| 332 | """ |
| 333 | Parse HTTP headers. |
| 334 | |
| 335 | Non-ASCII characters are represented with surrogate escapes. |
| 336 | |
| 337 | Args: |
| 338 | read_line: Generator-based coroutine that reads a LF-terminated line |
| 339 | or raises an exception if there isn't enough data. |
| 340 | |
| 341 | Raises: |
| 342 | EOFError: If the connection is closed without complete headers. |
| 343 | SecurityError: If the request exceeds a security limit. |
| 344 | ValueError: If the request isn't well formatted. |
| 345 | |
| 346 | """ |
| 347 | # https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 |
| 348 | |
| 349 | # We don't attempt to support obsolete line folding. |
| 350 | |
| 351 | headers = Headers() |
| 352 | for _ in range(MAX_NUM_HEADERS + 1): |
| 353 | try: |
| 354 | line = yield from parse_line(read_line) |
| 355 | except EOFError as exc: |
| 356 | raise EOFError("connection closed while reading HTTP headers") from exc |
| 357 | if line == b"": |
| 358 | break |
| 359 | |
| 360 | try: |
| 361 | raw_name, raw_value = line.split(b":", 1) |
| 362 | except ValueError: # not enough values to unpack (expected 2, got 1) |
| 363 | raise ValueError(f"invalid HTTP header line: {d(line)}") from None |
| 364 | if not _token_re.fullmatch(raw_name): |
| 365 | raise ValueError(f"invalid HTTP header name: {d(raw_name)}") |
| 366 | raw_value = raw_value.strip(b" \t") |
| 367 | if not _value_re.fullmatch(raw_value): |
| 368 | raise ValueError(f"invalid HTTP header value: {d(raw_value)}") |
| 369 | |
| 370 | name = raw_name.decode("ascii") # guaranteed to be ASCII at this point |
| 371 | value = raw_value.decode("ascii", "surrogateescape") |
| 372 | headers[name] = value |
| 373 | |
| 374 | else: |
| 375 | raise SecurityError("too many HTTP headers") |
| 376 | |
| 377 | return headers |
| 378 | |
| 379 | |
| 380 | def read_body( |
searching dependent graphs…