chore: update deps

This commit is contained in:
Rim
2025-03-29 16:05:52 -04:00
parent a177b9bb4c
commit 505a26c84d
1489 changed files with 27814 additions and 146817 deletions

View File

@ -1,3 +1,5 @@
'use strict'
const { addAbortListener } = require('../core/util')
const { RequestAbortedError } = require('../core/errors')
@ -6,13 +8,16 @@ const kSignal = Symbol('kSignal')
function abort (self) {
if (self.abort) {
self.abort()
self.abort(self[kSignal]?.reason)
} else {
self.onError(new RequestAbortedError())
self.reason = self[kSignal]?.reason ?? new RequestAbortedError()
}
removeSignal(self)
}
function addSignal (self, signal) {
self.reason = null
self[kSignal] = null
self[kListener] = null

View File

@ -1,7 +1,8 @@
'use strict'
const { AsyncResource } = require('async_hooks')
const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
const assert = require('node:assert')
const { AsyncResource } = require('node:async_hooks')
const { InvalidArgumentError, SocketError } = require('../core/errors')
const util = require('../core/util')
const { addSignal, removeSignal } = require('./abort-signal')
@ -32,10 +33,13 @@ class ConnectHandler extends AsyncResource {
}
onConnect (abort, context) {
if (!this.callback) {
throw new RequestAbortedError()
if (this.reason) {
abort(this.reason)
return
}
assert(this.callback)
this.abort = abort
this.context = context
}
@ -91,12 +95,14 @@ function connect (opts, callback) {
try {
const connectHandler = new ConnectHandler(opts, callback)
this.dispatch({ ...opts, method: 'CONNECT' }, connectHandler)
const connectOptions = { ...opts, method: 'CONNECT' }
this.dispatch(connectOptions, connectHandler)
} catch (err) {
if (typeof callback !== 'function') {
throw err
}
const opaque = opts && opts.opaque
const opaque = opts?.opaque
queueMicrotask(() => callback(err, { opaque }))
}
}

View File

@ -4,16 +4,18 @@ const {
Readable,
Duplex,
PassThrough
} = require('stream')
} = require('node:stream')
const assert = require('node:assert')
const { AsyncResource } = require('node:async_hooks')
const {
InvalidArgumentError,
InvalidReturnValueError,
RequestAbortedError
} = require('../core/errors')
const util = require('../core/util')
const { AsyncResource } = require('async_hooks')
const { addSignal, removeSignal } = require('./abort-signal')
const assert = require('assert')
function noop () {}
const kResume = Symbol('resume')
@ -92,7 +94,7 @@ class PipelineHandler extends AsyncResource {
this.context = null
this.onInfo = onInfo || null
this.req = new PipelineRequest().on('error', util.nop)
this.req = new PipelineRequest().on('error', noop)
this.ret = new Duplex({
readableObjectMode: opts.objectMode,
@ -100,7 +102,7 @@ class PipelineHandler extends AsyncResource {
read: () => {
const { body } = this
if (body && body.resume) {
if (body?.resume) {
body.resume()
}
},
@ -145,14 +147,15 @@ class PipelineHandler extends AsyncResource {
}
onConnect (abort, context) {
const { ret, res } = this
const { res } = this
if (this.reason) {
abort(this.reason)
return
}
assert(!res, 'pipeline cannot be retried')
if (ret.destroyed) {
throw new RequestAbortedError()
}
this.abort = abort
this.context = context
}
@ -182,7 +185,7 @@ class PipelineHandler extends AsyncResource {
context
})
} catch (err) {
this.res.on('error', util.nop)
this.res.on('error', noop)
throw err
}

View File

@ -1,14 +1,12 @@
'use strict'
const Readable = require('./readable')
const {
InvalidArgumentError,
RequestAbortedError
} = require('../core/errors')
const assert = require('node:assert')
const { AsyncResource } = require('node:async_hooks')
const { Readable } = require('./readable')
const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
const util = require('../core/util')
const { getResolveErrorBodyCallback } = require('./util')
const { AsyncResource } = require('async_hooks')
const { addSignal, removeSignal } = require('./abort-signal')
function noop () {}
class RequestHandler extends AsyncResource {
constructor (opts, callback) {
@ -16,7 +14,7 @@ class RequestHandler extends AsyncResource {
throw new InvalidArgumentError('invalid opts')
}
const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError, highWaterMark } = opts
const { signal, method, opaque, body, onInfo, responseHeaders, highWaterMark } = opts
try {
if (typeof callback !== 'function') {
@ -42,11 +40,12 @@ class RequestHandler extends AsyncResource {
super('UNDICI_REQUEST')
} catch (err) {
if (util.isStream(body)) {
util.destroy(body.on('error', util.nop), err)
util.destroy(body.on('error', noop), err)
}
throw err
}
this.method = method
this.responseHeaders = responseHeaders || null
this.opaque = opaque || null
this.callback = callback
@ -56,23 +55,32 @@ class RequestHandler extends AsyncResource {
this.trailers = {}
this.context = null
this.onInfo = onInfo || null
this.throwOnError = throwOnError
this.highWaterMark = highWaterMark
this.reason = null
this.removeAbortListener = null
if (util.isStream(body)) {
body.on('error', (err) => {
this.onError(err)
if (signal?.aborted) {
this.reason = signal.reason ?? new RequestAbortedError()
} else if (signal) {
this.removeAbortListener = util.addAbortListener(signal, () => {
this.reason = signal.reason ?? new RequestAbortedError()
if (this.res) {
util.destroy(this.res.on('error', noop), this.reason)
} else if (this.abort) {
this.abort(this.reason)
}
})
}
addSignal(this, signal)
}
onConnect (abort, context) {
if (!this.callback) {
throw new RequestAbortedError()
if (this.reason) {
abort(this.reason)
return
}
assert(this.callback)
this.abort = abort
this.context = context
}
@ -91,48 +99,48 @@ class RequestHandler extends AsyncResource {
const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers
const contentType = parsedHeaders['content-type']
const body = new Readable({ resume, abort, contentType, highWaterMark })
const contentLength = parsedHeaders['content-length']
const res = new Readable({
resume,
abort,
contentType,
contentLength: this.method !== 'HEAD' && contentLength
? Number(contentLength)
: null,
highWaterMark
})
if (this.removeAbortListener) {
res.on('close', this.removeAbortListener)
this.removeAbortListener = null
}
this.callback = null
this.res = body
this.res = res
if (callback !== null) {
if (this.throwOnError && statusCode >= 400) {
this.runInAsyncScope(getResolveErrorBodyCallback, null,
{ callback, body, contentType, statusCode, statusMessage, headers }
)
} else {
this.runInAsyncScope(callback, null, null, {
statusCode,
headers,
trailers: this.trailers,
opaque,
body,
context
})
}
this.runInAsyncScope(callback, null, null, {
statusCode,
headers,
trailers: this.trailers,
opaque,
body: res,
context
})
}
}
onData (chunk) {
const { res } = this
return res.push(chunk)
return this.res.push(chunk)
}
onComplete (trailers) {
const { res } = this
removeSignal(this)
util.parseHeaders(trailers, this.trailers)
res.push(null)
this.res.push(null)
}
onError (err) {
const { res, callback, body, opaque } = this
removeSignal(this)
if (callback) {
// TODO: Does this need queueMicrotask?
this.callback = null
@ -145,13 +153,22 @@ class RequestHandler extends AsyncResource {
this.res = null
// Ensure all queued handlers are invoked before destroying res.
queueMicrotask(() => {
util.destroy(res, err)
util.destroy(res.on('error', noop), err)
})
}
if (body) {
this.body = null
util.destroy(body, err)
if (util.isStream(body)) {
body.on('error', noop)
util.destroy(body, err)
}
}
if (this.removeAbortListener) {
this.removeAbortListener()
this.removeAbortListener = null
}
}
}
@ -166,12 +183,14 @@ function request (opts, callback) {
}
try {
this.dispatch(opts, new RequestHandler(opts, callback))
const handler = new RequestHandler(opts, callback)
this.dispatch(opts, handler)
} catch (err) {
if (typeof callback !== 'function') {
throw err
}
const opaque = opts && opts.opaque
const opaque = opts?.opaque
queueMicrotask(() => callback(err, { opaque }))
}
}

View File

@ -1,23 +1,21 @@
'use strict'
const { finished, PassThrough } = require('stream')
const {
InvalidArgumentError,
InvalidReturnValueError,
RequestAbortedError
} = require('../core/errors')
const assert = require('node:assert')
const { finished } = require('node:stream')
const { AsyncResource } = require('node:async_hooks')
const { InvalidArgumentError, InvalidReturnValueError } = require('../core/errors')
const util = require('../core/util')
const { getResolveErrorBodyCallback } = require('./util')
const { AsyncResource } = require('async_hooks')
const { addSignal, removeSignal } = require('./abort-signal')
function noop () {}
class StreamHandler extends AsyncResource {
constructor (opts, factory, callback) {
if (!opts || typeof opts !== 'object') {
throw new InvalidArgumentError('invalid opts')
}
const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
try {
if (typeof callback !== 'function') {
@ -43,7 +41,7 @@ class StreamHandler extends AsyncResource {
super('UNDICI_STREAM')
} catch (err) {
if (util.isStream(body)) {
util.destroy(body.on('error', util.nop), err)
util.destroy(body.on('error', noop), err)
}
throw err
}
@ -58,7 +56,6 @@ class StreamHandler extends AsyncResource {
this.trailers = null
this.body = body
this.onInfo = onInfo || null
this.throwOnError = throwOnError || false
if (util.isStream(body)) {
body.on('error', (err) => {
@ -70,16 +67,19 @@ class StreamHandler extends AsyncResource {
}
onConnect (abort, context) {
if (!this.callback) {
throw new RequestAbortedError()
if (this.reason) {
abort(this.reason)
return
}
assert(this.callback)
this.abort = abort
this.context = context
}
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
const { factory, opaque, context, callback, responseHeaders } = this
const { factory, opaque, context, responseHeaders } = this
const headers = responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
@ -92,55 +92,42 @@ class StreamHandler extends AsyncResource {
this.factory = null
let res
if (factory === null) {
return
}
if (this.throwOnError && statusCode >= 400) {
const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers
const contentType = parsedHeaders['content-type']
res = new PassThrough()
const res = this.runInAsyncScope(factory, null, {
statusCode,
headers,
opaque,
context
})
if (
!res ||
typeof res.write !== 'function' ||
typeof res.end !== 'function' ||
typeof res.on !== 'function'
) {
throw new InvalidReturnValueError('expected Writable')
}
// TODO: Avoid finished. It registers an unnecessary amount of listeners.
finished(res, { readable: false }, (err) => {
const { callback, res, opaque, trailers, abort } = this
this.res = null
if (err || !res.readable) {
util.destroy(res, err)
}
this.callback = null
this.runInAsyncScope(getResolveErrorBodyCallback, null,
{ callback, body: res, contentType, statusCode, statusMessage, headers }
)
} else {
if (factory === null) {
return
this.runInAsyncScope(callback, null, err || null, { opaque, trailers })
if (err) {
abort()
}
res = this.runInAsyncScope(factory, null, {
statusCode,
headers,
opaque,
context
})
if (
!res ||
typeof res.write !== 'function' ||
typeof res.end !== 'function' ||
typeof res.on !== 'function'
) {
throw new InvalidReturnValueError('expected Writable')
}
// TODO: Avoid finished. It registers an unnecessary amount of listeners.
finished(res, { readable: false }, (err) => {
const { callback, res, opaque, trailers, abort } = this
this.res = null
if (err || !res.readable) {
util.destroy(res, err)
}
this.callback = null
this.runInAsyncScope(callback, null, err || null, { opaque, trailers })
if (err) {
abort()
}
})
}
})
res.on('drain', resume)
@ -148,7 +135,7 @@ class StreamHandler extends AsyncResource {
const needDrain = res.writableNeedDrain !== undefined
? res.writableNeedDrain
: res._writableState && res._writableState.needDrain
: res._writableState?.needDrain
return needDrain !== true
}
@ -207,12 +194,14 @@ function stream (opts, factory, callback) {
}
try {
this.dispatch(opts, new StreamHandler(opts, factory, callback))
const handler = new StreamHandler(opts, factory, callback)
this.dispatch(opts, handler)
} catch (err) {
if (typeof callback !== 'function') {
throw err
}
const opaque = opts && opts.opaque
const opaque = opts?.opaque
queueMicrotask(() => callback(err, { opaque }))
}
}

View File

@ -1,10 +1,10 @@
'use strict'
const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
const { AsyncResource } = require('async_hooks')
const { InvalidArgumentError, SocketError } = require('../core/errors')
const { AsyncResource } = require('node:async_hooks')
const assert = require('node:assert')
const util = require('../core/util')
const { addSignal, removeSignal } = require('./abort-signal')
const assert = require('assert')
class UpgradeHandler extends AsyncResource {
constructor (opts, callback) {
@ -34,10 +34,13 @@ class UpgradeHandler extends AsyncResource {
}
onConnect (abort, context) {
if (!this.callback) {
throw new RequestAbortedError()
if (this.reason) {
abort(this.reason)
return
}
assert(this.callback)
this.abort = abort
this.context = null
}
@ -47,9 +50,9 @@ class UpgradeHandler extends AsyncResource {
}
onUpgrade (statusCode, rawHeaders, socket) {
const { callback, opaque, context } = this
assert(statusCode === 101)
assert.strictEqual(statusCode, 101)
const { callback, opaque, context } = this
removeSignal(this)
@ -88,16 +91,18 @@ function upgrade (opts, callback) {
try {
const upgradeHandler = new UpgradeHandler(opts, callback)
this.dispatch({
const upgradeOpts = {
...opts,
method: opts.method || 'GET',
upgrade: opts.protocol || 'Websocket'
}, upgradeHandler)
}
this.dispatch(upgradeOpts, upgradeHandler)
} catch (err) {
if (typeof callback !== 'function') {
throw err
}
const opaque = opts && opts.opaque
const opaque = opts?.opaque
queueMicrotask(() => callback(err, { opaque }))
}
}

View File

@ -2,27 +2,42 @@
'use strict'
const assert = require('assert')
const { Readable } = require('stream')
const { RequestAbortedError, NotSupportedError, InvalidArgumentError } = require('../core/errors')
const assert = require('node:assert')
const { Readable } = require('node:stream')
const { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = require('../core/errors')
const util = require('../core/util')
const { ReadableStreamFrom, toUSVString } = require('../core/util')
let Blob
const { ReadableStreamFrom } = require('../core/util')
const kConsume = Symbol('kConsume')
const kReading = Symbol('kReading')
const kBody = Symbol('kBody')
const kAbort = Symbol('abort')
const kAbort = Symbol('kAbort')
const kContentType = Symbol('kContentType')
const kContentLength = Symbol('kContentLength')
const kUsed = Symbol('kUsed')
const kBytesRead = Symbol('kBytesRead')
const noop = () => {}
module.exports = class BodyReadable extends Readable {
/**
* @class
* @extends {Readable}
* @see https://fetch.spec.whatwg.org/#body
*/
class BodyReadable extends Readable {
/**
* @param {object} opts
* @param {(this: Readable, size: number) => void} opts.resume
* @param {() => (void | null)} opts.abort
* @param {string} [opts.contentType = '']
* @param {number} [opts.contentLength]
* @param {number} [opts.highWaterMark = 64 * 1024]
*/
constructor ({
resume,
abort,
contentType = '',
contentLength,
highWaterMark = 64 * 1024 // Same as nodejs fs streams.
}) {
super({
@ -34,9 +49,19 @@ module.exports = class BodyReadable extends Readable {
this._readableState.dataEmitted = false
this[kAbort] = abort
/**
* @type {Consume | null}
*/
this[kConsume] = null
this[kBytesRead] = 0
/**
* @type {ReadableStream|null}
*/
this[kBody] = null
this[kUsed] = false
this[kContentType] = contentType
this[kContentLength] = Number.isFinite(contentLength) ? contentLength : null
// Is stream being consumed through Readable API?
// This is an optimization so that we avoid checking
@ -45,12 +70,12 @@ module.exports = class BodyReadable extends Readable {
this[kReading] = false
}
destroy (err) {
if (this.destroyed) {
// Node < 16
return this
}
/**
* @param {Error|null} err
* @param {(error:(Error|null)) => void} callback
* @returns {void}
*/
_destroy (err, callback) {
if (!err && !this._readableState.endEmitted) {
err = new RequestAbortedError()
}
@ -59,34 +84,49 @@ module.exports = class BodyReadable extends Readable {
this[kAbort]()
}
return super.destroy(err)
}
emit (ev, ...args) {
if (ev === 'data') {
// Node < 16.7
this._readableState.dataEmitted = true
} else if (ev === 'error') {
// Node < 16
this._readableState.errorEmitted = true
// Workaround for Node "bug". If the stream is destroyed in same
// tick as it is created, then a user who is waiting for a
// promise (i.e micro tick) for installing an 'error' listener will
// never get a chance and will always encounter an unhandled exception.
if (!this[kUsed]) {
setImmediate(() => {
callback(err)
})
} else {
callback(err)
}
return super.emit(ev, ...args)
}
on (ev, ...args) {
if (ev === 'data' || ev === 'readable') {
/**
* @param {string} event
* @param {(...args: any[]) => void} listener
* @returns {this}
*/
on (event, listener) {
if (event === 'data' || event === 'readable') {
this[kReading] = true
this[kUsed] = true
}
return super.on(ev, ...args)
return super.on(event, listener)
}
addListener (ev, ...args) {
return this.on(ev, ...args)
/**
* @param {string} event
* @param {(...args: any[]) => void} listener
* @returns {this}
*/
addListener (event, listener) {
return this.on(event, listener)
}
off (ev, ...args) {
const ret = super.off(ev, ...args)
if (ev === 'data' || ev === 'readable') {
/**
* @param {string|symbol} event
* @param {(...args: any[]) => void} listener
* @returns {this}
*/
off (event, listener) {
const ret = super.off(event, listener)
if (event === 'data' || event === 'readable') {
this[kReading] = (
this.listenerCount('data') > 0 ||
this.listenerCount('readable') > 0
@ -95,50 +135,107 @@ module.exports = class BodyReadable extends Readable {
return ret
}
removeListener (ev, ...args) {
return this.off(ev, ...args)
/**
* @param {string|symbol} event
* @param {(...args: any[]) => void} listener
* @returns {this}
*/
removeListener (event, listener) {
return this.off(event, listener)
}
/**
* @param {Buffer|null} chunk
* @returns {boolean}
*/
push (chunk) {
if (this[kConsume] && chunk !== null && this.readableLength === 0) {
this[kBytesRead] += chunk ? chunk.length : 0
if (this[kConsume] && chunk !== null) {
consumePush(this[kConsume], chunk)
return this[kReading] ? super.push(chunk) : true
}
return super.push(chunk)
}
// https://fetch.spec.whatwg.org/#dom-body-text
async text () {
/**
* Consumes and returns the body as a string.
*
* @see https://fetch.spec.whatwg.org/#dom-body-text
* @returns {Promise<string>}
*/
text () {
return consume(this, 'text')
}
// https://fetch.spec.whatwg.org/#dom-body-json
async json () {
/**
* Consumes and returns the body as a JavaScript Object.
*
* @see https://fetch.spec.whatwg.org/#dom-body-json
* @returns {Promise<unknown>}
*/
json () {
return consume(this, 'json')
}
// https://fetch.spec.whatwg.org/#dom-body-blob
async blob () {
/**
* Consumes and returns the body as a Blob
*
* @see https://fetch.spec.whatwg.org/#dom-body-blob
* @returns {Promise<Blob>}
*/
blob () {
return consume(this, 'blob')
}
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
async arrayBuffer () {
/**
* Consumes and returns the body as an Uint8Array.
*
* @see https://fetch.spec.whatwg.org/#dom-body-bytes
* @returns {Promise<Uint8Array>}
*/
bytes () {
return consume(this, 'bytes')
}
/**
* Consumes and returns the body as an ArrayBuffer.
*
* @see https://fetch.spec.whatwg.org/#dom-body-arraybuffer
* @returns {Promise<ArrayBuffer>}
*/
arrayBuffer () {
return consume(this, 'arrayBuffer')
}
// https://fetch.spec.whatwg.org/#dom-body-formdata
/**
* Not implemented
*
* @see https://fetch.spec.whatwg.org/#dom-body-formdata
* @throws {NotSupportedError}
*/
async formData () {
// TODO: Implement.
throw new NotSupportedError()
}
// https://fetch.spec.whatwg.org/#dom-body-bodyused
/**
* Returns true if the body is not null and the body has been consumed.
* Otherwise, returns false.
*
* @see https://fetch.spec.whatwg.org/#dom-body-bodyused
* @readonly
* @returns {boolean}
*/
get bodyUsed () {
return util.isDisturbed(this)
}
// https://fetch.spec.whatwg.org/#dom-body-body
/**
* @see https://fetch.spec.whatwg.org/#dom-body-body
* @readonly
* @returns {ReadableStream}
*/
get body () {
if (!this[kBody]) {
this[kBody] = ReadableStreamFrom(this)
@ -151,95 +248,161 @@ module.exports = class BodyReadable extends Readable {
return this[kBody]
}
dump (opts) {
let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144
const signal = opts && opts.signal
/**
* Dumps the response body by reading `limit` number of bytes.
* @param {object} opts
* @param {number} [opts.limit = 131072] Number of bytes to read.
* @param {AbortSignal} [opts.signal] An AbortSignal to cancel the dump.
* @returns {Promise<null>}
*/
async dump (opts) {
const signal = opts?.signal
if (signal) {
try {
if (typeof signal !== 'object' || !('aborted' in signal)) {
throw new InvalidArgumentError('signal must be an AbortSignal')
}
util.throwIfAborted(signal)
} catch (err) {
return Promise.reject(err)
if (signal != null && (typeof signal !== 'object' || !('aborted' in signal))) {
throw new InvalidArgumentError('signal must be an AbortSignal')
}
const limit = opts?.limit && Number.isFinite(opts.limit)
? opts.limit
: 128 * 1024
signal?.throwIfAborted()
if (this._readableState.closeEmitted) {
return null
}
return await new Promise((resolve, reject) => {
if (
(this[kContentLength] && (this[kContentLength] > limit)) ||
this[kBytesRead] > limit
) {
this.destroy(new AbortError())
}
}
if (this.closed) {
return Promise.resolve(null)
}
return new Promise((resolve, reject) => {
const signalListenerCleanup = signal
? util.addAbortListener(signal, () => {
this.destroy()
})
: noop
if (signal) {
const onAbort = () => {
this.destroy(signal.reason ?? new AbortError())
}
signal.addEventListener('abort', onAbort)
this
.on('close', function () {
signal.removeEventListener('abort', onAbort)
if (signal.aborted) {
reject(signal.reason ?? new AbortError())
} else {
resolve(null)
}
})
} else {
this.on('close', resolve)
}
this
.on('close', function () {
signalListenerCleanup()
if (signal && signal.aborted) {
reject(signal.reason || Object.assign(new Error('The operation was aborted'), { name: 'AbortError' }))
} else {
resolve(null)
}
})
.on('error', noop)
.on('data', function (chunk) {
limit -= chunk.length
if (limit <= 0) {
.on('data', () => {
if (this[kBytesRead] > limit) {
this.destroy()
}
})
.resume()
})
}
}
// https://streams.spec.whatwg.org/#readablestream-locked
function isLocked (self) {
// Consume is an implicit lock.
return (self[kBody] && self[kBody].locked === true) || self[kConsume]
}
// https://fetch.spec.whatwg.org/#body-unusable
function isUnusable (self) {
return util.isDisturbed(self) || isLocked(self)
}
async function consume (stream, type) {
if (isUnusable(stream)) {
throw new TypeError('unusable')
/**
* @param {BufferEncoding} encoding
* @returns {this}
*/
setEncoding (encoding) {
if (Buffer.isEncoding(encoding)) {
this._readableState.encoding = encoding
}
return this
}
}
/**
* @see https://streams.spec.whatwg.org/#readablestream-locked
* @param {BodyReadable} bodyReadable
* @returns {boolean}
*/
function isLocked (bodyReadable) {
// Consume is an implicit lock.
return bodyReadable[kBody]?.locked === true || bodyReadable[kConsume] !== null
}
/**
* @see https://fetch.spec.whatwg.org/#body-unusable
* @param {BodyReadable} bodyReadable
* @returns {boolean}
*/
function isUnusable (bodyReadable) {
return util.isDisturbed(bodyReadable) || isLocked(bodyReadable)
}
/**
* @typedef {object} Consume
* @property {string} type
* @property {BodyReadable} stream
* @property {((value?: any) => void)} resolve
* @property {((err: Error) => void)} reject
* @property {number} length
* @property {Buffer[]} body
*/
/**
* @param {BodyReadable} stream
* @param {string} type
* @returns {Promise<any>}
*/
function consume (stream, type) {
assert(!stream[kConsume])
return new Promise((resolve, reject) => {
stream[kConsume] = {
type,
stream,
resolve,
reject,
length: 0,
body: []
}
stream
.on('error', function (err) {
consumeFinish(this[kConsume], err)
})
.on('close', function () {
if (this[kConsume].body !== null) {
consumeFinish(this[kConsume], new RequestAbortedError())
if (isUnusable(stream)) {
const rState = stream._readableState
if (rState.destroyed && rState.closeEmitted === false) {
stream
.on('error', err => {
reject(err)
})
.on('close', () => {
reject(new TypeError('unusable'))
})
} else {
reject(rState.errored ?? new TypeError('unusable'))
}
} else {
queueMicrotask(() => {
stream[kConsume] = {
type,
stream,
resolve,
reject,
length: 0,
body: []
}
})
process.nextTick(consumeStart, stream[kConsume])
stream
.on('error', function (err) {
consumeFinish(this[kConsume], err)
})
.on('close', function () {
if (this[kConsume].body !== null) {
consumeFinish(this[kConsume], new RequestAbortedError())
}
})
consumeStart(stream[kConsume])
})
}
})
}
/**
* @param {Consume} consume
* @returns {void}
*/
function consumeStart (consume) {
if (consume.body === null) {
return
@ -247,15 +410,23 @@ function consumeStart (consume) {
const { _readableState: state } = consume.stream
for (const chunk of state.buffer) {
consumePush(consume, chunk)
if (state.bufferIndex) {
const start = state.bufferIndex
const end = state.buffer.length
for (let n = start; n < end; n++) {
consumePush(consume, state.buffer[n])
}
} else {
for (const chunk of state.buffer) {
consumePush(consume, chunk)
}
}
if (state.endEmitted) {
consumeEnd(this[kConsume])
consumeEnd(this[kConsume], this._readableState.encoding)
} else {
consume.stream.on('end', function () {
consumeEnd(this[kConsume])
consumeEnd(this[kConsume], this._readableState.encoding)
})
}
@ -266,29 +437,78 @@ function consumeStart (consume) {
}
}
function consumeEnd (consume) {
/**
* @param {Buffer[]} chunks
* @param {number} length
* @param {BufferEncoding} encoding
* @returns {string}
*/
function chunksDecode (chunks, length, encoding) {
if (chunks.length === 0 || length === 0) {
return ''
}
const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, length)
const bufferLength = buffer.length
// Skip BOM.
const start =
bufferLength > 2 &&
buffer[0] === 0xef &&
buffer[1] === 0xbb &&
buffer[2] === 0xbf
? 3
: 0
if (!encoding || encoding === 'utf8' || encoding === 'utf-8') {
return buffer.utf8Slice(start, bufferLength)
} else {
return buffer.subarray(start, bufferLength).toString(encoding)
}
}
/**
* @param {Buffer[]} chunks
* @param {number} length
* @returns {Uint8Array}
*/
function chunksConcat (chunks, length) {
if (chunks.length === 0 || length === 0) {
return new Uint8Array(0)
}
if (chunks.length === 1) {
// fast-path
return new Uint8Array(chunks[0])
}
const buffer = new Uint8Array(Buffer.allocUnsafeSlow(length).buffer)
let offset = 0
for (let i = 0; i < chunks.length; ++i) {
const chunk = chunks[i]
buffer.set(chunk, offset)
offset += chunk.length
}
return buffer
}
/**
* @param {Consume} consume
* @param {BufferEncoding} encoding
* @returns {void}
*/
function consumeEnd (consume, encoding) {
const { type, body, resolve, stream, length } = consume
try {
if (type === 'text') {
resolve(toUSVString(Buffer.concat(body)))
resolve(chunksDecode(body, length, encoding))
} else if (type === 'json') {
resolve(JSON.parse(Buffer.concat(body)))
resolve(JSON.parse(chunksDecode(body, length, encoding)))
} else if (type === 'arrayBuffer') {
const dst = new Uint8Array(length)
let pos = 0
for (const buf of body) {
dst.set(buf, pos)
pos += buf.byteLength
}
resolve(dst.buffer)
resolve(chunksConcat(body, length).buffer)
} else if (type === 'blob') {
if (!Blob) {
Blob = require('buffer').Blob
}
resolve(new Blob(body, { type: stream[kContentType] }))
} else if (type === 'bytes') {
resolve(chunksConcat(body, length))
}
consumeFinish(consume)
@ -297,11 +517,21 @@ function consumeEnd (consume) {
}
}
/**
* @param {Consume} consume
* @param {Buffer} chunk
* @returns {void}
*/
function consumePush (consume, chunk) {
consume.length += chunk.length
consume.body.push(chunk)
}
/**
* @param {Consume} consume
* @param {Error} [err]
* @returns {void}
*/
function consumeFinish (consume, err) {
if (consume.body === null) {
return
@ -313,6 +543,7 @@ function consumeFinish (consume, err) {
consume.resolve()
}
// Reset the consume object to allow for garbage collection.
consume.type = null
consume.stream = null
consume.resolve = null
@ -320,3 +551,8 @@ function consumeFinish (consume, err) {
consume.length = 0
consume.body = null
}
module.exports = {
Readable: BodyReadable,
chunksDecode
}

101
node_modules/undici/lib/api/util.js generated vendored
View File

@ -1,46 +1,95 @@
const assert = require('assert')
'use strict'
const assert = require('node:assert')
const {
ResponseStatusCodeError
} = require('../core/errors')
const { toUSVString } = require('../core/util')
const { chunksDecode } = require('./readable')
const CHUNK_LIMIT = 128 * 1024
async function getResolveErrorBodyCallback ({ callback, body, contentType, statusCode, statusMessage, headers }) {
assert(body)
let chunks = []
let limit = 0
let length = 0
for await (const chunk of body) {
chunks.push(chunk)
limit += chunk.length
if (limit > 128 * 1024) {
chunks = null
break
try {
for await (const chunk of body) {
chunks.push(chunk)
length += chunk.length
if (length > CHUNK_LIMIT) {
chunks = []
length = 0
break
}
}
} catch {
chunks = []
length = 0
// Do nothing....
}
if (statusCode === 204 || !contentType || !chunks) {
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers))
const message = `Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`
if (statusCode === 204 || !contentType || !length) {
queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers)))
return
}
const stackTraceLimit = Error.stackTraceLimit
Error.stackTraceLimit = 0
let payload
try {
if (contentType.startsWith('application/json')) {
const payload = JSON.parse(toUSVString(Buffer.concat(chunks)))
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload))
return
if (isContentTypeApplicationJson(contentType)) {
payload = JSON.parse(chunksDecode(chunks, length))
} else if (isContentTypeText(contentType)) {
payload = chunksDecode(chunks, length)
}
if (contentType.startsWith('text/')) {
const payload = toUSVString(Buffer.concat(chunks))
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload))
return
}
} catch (err) {
// Process in a fallback if error
} catch {
// process in a callback to avoid throwing in the microtask queue
} finally {
Error.stackTraceLimit = stackTraceLimit
}
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers))
queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers, payload)))
}
module.exports = { getResolveErrorBodyCallback }
const isContentTypeApplicationJson = (contentType) => {
return (
contentType.length > 15 &&
contentType[11] === '/' &&
contentType[0] === 'a' &&
contentType[1] === 'p' &&
contentType[2] === 'p' &&
contentType[3] === 'l' &&
contentType[4] === 'i' &&
contentType[5] === 'c' &&
contentType[6] === 'a' &&
contentType[7] === 't' &&
contentType[8] === 'i' &&
contentType[9] === 'o' &&
contentType[10] === 'n' &&
contentType[12] === 'j' &&
contentType[13] === 's' &&
contentType[14] === 'o' &&
contentType[15] === 'n'
)
}
const isContentTypeText = (contentType) => {
return (
contentType.length > 4 &&
contentType[4] === '/' &&
contentType[0] === 't' &&
contentType[1] === 'e' &&
contentType[2] === 'x' &&
contentType[3] === 't'
)
}
module.exports = {
getResolveErrorBodyCallback,
isContentTypeApplicationJson,
isContentTypeText
}