Skip to content

Commit 259edc3

Browse files
authored
[PR #9851/541d86d backport][3.10] Fix incorrect parsing of chunk extensions with the pure Python parser (#9853)
1 parent bc15db6 commit 259edc3

File tree

3 files changed

+58
-1
lines changed

3 files changed

+58
-1
lines changed

CHANGES/9851.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed incorrect parsing of chunk extensions with the pure Python parser -- by :user:`bdraco`.

aiohttp/http_parser.py

+7
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,13 @@ def feed_data(
845845
i = chunk.find(CHUNK_EXT, 0, pos)
846846
if i >= 0:
847847
size_b = chunk[:i] # strip chunk-extensions
848+
# Verify no LF in the chunk-extension
849+
if b"\n" in (ext := chunk[i:pos]):
850+
exc = BadHttpMessage(
851+
f"Unexpected LF in chunk-extension: {ext!r}"
852+
)
853+
set_exception(self.payload, exc)
854+
raise exc
848855
else:
849856
size_b = chunk[:pos]
850857

tests/test_http_parser.py

+50-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import aiohttp
1515
from aiohttp import http_exceptions, streams
16+
from aiohttp.base_protocol import BaseProtocol
1617
from aiohttp.http_parser import (
1718
NO_EXTENSIONS,
1819
DeflateBuffer,
@@ -1477,7 +1478,55 @@ async def test_parse_chunked_payload_split_chunks(response: Any) -> None:
14771478
assert await reader.read() == b"firstsecond"
14781479

14791480

1480-
def test_partial_url(parser: Any) -> None:
1481+
@pytest.mark.skipif(NO_EXTENSIONS, reason="Only tests C parser.")
1482+
async def test_parse_chunked_payload_with_lf_in_extensions_c_parser(
1483+
loop: asyncio.AbstractEventLoop, protocol: BaseProtocol
1484+
) -> None:
1485+
"""Test the C-parser with a chunked payload that has a LF in the chunk extensions."""
1486+
# The C parser will raise a BadHttpMessage from feed_data
1487+
parser = HttpRequestParserC(
1488+
protocol,
1489+
loop,
1490+
2**16,
1491+
max_line_size=8190,
1492+
max_field_size=8190,
1493+
)
1494+
payload = (
1495+
b"GET / HTTP/1.1\r\nHost: localhost:5001\r\n"
1496+
b"Transfer-Encoding: chunked\r\n\r\n2;\nxx\r\n4c\r\n0\r\n\r\n"
1497+
b"GET /admin HTTP/1.1\r\nHost: localhost:5001\r\n"
1498+
b"Transfer-Encoding: chunked\r\n\r\n0\r\n\r\n"
1499+
)
1500+
with pytest.raises(http_exceptions.BadHttpMessage, match="\\\\nxx"):
1501+
parser.feed_data(payload)
1502+
1503+
1504+
async def test_parse_chunked_payload_with_lf_in_extensions_py_parser(
1505+
loop: asyncio.AbstractEventLoop, protocol: BaseProtocol
1506+
) -> None:
1507+
"""Test the py-parser with a chunked payload that has a LF in the chunk extensions."""
1508+
# The py parser will not raise the BadHttpMessage directly, but instead
1509+
# it will set the exception on the StreamReader.
1510+
parser = HttpRequestParserPy(
1511+
protocol,
1512+
loop,
1513+
2**16,
1514+
max_line_size=8190,
1515+
max_field_size=8190,
1516+
)
1517+
payload = (
1518+
b"GET / HTTP/1.1\r\nHost: localhost:5001\r\n"
1519+
b"Transfer-Encoding: chunked\r\n\r\n2;\nxx\r\n4c\r\n0\r\n\r\n"
1520+
b"GET /admin HTTP/1.1\r\nHost: localhost:5001\r\n"
1521+
b"Transfer-Encoding: chunked\r\n\r\n0\r\n\r\n"
1522+
)
1523+
messages, _, _ = parser.feed_data(payload)
1524+
reader = messages[0][1]
1525+
assert isinstance(reader.exception(), http_exceptions.BadHttpMessage)
1526+
assert "\\nxx" in str(reader.exception())
1527+
1528+
1529+
def test_partial_url(parser: HttpRequestParser) -> None:
14811530
messages, upgrade, tail = parser.feed_data(b"GET /te")
14821531
assert len(messages) == 0
14831532
messages, upgrade, tail = parser.feed_data(b"st HTTP/1.1\r\n\r\n")

0 commit comments

Comments
 (0)