File CVE-2025-69227-raise-exceptions-not-asserts.patch of Package python-aiohttp.42491

From bc1319ec3cbff9438a758951a30907b072561259 Mon Sep 17 00:00:00 2001
From: Sam Bull <git@sambull.org>
Date: Sat, 3 Jan 2026 04:53:29 +0000
Subject: [PATCH] Replace asserts with exceptions (#11897) (#11914)

(cherry picked from commit d5bf65f15c0c718b6b95e9bc9d0914a92c51e60f)

Co-authored-by: J. Nick Koston <nick@home-assistant.io>
---
 aiohttp/multipart.py      | 10 ++++------
 aiohttp/web_request.py    |  8 +++-----
 tests/test_multipart.py   | 12 +++++++++++-
 tests/test_web_request.py | 24 +++++++++++++++++++++++-
 4 files changed, 41 insertions(+), 13 deletions(-)

Index: aiohttp-3.9.3/aiohttp/multipart.py
===================================================================
--- aiohttp-3.9.3.orig/aiohttp/multipart.py
+++ aiohttp-3.9.3/aiohttp/multipart.py
@@ -332,11 +332,8 @@ class BodyPartReader:
         self._read_bytes += len(chunk)
         if self._read_bytes == self._length:
             self._at_eof = True
-        if self._at_eof:
-            clrf = await self._content.readline()
-            assert (
-                b"\r\n" == clrf
-            ), "reader did not read all the data or it is malformed"
+        if self._at_eof and await self._content.readline() != b"\r\n":
+            raise ValueError("Reader did not read all the data or it is malformed")
         return chunk
 
     async def _read_chunk_from_length(self, size: int) -> bytes:
@@ -361,7 +358,8 @@ class BodyPartReader:
 
         chunk = await self._content.read(size)
         self._content_eof += int(self._content.at_eof())
-        assert self._content_eof < 3, "Reading after EOF"
+        if self._content_eof > 2:
+            raise ValueError("Reading after EOF")
         assert self._prev_chunk is not None
         window = self._prev_chunk + chunk
         sub = b"\r\n" + self._boundary
Index: aiohttp-3.9.3/aiohttp/web_request.py
===================================================================
--- aiohttp-3.9.3.orig/aiohttp/web_request.py
+++ aiohttp-3.9.3/aiohttp/web_request.py
@@ -717,7 +717,8 @@ class BaseRequest(MutableMapping[str, An
                 field_ct = field.headers.get(hdrs.CONTENT_TYPE)
 
                 if isinstance(field, BodyPartReader):
-                    assert field.name is not None
+                    if field.name is None:
+                        raise ValueError("Multipart field missing name.")
 
                     # Note that according to RFC 7578, the Content-Type header
                     # is optional, even for files, so we can't assume it's
Index: aiohttp-3.9.3/tests/test_multipart.py
===================================================================
--- aiohttp-3.9.3.orig/tests/test_multipart.py
+++ aiohttp-3.9.3/tests/test_multipart.py
@@ -5,6 +5,7 @@ import pathlib
 import zlib
 from unittest import mock
 
+from multidict import CIMultiDict, CIMultiDictProxy
 import pytest
 
 import aiohttp
@@ -201,11 +202,21 @@ class TestPartReader:
         with Stream(data) as stream:
             obj = aiohttp.BodyPartReader(BOUNDARY, {}, stream)
             result = b""
-            with pytest.raises(AssertionError):
+            with pytest.raises(ValueError):
                 for _ in range(4):
                     result += await obj.read_chunk(7)
         assert data == result
 
+    async def test_read_with_content_length_malformed_crlf(self) -> None:
+        # Content-Length is correct but data after content is not \r\n
+        content = b"Hello"
+        h = CIMultiDictProxy(CIMultiDict({"CONTENT-LENGTH": str(len(content))}))
+        # Malformed: "XX" instead of "\r\n" after content
+        with Stream(content + b"XX--:--") as stream:
+            obj = aiohttp.BodyPartReader(BOUNDARY, h, stream)
+            with pytest.raises(ValueError, match="malformed"):
+                await obj.read()
+
     async def test_read_boundary_with_incomplete_chunk(self) -> None:
         with Stream(b"") as stream:
 
Index: aiohttp-3.9.3/tests/test_web_request.py
===================================================================
--- aiohttp-3.9.3.orig/tests/test_web_request.py
+++ aiohttp-3.9.3/tests/test_web_request.py
@@ -10,6 +10,7 @@ from multidict import CIMultiDict, CIMul
 from yarl import URL
 
 from aiohttp import HttpVersion
+from aiohttp.base_protocol import BaseProtocol
 from aiohttp.http_parser import RawRequestMessage
 from aiohttp.streams import StreamReader
 from aiohttp.test_utils import make_mocked_request
@@ -629,7 +630,28 @@ async def test_multipart_formdata(protoc
     assert dict(result) == {"a": "b", "c": "d"}
 
 
-async def test_multipart_formdata_file(protocol) -> None:
+async def test_multipart_formdata_field_missing_name(protocol: BaseProtocol) -> None:
+    # Ensure ValueError is raised when Content-Disposition has no name
+    payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())
+    payload.feed_data(
+        b"-----------------------------326931944431359\r\n"
+        b"Content-Disposition: form-data\r\n"  # Missing name!
+        b"\r\n"
+        b"value\r\n"
+        b"-----------------------------326931944431359--\r\n"
+    )
+    content_type = (
+        "multipart/form-data; boundary=---------------------------326931944431359"
+    )
+    payload.feed_eof()
+    req = make_mocked_request(
+        "POST", "/", headers={"CONTENT-TYPE": content_type}, payload=payload
+    )
+    with pytest.raises(ValueError, match="Multipart field missing name"):
+        await req.post()
+
+
+async def test_multipart_formdata_file(protocol: BaseProtocol) -> None:
     # Make sure file uploads work, even without a content type
     payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop())
     payload.feed_data(
openSUSE Build Service is sponsored by