Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES/13246.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Stripped the trailing whitespace from header values in the C HTTP parser,
so that it matches the pure-Python parser and :rfc:`9110#section-5.5`
-- by :user:`LuShadowX`.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ Lubomir Gelo
Ludovic Gasc
Luis Pedrosa
Lukasz Marcin Dobrzanski
Lukshya Supyal
Lénárd Szolnoki
Makc Belousow
Manuel Miranda
Expand Down
10 changes: 7 additions & 3 deletions aiohttp/_http_parser.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -430,16 +430,20 @@ cdef class HttpParser:

cdef _process_header(self):
cdef str value
cdef bytes raw_value
if self._raw_name != b"":
name = find_header(self._raw_name)
value = self._raw_value.decode('utf-8', 'surrogateescape')
# llhttp strips the OWS before the field value but not after.
# https://www.rfc-editor.org/info/rfc9110/#section-5.5-3
raw_value = self._raw_value.rstrip(b" \t")
value = raw_value.decode('utf-8', 'surrogateescape')

# reject null bytes in header values - matches the Python parser
# check at http_parser.py. llhttp in lenient mode doesn't reject
# these itself, so we need to catch them here.
# ref: RFC 9110 section 5.5 (CTL chars forbidden in field values)
if "\x00" in value:
raise InvalidHeader(self._raw_value)
raise InvalidHeader(raw_value)

if not self._lax and name in SINGLETON_HEADERS:
if name in self._seen_singletons:
Expand All @@ -454,7 +458,7 @@ cdef class HttpParser:

self._has_value = False
self._header_name_size = 0
self._raw_headers.append((self._raw_name, self._raw_value))
self._raw_headers.append((self._raw_name, raw_value))
self._raw_name = b""
self._raw_value = b""

Expand Down
9 changes: 9 additions & 0 deletions tests/test_http_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,15 @@ def test_whitespace_before_header(parser: HttpRequestParser) -> None:
parser.feed_data(text)


def test_whitespace_around_header_value(parser: HttpRequestParser) -> None:
text = b"GET / HTTP/1.1\r\nHost: a\r\ntest: \t value \t \r\n\r\n"
messages, upgrade, tail = parser.feed_data(text)
msg = messages[0][0]

assert msg.headers["test"] == "value"
assert msg.raw_headers == ((b"Host", b"a"), (b"test", b"value"))


@pytest.fixture
def xfail_c_parser_status(request: pytest.FixtureRequest) -> None:
if isinstance(request.getfixturevalue("parser"), HttpRequestParserPy):
Expand Down
Loading