Repository navigation
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #13640 +/- ##
=======================================
Coverage 99.03% 99.03%
=======================================
Files 135 135
Lines 51399 51419 +20
Branches 2694 2695 +1
=======================================
+ Hits 50904 50924 +20
Misses 372 372
Partials 123 123
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
Merging this PR will not alter performance
Comparing Footnotes
|
|
| self._default_charset = charset.strip().decode() | ||
| await part.release() | ||
| await self._read_boundary() | ||
| if self._at_eof: |
There was a problem hiding this comment.
When a nested form-data part contains only _charset_ and its closing boundary is followed directly by the outer boundary, the next outer field loses its Content-Disposition header. Its content remains readable, but its name becomes None, so consumers cannot identify the field. This is a non-blocking concern in the newly enabled parsing path.
Artifacts
Executed nested multipart reproduction source
- The authored Python script constructs a charset-only inner multipart and asserts that the following outer sibling retains its name and content.
- The script ran against base and failed while parsing the inner closing boundary, before reaching the outer sibling.
- The script ran against PR head and read the sibling content, but its missing field name caused exit code 1.
- The script ran against base with an epilogue line and failed while parsing the inner closing boundary.
- The script ran against PR head with an epilogue line and verified the sibling name, content, and outer EOF with exit code 0.
Comments Outside DiffThese findings sit on lines the diff does not cover, so they could not be posted inline. Each one leaves this list once its file changes.
|
What do these changes do?
MultipartReader.next()has a special case for the RFC 7578 §4.6_charset_field: when the first part of a
multipart/form-databody is named_charset_,its value is read as the default charset and the next part is returned instead.
That branch read the
_charset_part's body and then calledfetch_next_part()directly, so the delimiter line that terminates the
_charset_part was neverconsumed.
fetch_next_part()therefore fed the boundary line intoHeadersParser, which raisedInvalidHeader. These changes release the_charset_part and read its delimiter with_read_boundary()before fetchingthe next part, and return
Nonewhen that delimiter turns out to be the closingone.
Are there changes in behavior for the user?
Yes — a
multipart/form-databody containing a_charset_field is now parsedinstead of raising. Previously the boundary line was consumed as a header, so
await request.post()raisedInvalidHeaderand the server answered500 Internal Server Error for every boundary that does not itself parse as a
name: valuepair — i.e. for every realistic boundary. The existing coverageonly passed because it uses the boundary
:, which makes the delimiter line--:parse as a (bogus) header named--, silently attached to the followingpart's headers. That stray header is gone now too. No API changes.
Is it a substantial burden for the maintainers to support this?
No. It is five lines inside the existing
_charset_branch, reusingBodyPartReader.release()andMultipartReader._read_boundary()— the same twosteps the ordinary
next()path already performs between parts. No new helpers,no new public surface.
Related issue number
None — found while probing the multipart header/boundary seams. Happy to file one
if you prefer an issue on record.
Checklist
CONTRIBUTORS.txt— already listedCHANGES/folderReproducer
Before:
500 500 Internal Server Errorwithaiohttp.http_exceptions.InvalidHeader: Invalid HTTP header: b'------WebKitFormBoundaryABC'raised from
multipart.py:832 fetch_next_part→multipart.py:935 _read_headers.After:
200 {"field1": "foo"}.Agent run output — tests before and after
Environment: pure-Python mode,
AIOHTTP_NO_EXTENSIONS=1 PYTHONPATH=. python -m pytest(CPython 3.11.15). The C extension only substitutes
HttpRequestParser/HttpResponseParser/RawRequestMessage/RawResponseMessage(
http_parser.py:1241-1248);MultipartReader._read_headers()always uses the pure-PythonHeadersParser, so this change has no Cython-dependent behaviour.1. New tests fail on the unpatched tree (
aiohttp/multipart.pyrestored fromorigin/master, new tests present):2. Whole multipart suite passes with the fix:
3. No regressions in the server request path:
4. Toolchain on the touched files:
Drafted with Claude Code (Claude Opus 5 and Fable 5.1); human review by @2sumtech pending before this leaves draft.