Skip to content

Commit

Permalink
[PR #9851/541d86d backport][3.10] Fix incorrect parsing of chunk exte…
Browse files Browse the repository at this point in the history
…nsions with the pure Python parser (#9853)
  • Loading branch information
bdraco authored Nov 13, 2024
1 parent bc15db6 commit 259edc3
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGES/9851.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed incorrect parsing of chunk extensions with the pure Python parser -- by :user:`bdraco`.
7 changes: 7 additions & 0 deletions aiohttp/http_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,13 @@ def feed_data(
i = chunk.find(CHUNK_EXT, 0, pos)
if i >= 0:
size_b = chunk[:i] # strip chunk-extensions
# Verify no LF in the chunk-extension
if b"\n" in (ext := chunk[i:pos]):
exc = BadHttpMessage(
f"Unexpected LF in chunk-extension: {ext!r}"
)
set_exception(self.payload, exc)
raise exc
else:
size_b = chunk[:pos]

Expand Down
51 changes: 50 additions & 1 deletion tests/test_http_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import aiohttp
from aiohttp import http_exceptions, streams
from aiohttp.base_protocol import BaseProtocol
from aiohttp.http_parser import (
NO_EXTENSIONS,
DeflateBuffer,
Expand Down Expand Up @@ -1477,7 +1478,55 @@ async def test_parse_chunked_payload_split_chunks(response: Any) -> None:
assert await reader.read() == b"firstsecond"


def test_partial_url(parser: Any) -> None:
@pytest.mark.skipif(NO_EXTENSIONS, reason="Only tests C parser.")
async def test_parse_chunked_payload_with_lf_in_extensions_c_parser(
loop: asyncio.AbstractEventLoop, protocol: BaseProtocol
) -> None:
"""Test the C-parser with a chunked payload that has a LF in the chunk extensions."""
# The C parser will raise a BadHttpMessage from feed_data
parser = HttpRequestParserC(
protocol,
loop,
2**16,
max_line_size=8190,
max_field_size=8190,
)
payload = (
b"GET / HTTP/1.1\r\nHost: localhost:5001\r\n"
b"Transfer-Encoding: chunked\r\n\r\n2;\nxx\r\n4c\r\n0\r\n\r\n"
b"GET /admin HTTP/1.1\r\nHost: localhost:5001\r\n"
b"Transfer-Encoding: chunked\r\n\r\n0\r\n\r\n"
)
with pytest.raises(http_exceptions.BadHttpMessage, match="\\\\nxx"):
parser.feed_data(payload)


async def test_parse_chunked_payload_with_lf_in_extensions_py_parser(
loop: asyncio.AbstractEventLoop, protocol: BaseProtocol
) -> None:
"""Test the py-parser with a chunked payload that has a LF in the chunk extensions."""
# The py parser will not raise the BadHttpMessage directly, but instead
# it will set the exception on the StreamReader.
parser = HttpRequestParserPy(
protocol,
loop,
2**16,
max_line_size=8190,
max_field_size=8190,
)
payload = (
b"GET / HTTP/1.1\r\nHost: localhost:5001\r\n"
b"Transfer-Encoding: chunked\r\n\r\n2;\nxx\r\n4c\r\n0\r\n\r\n"
b"GET /admin HTTP/1.1\r\nHost: localhost:5001\r\n"
b"Transfer-Encoding: chunked\r\n\r\n0\r\n\r\n"
)
messages, _, _ = parser.feed_data(payload)
reader = messages[0][1]
assert isinstance(reader.exception(), http_exceptions.BadHttpMessage)
assert "\\nxx" in str(reader.exception())


def test_partial_url(parser: HttpRequestParser) -> None:
messages, upgrade, tail = parser.feed_data(b"GET /te")
assert len(messages) == 0
messages, upgrade, tail = parser.feed_data(b"st HTTP/1.1\r\n\r\n")
Expand Down

0 comments on commit 259edc3

Please sign in to comment.