Index: aiohttp-3.8.5/CHANGES/9851.bugfix.rst =================================================================== --- /dev/null +++ aiohttp-3.8.5/CHANGES/9851.bugfix.rst @@ -0,0 +1 @@ +Fixed incorrect parsing of chunk extensions with the pure Python parser -- by :user:`bdraco`. Index: aiohttp-3.8.5/aiohttp/http_parser.py =================================================================== --- aiohttp-3.8.5.orig/aiohttp/http_parser.py +++ aiohttp-3.8.5/aiohttp/http_parser.py @@ -26,7 +26,7 @@ from yarl import URL from . import hdrs from .base_protocol import BaseProtocol -from .helpers import NO_EXTENSIONS, BaseTimerContext +from .helpers import NO_EXTENSIONS, BaseTimerContext, set_exception from .http_exceptions import ( BadHttpMessage, BadStatusLine, @@ -770,6 +770,14 @@ class HttpPayloadParser: 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 chunk[i:pos]: + ext = repr(chunk[i:pos]) + exc = BadHttpMessage( + "Unexpected LF in chunk-extension: %s" % ext + ) + set_exception(self.payload, exc) + raise exc else: size_b = chunk[:pos] Index: aiohttp-3.8.5/tests/test_http_parser.py =================================================================== --- aiohttp-3.8.5.orig/tests/test_http_parser.py +++ aiohttp-3.8.5/tests/test_http_parser.py @@ -12,6 +12,7 @@ from yarl import URL import aiohttp from aiohttp import http_exceptions, streams +from aiohttp.base_protocol import BaseProtocol from aiohttp.http_parser import ( NO_EXTENSIONS, DeflateBuffer, @@ -1202,3 +1203,27 @@ class TestDeflateBuffer: dbuf.feed_eof() assert buf.at_eof() + + +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, + 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()) Index: aiohttp-3.8.5/aiohttp/helpers.py =================================================================== --- aiohttp-3.8.5.orig/aiohttp/helpers.py +++ aiohttp-3.8.5/aiohttp/helpers.py @@ -796,8 +796,10 @@ def set_result(fut: "asyncio.Future[_T]" def set_exception(fut: "asyncio.Future[_T]", exc: BaseException) -> None: - if not fut.done(): - fut.set_exception(exc) + if asyncio.isfuture(fut) and fut.done(): + return + + fut.set_exception(exc) class ChainMapProxy(Mapping[str, Any]):