Skip to content

Commit 6a7b76d

Browse files
authored
Skip preamble before first multipart boundary (#262)
1 parent 4addb60 commit 6a7b76d

2 files changed

Lines changed: 47 additions & 12 deletions

File tree

python_multipart/multipart.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1105,7 +1105,11 @@ def data_callback(name: CallbackName, end_i: int, remaining: bool = False) -> No
11051105
if state == MultipartState.START:
11061106
# Skip leading newlines
11071107
if c == CR or c == LF:
1108-
i += 1
1108+
i = data.find(b"-", i)
1109+
if i == -1:
1110+
# No boundary candidate in this chunk, so ignore the content after the leading CR/LF.
1111+
i = length
1112+
break
11091113
continue
11101114

11111115
# index is used as in index into our boundary. Set to 0.

tests/test_multipart.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,24 +1335,55 @@ def on_field(f: Field) -> None:
13351335
self.assertEqual(fields[2].field_name, b"baz")
13361336
self.assertEqual(fields[2].value, b"asdf")
13371337

1338-
def test_multipart_parser_newlines_before_first_boundary(self) -> None:
1339-
"""This test makes sure that the parser does not handle when there is junk data after the last boundary."""
1340-
num = 5_000_000
1341-
data = (
1342-
"\r\n" * num + "--boundary\r\n"
1343-
'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
1344-
"Content-Type: text/plain\r\n\r\n"
1345-
"hello\r\n"
1346-
"--boundary--"
1347-
)
1338+
@parametrize(
1339+
"chunks",
1340+
[
1341+
[
1342+
b"\r\nignored preamble\r\n"
1343+
+ (
1344+
b"--boundary\r\n"
1345+
b'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
1346+
b"Content-Type: text/plain\r\n\r\n"
1347+
b"hello\r\n"
1348+
b"--boundary--"
1349+
)
1350+
],
1351+
[
1352+
b"\r\n" * 5_000_000
1353+
+ (
1354+
b"--boundary\r\n"
1355+
b'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
1356+
b"Content-Type: text/plain\r\n\r\n"
1357+
b"hello\r\n"
1358+
b"--boundary--"
1359+
)
1360+
],
1361+
[
1362+
b"\r\n" * 5_000_000,
1363+
(
1364+
b"--boundary\r\n"
1365+
b'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
1366+
b"Content-Type: text/plain\r\n\r\n"
1367+
b"hello\r\n"
1368+
b"--boundary--"
1369+
),
1370+
],
1371+
],
1372+
)
1373+
def test_multipart_parser_preamble_before_first_boundary(self, chunks: list[bytes]) -> None:
1374+
"""Parser must not hang or blow up on a preamble before the first boundary."""
13481375

13491376
files: list[File] = []
13501377

13511378
def on_file(f: File) -> None:
13521379
files.append(f)
13531380

13541381
f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
1355-
f.write(data.encode("latin-1"))
1382+
for chunk in chunks:
1383+
f.write(chunk)
1384+
1385+
assert len(files) == 1
1386+
self.assert_file_data(files[0], b"hello")
13561387

13571388
def test_multipart_parser_data_after_last_boundary(self) -> None:
13581389
"""This test makes sure that the parser does not handle when there is junk data after the last boundary."""

0 commit comments

Comments
 (0)