2016-04-05 10:43:12 -07:00
2015-08-10 12:35:01 -07:00
/ * *
2020-03-13 08:06:25 -07:00
* Body . js
2015-08-10 12:35:01 -07:00
*
2016-04-05 10:43:12 -07:00
* Body interface provides common methods for Request and Response
2015-08-10 12:35:01 -07:00
* /
2020-05-28 14:57:57 -07:00
import Stream , { PassThrough } from 'stream' ;
2020-05-22 21:00:20 -07:00
import { types } from 'util' ;
2018-11-05 01:42:51 -08:00
2020-03-13 08:06:25 -07:00
import Blob from 'fetch-blob' ;
2020-06-10 13:31:35 -07:00
2020-06-10 04:17:35 -07:00
import { FetchError } from './errors/fetch-error.js' ;
import { FetchBaseError } from './errors/base.js' ;
2020-06-10 13:31:35 -07:00
import { formDataIterator , getBoundary , getFormDataLength } from './utils/form-data.js' ;
import { isBlob , isURLSearchParameters , isFormData } from './utils/is.js' ;
2017-07-02 09:32:48 -07:00
2018-01-27 12:28:56 -08:00
const INTERNALS = Symbol ( 'Body internals' ) ;
2015-08-10 12:35:01 -07:00
/ * *
2018-01-27 12:28:56 -08:00
* Body mixin
2015-08-10 12:35:01 -07:00
*
2018-01-27 12:28:56 -08:00
* Ref : https : //fetch.spec.whatwg.org/#body
2017-02-26 14:29:40 -08:00
*
2015-08-10 12:35:01 -07:00
* @ param Stream body Readable stream
* @ param Object opts Response options
* @ return Void
* /
2020-05-23 10:41:14 -07:00
export default class Body {
constructor ( body , {
2020-05-25 04:30:05 -07:00
size = 0
2020-05-23 10:41:14 -07:00
} = { } ) {
2020-06-10 13:31:35 -07:00
let boundary = null ;
2020-05-23 10:41:14 -07:00
if ( body === null ) {
2020-05-25 04:30:05 -07:00
// Body is undefined or null
2020-05-23 10:41:14 -07:00
body = null ;
2020-05-25 08:11:56 -07:00
} else if ( isURLSearchParameters ( body ) ) {
// Body is a URLSearchParams
2020-05-23 10:41:14 -07:00
body = Buffer . from ( body . toString ( ) ) ;
} else if ( isBlob ( body ) ) {
2020-05-25 04:30:05 -07:00
// Body is blob
2020-05-23 10:41:14 -07:00
} else if ( Buffer . isBuffer ( body ) ) {
2020-05-25 04:30:05 -07:00
// Body is Buffer
2020-05-23 10:41:14 -07:00
} else if ( types . isAnyArrayBuffer ( body ) ) {
2020-05-25 04:30:05 -07:00
// Body is ArrayBuffer
2020-05-23 10:41:14 -07:00
body = Buffer . from ( body ) ;
} else if ( ArrayBuffer . isView ( body ) ) {
2020-05-25 04:30:05 -07:00
// Body is ArrayBufferView
2020-05-23 10:41:14 -07:00
body = Buffer . from ( body . buffer , body . byteOffset , body . byteLength ) ;
} else if ( body instanceof Stream ) {
2020-05-25 04:30:05 -07:00
// Body is stream
2020-06-10 13:31:35 -07:00
} else if ( isFormData ( body ) ) {
// Body is an instance of formdata-node
boundary = ` NodeFetchFormDataBoundary ${ getBoundary ( ) } ` ;
body = Stream . Readable . from ( formDataIterator ( body , boundary ) ) ;
2020-05-23 10:41:14 -07:00
} else {
2020-05-25 04:30:05 -07:00
// None of the above
// coerce to string then buffer
2020-05-23 10:41:14 -07:00
body = Buffer . from ( String ( body ) ) ;
}
2020-03-13 08:06:25 -07:00
2020-05-23 10:41:14 -07:00
this [ INTERNALS ] = {
body ,
2020-06-10 13:31:35 -07:00
boundary ,
2020-05-23 10:41:14 -07:00
disturbed : false ,
error : null
} ;
this . size = size ;
if ( body instanceof Stream ) {
2021-07-18 13:15:19 -07:00
body . on ( 'error' , error _ => {
const error = error _ instanceof FetchBaseError ?
error _ :
new FetchError ( ` Invalid response body while trying to fetch ${ this . url } : ${ error _ . message } ` , 'system' , error _ ) ;
2020-05-23 10:41:14 -07:00
this [ INTERNALS ] . error = error ;
} ) ;
}
2018-01-27 11:20:05 -08:00
}
2016-09-24 02:12:18 -07:00
2018-01-27 12:28:56 -08:00
get body ( ) {
return this [ INTERNALS ] . body ;
2020-05-23 10:41:14 -07:00
}
2018-01-27 12:28:56 -08:00
2016-10-10 11:50:04 -07:00
get bodyUsed ( ) {
2018-01-27 12:28:56 -08:00
return this [ INTERNALS ] . disturbed ;
2020-05-23 10:41:14 -07:00
}
2015-08-10 12:35:01 -07:00
2016-10-08 19:41:45 -07:00
/ * *
* Decode response as ArrayBuffer
*
* @ return Promise
* /
2020-05-25 08:11:56 -07:00
async arrayBuffer ( ) {
const { buffer , byteOffset , byteLength } = await consumeBody ( this ) ;
return buffer . slice ( byteOffset , byteOffset + byteLength ) ;
2020-05-23 10:41:14 -07:00
}
2016-10-08 19:41:45 -07:00
2016-10-15 14:21:33 -07:00
/ * *
* Return raw response as Blob
*
* @ return Promise
* /
2020-05-25 08:11:56 -07:00
async blob ( ) {
const ct = ( this . headers && this . headers . get ( 'content-type' ) ) || ( this [ INTERNALS ] . body && this [ INTERNALS ] . body . type ) || '' ;
2020-06-10 12:14:32 -07:00
const buf = await this . buffer ( ) ;
2020-05-25 08:11:56 -07:00
2020-06-10 12:14:32 -07:00
return new Blob ( [ buf ] , {
type : ct
2020-05-25 08:11:56 -07:00
} ) ;
2020-05-23 10:41:14 -07:00
}
2016-10-15 14:21:33 -07:00
2016-10-10 11:50:04 -07:00
/ * *
* Decode response as json
*
* @ return Promise
* /
2020-05-25 08:11:56 -07:00
async json ( ) {
const buffer = await consumeBody ( this ) ;
return JSON . parse ( buffer . toString ( ) ) ;
2020-05-23 10:41:14 -07:00
}
2016-03-19 00:41:25 -07:00
2016-10-10 11:50:04 -07:00
/ * *
* Decode response as text
*
* @ return Promise
* /
2020-05-25 08:11:56 -07:00
async text ( ) {
const buffer = await consumeBody ( this ) ;
return buffer . toString ( ) ;
2020-05-23 10:41:14 -07:00
}
2016-03-19 00:41:25 -07:00
2016-10-10 11:50:04 -07:00
/ * *
* Decode response as buffer ( non - spec api )
*
* @ return Promise
* /
buffer ( ) {
2020-05-25 08:11:56 -07:00
return consumeBody ( this ) ;
2018-11-15 06:50:32 -08:00
}
2020-05-23 10:41:14 -07:00
}
2017-02-26 14:29:40 -08:00
2018-01-27 12:28:56 -08:00
// In browsers, all properties are enumerable.
Object . defineProperties ( Body . prototype , {
2020-03-13 08:06:25 -07:00
body : { enumerable : true } ,
bodyUsed : { enumerable : true } ,
arrayBuffer : { enumerable : true } ,
blob : { enumerable : true } ,
json : { enumerable : true } ,
text : { enumerable : true }
2018-01-27 12:28:56 -08:00
} ) ;
2017-02-26 14:29:40 -08:00
/ * *
2018-01-27 12:28:56 -08:00
* Consume and convert an entire Body to a Buffer .
*
* Ref : https : //fetch.spec.whatwg.org/#concept-body-consume-body
2017-02-26 14:29:40 -08:00
*
2020-06-10 13:31:35 -07:00
* @ return Promise
2017-02-26 14:29:40 -08:00
* /
2020-05-28 14:57:57 -07:00
async function consumeBody ( data ) {
2020-05-25 08:11:56 -07:00
if ( data [ INTERNALS ] . disturbed ) {
2020-05-28 14:57:57 -07:00
throw new TypeError ( ` body used already for: ${ data . url } ` ) ;
2016-10-12 21:47:36 -07:00
}
2020-05-25 08:11:56 -07:00
data [ INTERNALS ] . disturbed = true ;
2016-03-19 00:41:25 -07:00
2020-05-25 08:11:56 -07:00
if ( data [ INTERNALS ] . error ) {
2020-05-28 14:57:57 -07:00
throw data [ INTERNALS ] . error ;
2018-01-27 11:20:05 -08:00
}
2020-05-25 08:11:56 -07:00
let { body } = data ;
2019-04-30 20:44:27 -07:00
2020-03-13 08:06:25 -07:00
// Body is null
2019-04-30 20:44:27 -07:00
if ( body === null ) {
2020-05-28 14:57:57 -07:00
return Buffer . alloc ( 0 ) ;
2017-02-26 14:29:40 -08:00
}
2016-03-19 00:41:25 -07:00
2020-03-13 08:06:25 -07:00
// Body is blob
2019-04-30 20:44:27 -07:00
if ( isBlob ( body ) ) {
2021-07-18 13:15:19 -07:00
body = Stream . Readable . from ( body . stream ( ) ) ;
2019-04-30 20:44:27 -07:00
}
2020-03-13 08:06:25 -07:00
// Body is buffer
2019-04-30 20:44:27 -07:00
if ( Buffer . isBuffer ( body ) ) {
2020-05-28 14:57:57 -07:00
return body ;
2017-02-26 14:29:40 -08:00
}
2016-12-05 20:25:13 -08:00
2020-05-25 08:11:56 -07:00
/* c8 ignore next 3 */
2019-04-30 20:44:27 -07:00
if ( ! ( body instanceof Stream ) ) {
2020-05-28 14:57:57 -07:00
return Buffer . alloc ( 0 ) ;
2017-02-26 14:29:40 -08:00
}
2016-03-19 00:41:25 -07:00
2020-03-13 08:06:25 -07:00
// Body is stream
2017-02-26 14:29:40 -08:00
// get ready to actually consume the body
2020-03-13 08:06:25 -07:00
const accum = [ ] ;
2017-02-26 14:29:40 -08:00
let accumBytes = 0 ;
2020-05-28 14:57:57 -07:00
try {
for await ( const chunk of body ) {
if ( data . size > 0 && accumBytes + chunk . length > data . size ) {
2021-07-18 13:15:19 -07:00
const error = new FetchError ( ` content size at ${ data . url } over limit: ${ data . size } ` , 'max-size' ) ;
body . destroy ( error ) ;
throw error ;
2017-02-26 14:29:40 -08:00
}
accumBytes += chunk . length ;
accum . push ( chunk ) ;
2020-05-28 14:57:57 -07:00
}
} catch ( error ) {
2021-07-18 13:15:19 -07:00
const error _ = error instanceof FetchBaseError ? error : new FetchError ( ` Invalid response body while trying to fetch ${ data . url } : ${ error . message } ` , 'system' , error ) ;
throw error _ ;
2020-05-28 14:57:57 -07:00
}
2016-03-19 00:41:25 -07:00
2020-05-28 14:57:57 -07:00
if ( body . readableEnded === true || body . _readableState . ended === true ) {
try {
2020-06-10 04:16:51 -07:00
if ( accum . every ( c => typeof c === 'string' ) ) {
return Buffer . from ( accum . join ( '' ) ) ;
}
2020-05-28 14:57:57 -07:00
return Buffer . concat ( accum , accumBytes ) ;
} catch ( error ) {
throw new FetchError ( ` Could not create Buffer from response body for ${ data . url } : ${ error . message } ` , 'system' , error ) ;
}
} else {
throw new FetchError ( ` Premature close of server response while trying to fetch ${ data . url } ` ) ;
}
}
2016-03-19 00:41:25 -07:00
2016-03-19 03:06:33 -07:00
/ * *
* Clone body given Res / Req instance
*
2020-03-13 08:06:25 -07:00
* @ param Mixed instance Response or Request instance
* @ param String highWaterMark highWaterMark for both PassThrough body streams
2016-03-19 03:06:33 -07:00
* @ return Mixed
* /
2020-05-25 08:11:56 -07:00
export const clone = ( instance , highWaterMark ) => {
2020-03-13 08:06:25 -07:00
let p1 ;
let p2 ;
let { body } = instance ;
2016-03-19 03:06:33 -07:00
2020-03-13 08:06:25 -07:00
// Don't allow cloning a used body
2016-03-19 03:06:33 -07:00
if ( instance . bodyUsed ) {
throw new Error ( 'cannot clone body after it is used' ) ;
}
2020-03-13 08:06:25 -07:00
// Check that body is a stream and not form-data object
2016-03-23 00:02:04 -07:00
// note: we can't clone the form-data object without having it as a dependency
2017-02-26 18:29:57 -08:00
if ( ( body instanceof Stream ) && ( typeof body . getBoundary !== 'function' ) ) {
2020-03-13 08:06:25 -07:00
// Tee instance body
p1 = new PassThrough ( { highWaterMark } ) ;
p2 = new PassThrough ( { highWaterMark } ) ;
2016-05-25 10:01:56 -07:00
body . pipe ( p1 ) ;
body . pipe ( p2 ) ;
2020-03-13 08:06:25 -07:00
// Set instance body to teed body and return the other teed body
2018-01-27 12:28:56 -08:00
instance [ INTERNALS ] . body = p1 ;
2016-05-25 10:01:56 -07:00
body = p2 ;
2016-03-19 03:06:33 -07:00
}
return body ;
2020-05-25 08:11:56 -07:00
} ;
2016-03-19 03:06:33 -07:00
2016-11-23 12:42:24 -08:00
/ * *
* Performs the operation "extract a `Content-Type` value from |object|" as
* specified in the specification :
* https : //fetch.spec.whatwg.org/#concept-bodyinit-extract
*
2018-01-27 12:28:56 -08:00
* This function assumes that instance . body is present .
2016-11-23 12:42:24 -08:00
*
2020-03-13 08:06:25 -07:00
* @ param { any } body Any options . body input
* @ returns { string | null }
2016-11-23 12:42:24 -08:00
* /
2020-06-10 13:31:35 -07:00
export const extractContentType = ( body , request ) => {
2020-03-13 08:06:25 -07:00
// Body is null or undefined
2016-12-05 20:25:13 -08:00
if ( body === null ) {
return null ;
2020-03-13 08:06:25 -07:00
}
// Body is string
if ( typeof body === 'string' ) {
2016-11-23 12:42:24 -08:00
return 'text/plain;charset=UTF-8' ;
2020-03-13 08:06:25 -07:00
}
// Body is a URLSearchParams
2020-05-25 08:11:56 -07:00
if ( isURLSearchParameters ( body ) ) {
2017-06-11 22:29:50 -07:00
return 'application/x-www-form-urlencoded;charset=UTF-8' ;
2020-03-13 08:06:25 -07:00
}
// Body is blob
if ( isBlob ( body ) ) {
2016-12-05 20:25:13 -08:00
return body . type || null ;
2020-03-13 08:06:25 -07:00
}
// Body is a Buffer (Buffer, ArrayBuffer or ArrayBufferView)
2020-05-22 21:00:20 -07:00
if ( Buffer . isBuffer ( body ) || types . isAnyArrayBuffer ( body ) || ArrayBuffer . isView ( body ) ) {
2018-03-04 16:40:39 -08:00
return null ;
2020-03-13 08:06:25 -07:00
}
// Detect form data input from form-data module
if ( body && typeof body . getBoundary === 'function' ) {
2016-12-05 20:25:13 -08:00
return ` multipart/form-data;boundary= ${ body . getBoundary ( ) } ` ;
2020-03-13 08:06:25 -07:00
}
2020-06-10 13:31:35 -07:00
if ( isFormData ( body ) ) {
return ` multipart/form-data; boundary= ${ request [ INTERNALS ] . boundary } ` ;
}
2020-03-13 08:06:25 -07:00
// Body is stream - can't really do much about this
if ( body instanceof Stream ) {
2016-12-05 20:25:13 -08:00
return null ;
2016-11-23 12:42:24 -08:00
}
2020-03-13 08:06:25 -07:00
// Body constructor defaults other things to string
return 'text/plain;charset=UTF-8' ;
2020-05-25 08:11:56 -07:00
} ;
2016-11-23 12:42:24 -08:00
2018-01-27 11:49:12 -08:00
/ * *
* The Fetch Standard treats this as if "total bytes" is a property on the body .
* For us , we have to explicitly get it with a function .
*
* ref : https : //fetch.spec.whatwg.org/#concept-body-total-bytes
*
2020-03-13 08:06:25 -07:00
* @ param { any } obj . body Body object from the Body instance .
* @ returns { number | null }
2018-01-27 11:49:12 -08:00
* /
2020-06-10 13:31:35 -07:00
export const getTotalBytes = request => {
const { body } = request ;
2020-03-13 08:06:25 -07:00
// Body is null or undefined
2016-12-05 20:25:13 -08:00
if ( body === null ) {
return 0 ;
2020-03-13 08:06:25 -07:00
}
// Body is Blob
if ( isBlob ( body ) ) {
2019-04-30 20:44:27 -07:00
return body . size ;
2020-03-13 08:06:25 -07:00
}
// Body is Buffer
if ( Buffer . isBuffer ( body ) ) {
2016-12-05 20:30:00 -08:00
return body . length ;
2016-12-05 20:25:13 -08:00
}
2020-03-13 08:06:25 -07:00
// Detect form data input from form-data module
if ( body && typeof body . getLengthSync === 'function' ) {
return body . hasKnownLength && body . hasKnownLength ( ) ? body . getLengthSync ( ) : null ;
}
2020-06-10 13:31:35 -07:00
// Body is a spec-compliant form-data
if ( isFormData ( body ) ) {
return getFormDataLength ( request [ INTERNALS ] . boundary ) ;
}
2020-03-13 08:06:25 -07:00
// Body is stream
return null ;
2020-05-25 08:11:56 -07:00
} ;
2016-12-05 20:25:13 -08:00
2018-01-27 11:49:12 -08:00
/ * *
* Write a Body to a Node . js WritableStream ( e . g . http . Request ) object .
*
2020-03-13 08:06:25 -07:00
* @ param { Stream . Writable } dest The stream to write to .
* @ param obj . body Body object from the Body instance .
* @ returns { void }
2018-01-27 11:49:12 -08:00
* /
2020-05-25 08:11:56 -07:00
export const writeToStream = ( dest , { body } ) => {
2016-12-05 20:25:13 -08:00
if ( body === null ) {
2020-03-13 08:06:25 -07:00
// Body is null
2016-12-05 20:25:13 -08:00
dest . end ( ) ;
2019-04-30 20:44:27 -07:00
} else if ( isBlob ( body ) ) {
2020-03-13 08:06:25 -07:00
// Body is Blob
2021-07-18 13:15:19 -07:00
Stream . Readable . from ( body . stream ( ) ) . pipe ( dest ) ;
2016-12-05 20:25:13 -08:00
} else if ( Buffer . isBuffer ( body ) ) {
2020-03-13 08:06:25 -07:00
// Body is buffer
2016-12-05 20:25:13 -08:00
dest . write ( body ) ;
2020-03-13 08:06:25 -07:00
dest . end ( ) ;
2016-12-05 21:09:54 -08:00
} else {
2020-03-13 08:06:25 -07:00
// Body is stream
2016-12-05 20:25:13 -08:00
body . pipe ( dest ) ;
2016-11-23 13:39:35 -08:00
}
2020-05-25 08:11:56 -07:00
} ;