Skip to content

Sort RFC 2231 filename continuations numerically - #13500

Open
2sumtech wants to merge 4 commits into
aio-libs:masterfrom
2sumtech:fix/rfc2231-continuation-order
Open

Sort RFC 2231 filename continuations numerically#13500
2sumtech wants to merge 4 commits into
aio-libs:masterfrom
2sumtech:fix/rfc2231-continuation-order

Conversation

@2sumtech

Copy link
Copy Markdown

What do these changes do?

Fix content_disposition_filename() assembling RFC 2231 continuation sections in lexicographic order: filename*10 sorted before filename*2, so filenames split into 10+ sections were silently truncated after two sections. Sections are now sorted by their integer index (RFC 2231 Section 3), and each section is decoded according to its own extended (*N*, percent-encoded) or quoted (*N, literal) marker (RFC 2231 Section 4.1), using the charset declared by the initial extended section. This also stops apostrophes inside quoted sections from being misread as an RFC 5987 charset'language' prefix, which previously raised an uncaught ValueError (one apostrophe) or dropped most of the filename (two).

Fixes #13499

Are there changes in behavior for the user?

Only for malformed-or-mishandled inputs: 10+-section names are now complete; apostrophes in quoted sections are literal; extended sections are always percent-decoded even when the initial section carries no charset'' prefix (previously decoding was keyed on an apostrophe appearing anywhere in the joined value). Missing-section, leading-zero, and starts-at-1 handling is unchanged and covered by the existing greenbytes-derived tests.

Testing

Five new tests in tests/test_multipart_helpers.py::TestContentDispositionFilename (11-section reassembly plain and with charset, quoted/extended mix, apostrophes, single-section guard); all fail (or crash with the ValueError) on master and pass with the fix. Full test_multipart_helpers.py + test_multipart.py: 253 passed. flake8/mypy clean on touched files; CHANGES fragment 13499.bugfix.rst included.

Disclosure

Drafted with Claude Code (Fable 5); opened as draft pending human review per AGENTS.md.

content_disposition_filename() assembled filename*N continuation
sections with a lexicographic sort, so filename*10 sorted before
filename*2 and any filename split into 10 or more sections was
silently truncated after the first two. RFC 2231 Section 3 requires
numeric ordering of continuation sections.

Sort the sections by their integer index and decode each section
according to its own extended (*N*, percent-encoded) or quoted (*N,
literal) marker per RFC 2231 Section 4.1, using the charset declared
by the initial extended section. This also stops apostrophes inside
quoted sections from being misread as an RFC 5987 charset prefix,
which previously raised ValueError or dropped parts of the name.
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided There is a change note present in this PR label Aug 19, 2026
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.00%. Comparing base (8c8906a) to head (dcfbad1).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #13500   +/-   ##
=======================================
  Coverage   99.00%   99.00%           
=======================================
  Files         132      132           
  Lines       49635    49701   +66     
  Branches     2575     2583    +8     
=======================================
+ Hits        49141    49207   +66     
  Misses        370      370           
  Partials      124      124           
Flag Coverage Δ
Autobahn 22.03% <16.04%> (-0.01%) ⬇️
CI-GHA 98.91% <100.00%> (+<0.01%) ⬆️
OS-Linux 98.68% <100.00%> (-0.01%) ⬇️
OS-Windows 97.01% <100.00%> (+<0.01%) ⬆️
OS-macOS 97.93% <100.00%> (+<0.01%) ⬆️
Py-3.10 98.13% <100.00%> (+<0.01%) ⬆️
Py-3.11 98.37% <100.00%> (+<0.01%) ⬆️
Py-3.12 98.46% <100.00%> (+<0.01%) ⬆️
Py-3.13 98.44% <100.00%> (-0.01%) ⬇️
Py-3.14 98.47% <100.00%> (+<0.01%) ⬆️
Py-3.14t 97.55% <100.00%> (-0.01%) ⬇️
Py-pypy-3.11 97.42% <100.00%> (+0.02%) ⬆️
VM-macos 97.93% <100.00%> (+<0.01%) ⬆️
VM-ubuntu 98.68% <100.00%> (-0.01%) ⬇️
VM-windows 97.01% <100.00%> (+<0.01%) ⬆️
cython-coverage 82.19% <20.51%> (-0.06%) ⬇️

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.

@2sumtech
2sumtech marked this pull request as ready for review August 19, 2026 16:34
@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (4): Last reviewed commit: "Cover a multibyte character split across..." | Re-trigger Greptile

Comment thread aiohttp/multipart.py Outdated
Comment thread CHANGES/13499.bugfix.rst Outdated
Comment on lines +1 to +4
Fixed :func:`~aiohttp.content_disposition_filename` truncating filenames split
into 10 or more RFC 2231 continuation sections by sorting the sections
numerically instead of lexicographically, and decoding each section according
to its own extended/quoted marker -- by :user:`2sumtech`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Add the PR-numbered changelog symlink

This adds only the issue-numbered fragment, while the repository convention requires a CHANGES/13500.bugfix.rst symlink to 13499.bugfix.rst when the issue and PR numbers differ. Without it, the change does not follow the project's changelog bookkeeping convention.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@codspeed-hq

codspeed-hq Bot commented Aug 19, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 84 untouched benchmarks
⏩ 83 skipped benchmarks1


Comparing 2sumtech:fix/rfc2231-continuation-order (dcfbad1) with master (8c8906a)

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.

Percent-decoding each extended section on its own dropped the whole
filename when a multibyte character straddled a section boundary.
Join the octets of adjacent extended sections before decoding, and
reference the changelog fragment by PR number via a symlink.

The changelog fragment no longer uses a :func: role, because
content_disposition_filename is not in the API docs and the dangling
reference failed the docs build.
Adds tests for octets that stay undecodable once the sections are
joined, an unknown charset, and a non-numeric section index, so the
new branches are exercised.
A three-octet character can land in three different extended sections;
exercise that the whole run is still joined before decoding.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

content_disposition_filename() truncates filenames split into 10+ RFC 2231 continuation sections (lexicographic sort)

1 participant