From b379eb8f68ef9cdcc86a79e8229cc348b215fdab Mon Sep 17 00:00:00 2001
From: Marcelo Trylesinski <marcelotryle@gmail.com>
Date: Fri, 10 Apr 2026 15:13:24 +0200
Subject: [PATCH 1/2] Skip leading CRLF before first boundary

---
 multipart/multipart.py |  5 +++-
 tests/test_multipart.py       | 43 ++++++++++++++++++++++++++---------
 2 files changed, 36 insertions(+), 12 deletions(-)

Index: python_multipart-0.0.9/multipart/multipart.py
===================================================================
--- python_multipart-0.0.9.orig/multipart/multipart.py
+++ python_multipart-0.0.9/multipart/multipart.py
@@ -1156,7 +1156,11 @@ class MultipartParser(BaseParser):
             if state == MultipartState.START:
                 # Skip leading newlines
                 if c == CR or c == LF:
-                    i += 1
+                    i = data.find(b"-", i)
+                    if i == -1:
+                        # No boundary candidate in this chunk, so ignore the content after the leading CR/LF.
+                        i = length
+                        break
                     self.logger.debug("Skipping leading CR/LF at %d", i)
                     continue
 
Index: python_multipart-0.0.9/tests/test_multipart.py
===================================================================
--- python_multipart-0.0.9.orig/tests/test_multipart.py
+++ python_multipart-0.0.9/tests/test_multipart.py
@@ -1163,6 +1163,56 @@ class TestFormParser(unittest.TestCase):
         self.assertEqual(fields[2].field_name, b"baz")
         self.assertEqual(fields[2].value, b"asdf")
 
+    @parametrize(
+        "chunks",
+        [
+            [
+                b"\r\nignored preamble\r\n"
+                + (
+                    b"--boundary\r\n"
+                    b'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
+                    b"Content-Type: text/plain\r\n\r\n"
+                    b"hello\r\n"
+                    b"--boundary--"
+                )
+            ],
+            [
+                b"\r\n" * 5_000_000
+                + (
+                    b"--boundary\r\n"
+                    b'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
+                    b"Content-Type: text/plain\r\n\r\n"
+                    b"hello\r\n"
+                    b"--boundary--"
+                )
+            ],
+            [
+                b"\r\n" * 5_000_000,
+                (
+                    b"--boundary\r\n"
+                    b'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
+                    b"Content-Type: text/plain\r\n\r\n"
+                    b"hello\r\n"
+                    b"--boundary--"
+                ),
+            ],
+        ],
+    )
+    def test_multipart_parser_preamble_before_first_boundary(self, chunks):
+        """Parser must not hang or blow up on a preamble before the first boundary."""
+
+        files = []
+
+        def on_file(f):
+            files.append(f)
+
+        f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
+        for chunk in chunks:
+            f.write(chunk)
+
+        assert len(files) == 1
+        self.assert_file_data(files[0], b"hello")
+
     @pytest.fixture(autouse=True)
     def inject_fixtures(self, caplog: pytest.LogCaptureFixture) -> None:
         self._caplog = caplog
