File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -319,6 +319,30 @@ describe('stream limits', () => {
319319 ) . rejects . toBeInstanceOf ( MultipartFieldValidationError )
320320 } )
321321
322+ it ( 'leaves a body that was already read to fail as a server error' , async ( ) => {
323+ const input = new FormData ( )
324+ input . append ( 'name' , 'example' )
325+ const encoded = new Request ( 'http://localhost/upload' , { method : 'POST' , body : input } )
326+ const bytes = new Uint8Array ( await encoded . arrayBuffer ( ) )
327+ const request = new Request ( 'http://localhost/upload' , {
328+ method : 'POST' ,
329+ headers : {
330+ 'content-type' : encoded . headers . get ( 'content-type' ) ?? '' ,
331+ 'content-length' : String ( bytes . byteLength ) ,
332+ } ,
333+ body : bytes ,
334+ } )
335+ await request . text ( )
336+
337+ const failure = await readFormDataWithLimit ( request , {
338+ maxBytes : 1024 * 1024 ,
339+ label : 'multipart body' ,
340+ } ) . catch ( ( error : unknown ) => error )
341+
342+ expect ( failure ) . toBeInstanceOf ( TypeError )
343+ expect ( failure ) . not . toBeInstanceOf ( MultipartFieldValidationError )
344+ } )
345+
322346 it ( 'rejects multipart streams without content-length once bytes exceed the limit' , async ( ) => {
323347 const request = new Request ( 'http://localhost/upload' , {
324348 method : 'POST' ,
Original file line number Diff line number Diff line change @@ -69,6 +69,7 @@ export interface ReadFormDataWithLimitRequest {
6969 method : string
7070 headers ?: Headers
7171 body ?: ReadableStream < Uint8Array > | null
72+ bodyUsed ?: boolean
7273 formData : ( ) => Promise < FormData >
7374}
7475
@@ -158,9 +159,14 @@ export async function readFormDataWithLimit(
158159 * The fetch body parser rejects a body that does not decode as the content type it
159160 * declares (a JSON body, a boundary the body never uses) with a bare `TypeError`. That
160161 * is the caller's malformed input, so it surfaces as the same validation failure every
161- * multipart route already answers with a 400.
162+ * multipart route already answers with a 400. So does a declared-length body that ends
163+ * early, since the client sent an incomplete request. A body some earlier code already
164+ * consumed is a server bug, so that failure is left to surface as it is.
162165 */
163- async function parseFormData ( request : Pick < Request , 'formData' > ) : Promise < FormData > {
166+ async function parseFormData (
167+ request : Pick < ReadFormDataWithLimitRequest , 'formData' | 'bodyUsed' >
168+ ) : Promise < FormData > {
169+ if ( request . bodyUsed ) return request . formData ( )
164170 try {
165171 return await request . formData ( )
166172 } catch ( error ) {
You can’t perform that action at this time.
0 commit comments