fix: enforce parts limit after exceeding it - #1446
Conversation
Count multipart parts in Multer so requests with exactly the configured number of fields and files are accepted, while requests that exceed the limit still fail with LIMIT_PART_COUNT. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MohammedAlkindi
left a comment
There was a problem hiding this comment.
Ran this on Windows 11, Node 24.18.0, against main at 4203ccd.
The off-by-one is real and the fix discriminates. This suite is nondeterministic on Windows (base runs vary 70–71 passing / 12–13 failing on the same commit), so raw counts are useless — I compared failing-test sets with the lib reverted against the PR head, same test files both sides:
- Fails only with the old lib:
Error Handling should allow parts up to the limit— the PR's new test, i.e. a request with exactlyparts: 2parts wrongly aborts withLIMIT_PART_COUNTtoday, exactly as #1418 describes. - Fails only on the PR: nothing. (The other difference in the reverted run was the known-flaky
Select Fieldbefore-all hook, which comes and goes on identical commits here.)
Run directly, the three parts-limit tests pass on the PR head.
On the implementation: both the field and file handlers route through incrementPartCount(), so the count covers all part kinds; the typeof/isFinite guard means no behaviour change when parts is unset; and the object spread matches existing usage in this file (lines ~259/269), so no style drift.
One semantic shift worth a sentence in the description: since parts is now stripped from the limits busboy sees, busboy will begin parsing the N+1th part — headers included — before multer aborts, where previously busboy stopped emitting at its own boundary. For well-formed requests that's invisible; for a hostile stream it means the abort lands one part-header later than before. I don't think it matters given LIMIT_PART_COUNT still fires and the request is torn down, but it's the kind of thing a future reader will wonder about, and the PR description is the right place to say it was considered.
Also worth noting for reviewers: busboyLimits() returns the original object untouched when parts isn't set — no copy — so there's no per-request allocation cost in the common path. Nice touch.
Fixes #1418
Reproduction
A multipart request with exactly two parts (
field0plustiny0) andlimits: { parts: 2 }currently completes parsing and then fails withLIMIT_PART_COUNTat the closing boundary.Root cause
Busboy emits
partsLimitwhen its internal boundary counter reaches the configured value. Multer forwards that event directly, so a request with exactly the allowed number of fields/files is treated as over the limit.Fix
Multer now removes
partsfrom the limits passed to Busboy and counts actualfield/fileparts itself. It raisesLIMIT_PART_COUNTonly when the request exceeds the configured number of parts; the behavior for over-limit requests is unchanged.Compatibility notes
This changes
limits.partsto the documented max number of fields plus files. Requests exactly at the limit are now accepted; requests over the limit still fail withLIMIT_PART_COUNT.Validation
npm test -- --grep "should allow parts up to the limit"fails withLIMIT_PART_COUNT(0 passing, 1 failing).npm test -- --grep "parts limit|parts up to"-> 2 passing.npm run lint-> passed.npm test-> 72 passing, 12 failing. The same checkout before this patch reports 71 passing, 12 failing; failures are pre-existing Windows fixture line-ending/path assertions (e.g. expected 1778 but got 1803, and/testforme-path separator check).