22
33const Busboy = require ( 'busboy' )
44const util = require ( '../core/util' )
5- const { ReadableStreamFrom, isBlobLike, isReadableStreamLike, readableStreamClose } = require ( './util' )
5+ const {
6+ ReadableStreamFrom,
7+ isBlobLike,
8+ isReadableStreamLike,
9+ readableStreamClose,
10+ createDeferredPromise,
11+ fullyReadBody
12+ } = require ( './util' )
613const { FormData } = require ( './formdata' )
714const { kState } = require ( './symbols' )
815const { webidl } = require ( './webidl' )
@@ -13,7 +20,6 @@ const assert = require('assert')
1320const { isErrored } = require ( '../core/util' )
1421const { isUint8Array, isArrayBuffer } = require ( 'util/types' )
1522const { File : UndiciFile } = require ( './file' )
16- const { StringDecoder } = require ( 'string_decoder' )
1723const { parseMIMEType, serializeAMimeType } = require ( './dataURL' )
1824
1925let ReadableStream = globalThis . ReadableStream
@@ -313,26 +319,45 @@ function bodyMixinMethods (instance) {
313319 const methods = {
314320 blob ( ) {
315321 // The blob() method steps are to return the result of
316- // running consume body with this and Blob.
317- return specConsumeBody ( this , 'Blob' , instance )
322+ // running consume body with this and the following step
323+ // given a byte sequence bytes: return a Blob whose
324+ // contents are bytes and whose type attribute is this’s
325+ // MIME type.
326+ return specConsumeBody ( this , ( bytes ) => {
327+ let mimeType = bodyMimeType ( this )
328+
329+ if ( mimeType === 'failure' ) {
330+ mimeType = ''
331+ } else if ( mimeType ) {
332+ mimeType = serializeAMimeType ( mimeType )
333+ }
334+
335+ // Return a Blob whose contents are bytes and type attribute
336+ // is mimeType.
337+ return new Blob ( [ bytes ] , { type : mimeType } )
338+ } , instance )
318339 } ,
319340
320341 arrayBuffer ( ) {
321- // The arrayBuffer() method steps are to return the
322- // result of running consume body with this and ArrayBuffer.
323- return specConsumeBody ( this , 'ArrayBuffer' , instance )
342+ // The arrayBuffer() method steps are to return the result
343+ // of running consume body with this and the following step
344+ // given a byte sequence bytes: return a new ArrayBuffer
345+ // whose contents are bytes.
346+ return specConsumeBody ( this , ( bytes ) => {
347+ return new Uint8Array ( bytes ) . buffer
348+ } , instance )
324349 } ,
325350
326351 text ( ) {
327- // The text() method steps are to return the result of
328- // running consume body with this and text .
329- return specConsumeBody ( this , 'text' , instance )
352+ // The text() method steps are to return the result of running
353+ // consume body with this and UTF-8 decode .
354+ return specConsumeBody ( this , utf8DecodeBytes , instance )
330355 } ,
331356
332357 json ( ) {
333- // The json() method steps are to return the result of
334- // running consume body with this and JSON.
335- return specConsumeBody ( this , 'JSON' , instance )
358+ // The json() method steps are to return the result of running
359+ // consume body with this and parse JSON from bytes .
360+ return specConsumeBody ( this , parseJSONFromBytes , instance )
336361 } ,
337362
338363 async formData ( ) {
@@ -455,8 +480,13 @@ function mixinBody (prototype) {
455480 Object . assign ( prototype . prototype , bodyMixinMethods ( prototype ) )
456481}
457482
458- // https://fetch.spec.whatwg.org/#concept-body-consume-body
459- async function specConsumeBody ( object , type , instance ) {
483+ /**
484+ * @see https://fetch.spec.whatwg.org/#concept-body-consume-body
485+ * @param {Response|Request } object
486+ * @param {(value: unknown) => unknown } convertBytesToJSValue
487+ * @param {Response|Request } instance
488+ */
489+ async function specConsumeBody ( object , convertBytesToJSValue , instance ) {
460490 webidl . brandCheck ( object , instance )
461491
462492 throwIfAborted ( object [ kState ] )
@@ -467,71 +497,37 @@ async function specConsumeBody (object, type, instance) {
467497 throw new TypeError ( 'Body is unusable' )
468498 }
469499
470- // 2. Let promise be a promise resolved with an empty byte
471- // sequence.
472- let promise
473-
474- // 3. If object’s body is non-null, then set promise to the
475- // result of fully reading body as promise given object’s
476- // body.
477- if ( object [ kState ] . body != null ) {
478- promise = await fullyReadBodyAsPromise ( object [ kState ] . body )
479- } else {
480- // step #2
481- promise = { size : 0 , bytes : [ new Uint8Array ( ) ] }
500+ // 2. Let promise be a new promise.
501+ const promise = createDeferredPromise ( )
502+
503+ // 3. Let errorSteps given error be to reject promise with error.
504+ const errorSteps = ( error ) => promise . reject ( error )
505+
506+ // 4. Let successSteps given a byte sequence data be to resolve
507+ // promise with the result of running convertBytesToJSValue
508+ // with data. If that threw an exception, then run errorSteps
509+ // with that exception.
510+ const successSteps = ( data ) => {
511+ try {
512+ promise . resolve ( convertBytesToJSValue ( data ) )
513+ } catch ( e ) {
514+ errorSteps ( e )
515+ }
482516 }
483517
484- // 4. Let steps be to return the result of package data with
485- // the first argument given, type, and object’s MIME type.
486- const mimeType = type === 'Blob' || type === 'FormData'
487- ? bodyMimeType ( object )
488- : undefined
489-
490- // 5. Return the result of upon fulfillment of promise given
491- // steps.
492- return packageData ( promise , type , mimeType )
493- }
494-
495- /**
496- * @see https://fetch.spec.whatwg.org/#concept-body-package-data
497- * @param {{ size: number, bytes: Uint8Array[] } } bytes
498- * @param {string } type
499- * @param {ReturnType<typeof parseMIMEType>|undefined } mimeType
500- */
501- function packageData ( { bytes, size } , type , mimeType ) {
502- switch ( type ) {
503- case 'ArrayBuffer' : {
504- // Return a new ArrayBuffer whose contents are bytes.
505- const uint8 = new Uint8Array ( size )
506- let offset = 0
507-
508- for ( const chunk of bytes ) {
509- uint8 . set ( chunk , offset )
510- offset += chunk . byteLength
511- }
518+ // 5. If object’s body is null, then run successSteps with an
519+ // empty byte sequence.
520+ if ( object [ kState ] . body == null ) {
521+ successSteps ( new Uint8Array ( ) )
522+ return promise . promise
523+ }
512524
513- return uint8 . buffer
514- }
515- case 'Blob' : {
516- if ( mimeType === 'failure' ) {
517- mimeType = ''
518- } else if ( mimeType ) {
519- mimeType = serializeAMimeType ( mimeType )
520- }
525+ // 6. Otherwise, fully read object’s body given successSteps,
526+ // errorSteps, and object’s relevant global object.
527+ fullyReadBody ( object [ kState ] . body , successSteps , errorSteps )
521528
522- // Return a Blob whose contents are bytes and type attribute
523- // is mimeType.
524- return new Blob ( bytes , { type : mimeType } )
525- }
526- case 'JSON' : {
527- // Return the result of running parse JSON from bytes on bytes.
528- return JSON . parse ( utf8DecodeBytes ( bytes ) )
529- }
530- case 'text' : {
531- // 1. Return the result of running UTF-8 decode on bytes.
532- return utf8DecodeBytes ( bytes )
533- }
534- }
529+ // 7. Return promise.
530+ return promise . promise
535531}
536532
537533// https://fetch.spec.whatwg.org/#body-unusable
@@ -542,73 +538,40 @@ function bodyUnusable (body) {
542538 return body != null && ( body . stream . locked || util . isDisturbed ( body . stream ) )
543539}
544540
545- // https://fetch.spec.whatwg.org/#fully-reading-body-as-promise
546- async function fullyReadBodyAsPromise ( body ) {
547- // 1. Let reader be the result of getting a reader for body’s
548- // stream. If that threw an exception, then return a promise
549- // rejected with that exception.
550- const reader = body . stream . getReader ( )
551-
552- // 2. Return the result of reading all bytes from reader.
553- /** @type {Uint8Array[] } */
554- const bytes = [ ]
555- let size = 0
556-
557- while ( true ) {
558- const { done, value } = await reader . read ( )
559-
560- if ( done ) {
561- break
562- }
563-
564- // https://streams.spec.whatwg.org/#read-loop
565- // If chunk is not a Uint8Array object, reject promise with
566- // a TypeError and abort these steps.
567- if ( ! isUint8Array ( value ) ) {
568- throw new TypeError ( 'Value is not a Uint8Array.' )
569- }
570-
571- bytes . push ( value )
572- size += value . byteLength
573- }
574-
575- return { size, bytes }
576- }
577-
578541/**
579542 * @see https://encoding.spec.whatwg.org/#utf-8-decode
580- * @param {Uint8Array[] } ioQueue
543+ * @param {Buffer } buffer
581544 */
582- function utf8DecodeBytes ( ioQueue ) {
583- if ( ioQueue . length === 0 ) {
545+ function utf8DecodeBytes ( buffer ) {
546+ if ( buffer . length === 0 ) {
584547 return ''
585548 }
586549
587- // 1. Let buffer be the result of peeking three bytes
588- // from ioQueue, converted to a byte sequence.
589- const buffer = ioQueue [ 0 ]
550+ // 1. Let buffer be the result of peeking three bytes from
551+ // ioQueue, converted to a byte sequence.
590552
591553 // 2. If buffer is 0xEF 0xBB 0xBF, then read three
592554 // bytes from ioQueue. (Do nothing with those bytes.)
593555 if ( buffer [ 0 ] === 0xEF && buffer [ 1 ] === 0xBB && buffer [ 2 ] === 0xBF ) {
594- ioQueue [ 0 ] = ioQueue [ 0 ] . subarray ( 3 )
556+ buffer = buffer . subarray ( 3 )
595557 }
596558
597559 // 3. Process a queue with an instance of UTF-8’s
598560 // decoder, ioQueue, output, and "replacement".
599- const decoder = new StringDecoder ( 'utf-8' )
600- let output = ''
601-
602- for ( const chunk of ioQueue ) {
603- output += decoder . write ( chunk )
604- }
605-
606- output += decoder . end ( )
561+ const output = new TextDecoder ( ) . decode ( buffer )
607562
608563 // 4. Return output.
609564 return output
610565}
611566
567+ /**
568+ * @see https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value
569+ * @param {Uint8Array } bytes
570+ */
571+ function parseJSONFromBytes ( bytes ) {
572+ return JSON . parse ( utf8DecodeBytes ( bytes ) )
573+ }
574+
612575/**
613576 * @see https://fetch.spec.whatwg.org/#concept-body-mime-type
614577 * @param {import('./response').Response|import('./request').Request } object
0 commit comments