Validate HTTP message headers to avoid request smuggling attacks. Raises a ValueError if they are malformed.
(message: Message)
| 64 | |
| 65 | |
| 66 | def validate_headers(message: Message) -> None: |
| 67 | """ |
| 68 | Validate HTTP message headers to avoid request smuggling attacks. |
| 69 | |
| 70 | Raises a ValueError if they are malformed. |
| 71 | """ |
| 72 | |
| 73 | te = [] |
| 74 | cl = [] |
| 75 | |
| 76 | for name, value in message.headers.fields: |
| 77 | if not _valid_header_name.match(name): |
| 78 | raise ValueError(f"invalid header name: {name!r}") |
| 79 | match name.lower(): |
| 80 | case b"transfer-encoding": |
| 81 | te.append(value) |
| 82 | case b"content-length": |
| 83 | cl.append(value) |
| 84 | |
| 85 | if te and cl: |
| 86 | # > A server MAY reject a request that contains both Content-Length and Transfer-Encoding or process such a |
| 87 | # > request in accordance with the Transfer-Encoding alone. |
| 88 | |
| 89 | # > A sender MUST NOT send a Content-Length header field in any message that contains a Transfer-Encoding header |
| 90 | # > field. |
| 91 | raise ValueError( |
| 92 | "message with both transfer-encoding and content-length headers" |
| 93 | ) |
| 94 | elif te: |
| 95 | if len(te) > 1: |
| 96 | raise ValueError(f"multiple transfer-encoding headers: {te!r}") |
| 97 | # > Transfer-Encoding was added in HTTP/1.1. It is generally assumed that implementations advertising only |
| 98 | # > HTTP/1.0 support will not understand how to process transfer-encoded content, and that an HTTP/1.0 message |
| 99 | # > received with a Transfer-Encoding is likely to have been forwarded without proper handling of the chunked |
| 100 | # > transfer coding in transit. |
| 101 | # |
| 102 | # > A client MUST NOT send a request containing Transfer-Encoding unless it knows the server will handle |
| 103 | # > HTTP/1.1 requests (or later minor revisions); such knowledge might be in the form of specific user |
| 104 | # > configuration or by remembering the version of a prior received response. A server MUST NOT send a response |
| 105 | # > containing Transfer-Encoding unless the corresponding request indicates HTTP/1.1 (or later minor revisions). |
| 106 | if not message.is_http11: |
| 107 | raise ValueError( |
| 108 | f"unexpected HTTP transfer-encoding {te[0]!r} for {message.http_version}" |
| 109 | ) |
| 110 | # > A server MUST NOT send a Transfer-Encoding header field in any response with a status code of 1xx |
| 111 | # > (Informational) or 204 (No Content). |
| 112 | if isinstance(message, Response) and ( |
| 113 | 100 <= message.status_code <= 199 or message.status_code == 204 |
| 114 | ): |
| 115 | raise ValueError( |
| 116 | f"unexpected HTTP transfer-encoding {te[0]!r} for response with status code {message.status_code}" |
| 117 | ) |
| 118 | # > If a Transfer-Encoding header field is present in a request and the chunked transfer coding is not the final |
| 119 | # > encoding, the message body length cannot be determined reliably; the server MUST respond with the 400 (Bad |
| 120 | # > Request) status code and then close the connection. |
| 121 | te_parsed = parse_transfer_encoding(te[0]) |
| 122 | match te_parsed: |
| 123 | case "chunked" | "compress,chunked" | "deflate,chunked" | "gzip,chunked": |
searching dependent graphs…