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

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

Index: python_multipart-0.0.20/python_multipart/multipart.py
===================================================================
--- python_multipart-0.0.20.orig/python_multipart/multipart.py
+++ python_multipart-0.0.20/python_multipart/multipart.py
@@ -1107,7 +1107,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
                     continue
 
                 # index is used as in index into our boundary.  Set to 0.
Index: python_multipart-0.0.20/tests/test_multipart.py
===================================================================
--- python_multipart-0.0.20.orig/tests/test_multipart.py
+++ python_multipart-0.0.20/tests/test_multipart.py
@@ -1212,16 +1212,43 @@ class TestFormParser(unittest.TestCase):
         self.assertEqual(fields[2].field_name, b"baz")
         self.assertEqual(fields[2].value, b"asdf")
 
-    def test_multipart_parser_newlines_before_first_boundary(self) -> None:
-        """This test makes sure that the parser does not handle when there is junk data after the last boundary."""
-        num = 5_000_000
-        data = (
-            "\r\n" * num + "--boundary\r\n"
-            'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
-            "Content-Type: text/plain\r\n\r\n"
-            "hello\r\n"
-            "--boundary--"
-        )
+    @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: list[bytes]) -> None:
+        """Parser must not hang or blow up on a preamble before the first boundary."""
 
         files: list[File] = []
 
@@ -1229,7 +1256,11 @@ class TestFormParser(unittest.TestCase):
             files.append(cast(File, f))
 
         f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
-        f.write(data.encode("latin-1"))
+        for chunk in chunks:
+            f.write(chunk)
+
+        assert len(files) == 1
+        self.assert_file_data(files[0], b"hello")
 
     def test_multipart_parser_data_after_last_boundary(self) -> None:
         """This test makes sure that the parser does not handle when there is junk data after the last boundary."""
