Skip to content

Commit d15f07c

Browse files
Upgrade to llhttp 9.2.1 (#8292) (#8297)
Fixes #8291. (cherry picked from commit 4d72dca)
1 parent 38dd9b8 commit d15f07c

File tree

4 files changed

+57
-19
lines changed

4 files changed

+57
-19
lines changed

CHANGES/8292.feature.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Upgraded to LLHTTP 9.2.1, and started rejecting obsolete line folding in Python parser to match -- by :user:`Dreamsorcerer`.

aiohttp/http_parser.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,12 @@ def __init__(
128128
max_line_size: int = 8190,
129129
max_headers: int = 32768,
130130
max_field_size: int = 8190,
131+
lax: bool = False,
131132
) -> None:
132133
self.max_line_size = max_line_size
133134
self.max_headers = max_headers
134135
self.max_field_size = max_field_size
136+
self._lax = lax
135137

136138
def parse_headers(
137139
self, lines: List[bytes]
@@ -178,7 +180,7 @@ def parse_headers(
178180
line = lines[lines_idx]
179181

180182
# consume continuation lines
181-
continuation = line and line[0] in (32, 9) # (' ', '\t')
183+
continuation = self._lax and line and line[0] in (32, 9) # (' ', '\t')
182184

183185
# Deprecated: https://www.rfc-editor.org/rfc/rfc9112.html#name-obsolete-line-folding
184186
if continuation:
@@ -273,7 +275,9 @@ def __init__(
273275
self._payload_parser: Optional[HttpPayloadParser] = None
274276
self._auto_decompress = auto_decompress
275277
self._limit = limit
276-
self._headers_parser = HeadersParser(max_line_size, max_headers, max_field_size)
278+
self._headers_parser = HeadersParser(
279+
max_line_size, max_headers, max_field_size, self.lax
280+
)
277281

278282
@abc.abstractmethod
279283
def parse_message(self, lines: List[bytes]) -> _MsgT:

tests/test_http_parser.py

+49-16
Original file line numberDiff line numberDiff line change
@@ -108,22 +108,32 @@ def test_c_parser_loaded():
108108

109109
def test_parse_headers(parser: Any) -> None:
110110
text = b"""GET /test HTTP/1.1\r
111-
test: line\r
112-
continue\r
111+
test: a line\r
113112
test2: data\r
114113
\r
115114
"""
116115
messages, upgrade, tail = parser.feed_data(text)
117116
assert len(messages) == 1
118117
msg = messages[0][0]
119118

120-
assert list(msg.headers.items()) == [("test", "line continue"), ("test2", "data")]
121-
assert msg.raw_headers == ((b"test", b"line continue"), (b"test2", b"data"))
119+
assert list(msg.headers.items()) == [("test", "a line"), ("test2", "data")]
120+
assert msg.raw_headers == ((b"test", b"a line"), (b"test2", b"data"))
122121
assert not msg.should_close
123122
assert msg.compression is None
124123
assert not msg.upgrade
125124

126125

126+
def test_reject_obsolete_line_folding(parser: Any) -> None:
127+
text = b"""GET /test HTTP/1.1\r
128+
test: line\r
129+
Content-Length: 48\r
130+
test2: data\r
131+
\r
132+
"""
133+
with pytest.raises(http_exceptions.BadHttpMessage):
134+
parser.feed_data(text)
135+
136+
127137
@pytest.mark.skipif(NO_EXTENSIONS, reason="Only tests C parser.")
128138
def test_invalid_character(loop: Any, protocol: Any, request: Any) -> None:
129139
parser = HttpRequestParserC(
@@ -353,8 +363,8 @@ def test_parse_delayed(parser) -> None:
353363

354364
def test_headers_multi_feed(parser) -> None:
355365
text1 = b"GET /test HTTP/1.1\r\n"
356-
text2 = b"test: line\r"
357-
text3 = b"\n continue\r\n\r\n"
366+
text2 = b"test: line"
367+
text3 = b" continue\r\n\r\n"
358368

359369
messages, upgrade, tail = parser.feed_data(text1)
360370
assert len(messages) == 0
@@ -713,31 +723,30 @@ def test_max_header_value_size_under_limit(parser) -> None:
713723

714724

715725
@pytest.mark.parametrize("size", [40965, 8191])
716-
def test_max_header_value_size_continuation(parser, size) -> None:
726+
def test_max_header_value_size_continuation(response, size) -> None:
717727
name = b"T" * (size - 5)
718-
text = b"GET /test HTTP/1.1\r\n" b"data: test\r\n " + name + b"\r\n\r\n"
728+
text = b"HTTP/1.1 200 Ok\r\ndata: test\r\n " + name + b"\r\n\r\n"
719729

720730
match = f"400, message:\n Got more than 8190 bytes \\({size}\\) when reading"
721731
with pytest.raises(http_exceptions.LineTooLong, match=match):
722-
parser.feed_data(text)
732+
response.feed_data(text)
723733

724734

725-
def test_max_header_value_size_continuation_under_limit(parser) -> None:
735+
def test_max_header_value_size_continuation_under_limit(response) -> None:
726736
value = b"A" * 8185
727-
text = b"GET /test HTTP/1.1\r\n" b"data: test\r\n " + value + b"\r\n\r\n"
737+
text = b"HTTP/1.1 200 Ok\r\ndata: test\r\n " + value + b"\r\n\r\n"
728738

729-
messages, upgrade, tail = parser.feed_data(text)
739+
messages, upgrade, tail = response.feed_data(text)
730740
msg = messages[0][0]
731-
assert msg.method == "GET"
732-
assert msg.path == "/test"
741+
assert msg.code == 200
742+
assert msg.reason == "Ok"
733743
assert msg.version == (1, 1)
734744
assert msg.headers == CIMultiDict({"data": "test " + value.decode()})
735745
assert msg.raw_headers == ((b"data", b"test " + value),)
736-
assert not msg.should_close
746+
# assert not msg.should_close # TODO: https://github.com/nodejs/llhttp/issues/354
737747
assert msg.compression is None
738748
assert not msg.upgrade
739749
assert not msg.chunked
740-
assert msg.url == URL("/test")
741750

742751

743752
def test_http_request_parser(parser) -> None:
@@ -991,6 +1000,30 @@ def test_http_response_parser_utf8_without_reason(response: Any) -> None:
9911000
assert not tail
9921001

9931002

1003+
def test_http_response_parser_obs_line_folding(response: Any) -> None:
1004+
text = b"HTTP/1.1 200 Ok\r\ntest: line\r\n continue\r\n\r\n"
1005+
1006+
messages, upgraded, tail = response.feed_data(text)
1007+
assert len(messages) == 1
1008+
msg = messages[0][0]
1009+
1010+
assert msg.version == (1, 1)
1011+
assert msg.code == 200
1012+
assert msg.reason == "Ok"
1013+
assert msg.headers == CIMultiDict([("TEST", "line continue")])
1014+
assert msg.raw_headers == ((b"test", b"line continue"),)
1015+
assert not upgraded
1016+
assert not tail
1017+
1018+
1019+
@pytest.mark.dev_mode
1020+
def test_http_response_parser_strict_obs_line_folding(response: Any) -> None:
1021+
text = b"HTTP/1.1 200 Ok\r\ntest: line\r\n continue\r\n\r\n"
1022+
1023+
with pytest.raises(http_exceptions.BadHttpMessage):
1024+
response.feed_data(text)
1025+
1026+
9941027
@pytest.mark.parametrize("size", [40962, 8191])
9951028
def test_http_response_parser_bad_status_line_too_long(response, size) -> None:
9961029
reason = b"t" * (size - 2)

0 commit comments

Comments
 (0)