1
1
'use strict'
2
2
3
- const Busboy = require ( '@fastify/busboy' )
4
3
const util = require ( '../../core/util' )
5
4
const {
6
5
ReadableStreamFrom,
@@ -9,23 +8,20 @@ const {
9
8
readableStreamClose,
10
9
createDeferredPromise,
11
10
fullyReadBody,
12
- extractMimeType
11
+ extractMimeType,
12
+ utf8DecodeBytes
13
13
} = require ( './util' )
14
14
const { FormData } = require ( './formdata' )
15
15
const { kState } = require ( './symbols' )
16
16
const { webidl } = require ( './webidl' )
17
- const { Blob, File : NativeFile } = require ( 'node:buffer' )
17
+ const { Blob } = require ( 'node:buffer' )
18
18
const assert = require ( 'node:assert' )
19
19
const { isErrored } = require ( '../../core/util' )
20
20
const { isArrayBuffer } = require ( 'node:util/types' )
21
- const { File : UndiciFile } = require ( './file' )
22
21
const { serializeAMimeType } = require ( './data-url' )
23
- const { Readable } = require ( 'node:stream ' )
22
+ const { multipartFormDataParser } = require ( './formdata-parser ' )
24
23
25
- /** @type {globalThis['File'] } */
26
- const File = NativeFile ?? UndiciFile
27
24
const textEncoder = new TextEncoder ( )
28
- const textDecoder = new TextDecoder ( )
29
25
30
26
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
31
27
function extractBody ( object , keepalive = false ) {
@@ -338,116 +334,56 @@ function bodyMixinMethods (instance) {
338
334
return consumeBody ( this , parseJSONFromBytes , instance )
339
335
} ,
340
336
341
- async formData ( ) {
342
- webidl . brandCheck ( this , instance )
343
-
344
- throwIfAborted ( this [ kState ] )
345
-
346
- // 1. Let mimeType be the result of get the MIME type with this.
347
- const mimeType = bodyMimeType ( this )
348
-
349
- // If mimeType’s essence is "multipart/form-data", then:
350
- if ( mimeType !== null && mimeType . essence === 'multipart/form-data' ) {
351
- const responseFormData = new FormData ( )
352
-
353
- let busboy
354
-
355
- try {
356
- busboy = new Busboy ( {
357
- headers : {
358
- 'content-type' : serializeAMimeType ( mimeType )
359
- } ,
360
- preservePath : true
361
- } )
362
- } catch ( err ) {
363
- throw new DOMException ( `${ err } ` , 'AbortError' )
364
- }
365
-
366
- busboy . on ( 'field' , ( name , value ) => {
367
- responseFormData . append ( name , value )
368
- } )
369
- busboy . on ( 'file' , ( name , value , filename , encoding , mimeType ) => {
370
- const chunks = [ ]
371
-
372
- if ( encoding === 'base64' || encoding . toLowerCase ( ) === 'base64' ) {
373
- let base64chunk = ''
374
-
375
- value . on ( 'data' , ( chunk ) => {
376
- base64chunk += chunk . toString ( ) . replace ( / [ \r \n ] / gm, '' )
377
-
378
- const end = base64chunk . length - base64chunk . length % 4
379
- chunks . push ( Buffer . from ( base64chunk . slice ( 0 , end ) , 'base64' ) )
380
-
381
- base64chunk = base64chunk . slice ( end )
382
- } )
383
- value . on ( 'end' , ( ) => {
384
- chunks . push ( Buffer . from ( base64chunk , 'base64' ) )
385
- responseFormData . append ( name , new File ( chunks , filename , { type : mimeType } ) )
386
- } )
387
- } else {
388
- value . on ( 'data' , ( chunk ) => {
389
- chunks . push ( chunk )
390
- } )
391
- value . on ( 'end' , ( ) => {
392
- responseFormData . append ( name , new File ( chunks , filename , { type : mimeType } ) )
393
- } )
394
- }
395
- } )
396
-
397
- const busboyResolve = new Promise ( ( resolve , reject ) => {
398
- busboy . on ( 'finish' , resolve )
399
- busboy . on ( 'error' , ( err ) => reject ( new TypeError ( err ) ) )
400
- } )
401
-
402
- if ( this . body !== null ) {
403
- Readable . from ( this [ kState ] . body . stream ) . pipe ( busboy )
404
- }
337
+ formData ( ) {
338
+ // The formData() method steps are to return the result of running
339
+ // consume body with this and the following step given a byte sequence bytes:
340
+ return consumeBody ( this , ( value ) => {
341
+ // 1. Let mimeType be the result of get the MIME type with this.
342
+ const mimeType = bodyMimeType ( this )
343
+
344
+ // 2. If mimeType is non-null, then switch on mimeType’s essence and run
345
+ // the corresponding steps:
346
+ if ( mimeType !== null ) {
347
+ switch ( mimeType . essence ) {
348
+ case 'multipart/form-data' : {
349
+ // 1. ... [long step]
350
+ const parsed = multipartFormDataParser ( value , mimeType )
351
+
352
+ // 2. If that fails for some reason, then throw a TypeError.
353
+ if ( parsed === 'failure' ) {
354
+ throw new TypeError ( 'Failed to parse body as FormData.' )
355
+ }
356
+
357
+ // 3. Return a new FormData object, appending each entry,
358
+ // resulting from the parsing operation, to its entry list.
359
+ const fd = new FormData ( )
360
+ fd [ kState ] = parsed
361
+
362
+ return fd
363
+ }
364
+ case 'application/x-www-form-urlencoded' : {
365
+ // 1. Let entries be the result of parsing bytes.
366
+ const entries = new URLSearchParams ( value . toString ( ) )
405
367
406
- await busboyResolve
368
+ // 2. If entries is failure, then throw a TypeError.
407
369
408
- return responseFormData
409
- } else if ( mimeType !== null && mimeType . essence === 'application/x-www-form-urlencoded' ) {
410
- // Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then:
370
+ // 3. Return a new FormData object whose entry list is entries.
371
+ const fd = new FormData ( )
411
372
412
- // 1. Let entries be the result of parsing bytes.
413
- let entries
414
- try {
415
- let text = ''
416
- // application/x-www-form-urlencoded parser will keep the BOM.
417
- // https://url.spec.whatwg.org/#concept-urlencoded-parser
418
- // Note that streaming decoder is stateful and cannot be reused
419
- const stream = this [ kState ] . body . stream . pipeThrough ( new TextDecoderStream ( 'utf-8' , { ignoreBOM : true } ) )
373
+ for ( const [ name , value ] of entries ) {
374
+ fd . append ( name , value )
375
+ }
420
376
421
- for await ( const chunk of stream ) {
422
- text += chunk
377
+ return fd
378
+ }
423
379
}
424
-
425
- entries = new URLSearchParams ( text )
426
- } catch ( err ) {
427
- // istanbul ignore next: Unclear when new URLSearchParams can fail on a string.
428
- // 2. If entries is failure, then throw a TypeError.
429
- throw new TypeError ( err )
430
380
}
431
381
432
- // 3. Return a new FormData object whose entries are entries.
433
- const formData = new FormData ( )
434
- for ( const [ name , value ] of entries ) {
435
- formData . append ( name , value )
436
- }
437
- return formData
438
- } else {
439
- // Wait a tick before checking if the request has been aborted.
440
- // Otherwise, a TypeError can be thrown when an AbortError should.
441
- await Promise . resolve ( )
442
-
443
- throwIfAborted ( this [ kState ] )
444
-
445
- // Otherwise, throw a TypeError.
446
- throw webidl . errors . exception ( {
447
- header : `${ instance . name } .formData` ,
448
- message : 'Could not parse content as FormData.'
449
- } )
450
- }
382
+ // 3. Throw a TypeError.
383
+ throw new TypeError (
384
+ 'Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".'
385
+ )
386
+ } , instance )
451
387
}
452
388
}
453
389
@@ -516,32 +452,6 @@ function bodyUnusable (body) {
516
452
return body != null && ( body . stream . locked || util . isDisturbed ( body . stream ) )
517
453
}
518
454
519
- /**
520
- * @see https://encoding.spec.whatwg.org/#utf-8-decode
521
- * @param {Buffer } buffer
522
- */
523
- function utf8DecodeBytes ( buffer ) {
524
- if ( buffer . length === 0 ) {
525
- return ''
526
- }
527
-
528
- // 1. Let buffer be the result of peeking three bytes from
529
- // ioQueue, converted to a byte sequence.
530
-
531
- // 2. If buffer is 0xEF 0xBB 0xBF, then read three
532
- // bytes from ioQueue. (Do nothing with those bytes.)
533
- if ( buffer [ 0 ] === 0xEF && buffer [ 1 ] === 0xBB && buffer [ 2 ] === 0xBF ) {
534
- buffer = buffer . subarray ( 3 )
535
- }
536
-
537
- // 3. Process a queue with an instance of UTF-8’s
538
- // decoder, ioQueue, output, and "replacement".
539
- const output = textDecoder . decode ( buffer )
540
-
541
- // 4. Return output.
542
- return output
543
- }
544
-
545
455
/**
546
456
* @see https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value
547
457
* @param {Uint8Array } bytes
0 commit comments