From 259edc369075de63e6f3a4eaade058c62af0df71 Mon Sep 17 00:00:00 2001
From: "J. Nick Koston" <nick@koston.org>
Date: Wed, 13 Nov 2024 08:50:36 -0600
Subject: [PATCH] [PR #9851/541d86d backport][3.10] Fix incorrect parsing of
 chunk extensions with the pure Python parser (#9853)

--- python-aiohttp-3.8.4.orig/aiohttp/http_parser.py
+++ python-aiohttp-3.8.4/aiohttp/http_parser.py
@@ -785,6 +785,13 @@ 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 (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]
 
--- python-aiohttp-3.8.4.orig/tests/test_http_parser.py
+++ python-aiohttp-3.8.4/tests/test_http_parser.py
@@ -11,6 +11,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,
@@ -882,6 +883,53 @@ def test_parse_no_length_payload(parser)
     msg, payload = parser.feed_data(text)[0][0]
     assert payload.is_eof()
 
+@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) -> None:
     messages, upgrade, tail = parser.feed_data(b"GET /te")
