Skip to content

Commit 5e89842

Browse files
authored
Enforce max_line_size on complete chunk-size lines in pure-Python parser (aio-libs#12832)
1 parent ccf218a commit 5e89842

3 files changed

Lines changed: 58 additions & 0 deletions

File tree

CHANGES/12832.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed the pure-Python HTTP parser not enforcing ``max_line_size`` on a chunk-size line when the whole line arrived in a single read; the limit was only applied to chunk-size metadata split across reads. The complete-line case is now checked too, matching the split-line behavior -- by :user:`bdraco`.

aiohttp/http_parser.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,10 @@ def feed_data(
937937
if self._chunk == ChunkState.PARSE_CHUNKED_SIZE:
938938
pos = chunk.find(SEP)
939939
if pos >= 0:
940+
# Only chunk-size lines reach here; trailers enforce
941+
# _max_field_size separately in PARSE_TRAILERS below.
942+
if pos > self._max_line_size:
943+
raise LineTooLong(chunk[:100] + b"...", self._max_line_size)
940944
i = chunk.find(CHUNK_EXT, 0, pos)
941945
if i >= 0:
942946
size_b = chunk[:i] # strip chunk-extensions

tests/test_http_parser.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2399,6 +2399,59 @@ async def test_parse_chunked_payload_size_error(
23992399
p.feed_data(b"blah\r\n")
24002400
assert isinstance(out.exception(), http_exceptions.TransferEncodingError)
24012401

2402+
async def test_chunked_chunk_size_line_too_long(
2403+
self, protocol: BaseProtocol
2404+
) -> None:
2405+
"""A complete oversized chunk-size line is rejected with LineTooLong."""
2406+
out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())
2407+
p = HttpPayloadParser(
2408+
out, chunked=True, headers_parser=HeadersParser(), max_line_size=32
2409+
)
2410+
size_line = b"1;" + b"a" * 4096 + b"\r\n"
2411+
with pytest.raises(http_exceptions.LineTooLong):
2412+
p.feed_data(size_line)
2413+
2414+
async def test_chunked_chunk_size_line_within_limit(
2415+
self, protocol: BaseProtocol
2416+
) -> None:
2417+
"""A small chunk-size line still parses when max_line_size is low."""
2418+
out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())
2419+
p = HttpPayloadParser(
2420+
out, chunked=True, headers_parser=HeadersParser(), max_line_size=32
2421+
)
2422+
p.feed_data(b"1\r\nx\r\n0\r\n\r\n")
2423+
assert out.is_eof()
2424+
assert b"x" == b"".join(out._buffer)
2425+
2426+
async def test_chunked_chunk_size_line_at_limit(
2427+
self, protocol: BaseProtocol
2428+
) -> None:
2429+
"""A chunk-size line of exactly max_line_size bytes is accepted (>, not >=)."""
2430+
out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())
2431+
p = HttpPayloadParser(
2432+
out, chunked=True, headers_parser=HeadersParser(), max_line_size=32
2433+
)
2434+
# "1;" + 30 * "a" is exactly 32 bytes before the CRLF.
2435+
size_line = b"1;" + b"a" * 30
2436+
assert len(size_line) == 32
2437+
p.feed_data(size_line + b"\r\nx\r\n0\r\n\r\n")
2438+
assert out.is_eof()
2439+
assert b"x" == b"".join(out._buffer)
2440+
2441+
async def test_chunked_chunk_size_line_one_over_limit(
2442+
self, protocol: BaseProtocol
2443+
) -> None:
2444+
"""A chunk-size line one byte over max_line_size is rejected."""
2445+
out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())
2446+
p = HttpPayloadParser(
2447+
out, chunked=True, headers_parser=HeadersParser(), max_line_size=32
2448+
)
2449+
# "1;" + 31 * "a" is 33 bytes before the CRLF.
2450+
size_line = b"1;" + b"a" * 31
2451+
assert len(size_line) == 33
2452+
with pytest.raises(http_exceptions.LineTooLong):
2453+
p.feed_data(size_line + b"\r\nx\r\n0\r\n\r\n")
2454+
24022455
async def test_parse_chunked_payload_size_data_mismatch(
24032456
self, protocol: BaseProtocol
24042457
) -> None:

0 commit comments

Comments
 (0)