tim-smart / tim-smart/multipasta
maxPartSize and maxParts report the violation but do not abort the parse (missing `return`)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 4
- Forks
- 3
- PR merge metrics
- No merged PRs in 30d
Description
Summary
maxPartSize and maxParts are reported via onError but do not stop the parse: the callback fires and then execution falls through, so the remaining bytes/parts are still delivered to onFile/onField and onDone still fires. maxTotalSize is the only limit that actually aborts — it is the one branch that returns.
Latest release (0.2.8) is affected.
Reproduction
import * as MP from "multipasta"
const boundary = "X"
const body =
`--${boundary}\r\n` +
`Content-Disposition: form-data; name="f"; filename="big.bin"\r\n` +
`Content-Type: application/octet-stream\r\n\r\n` +
"A".repeat(100) +
`\r\n--${boundary}--\r\n`
const errors = []
let received = 0
let done = false
const parser = MP.make({
headers: { "content-type": `multipart/form-data; boundary=${boundary}` },
maxPartSize: 10, // the part is 100 bytes — 10x over
onField: () => {},
onFile: () => (chunk) => { if (chunk !== null) received += chunk.length },
onError: (e) => errors.push(e._tag),
onDone: () => { done = true },
})
parser.write(Buffer.from(body))
parser.end()
console.log(errors, received, done)
Output on multipasta@0.2.8:
onError fired: ["ReachedLimit"]
bytes delivered: 100 <- expected 0 after the limit trips
onDone fired: true <- parse ran to completion despite the violation
maxParts behaves the same way — with maxParts: 1 and four parts, onError fires three times and all four fields are still delivered:
onError fired: ["ReachedLimit","ReachedLimit","ReachedLimit"]
fields parsed: ["f0","f1","f2","f3"]
onDone fired: true
Cause
src/internal/multipart.ts — neither limit returns after reporting:
// line 126 — maxParts
if (state.parts > maxParts) {
onError(errMaxParts) // no return
}
// line 131 — maxPartSize
if ((state.partSize += chunk.length) > maxPartSize) {
onError(errMaxPartSize) // no return
}
// line 215 — maxTotalSize, the one that works
if ((state.totalSize += chunk.length) > maxTotalSize) {
return onError(errMaxTotalSize)
}
Expected behaviour
Reporting a limit should stop the parse the way maxTotalSize does, so a consumer cannot end up with bytes it declared it did not want.
Impact
For a consumer that writes parts to disk as they arrive, an oversized part is fully written before anything can react to the error — so the limit does not bound what lands on disk. It also compounds with a known issue downstream: @effect/platform stores the onError failure and the onDone completion in the same slot, so the still-firing onDone overwrote the reported error (Effect-TS/effect#6392, fixed for Effect v4 in Effect-TS/effect#6395; the @effect/platform 0.x line still carries it). With that combination a request 10 bytes over maxFileSize is stored and answered 200.
Happy to send a PR adding the three returns if that is the fix you would want.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/internal/multipart.ts at the maxParts and maxPartSize checks around lines 126 and 131, and compare their control flow with the maxTotalSize check around line 215. Run the provided multipart reproduction and the maxParts scenario; done means each limit reports its error without delivering further data or firing onDone.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100