Repository navigation
Conversation
…read .body stream
|
Updated 8:08 PM PT - Oct 11th, 2026
✅ @robobun, your commit be1f3a208af21f110fa4ee8e9b7dae5011bf52ae passed in 🧪 To try this PR locally: bunx bun-pr 44999That installs a local version of the PR into your bun-44999 --bun |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughFormData parsing now checks for an encoder before lifting a locked body. If no encoder is available, parsing rejects without lifting the body. Tests cover body reuse after failed parsing and parsing when the Content-Type header is absent or empty. ChangesFormData Body Parsing
Suggested reviewers: Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline findings, I also checked whether moving the lift behind the type check changes the unusable-stream / already-has-reader ordering for a Locked body — it does not, those two checks still run first in the same order; and whether the single remaining to_blob_if_possible() after an encoder is obtained can miss a Locked body — it is gated on matches!(value, Value::Locked(_)) and runs before the existing Locked branch, so the parse path is unchanged for form-data bodies.
Extended reasoning...
The change is confined to BodyMixin::get_form_data and two new helpers in src/runtime/webcore/Body.rs plus a new describe block in test/js/web/fetch/body.test.ts; it touches no auth, crypto or injection surface. Two confirmed inline findings (empty Content-Type header handled differently by fast_has vs fast_get, and the Source::Blob-only fallback missing file-backed bodies) mean a human should weigh them, so this is a findings-present ruled-out note only.
|
Reproduced on the released 1.4.3 line with the reduced program from the report, run on that build and on this branch. The string-body line reads the same way. Test: |
Problem
const s = r.body; await r.formData()on a body that is not form data rejects and leavesslocked whiler.bodyUsedis false.s.getReader()throwsReadableStream is locked, andBun.write(path, r)fails withERR_BODY_ALREADY_USED.get_form_data(src/runtime/webcore/Body.rs) callsto_blob_if_possible()before it looks at the Content-Type. Since Close a body's stream when a consumer takes its payload without reading it #42516 that lift closes and locks the stream.Fix
formData()decides the type first. It lifts the payload only when the body is form data, and then it consumes it.Content-Typeheader, the type of the Blob behind the stream decides, as it does after a lift.form_data_encodingreads it from the stream's source.test/js/web/html/FormData.test.ts(34 new cases, 13 fail on main).Background
.bodymakes a bodyLocked: its payload sits behind a native stream. A lift takes the payload out as a Blob and spends the stream.formData()leaves the body as it was, as in every Bun release.Downsides
.bodywas not read: it takes the same path as before. Checked thebody,FormData,body-cloneandbody-streamsuites.size).Notes
const r = new Response(Bun.file(p)), s = r.body, aformData()that fails on the type, then five uses (reads,s.tee(),new Response(s).text(),Bun.write(path, r),r.text()):bodyUsedfalse,s.lockedtrue, four uses throw andr.text()gives the file.bodyUsedfalse,s.lockedfalse, all five give the whole file. The same holds for a string body.Content-Typeheader still wins over the body's own type.new Response(new URLSearchParams("a=1"), { headers: { "content-type": "application/json" } })rejects with or without a.bodyread.Content-Typeheader counts as no header, as it does inget_content_type: the check uses the samefast_getcall.fast_hasin place offast_getthe empty-header case fails.formData()(Response::get_content_typeis also the MIME check ofWebAssembly.compileStreamingand does not change here), say in the code that Fetch reads first, and keep the tests out of the describe that pins what undici does.Bun.serveroutes). The two touch different functions.FormData(183 pass with the new cases),body(808 pass. The two "string body consumption does not leak" json cases time out at 5 s on the debug build, as they do without this change. The release build passes all),body-clone(100),body-stream(9086),body-mixin-errors(23),request(19),response(22 pass. "handle stack overflow" fails on the debug build, as it does without this change).get_content_typesees that type also for a body behind a stream. Then the read of the stream's Blob type in this PR is not needed, and this PR reduces to the move of the lift behind the type check.