Skip to content

Fix read_chunk() on body parts with a zero Content-Length - #13760

Merged
Dreamsorcerer merged 6 commits into
aio-libs:masterfrom
istoolsfox:fix/multipart-zero-length-read-chunk
Sep 29, 2026
Merged

Dreamsorcerer merged 6 commits into
aio-libs:masterfrom
istoolsfox:fix/multipart-zero-length-read-chunk

Conversation

@istoolsfox

@istoolsfox istoolsfox commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

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.

read_chunk() picks its read strategy by testing self._length for
truthiness, so a part with an explicit Content-Length: 0 (legal outside
multipart/form-data, where the RFC 7578 special case nulls the length)
fell through to _read_chunk_from_stream(). That path asserts the
requested chunk size is at least the boundary length, so callers asking
for a small chunk on an empty part hit an AssertionError instead of
getting an immediate empty chunk.

Test the length against None instead: an empty part now yields an empty
chunk and reaches EOF like any other part with a known length.

Fixes aio-libs#13758.
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided There is a change note present in this PR label Sep 20, 2026
@codecov

codecov Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.04%. Comparing base (60bffb4) to head (976682f).
⚠️ Report is 3 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #13760   +/-   ##
=======================================
  Coverage   99.04%   99.04%           
=======================================
  Files         135      135           
  Lines       51562    51569    +7     
  Branches     2696     2696           
=======================================
+ Hits        51068    51075    +7     
  Misses        371      371           
  Partials      123      123           
Flag Coverage Δ
Autobahn 21.89% <12.50%> (-0.01%) ⬇️
CI-GHA 98.87% <100.00%> (+<0.01%) ⬆️
OS-Linux 98.66% <100.00%> (-0.01%) ⬇️
OS-Windows 97.29% <100.00%> (+<0.01%) ⬆️
OS-macOS 98.15% <100.00%> (-0.01%) ⬇️
Py-3.10 98.09% <100.00%> (+<0.01%) ⬆️
Py-3.11 98.31% <100.00%> (-0.01%) ⬇️
Py-3.12 98.40% <100.00%> (+<0.01%) ⬆️
Py-3.13 98.40% <100.00%> (-0.01%) ⬇️
Py-3.14 98.43% <100.00%> (-0.01%) ⬇️
Py-3.15 98.42% <100.00%> (-0.01%) ⬇️
Py-3.15t 97.80% <100.00%> (-0.01%) ⬇️
Py-pypy-3.12 96.51% <100.00%> (-0.02%) ⬇️
VM-macos 98.15% <100.00%> (-0.01%) ⬇️
VM-ubuntu 98.66% <100.00%> (-0.01%) ⬇️
VM-windows 97.29% <100.00%> (+<0.01%) ⬆️
cython-coverage 83.29% <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.

@greptile-apps

greptile-apps Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

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

No outstanding findings block merging.

Reviews (5) · Last reviewed commit: "Merge branch 'master' into fix/multipart..."

@codspeed

codspeed Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 97 untouched benchmarks
⏩ 83 skipped benchmarks1


Comparing istoolsfox:fix/multipart-zero-length-read-chunk (976682f) with master (60bffb4)2

Open in CodSpeed

Footnotes

  1. 83 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 master (ed74d78) during the generation of this report, so 60bffb4 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@istoolsfox
istoolsfox force-pushed the fix/multipart-zero-length-read-chunk branch from a2dd75b to 3fabea9 Compare September 20, 2026 09:34
@Polandia94 Polandia94 added backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot backport-3.15 Trigger automatic backporting to the 3.15 release branch by Patchback robot labels Sep 21, 2026
Comment thread tests/test_multipart.py Outdated
@Dreamsorcerer
Dreamsorcerer merged commit ced8133 into aio-libs:master Sep 29, 2026
56 checks passed
@patchback

patchback Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Backport to 3.15: 💚 backport PR created

✅ Backport PR branch: patchback/backports/3.15/ced8133ea466a3bbe39d01a42bef79a5d2dbf1b5/pr-13760

Backported as #13853

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

@patchback

patchback Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Backport to 3.14: 💚 backport PR created

✅ Backport PR branch: patchback/backports/3.14/ced8133ea466a3bbe39d01a42bef79a5d2dbf1b5/pr-13760

Backported as #13854

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

Dreamsorcerer pushed a commit that referenced this pull request Sep 29, 2026
…th a zero Content-Length (#13853)

**This is a backport of PR #13760 as merged into master
(ced8133).**
Dreamsorcerer pushed a commit that referenced this pull request Sep 29, 2026
…th a zero Content-Length (#13854)

**This is a backport of PR #13760 as merged into master
(ced8133).**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentscan:automated-account backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot backport-3.15 Trigger automatic backporting to the 3.15 release branch by Patchback robot bot:chronographer:provided There is a change note present in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BodyPartReader.read_chunk() crashes with AssertionError on a body part with Content-Length: 0

3 participants