File CVE-2024-41671.patch of Package python-Twisted.34938

Index: Twisted-15.2.1/twisted/web/http.py
===================================================================
--- Twisted-15.2.1.orig/twisted/web/http.py
+++ Twisted-15.2.1/twisted/web/http.py
@@ -1584,6 +1584,9 @@ class _ChunkedTransferDecoder(object):
         self.finishCallback = finishCallback
         self._buffer = bytearray()
         self._start = 0
+        self._trailerHeaders = []
+        self._maxTrailerHeadersSize = 2**16
+        self._receivedTrailerHeadersSize = 0
 
 
     def _dataReceived_CHUNK_LENGTH(self):
@@ -1656,6 +1659,42 @@ class _ChunkedTransferDecoder(object):
         self.finishCallback(data)
         return False
 
+        eolIndex = self._buffer.find(b"\r\n", self._start)
+
+        if eolIndex == -1:
+            # Still no end of network line marker found.
+            #
+            # Check if we've run up against the trailer size limit: if the next
+            # read contains the terminating CRLF then we'll have this many bytes
+            # of trailers (including the CRLFs).
+            minTrailerSize = (
+                self._receivedTrailerHeadersSize
+                + len(self._buffer)
+                + (1 if self._buffer.endswith(b"\r") else 2)
+            )
+            if minTrailerSize > self._maxTrailerHeadersSize:
+                raise _MalformedChunkedDataError("Trailer headers data is too long.")
+            # Continue processing more data.
+            return False
+
+        if eolIndex > 0:
+            # A trailer header was detected.
+            self._trailerHeaders.append(self._buffer[0:eolIndex])
+            del self._buffer[0 : eolIndex + 2]
+            self._start = 0
+            self._receivedTrailerHeadersSize += eolIndex + 2
+            if self._receivedTrailerHeadersSize > self._maxTrailerHeadersSize:
+                raise _MalformedChunkedDataError("Trailer headers data is too long.")
+            return True
+
+        # eolIndex in this part of code is equal to 0
+
+        data = memoryview(self._buffer)[2:].tobytes()
+
+        del self._buffer[:]
+        self.state = "FINISHED"
+        self.finishCallback(data)
+        return False
 
     def _dataReceived_BODY(self):
         if len(self._buffer) >= self.length:
@@ -1809,8 +1848,8 @@ class HTTPChannel(basic.LineReceiver, po
 
 
     def _finishRequestBody(self, data):
-        self.allContentReceived()
         self.setLineMode(data)
+        self.allContentReceived()
 
     def _maybeChooseTransferDecoder(self, header, data):
         """
@@ -1826,6 +1865,8 @@ class HTTPChannel(basic.LineReceiver, po
 
         # Can this header determine the length?
         if header == b'content-length':
+            if not data.isdigit():
+                return fail()
             try:
                 length = int(data)
             except ValueError:
@@ -1874,7 +1915,7 @@ class HTTPChannel(basic.LineReceiver, po
 
         header, data = line.split(b':', 1)
         header = header.lower()
-        data = data.strip()
+        data = data.strip(b" \t")
 
         if not self._maybeChooseTransferDecoder(header,data):
             return False
Index: Twisted-15.2.1/twisted/web/test/test_http.py
===================================================================
--- Twisted-15.2.1.orig/twisted/web/test/test_http.py
+++ Twisted-15.2.1/twisted/web/test/test_http.py
@@ -2606,3 +2606,83 @@ class HexHelperTests(unittest.Synchronou
         for s in self.badStrings:
             self.assertRaises(ValueError, http._hexint, s)
 
+
+class PipeliningBodyTests(unittest.TestCase, ResponseTestMixin):
+    """
+    Pipelined requests get buffered and executed in the order received,
+    not processed in parallel.
+    """
+
+    requests = (
+        b"POST / HTTP/1.1\r\n"
+        b"Content-Length: 10\r\n"
+        b"\r\n"
+        b"0123456789"
+        # Chunk encoded request.
+        b"POST / HTTP/1.1\r\n"
+        b"Transfer-Encoding: chunked\r\n"
+        b"\r\n"
+        b"a\r\n"
+        b"0123456789\r\n"
+        b"0\r\n"
+        b"\r\n"
+    )
+
+    expectedResponses = [
+        (
+            b"HTTP/1.1 200 OK",
+            b"Request: /",
+            b"Command: POST",
+            b"Version: HTTP/1.1",
+            b"Content-Length: 21",
+            b"'''\n10\n0123456789'''\n",
+        ),
+        (
+            b"HTTP/1.1 200 OK",
+            b"Request: /",
+            b"Command: POST",
+            b"Version: HTTP/1.1",
+            b"Content-Length: 23",
+            b"'''\nNone\n0123456789'''\n",
+        ),
+    ]
+
+    def test_immediateTinyTube(self):
+        """
+        Imitate a slow connection that delivers one byte at a time.
+
+        (L{DummyHTTPHandler}) immediately responds, but no more
+        than one
+        """
+        b = StringTransport()
+        a = http.HTTPChannel()
+        a.requestFactory = DummyHTTPHandler
+        a.makeConnection(b)
+
+        # one byte at a time, to stress it.
+        for byte in iterbytes(self.requests):
+            a.dataReceived(byte)
+            # There is never more than one request dispatched at a time:
+            self.assertLessEqual(len(a.requests), 1)
+
+        self.assertResponseEquals(b.value(), self.expectedResponses)
+
+    def test_immediateDumpTruck(self):
+        """
+        Imitate a fast connection where several pipelined
+        requests arrive in a single read. The request handler
+        (L{DummyHTTPHandler}) immediately responds.
+
+        This doesn't check the at-most-one pending request
+        invariant but exercises otherwise uncovered code paths.
+        See GHSA-c8m8-j448-xjx7.
+        """
+        b = StringTransport()
+        a = http.HTTPChannel()
+        a.requestFactory = DummyHTTPHandler
+        a.makeConnection(b)
+
+        # All bytes at once to ensure there's stuff to buffer.
+        a.dataReceived(self.requests)
+
+        self.assertResponseEquals(b.value(), self.expectedResponses)
openSUSE Build Service is sponsored by