Skip to content

Commit 9205a0e

Browse files
authored
Hard break if found data after last boundary on MultipartParser (#189)
1 parent 170e604 commit 9205a0e

File tree

2 files changed

+43
-5
lines changed

2 files changed

+43
-5
lines changed

python_multipart/multipart.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,7 +1105,6 @@ def data_callback(name: CallbackName, end_i: int, remaining: bool = False) -> No
11051105
# Skip leading newlines
11061106
if c == CR or c == LF:
11071107
i += 1
1108-
self.logger.debug("Skipping leading CR/LF at %d", i)
11091108
continue
11101109

11111110
# index is used as in index into our boundary. Set to 0.
@@ -1398,9 +1397,10 @@ def data_callback(name: CallbackName, end_i: int, remaining: bool = False) -> No
13981397
i -= 1
13991398

14001399
elif state == MultipartState.END:
1401-
# Do nothing and just consume a byte in the end state.
1402-
if c not in (CR, LF):
1403-
self.logger.warning("Consuming a byte '0x%x' in the end state", c) # pragma: no cover
1400+
# Skip data after the last boundary.
1401+
self.logger.warning("Skipping data after last boundary")
1402+
i = length
1403+
break
14041404

14051405
else: # pragma: no cover (error case)
14061406
# We got into a strange state somehow! Just stop processing.

tests/test_multipart.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ def test_http(self, param: TestParams) -> None:
825825
return
826826

827827
# No error!
828-
self.assertEqual(processed, len(param["test"]))
828+
self.assertEqual(processed, len(param["test"]), param["name"])
829829

830830
# Assert that the parser gave us the appropriate fields/files.
831831
for e in param["result"]["expected"]:
@@ -1210,6 +1210,44 @@ def on_field(f: FieldProtocol) -> None:
12101210
self.assertEqual(fields[2].field_name, b"baz")
12111211
self.assertEqual(fields[2].value, b"asdf")
12121212

1213+
def test_multipart_parser_newlines_before_first_boundary(self) -> None:
1214+
"""This test makes sure that the parser does not handle when there is junk data after the last boundary."""
1215+
num = 5_000_000
1216+
data = (
1217+
"\r\n" * num + "--boundary\r\n"
1218+
'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
1219+
"Content-Type: text/plain\r\n\r\n"
1220+
"hello\r\n"
1221+
"--boundary--"
1222+
)
1223+
1224+
files: list[File] = []
1225+
1226+
def on_file(f: FileProtocol) -> None:
1227+
files.append(cast(File, f))
1228+
1229+
f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
1230+
f.write(data.encode("latin-1"))
1231+
1232+
def test_multipart_parser_data_after_last_boundary(self) -> None:
1233+
"""This test makes sure that the parser does not handle when there is junk data after the last boundary."""
1234+
num = 50_000_000
1235+
data = (
1236+
"--boundary\r\n"
1237+
'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
1238+
"Content-Type: text/plain\r\n\r\n"
1239+
"hello\r\n"
1240+
"--boundary--" + "-" * num + "\r\n"
1241+
)
1242+
1243+
files: list[File] = []
1244+
1245+
def on_file(f: FileProtocol) -> None:
1246+
files.append(cast(File, f))
1247+
1248+
f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
1249+
f.write(data.encode("latin-1"))
1250+
12131251
def test_max_size_multipart(self) -> None:
12141252
# Load test data.
12151253
test_file = "single_field_single_file.http"

0 commit comments

Comments
 (0)