diff options
| author | Jiaying Song <jiaying.song.cn@windriver.com> | 2025-07-16 17:22:22 +0800 |
|---|---|---|
| committer | Armin Kuster <akuster808@gmail.com> | 2025-07-27 14:35:10 -0400 |
| commit | 59d381adcaf70ccae5e78a8a21e2afbc59a52165 (patch) | |
| tree | 0aae5dd5ebf1f7d313738095fd86c38a4d720d52 /meta-python | |
| parent | 0883565b5dc963318111b04fd0c5dbf1d1b5fa2d (diff) | |
| download | meta-openembedded-59d381adcaf70ccae5e78a8a21e2afbc59a52165.tar.gz | |
python3-aiohttp: fix CVE-2025-53643
AIOHTTP is an asynchronous HTTP client/server framework for asyncio and
Python. Prior to version 3.12.14, the Python parser is vulnerable to a
request smuggling vulnerability due to not parsing trailer sections of
an HTTP request. If a pure Python version of aiohttp is installed (i.e.
without the usual C extensions) or AIOHTTP_NO_EXTENSIONS is enabled,
then an attacker may be able to execute a request smuggling attack to
bypass certain firewalls or proxy protections. Version 3.12.14 contains
a patch for this issue.
References:
https://nvd.nist.gov/vuln/detail/CVE-2025-53643
Signed-off-by: Jiaying Song <jiaying.song.cn@windriver.com>
Signed-off-by: Armin Kuster <akuster808@gmail.com>
Diffstat (limited to 'meta-python')
| -rw-r--r-- | meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-53643.patch | 526 | ||||
| -rw-r--r-- | meta-python/recipes-devtools/python/python3-aiohttp_3.11.16.bb | 2 |
2 files changed, 528 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-53643.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-53643.patch new file mode 100644 index 0000000000..fd8f83d563 --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-53643.patch | |||
| @@ -0,0 +1,526 @@ | |||
| 1 | From 5a3e141bca22757e60b3c584b8ac7eee90708b8b Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Sam Bull <git@sambull.org> | ||
| 3 | Date: Wed, 9 Jul 2025 19:55:22 +0100 | ||
| 4 | Subject: [PATCH] Add trailer parsing logic (#11269) (#11287) | ||
| 5 | |||
| 6 | (cherry picked from commit 7dd4b5535e6bf9c2d2f05fde638517bff065ba74) | ||
| 7 | |||
| 8 | CVE-2025-53643.patch | ||
| 9 | |||
| 10 | Upstream-Status: Backport | ||
| 11 | [https://github.com/aio-libs/aiohttp/commit/e8d774f635dc6d1cd3174d0e38891da5de0e2b6a] | ||
| 12 | |||
| 13 | Signed-off-by: Jiaying Song <jiaying.song.cn@windriver.com> | ||
| 14 | --- | ||
| 15 | CHANGES/11269.feature.rst | 1 + | ||
| 16 | aiohttp/http_parser.py | 70 +++++++++--------- | ||
| 17 | aiohttp/multipart.py | 2 +- | ||
| 18 | tests/test_http_parser.py | 148 ++++++++++++++++---------------------- | ||
| 19 | 4 files changed, 100 insertions(+), 121 deletions(-) | ||
| 20 | create mode 100644 CHANGES/11269.feature.rst | ||
| 21 | |||
| 22 | diff --git a/CHANGES/11269.feature.rst b/CHANGES/11269.feature.rst | ||
| 23 | new file mode 100644 | ||
| 24 | index 000000000..92cf173be | ||
| 25 | --- /dev/null | ||
| 26 | +++ b/CHANGES/11269.feature.rst | ||
| 27 | @@ -0,0 +1 @@ | ||
| 28 | +Added initial trailer parsing logic to Python HTTP parser -- by :user:`Dreamsorcerer`. | ||
| 29 | diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py | ||
| 30 | index 1b8b5b4d4..7d8db6d6f 100644 | ||
| 31 | --- a/aiohttp/http_parser.py | ||
| 32 | +++ b/aiohttp/http_parser.py | ||
| 33 | @@ -142,8 +142,8 @@ class HeadersParser: | ||
| 34 | # note: "raw" does not mean inclusion of OWS before/after the field value | ||
| 35 | raw_headers = [] | ||
| 36 | |||
| 37 | - lines_idx = 1 | ||
| 38 | - line = lines[1] | ||
| 39 | + lines_idx = 0 | ||
| 40 | + line = lines[lines_idx] | ||
| 41 | line_count = len(lines) | ||
| 42 | |||
| 43 | while line: | ||
| 44 | @@ -400,6 +400,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]): | ||
| 45 | response_with_body=self.response_with_body, | ||
| 46 | auto_decompress=self._auto_decompress, | ||
| 47 | lax=self.lax, | ||
| 48 | + headers_parser=self._headers_parser, | ||
| 49 | ) | ||
| 50 | if not payload_parser.done: | ||
| 51 | self._payload_parser = payload_parser | ||
| 52 | @@ -418,6 +419,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]): | ||
| 53 | compression=msg.compression, | ||
| 54 | auto_decompress=self._auto_decompress, | ||
| 55 | lax=self.lax, | ||
| 56 | + headers_parser=self._headers_parser, | ||
| 57 | ) | ||
| 58 | elif not empty_body and length is None and self.read_until_eof: | ||
| 59 | payload = StreamReader( | ||
| 60 | @@ -436,6 +438,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]): | ||
| 61 | response_with_body=self.response_with_body, | ||
| 62 | auto_decompress=self._auto_decompress, | ||
| 63 | lax=self.lax, | ||
| 64 | + headers_parser=self._headers_parser, | ||
| 65 | ) | ||
| 66 | if not payload_parser.done: | ||
| 67 | self._payload_parser = payload_parser | ||
| 68 | @@ -473,6 +476,10 @@ class HttpParser(abc.ABC, Generic[_MsgT]): | ||
| 69 | |||
| 70 | eof = True | ||
| 71 | data = b"" | ||
| 72 | + if isinstance( | ||
| 73 | + underlying_exc, (InvalidHeader, TransferEncodingError) | ||
| 74 | + ): | ||
| 75 | + raise | ||
| 76 | |||
| 77 | if eof: | ||
| 78 | start_pos = 0 | ||
| 79 | @@ -635,7 +642,7 @@ class HttpRequestParser(HttpParser[RawRequestMessage]): | ||
| 80 | compression, | ||
| 81 | upgrade, | ||
| 82 | chunked, | ||
| 83 | - ) = self.parse_headers(lines) | ||
| 84 | + ) = self.parse_headers(lines[1:]) | ||
| 85 | |||
| 86 | if close is None: # then the headers weren't set in the request | ||
| 87 | if version_o <= HttpVersion10: # HTTP 1.0 must asks to not close | ||
| 88 | @@ -721,7 +728,7 @@ class HttpResponseParser(HttpParser[RawResponseMessage]): | ||
| 89 | compression, | ||
| 90 | upgrade, | ||
| 91 | chunked, | ||
| 92 | - ) = self.parse_headers(lines) | ||
| 93 | + ) = self.parse_headers(lines[1:]) | ||
| 94 | |||
| 95 | if close is None: | ||
| 96 | if version_o <= HttpVersion10: | ||
| 97 | @@ -764,6 +771,8 @@ class HttpPayloadParser: | ||
| 98 | response_with_body: bool = True, | ||
| 99 | auto_decompress: bool = True, | ||
| 100 | lax: bool = False, | ||
| 101 | + *, | ||
| 102 | + headers_parser: HeadersParser, | ||
| 103 | ) -> None: | ||
| 104 | self._length = 0 | ||
| 105 | self._type = ParseState.PARSE_UNTIL_EOF | ||
| 106 | @@ -772,6 +781,8 @@ class HttpPayloadParser: | ||
| 107 | self._chunk_tail = b"" | ||
| 108 | self._auto_decompress = auto_decompress | ||
| 109 | self._lax = lax | ||
| 110 | + self._headers_parser = headers_parser | ||
| 111 | + self._trailer_lines: list[bytes] = [] | ||
| 112 | self.done = False | ||
| 113 | |||
| 114 | # payload decompression wrapper | ||
| 115 | @@ -848,7 +859,7 @@ class HttpPayloadParser: | ||
| 116 | size_b = chunk[:i] # strip chunk-extensions | ||
| 117 | # Verify no LF in the chunk-extension | ||
| 118 | if b"\n" in (ext := chunk[i:pos]): | ||
| 119 | - exc = BadHttpMessage( | ||
| 120 | + exc = TransferEncodingError( | ||
| 121 | f"Unexpected LF in chunk-extension: {ext!r}" | ||
| 122 | ) | ||
| 123 | set_exception(self.payload, exc) | ||
| 124 | @@ -869,7 +880,7 @@ class HttpPayloadParser: | ||
| 125 | |||
| 126 | chunk = chunk[pos + len(SEP) :] | ||
| 127 | if size == 0: # eof marker | ||
| 128 | - self._chunk = ChunkState.PARSE_MAYBE_TRAILERS | ||
| 129 | + self._chunk = ChunkState.PARSE_TRAILERS | ||
| 130 | if self._lax and chunk.startswith(b"\r"): | ||
| 131 | chunk = chunk[1:] | ||
| 132 | else: | ||
| 133 | @@ -907,38 +918,31 @@ class HttpPayloadParser: | ||
| 134 | self._chunk_tail = chunk | ||
| 135 | return False, b"" | ||
| 136 | |||
| 137 | - # if stream does not contain trailer, after 0\r\n | ||
| 138 | - # we should get another \r\n otherwise | ||
| 139 | - # trailers needs to be skipped until \r\n\r\n | ||
| 140 | - if self._chunk == ChunkState.PARSE_MAYBE_TRAILERS: | ||
| 141 | - head = chunk[: len(SEP)] | ||
| 142 | - if head == SEP: | ||
| 143 | - # end of stream | ||
| 144 | - self.payload.feed_eof() | ||
| 145 | - return True, chunk[len(SEP) :] | ||
| 146 | - # Both CR and LF, or only LF may not be received yet. It is | ||
| 147 | - # expected that CRLF or LF will be shown at the very first | ||
| 148 | - # byte next time, otherwise trailers should come. The last | ||
| 149 | - # CRLF which marks the end of response might not be | ||
| 150 | - # contained in the same TCP segment which delivered the | ||
| 151 | - # size indicator. | ||
| 152 | - if not head: | ||
| 153 | - return False, b"" | ||
| 154 | - if head == SEP[:1]: | ||
| 155 | - self._chunk_tail = head | ||
| 156 | - return False, b"" | ||
| 157 | - self._chunk = ChunkState.PARSE_TRAILERS | ||
| 158 | - | ||
| 159 | - # read and discard trailer up to the CRLF terminator | ||
| 160 | if self._chunk == ChunkState.PARSE_TRAILERS: | ||
| 161 | pos = chunk.find(SEP) | ||
| 162 | - if pos >= 0: | ||
| 163 | - chunk = chunk[pos + len(SEP) :] | ||
| 164 | - self._chunk = ChunkState.PARSE_MAYBE_TRAILERS | ||
| 165 | - else: | ||
| 166 | + if pos < 0: # No line found | ||
| 167 | self._chunk_tail = chunk | ||
| 168 | return False, b"" | ||
| 169 | |||
| 170 | + line = chunk[:pos] | ||
| 171 | + chunk = chunk[pos + len(SEP) :] | ||
| 172 | + if SEP == b"\n": # For lax response parsing | ||
| 173 | + line = line.rstrip(b"\r") | ||
| 174 | + self._trailer_lines.append(line) | ||
| 175 | + | ||
| 176 | + # \r\n\r\n found, end of stream | ||
| 177 | + if self._trailer_lines[-1] == b"": | ||
| 178 | + # Headers and trailers are defined the same way, | ||
| 179 | + # so we reuse the HeadersParser here. | ||
| 180 | + try: | ||
| 181 | + trailers, raw_trailers = self._headers_parser.parse_headers( | ||
| 182 | + self._trailer_lines | ||
| 183 | + ) | ||
| 184 | + finally: | ||
| 185 | + self._trailer_lines.clear() | ||
| 186 | + self.payload.feed_eof() | ||
| 187 | + return True, chunk | ||
| 188 | + | ||
| 189 | # Read all bytes until eof | ||
| 190 | elif self._type == ParseState.PARSE_UNTIL_EOF: | ||
| 191 | self.payload.feed_data(chunk, len(chunk)) | ||
| 192 | diff --git a/aiohttp/multipart.py b/aiohttp/multipart.py | ||
| 193 | index bd4d8ae1d..6faff0e9f 100644 | ||
| 194 | --- a/aiohttp/multipart.py | ||
| 195 | +++ b/aiohttp/multipart.py | ||
| 196 | @@ -767,7 +767,7 @@ class MultipartReader: | ||
| 197 | raise ValueError(f"Invalid boundary {chunk!r}, expected {self._boundary!r}") | ||
| 198 | |||
| 199 | async def _read_headers(self) -> "CIMultiDictProxy[str]": | ||
| 200 | - lines = [b""] | ||
| 201 | + lines = [] | ||
| 202 | while True: | ||
| 203 | chunk = await self._content.readline() | ||
| 204 | chunk = chunk.strip() | ||
| 205 | diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py | ||
| 206 | index 58fef625f..385452c1c 100644 | ||
| 207 | --- a/tests/test_http_parser.py | ||
| 208 | +++ b/tests/test_http_parser.py | ||
| 209 | @@ -17,6 +17,7 @@ from aiohttp.base_protocol import BaseProtocol | ||
| 210 | from aiohttp.http_parser import ( | ||
| 211 | NO_EXTENSIONS, | ||
| 212 | DeflateBuffer, | ||
| 213 | + HeadersParser, | ||
| 214 | HttpPayloadParser, | ||
| 215 | HttpRequestParser, | ||
| 216 | HttpRequestParserPy, | ||
| 217 | @@ -244,41 +245,13 @@ def test_content_length_transfer_encoding(parser: Any) -> None: | ||
| 218 | parser.feed_data(text) | ||
| 219 | |||
| 220 | |||
| 221 | -def test_bad_chunked_py(loop: Any, protocol: Any) -> None: | ||
| 222 | +def test_bad_chunked(parser: HttpRequestParser) -> None: | ||
| 223 | """Test that invalid chunked encoding doesn't allow content-length to be used.""" | ||
| 224 | - parser = HttpRequestParserPy( | ||
| 225 | - protocol, | ||
| 226 | - loop, | ||
| 227 | - 2**16, | ||
| 228 | - max_line_size=8190, | ||
| 229 | - max_field_size=8190, | ||
| 230 | - ) | ||
| 231 | - text = ( | ||
| 232 | - b"GET / HTTP/1.1\r\nHost: a\r\nTransfer-Encoding: chunked\r\n\r\n0_2e\r\n\r\n" | ||
| 233 | - + b"GET / HTTP/1.1\r\nHost: a\r\nContent-Length: 5\r\n\r\n0\r\n\r\n" | ||
| 234 | - ) | ||
| 235 | - messages, upgrade, tail = parser.feed_data(text) | ||
| 236 | - assert isinstance(messages[0][1].exception(), http_exceptions.TransferEncodingError) | ||
| 237 | - | ||
| 238 | - | ||
| 239 | -@pytest.mark.skipif( | ||
| 240 | - "HttpRequestParserC" not in dir(aiohttp.http_parser), | ||
| 241 | - reason="C based HTTP parser not available", | ||
| 242 | -) | ||
| 243 | -def test_bad_chunked_c(loop: Any, protocol: Any) -> None: | ||
| 244 | - """C parser behaves differently. Maybe we should align them later.""" | ||
| 245 | - parser = HttpRequestParserC( | ||
| 246 | - protocol, | ||
| 247 | - loop, | ||
| 248 | - 2**16, | ||
| 249 | - max_line_size=8190, | ||
| 250 | - max_field_size=8190, | ||
| 251 | - ) | ||
| 252 | text = ( | ||
| 253 | b"GET / HTTP/1.1\r\nHost: a\r\nTransfer-Encoding: chunked\r\n\r\n0_2e\r\n\r\n" | ||
| 254 | + b"GET / HTTP/1.1\r\nHost: a\r\nContent-Length: 5\r\n\r\n0\r\n\r\n" | ||
| 255 | ) | ||
| 256 | - with pytest.raises(http_exceptions.BadHttpMessage): | ||
| 257 | + with pytest.raises(http_exceptions.BadHttpMessage, match="0_2e"): | ||
| 258 | parser.feed_data(text) | ||
| 259 | |||
| 260 | |||
| 261 | @@ -1158,8 +1131,8 @@ async def test_http_response_parser_bad_chunked_strict_py(loop, protocol) -> Non | ||
| 262 | text = ( | ||
| 263 | b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5 \r\nabcde\r\n0\r\n\r\n" | ||
| 264 | ) | ||
| 265 | - messages, upgrade, tail = response.feed_data(text) | ||
| 266 | - assert isinstance(messages[0][1].exception(), http_exceptions.TransferEncodingError) | ||
| 267 | + with pytest.raises(http_exceptions.TransferEncodingError, match="5"): | ||
| 268 | + response.feed_data(text) | ||
| 269 | |||
| 270 | |||
| 271 | @pytest.mark.dev_mode | ||
| 272 | @@ -1295,7 +1268,27 @@ def test_parse_chunked_payload_chunk_extension(parser) -> None: | ||
| 273 | assert payload.is_eof() | ||
| 274 | |||
| 275 | |||
| 276 | -def test_parse_no_length_or_te_on_post(loop: Any, protocol: Any, request_cls: Any): | ||
| 277 | +async def test_request_chunked_with_trailer(parser: HttpRequestParser) -> None: | ||
| 278 | + text = b"GET /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n0\r\ntest: trailer\r\nsecond: test trailer\r\n\r\n" | ||
| 279 | + messages, upgraded, tail = parser.feed_data(text) | ||
| 280 | + assert not tail | ||
| 281 | + msg, payload = messages[0] | ||
| 282 | + assert await payload.read() == b"test" | ||
| 283 | + | ||
| 284 | + # TODO: Add assertion of trailers when API added. | ||
| 285 | + | ||
| 286 | + | ||
| 287 | +async def test_request_chunked_reject_bad_trailer(parser: HttpRequestParser) -> None: | ||
| 288 | + text = b"GET /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nbad\ntrailer\r\n\r\n" | ||
| 289 | + with pytest.raises(http_exceptions.BadHttpMessage, match=r"b'bad\\ntrailer'"): | ||
| 290 | + parser.feed_data(text) | ||
| 291 | + | ||
| 292 | + | ||
| 293 | +def test_parse_no_length_or_te_on_post( | ||
| 294 | + loop: asyncio.AbstractEventLoop, | ||
| 295 | + protocol: BaseProtocol, | ||
| 296 | + request_cls: type[HttpRequestParser], | ||
| 297 | +) -> None: | ||
| 298 | parser = request_cls(protocol, loop, limit=2**16) | ||
| 299 | text = b"POST /test HTTP/1.1\r\n\r\n" | ||
| 300 | msg, payload = parser.feed_data(text)[0][0] | ||
| 301 | @@ -1478,19 +1471,10 @@ async def test_parse_chunked_payload_split_chunks(response: Any) -> None: | ||
| 302 | assert await reader.read() == b"firstsecond" | ||
| 303 | |||
| 304 | |||
| 305 | -@pytest.mark.skipif(NO_EXTENSIONS, reason="Only tests C parser.") | ||
| 306 | -async def test_parse_chunked_payload_with_lf_in_extensions_c_parser( | ||
| 307 | - loop: asyncio.AbstractEventLoop, protocol: BaseProtocol | ||
| 308 | +async def test_parse_chunked_payload_with_lf_in_extensions( | ||
| 309 | + parser: HttpRequestParser, | ||
| 310 | ) -> None: | ||
| 311 | - """Test the C-parser with a chunked payload that has a LF in the chunk extensions.""" | ||
| 312 | - # The C parser will raise a BadHttpMessage from feed_data | ||
| 313 | - parser = HttpRequestParserC( | ||
| 314 | - protocol, | ||
| 315 | - loop, | ||
| 316 | - 2**16, | ||
| 317 | - max_line_size=8190, | ||
| 318 | - max_field_size=8190, | ||
| 319 | - ) | ||
| 320 | + """Test chunked payload that has a LF in the chunk extensions.""" | ||
| 321 | payload = ( | ||
| 322 | b"GET / HTTP/1.1\r\nHost: localhost:5001\r\n" | ||
| 323 | b"Transfer-Encoding: chunked\r\n\r\n2;\nxx\r\n4c\r\n0\r\n\r\n" | ||
| 324 | @@ -1501,31 +1485,6 @@ async def test_parse_chunked_payload_with_lf_in_extensions_c_parser( | ||
| 325 | parser.feed_data(payload) | ||
| 326 | |||
| 327 | |||
| 328 | -async def test_parse_chunked_payload_with_lf_in_extensions_py_parser( | ||
| 329 | - loop: asyncio.AbstractEventLoop, protocol: BaseProtocol | ||
| 330 | -) -> None: | ||
| 331 | - """Test the py-parser with a chunked payload that has a LF in the chunk extensions.""" | ||
| 332 | - # The py parser will not raise the BadHttpMessage directly, but instead | ||
| 333 | - # it will set the exception on the StreamReader. | ||
| 334 | - parser = HttpRequestParserPy( | ||
| 335 | - protocol, | ||
| 336 | - loop, | ||
| 337 | - 2**16, | ||
| 338 | - max_line_size=8190, | ||
| 339 | - max_field_size=8190, | ||
| 340 | - ) | ||
| 341 | - payload = ( | ||
| 342 | - b"GET / HTTP/1.1\r\nHost: localhost:5001\r\n" | ||
| 343 | - b"Transfer-Encoding: chunked\r\n\r\n2;\nxx\r\n4c\r\n0\r\n\r\n" | ||
| 344 | - b"GET /admin HTTP/1.1\r\nHost: localhost:5001\r\n" | ||
| 345 | - b"Transfer-Encoding: chunked\r\n\r\n0\r\n\r\n" | ||
| 346 | - ) | ||
| 347 | - messages, _, _ = parser.feed_data(payload) | ||
| 348 | - reader = messages[0][1] | ||
| 349 | - assert isinstance(reader.exception(), http_exceptions.BadHttpMessage) | ||
| 350 | - assert "\\nxx" in str(reader.exception()) | ||
| 351 | - | ||
| 352 | - | ||
| 353 | def test_partial_url(parser: HttpRequestParser) -> None: | ||
| 354 | messages, upgrade, tail = parser.feed_data(b"GET /te") | ||
| 355 | assert len(messages) == 0 | ||
| 356 | @@ -1612,7 +1571,7 @@ def test_parse_bad_method_for_c_parser_raises(loop, protocol): | ||
| 357 | class TestParsePayload: | ||
| 358 | async def test_parse_eof_payload(self, protocol: BaseProtocol) -> None: | ||
| 359 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 360 | - p = HttpPayloadParser(out) | ||
| 361 | + p = HttpPayloadParser(out, headers_parser=HeadersParser()) | ||
| 362 | p.feed_data(b"data") | ||
| 363 | p.feed_eof() | ||
| 364 | |||
| 365 | @@ -1622,7 +1581,7 @@ class TestParsePayload: | ||
| 366 | async def test_parse_length_payload_eof(self, protocol: BaseProtocol) -> None: | ||
| 367 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 368 | |||
| 369 | - p = HttpPayloadParser(out, length=4) | ||
| 370 | + p = HttpPayloadParser(out, length=4, headers_parser=HeadersParser()) | ||
| 371 | p.feed_data(b"da") | ||
| 372 | |||
| 373 | with pytest.raises(http_exceptions.ContentLengthError): | ||
| 374 | @@ -1632,7 +1591,7 @@ class TestParsePayload: | ||
| 375 | self, protocol: BaseProtocol | ||
| 376 | ) -> None: | ||
| 377 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 378 | - p = HttpPayloadParser(out, chunked=True) | ||
| 379 | + p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser()) | ||
| 380 | with pytest.raises(http_exceptions.TransferEncodingError): | ||
| 381 | p.feed_data(b"blah\r\n") | ||
| 382 | assert isinstance(out.exception(), http_exceptions.TransferEncodingError) | ||
| 383 | @@ -1641,7 +1600,7 @@ class TestParsePayload: | ||
| 384 | self, protocol: BaseProtocol | ||
| 385 | ) -> None: | ||
| 386 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 387 | - p = HttpPayloadParser(out, chunked=True) | ||
| 388 | + p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser()) | ||
| 389 | p.feed_data(b"4\r\nasdf\r\n0\r\n") | ||
| 390 | p.feed_data(b"\r\n") | ||
| 391 | |||
| 392 | @@ -1652,7 +1611,7 @@ class TestParsePayload: | ||
| 393 | self, protocol: BaseProtocol | ||
| 394 | ) -> None: | ||
| 395 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 396 | - p = HttpPayloadParser(out, chunked=True) | ||
| 397 | + p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser()) | ||
| 398 | p.feed_data(b"4\r\nasdf\r\n0\r\n\r") | ||
| 399 | p.feed_data(b"\n") | ||
| 400 | |||
| 401 | @@ -1663,7 +1622,7 @@ class TestParsePayload: | ||
| 402 | self, protocol: BaseProtocol | ||
| 403 | ) -> None: | ||
| 404 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 405 | - p = HttpPayloadParser(out, chunked=True) | ||
| 406 | + p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser()) | ||
| 407 | p.feed_data(b"4\r\nasdf\r\n0\r\n") | ||
| 408 | p.feed_data(b"Content-MD5: 912ec803b2ce49e4a541068d495ab570\r\n") | ||
| 409 | p.feed_data(b"\r\n") | ||
| 410 | @@ -1675,7 +1634,7 @@ class TestParsePayload: | ||
| 411 | self, protocol: BaseProtocol | ||
| 412 | ) -> None: | ||
| 413 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 414 | - p = HttpPayloadParser(out, chunked=True) | ||
| 415 | + p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser()) | ||
| 416 | p.feed_data(b"4\r\nasdf\r\n0\r\n") | ||
| 417 | p.feed_data(b"Content-MD5: 912ec803b2ce49e4a541068d495ab570\r\n\r") | ||
| 418 | p.feed_data(b"\n") | ||
| 419 | @@ -1687,7 +1646,7 @@ class TestParsePayload: | ||
| 420 | self, protocol: BaseProtocol | ||
| 421 | ) -> None: | ||
| 422 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 423 | - p = HttpPayloadParser(out, chunked=True) | ||
| 424 | + p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser()) | ||
| 425 | p.feed_data(b"4\r\nasdf\r\n0\r\nContent-MD5: ") | ||
| 426 | p.feed_data(b"912ec803b2ce49e4a541068d495ab570\r\n\r\n") | ||
| 427 | |||
| 428 | @@ -1698,7 +1657,7 @@ class TestParsePayload: | ||
| 429 | self, protocol: BaseProtocol | ||
| 430 | ) -> None: | ||
| 431 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 432 | - p = HttpPayloadParser(out, chunked=True) | ||
| 433 | + p = HttpPayloadParser(out, chunked=True, headers_parser=HeadersParser()) | ||
| 434 | p.feed_data(b"4\r\nasdf\r\n0\r\nC") | ||
| 435 | p.feed_data(b"ontent-MD5: 912ec803b2ce49e4a541068d495ab570\r\n\r\n") | ||
| 436 | |||
| 437 | @@ -1707,7 +1666,7 @@ class TestParsePayload: | ||
| 438 | |||
| 439 | async def test_http_payload_parser_length(self, protocol: BaseProtocol) -> None: | ||
| 440 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 441 | - p = HttpPayloadParser(out, length=2) | ||
| 442 | + p = HttpPayloadParser(out, length=2, headers_parser=HeadersParser()) | ||
| 443 | eof, tail = p.feed_data(b"1245") | ||
| 444 | assert eof | ||
| 445 | |||
| 446 | @@ -1720,7 +1679,9 @@ class TestParsePayload: | ||
| 447 | |||
| 448 | length = len(COMPRESSED) | ||
| 449 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 450 | - p = HttpPayloadParser(out, length=length, compression="deflate") | ||
| 451 | + p = HttpPayloadParser( | ||
| 452 | + out, length=length, compression="deflate", headers_parser=HeadersParser() | ||
| 453 | + ) | ||
| 454 | p.feed_data(COMPRESSED) | ||
| 455 | assert b"data" == out._buffer[0] | ||
| 456 | assert out.is_eof() | ||
| 457 | @@ -1734,7 +1695,9 @@ class TestParsePayload: | ||
| 458 | |||
| 459 | length = len(COMPRESSED) | ||
| 460 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 461 | - p = HttpPayloadParser(out, length=length, compression="deflate") | ||
| 462 | + p = HttpPayloadParser( | ||
| 463 | + out, length=length, compression="deflate", headers_parser=HeadersParser() | ||
| 464 | + ) | ||
| 465 | p.feed_data(COMPRESSED) | ||
| 466 | assert b"data" == out._buffer[0] | ||
| 467 | assert out.is_eof() | ||
| 468 | @@ -1747,7 +1710,9 @@ class TestParsePayload: | ||
| 469 | |||
| 470 | length = len(COMPRESSED) | ||
| 471 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 472 | - p = HttpPayloadParser(out, length=length, compression="deflate") | ||
| 473 | + p = HttpPayloadParser( | ||
| 474 | + out, length=length, compression="deflate", headers_parser=HeadersParser() | ||
| 475 | + ) | ||
| 476 | p.feed_data(COMPRESSED) | ||
| 477 | |||
| 478 | assert b"data" == out._buffer[0] | ||
| 479 | @@ -1757,7 +1722,9 @@ class TestParsePayload: | ||
| 480 | self, protocol: BaseProtocol | ||
| 481 | ) -> None: | ||
| 482 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 483 | - p = HttpPayloadParser(out, compression="deflate") | ||
| 484 | + p = HttpPayloadParser( | ||
| 485 | + out, compression="deflate", headers_parser=HeadersParser() | ||
| 486 | + ) | ||
| 487 | # Feeding one correct byte should be enough to choose exact | ||
| 488 | # deflate decompressor | ||
| 489 | p.feed_data(b"x") | ||
| 490 | @@ -1769,7 +1736,9 @@ class TestParsePayload: | ||
| 491 | self, protocol: BaseProtocol | ||
| 492 | ) -> None: | ||
| 493 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 494 | - p = HttpPayloadParser(out, compression="deflate") | ||
| 495 | + p = HttpPayloadParser( | ||
| 496 | + out, compression="deflate", headers_parser=HeadersParser() | ||
| 497 | + ) | ||
| 498 | # Feeding one wrong byte should be enough to choose exact | ||
| 499 | # deflate decompressor | ||
| 500 | p.feed_data(b"K") | ||
| 501 | @@ -1781,7 +1750,7 @@ class TestParsePayload: | ||
| 502 | self, protocol: BaseProtocol | ||
| 503 | ) -> None: | ||
| 504 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 505 | - p = HttpPayloadParser(out, length=0) | ||
| 506 | + p = HttpPayloadParser(out, length=0, headers_parser=HeadersParser()) | ||
| 507 | assert p.done | ||
| 508 | assert out.is_eof() | ||
| 509 | |||
| 510 | @@ -1789,7 +1758,12 @@ class TestParsePayload: | ||
| 511 | async def test_http_payload_brotli(self, protocol: BaseProtocol) -> None: | ||
| 512 | compressed = brotli.compress(b"brotli data") | ||
| 513 | out = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) | ||
| 514 | - p = HttpPayloadParser(out, length=len(compressed), compression="br") | ||
| 515 | + p = HttpPayloadParser( | ||
| 516 | + out, | ||
| 517 | + length=len(compressed), | ||
| 518 | + compression="br", | ||
| 519 | + headers_parser=HeadersParser(), | ||
| 520 | + ) | ||
| 521 | p.feed_data(compressed) | ||
| 522 | assert b"brotli data" == out._buffer[0] | ||
| 523 | assert out.is_eof() | ||
| 524 | -- | ||
| 525 | 2.34.1 | ||
| 526 | |||
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp_3.11.16.bb b/meta-python/recipes-devtools/python/python3-aiohttp_3.11.16.bb index 960272e9a3..5267ad800b 100644 --- a/meta-python/recipes-devtools/python/python3-aiohttp_3.11.16.bb +++ b/meta-python/recipes-devtools/python/python3-aiohttp_3.11.16.bb | |||
| @@ -6,6 +6,8 @@ LIC_FILES_CHKSUM = "file://LICENSE.txt;md5=748073912af33aa59430d3702aa32d41" | |||
| 6 | 6 | ||
| 7 | SRC_URI[sha256sum] = "16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8" | 7 | SRC_URI[sha256sum] = "16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8" |
| 8 | 8 | ||
| 9 | SRC_URI += "file://CVE-2025-53643.patch" | ||
| 10 | |||
| 9 | inherit python_setuptools_build_meta pypi | 11 | inherit python_setuptools_build_meta pypi |
| 10 | 12 | ||
| 11 | RDEPENDS:${PN} = "\ | 13 | RDEPENDS:${PN} = "\ |
