Skip to content

[PR #13760/ced8133e backport][3.15] Fix read_chunk() on body parts with a zero Content-Length - #13853

Merged
Dreamsorcerer merged 2 commits into
3.15from
patchback/backports/3.15/ced8133ea466a3bbe39d01a42bef79a5d2dbf1b5/pr-13760
Sep 29, 2026
Merged

Dreamsorcerer merged 2 commits into
3.15from
patchback/backports/3.15/ced8133ea466a3bbe39d01a42bef79a5d2dbf1b5/pr-13760

Conversation

@patchback

@patchback patchback Bot commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

This is a backport of PR #13760 as merged into master (ced8133).

What do these changes do?

BodyPartReader.read_chunk() chose its read strategy by testing
self._length for truthiness. A part with an explicit Content-Length: 0 (legal
outside multipart/form-data, where the RFC 7578 special case nulls the
length out) therefore fell through to _read_chunk_from_stream(), whose
minimum chunk size assertion then failed for chunk sizes below the boundary
length:

AssertionError: Chunk size must be greater or equal than boundary length + 2

The truthiness test now checks against None instead, so an empty part takes
the length-based path: read_chunk() returns an immediate empty chunk and the
part reaches EOF, like any other part with a known length. The line dates back
to 2016 (f1351a3), when the stream fallback was introduced.

Are there changes in behavior for developers?

  • read_chunk(size) on a Content-Length: 0 part returns b"" for any size
    instead of raising AssertionError when size < len(boundary) + 2.
  • read() / release() on such parts already worked with the default chunk
    size (which satisfies the assertion by accident) and are unaffected beyond
    now being correct for small configured chunk sizes too.
  • multipart/form-data parts are unchanged: their Content-Length header is
    ignored per RFC 7578 §4.8, _length stays None, and the stream fallback
    still applies.
  • Streaming parts (no Content-Length) are unchanged: _length is None routes
    them to _read_chunk_from_stream() exactly as before.

Passing a negative chunk size remains a preexisting wart of the length-based
path (it makes the underlying read() drain the remaining stream before
failing); it affects parts with a positive Content-Length on master the
same way and is out of scope here.

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes
  • If you provide code modification, please add yourself to CONTRIBUTORS.txt (added in this PR)
  • Add a news fragment in the CHANGES/ folder (PR number will follow once opened)

Fixes #13758.

Comment thread tests/test_multipart.py Outdated
@codecov

codecov Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.34%. Comparing base (baf1e78) to head (5c9f400).
⚠️ Report is 2 commits behind head on 3.15.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             3.15   #13853      +/-   ##
==========================================
- Coverage   96.35%   96.34%   -0.01%     
==========================================
  Files         161      161              
  Lines       52880    52887       +7     
  Branches     2888     2888              
==========================================
+ Hits        50952    50956       +4     
- Misses       1749     1752       +3     
  Partials      179      179              
Flag Coverage Δ
Autobahn 21.39% <12.50%> (-0.01%) ⬇️
CI-GHA 96.14% <100.00%> (-0.01%) ⬇️
OS-Linux 95.94% <100.00%> (-0.01%) ⬇️
OS-Windows 94.03% <100.00%> (+<0.01%) ⬆️
OS-macOS 95.44% <100.00%> (+<0.01%) ⬆️
Py-3.10 95.37% <100.00%> (+<0.01%) ⬆️
Py-3.11 95.62% <100.00%> (+<0.01%) ⬆️
Py-3.12 95.70% <100.00%> (-0.01%) ⬇️
Py-3.13 95.69% <100.00%> (+<0.01%) ⬆️
Py-3.14 95.71% <100.00%> (+<0.01%) ⬆️
Py-3.15 95.71% <100.00%> (-0.01%) ⬇️
Py-3.15t 95.08% <100.00%> (+<0.01%) ⬆️
Py-pypy-3.12 93.74% <100.00%> (-0.01%) ⬇️
VM-macos 95.44% <100.00%> (+<0.01%) ⬆️
VM-ubuntu 95.94% <100.00%> (-0.01%) ⬇️
VM-windows 94.03% <100.00%> (+<0.01%) ⬆️
cython-coverage 79.35% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@codspeed

codspeed Bot commented Sep 29, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 94 untouched benchmarks
⏩ 86 skipped benchmarks1


Comparing patchback/backports/3.15/ced8133ea466a3bbe39d01a42bef79a5d2dbf1b5/pr-13760 (5c9f400) with 3.15 (baf1e78)2

Open in CodSpeed

Footnotes

  1. 86 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

  2. No successful run was found on 3.15 (3c60ac6) during the generation of this report, so baf1e78 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Fixes a crash in multipart body reading with zero content length.

Safe to merge; no blocking issues were found.

What we checked:

  • Ran a focused multipart parsing script against pre-fix and current reading methods; empty-first, empty-last, base64, unconsumed-empty, and nonempty-control multipart cases parsed through the final boundary, and the zero-length-part regression test passed. T-Rex
  • Ran an authored zero-length multipart probe against both revisions; exercised successive reads, decoded reads, multipart parsing, and HTTP requests with ordinary and base64 zero-length parts; every HTTP request returned 200 OK and the probe exited successfully. T-Rex
  • Ran two comparison runs that exited with code 0 and parsed every expected part through the final boundary; the script and captured command output were attached and no tracked files were modified. T-Rex
  • Validated that every POST /multipart request returned 200 OK before and after the change; zero-length parts produced empty chunks or empty reads, reached EOF, and the next part returned NEXT-PART-DATA; both probe commands exited 0 and no tracked files were changed. T-Rex

Reviews (1) · Last reviewed commit: "Apply suggestion from @Dreamsorcerer"

@Dreamsorcerer
Dreamsorcerer merged commit b8db017 into 3.15 Sep 29, 2026
51 of 52 checks passed
@Dreamsorcerer
Dreamsorcerer deleted the patchback/backports/3.15/ced8133ea466a3bbe39d01a42bef79a5d2dbf1b5/pr-13760 branch September 29, 2026 02:07
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.

2 participants