From 2eff6d5f40daea66db30f87e4ee24e9970e9f899 Mon Sep 17 00:00:00 2001 From: Thomas Rausch Date: Mon, 10 Apr 2023 00:49:56 +0200 Subject: [PATCH] Add config option to pass raw h11 headers --- docs/how_to_guides/configuring.rst | 3 +++ src/hypercorn/config.py | 1 + src/hypercorn/protocol/h11.py | 9 ++++++++- tests/protocol/test_h11.py | 27 +++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/how_to_guides/configuring.rst b/docs/how_to_guides/configuring.rst index d88669d9..25dff12d 100644 --- a/docs/how_to_guides/configuring.rst +++ b/docs/how_to_guides/configuring.rst @@ -108,6 +108,9 @@ read_timeout ``--read-timeout`` Seconds to wait before group ``-g``, ``--group`` Group to own any unix sockets. h11_max_incomplete_size N/A The max HTTP/1.1 request line + headers 16KiB size in bytes. +h11_pass_raw_headers N/A Pass the raw headers from h11 to the ``False`` + Request object, which preserves header + casing. h2_max_concurrent_streams N/A Maximum number of HTTP/2 concurrent 100 streams. h2_max_header_list_size N/A Maximum number of HTTP/2 headers. 65536 diff --git a/src/hypercorn/config.py b/src/hypercorn/config.py index ecfa1bde..26f50f00 100644 --- a/src/hypercorn/config.py +++ b/src/hypercorn/config.py @@ -77,6 +77,7 @@ class Config: read_timeout: Optional[int] = None group: Optional[int] = None h11_max_incomplete_size = 16 * 1024 * BYTES + h11_pass_raw_headers = False h2_max_concurrent_streams = 100 h2_max_header_list_size = 2**16 h2_max_inbound_frame_size = 2**14 * OCTETS diff --git a/src/hypercorn/protocol/h11.py b/src/hypercorn/protocol/h11.py index e18d4884..e72b9d62 100755 --- a/src/hypercorn/protocol/h11.py +++ b/src/hypercorn/protocol/h11.py @@ -219,10 +219,17 @@ async def _create_stream(self, request: h11.Request) -> None: self.stream_send, STREAM_ID, ) + + if self.config.h11_pass_raw_headers: + # uses unmodified header names as they are passed by the client + headers = request.headers.raw_items() + else: + headers = list(request.headers) + await self.stream.handle( Request( stream_id=STREAM_ID, - headers=list(request.headers), + headers=headers, http_version=request.http_version.decode(), method=request.method.decode("ascii").upper(), raw_path=request.target, diff --git a/tests/protocol/test_h11.py b/tests/protocol/test_h11.py index 20e00917..fcec360e 100755 --- a/tests/protocol/test_h11.py +++ b/tests/protocol/test_h11.py @@ -197,6 +197,33 @@ async def test_protocol_handle_request(protocol: H11Protocol) -> None: ] +@pytest.mark.asyncio +async def test_protocol_handle_request_with_raw_headers(protocol: H11Protocol) -> None: + protocol.config.h11_pass_raw_headers = True + client = h11.Connection(h11.CLIENT) + headers = BASIC_HEADERS + [('FOO_BAR', 'foobar')] + await protocol.handle( + RawData(data=client.send(h11.Request(method="GET", target="/?a=b", headers=headers))) + ) + protocol.stream.handle.assert_called() # type: ignore + assert protocol.stream.handle.call_args_list == [ # type: ignore + call( + Request( + stream_id=1, + headers=[ + (b"Host", b"hypercorn"), + (b"Connection", b"close"), + (b"FOO_BAR", b"foobar"), + ], + http_version="1.1", + method="GET", + raw_path=b"/?a=b", + ) + ), + call(EndBody(stream_id=1)), + ] + + @pytest.mark.asyncio async def test_protocol_handle_protocol_error(protocol: H11Protocol) -> None: await protocol.handle(RawData(data=b"broken nonsense\r\n\r\n"))