|
8 | 8 | MAX_RESPONSE_CHUNK_SIZE = 61440 # 64kb |
9 | 9 |
|
10 | 10 |
|
| 11 | +def _nocrlf(value: bytes) -> bytes: |
| 12 | + """Sanitize the given value to prevent CRLF injection.""" |
| 13 | + return value.replace(b"\r", b"").replace(b"\n", b"") |
| 14 | + |
| 15 | + |
11 | 16 | # Header writing utilities |
12 | 17 | def write_header(header): |
13 | | - return header[0] + b": " + header[1] + b"\r\n" |
| 18 | + """ |
| 19 | + This function writes a single HTTP header. It is used only by the HTTP Client part, |
| 20 | + because the server relies on the ASGI server to handle headers. |
| 21 | + """ |
| 22 | + # Sanitize header name and value to prevent CRLF injection |
| 23 | + return _nocrlf(header[0]) + b": " + _nocrlf(header[1]) + b"\r\n" |
14 | 24 |
|
15 | 25 |
|
16 | 26 | def write_headers(headers): |
@@ -43,28 +53,31 @@ def _get_status_line(status_code: int): |
43 | 53 | } |
44 | 54 |
|
45 | 55 |
|
46 | | -def get_status_line(status: int): |
| 56 | +def get_status_line(status: int) -> bytes: |
47 | 57 | return STATUS_LINES[status] |
48 | 58 |
|
49 | 59 |
|
50 | | -def write_request_method(request: Request): |
| 60 | +def write_request_method(request: Request) -> bytes: |
| 61 | + # RFC 7230: method must be a valid token |
| 62 | + if not re.match(r'^[!#$%&\'*+\-.0-9A-Z^_`a-z|~]+$', request.method): |
| 63 | + raise ValueError(f"Invalid HTTP method: {request.method!r}") |
51 | 64 | return request.method.encode() |
52 | 65 |
|
53 | 66 |
|
54 | | -def write_request_uri(request: Request): |
| 67 | +def write_request_uri(request: Request) -> bytes: |
55 | 68 | url = request.url |
56 | | - p = url.path or b"/" |
| 69 | + p = _nocrlf(url.path or b"/") |
57 | 70 | if url.query: |
58 | | - return p + b"?" + url.query |
| 71 | + return p + b"?" + _nocrlf(url.query) |
59 | 72 | return p |
60 | 73 |
|
61 | 74 |
|
62 | | -def ensure_host_header(request: Request): |
| 75 | +def ensure_host_header(request: Request) -> None: |
63 | 76 | if request.url.host: |
64 | 77 | request._add_header_if_missing(b"host", request.url.host) |
65 | 78 |
|
66 | 79 |
|
67 | | -def should_use_chunked_encoding(content: Content): |
| 80 | +def should_use_chunked_encoding(content: Content) -> bool: |
68 | 81 | return content.length < 0 |
69 | 82 |
|
70 | 83 |
|
|
0 commit comments