Skip to content

Silently discard epilogue data after the closing boundary#259

Merged
Kludex merged 3 commits intomasterfrom
drop-epilogue-warning
Apr 10, 2026
Merged

Silently discard epilogue data after the closing boundary#259
Kludex merged 3 commits intomasterfrom
drop-epilogue-warning

Conversation

@Kludex
Copy link
Copy Markdown
Owner

@Kludex Kludex commented Apr 10, 2026

Summary

  • Drop the "Skipping data after last boundary" warning. RFC 2046 section 5.1.1 allows an optional CRLF + epilogue after the closing boundary, and both Django's MultiPartParser and Werkzeug's sansio multipart parser accept it silently - python-multipart was the outlier.
  • This also fixes the split-chunk CRLF case reported in "Skipping data after last boundary" warning when CRLF is split across chunks #246: the previous guard at multipart.py:1417 only paired \r with \n when both landed in the same write() call, so a trailing \r at the end of one chunk followed by \n in the next (common behind network proxies) produced a spurious warning.
  • The epilogue is still short-circuited via i = length; break, preserving the O(1) behavior added in Hard break if found data after last boundary on MultipartParser #189 - no epilogue scanning, no DoS surface.

Test plan

  • New test_multipart_parser_epilogue_emits_no_warnings covers single-chunk CRLF, split CRLF across two writes, boundary-then-CRLF split, and non-empty epilogue - all asserting zero warnings.
  • Existing large-input tests (test_multipart_parser_data_after_last_boundary, 50M trailing bytes) still short-circuit.
  • pytest --cov - 134 passed, 100% coverage.
  • ruff check, ruff format --check, mypy --strict all clean.

Closes #246.

Django's MultiPartParser and Werkzeug's sansio multipart parser both
silently accept (and ignore) the optional CRLF + epilogue that RFC 2046
section 5.1.1 allows after the closing boundary. python-multipart was
logging "Skipping data after last boundary" instead, and the single-
chunk guard added in #193 also missed the case where the trailing CRLF
was split across two writes - a trailing `\r` at the end of one chunk
would fall through to the warning branch because `i + 1 < length` was
false, even though the `\n` arrived in the next chunk.

Drop the warning and the split-chunk guard entirely. The epilogue is
short-circuited the same way as before (no O(N) scan), just without
the spurious log line.

Closes #246.
Copy link
Copy Markdown
Owner Author

Kludex commented Apr 10, 2026

I ran a quick parser-only benchmark comparing this PR (3850e7e) with its base (d3a4698e) on macOS arm64, Python 3.14.3.

Payload shape:

valid = (
    b"--boundary\r\n"
    b"Content-Disposition: form-data; name=\"f\"\r\n"
    b"\r\nval\r\n"
    b"--boundary--"
)

crlf_epilogue = valid + b"\r\n" * (size // 2)
non_crlf_epilogue = valid + b"X" * size

Results:

Size Base: CRLF epilogue PR: CRLF epilogue Base: non-CRLF epilogue PR: non-CRLF epilogue
1 MB 0.233151s 0.000036s 0.000126s 0.000054s
5 MB 1.096270s 0.000024s 0.000078s 0.000020s
10 MB 2.187200s 0.000027s 0.000069s 0.000020s
25 MB 5.471226s 0.000032s 0.000074s 0.000023s
50 MB 10.965380s 0.000030s 0.000068s 0.000026s

So this makes CRLF epilogue handling take the same short-circuit path as other epilogue data.

Comment on lines +1383 to +1385
Covers both the single-chunk case and the case where the trailing
CRLF is split across `write()` calls (e.g. when `\r` and `\n` land in
separate TCP chunks behind a proxy).
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the 120 line limit?

@Kludex Kludex force-pushed the drop-epilogue-warning branch 4 times, most recently from f71d297 to ece73d3 Compare April 10, 2026 12:55
@Kludex Kludex force-pushed the drop-epilogue-warning branch from ece73d3 to 13cef88 Compare April 10, 2026 13:03
@Kludex Kludex enabled auto-merge (squash) April 10, 2026 13:54
@Kludex Kludex merged commit d4452a7 into master Apr 10, 2026
12 checks passed
@Kludex Kludex deleted the drop-epilogue-warning branch April 10, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Skipping data after last boundary" warning when CRLF is split across chunks

1 participant