mirror of
https://github.com/oven-sh/setup-bun.git
synced 2025-07-19 21:18:22 +02:00
chore: base
This commit is contained in:
parent
b04d87b14c
commit
e187173d21
425 changed files with 1080881 additions and 5 deletions
147
node_modules/undici/lib/agent.js
generated
vendored
Normal file
147
node_modules/undici/lib/agent.js
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
'use strict'
|
||||
|
||||
const { InvalidArgumentError } = require('./core/errors')
|
||||
const { kClients, kRunning, kClose, kDestroy, kDispatch } = require('./core/symbols')
|
||||
const DispatcherBase = require('./dispatcher-base')
|
||||
const Pool = require('./pool')
|
||||
const Client = require('./client')
|
||||
const util = require('./core/util')
|
||||
const RedirectHandler = require('./handler/redirect')
|
||||
const { WeakRef, FinalizationRegistry } = require('./compat/dispatcher-weakref')()
|
||||
|
||||
const kOnConnect = Symbol('onConnect')
|
||||
const kOnDisconnect = Symbol('onDisconnect')
|
||||
const kOnConnectionError = Symbol('onConnectionError')
|
||||
const kMaxRedirections = Symbol('maxRedirections')
|
||||
const kOnDrain = Symbol('onDrain')
|
||||
const kFactory = Symbol('factory')
|
||||
const kFinalizer = Symbol('finalizer')
|
||||
const kOptions = Symbol('options')
|
||||
|
||||
function defaultFactory (origin, opts) {
|
||||
return opts && opts.connections === 1
|
||||
? new Client(origin, opts)
|
||||
: new Pool(origin, opts)
|
||||
}
|
||||
|
||||
class Agent extends DispatcherBase {
|
||||
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
||||
super()
|
||||
|
||||
if (typeof factory !== 'function') {
|
||||
throw new InvalidArgumentError('factory must be a function.')
|
||||
}
|
||||
|
||||
if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') {
|
||||
throw new InvalidArgumentError('connect must be a function or an object')
|
||||
}
|
||||
|
||||
if (!Number.isInteger(maxRedirections) || maxRedirections < 0) {
|
||||
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
||||
}
|
||||
|
||||
if (connect && typeof connect !== 'function') {
|
||||
connect = { ...connect }
|
||||
}
|
||||
|
||||
this[kOptions] = { ...util.deepClone(options), connect }
|
||||
this[kMaxRedirections] = maxRedirections
|
||||
this[kFactory] = factory
|
||||
this[kClients] = new Map()
|
||||
this[kFinalizer] = new FinalizationRegistry(/* istanbul ignore next: gc is undeterministic */ key => {
|
||||
const ref = this[kClients].get(key)
|
||||
if (ref !== undefined && ref.deref() === undefined) {
|
||||
this[kClients].delete(key)
|
||||
}
|
||||
})
|
||||
|
||||
const agent = this
|
||||
|
||||
this[kOnDrain] = (origin, targets) => {
|
||||
agent.emit('drain', origin, [agent, ...targets])
|
||||
}
|
||||
|
||||
this[kOnConnect] = (origin, targets) => {
|
||||
agent.emit('connect', origin, [agent, ...targets])
|
||||
}
|
||||
|
||||
this[kOnDisconnect] = (origin, targets, err) => {
|
||||
agent.emit('disconnect', origin, [agent, ...targets], err)
|
||||
}
|
||||
|
||||
this[kOnConnectionError] = (origin, targets, err) => {
|
||||
agent.emit('connectionError', origin, [agent, ...targets], err)
|
||||
}
|
||||
}
|
||||
|
||||
get [kRunning] () {
|
||||
let ret = 0
|
||||
for (const ref of this[kClients].values()) {
|
||||
const client = ref.deref()
|
||||
/* istanbul ignore next: gc is undeterministic */
|
||||
if (client) {
|
||||
ret += client[kRunning]
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
[kDispatch] (opts, handler) {
|
||||
let key
|
||||
if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) {
|
||||
key = String(opts.origin)
|
||||
} else {
|
||||
throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
|
||||
}
|
||||
|
||||
const ref = this[kClients].get(key)
|
||||
|
||||
let dispatcher = ref ? ref.deref() : null
|
||||
if (!dispatcher) {
|
||||
dispatcher = this[kFactory](opts.origin, this[kOptions])
|
||||
.on('drain', this[kOnDrain])
|
||||
.on('connect', this[kOnConnect])
|
||||
.on('disconnect', this[kOnDisconnect])
|
||||
.on('connectionError', this[kOnConnectionError])
|
||||
|
||||
this[kClients].set(key, new WeakRef(dispatcher))
|
||||
this[kFinalizer].register(dispatcher, key)
|
||||
}
|
||||
|
||||
const { maxRedirections = this[kMaxRedirections] } = opts
|
||||
if (maxRedirections != null && maxRedirections !== 0) {
|
||||
opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
|
||||
handler = new RedirectHandler(this, maxRedirections, opts, handler)
|
||||
}
|
||||
|
||||
return dispatcher.dispatch(opts, handler)
|
||||
}
|
||||
|
||||
async [kClose] () {
|
||||
const closePromises = []
|
||||
for (const ref of this[kClients].values()) {
|
||||
const client = ref.deref()
|
||||
/* istanbul ignore else: gc is undeterministic */
|
||||
if (client) {
|
||||
closePromises.push(client.close())
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(closePromises)
|
||||
}
|
||||
|
||||
async [kDestroy] (err) {
|
||||
const destroyPromises = []
|
||||
for (const ref of this[kClients].values()) {
|
||||
const client = ref.deref()
|
||||
/* istanbul ignore else: gc is undeterministic */
|
||||
if (client) {
|
||||
destroyPromises.push(client.destroy(err))
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(destroyPromises)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Agent
|
57
node_modules/undici/lib/api/abort-signal.js
generated
vendored
Normal file
57
node_modules/undici/lib/api/abort-signal.js
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
const { RequestAbortedError } = require('../core/errors')
|
||||
|
||||
const kListener = Symbol('kListener')
|
||||
const kSignal = Symbol('kSignal')
|
||||
|
||||
function abort (self) {
|
||||
if (self.abort) {
|
||||
self.abort()
|
||||
} else {
|
||||
self.onError(new RequestAbortedError())
|
||||
}
|
||||
}
|
||||
|
||||
function addSignal (self, signal) {
|
||||
self[kSignal] = null
|
||||
self[kListener] = null
|
||||
|
||||
if (!signal) {
|
||||
return
|
||||
}
|
||||
|
||||
if (signal.aborted) {
|
||||
abort(self)
|
||||
return
|
||||
}
|
||||
|
||||
self[kSignal] = signal
|
||||
self[kListener] = () => {
|
||||
abort(self)
|
||||
}
|
||||
|
||||
if ('addEventListener' in self[kSignal]) {
|
||||
self[kSignal].addEventListener('abort', self[kListener])
|
||||
} else {
|
||||
self[kSignal].addListener('abort', self[kListener])
|
||||
}
|
||||
}
|
||||
|
||||
function removeSignal (self) {
|
||||
if (!self[kSignal]) {
|
||||
return
|
||||
}
|
||||
|
||||
if ('removeEventListener' in self[kSignal]) {
|
||||
self[kSignal].removeEventListener('abort', self[kListener])
|
||||
} else {
|
||||
self[kSignal].removeListener('abort', self[kListener])
|
||||
}
|
||||
|
||||
self[kSignal] = null
|
||||
self[kListener] = null
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addSignal,
|
||||
removeSignal
|
||||
}
|
98
node_modules/undici/lib/api/api-connect.js
generated
vendored
Normal file
98
node_modules/undici/lib/api/api-connect.js
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
'use strict'
|
||||
|
||||
const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
|
||||
const { AsyncResource } = require('async_hooks')
|
||||
const util = require('../core/util')
|
||||
const { addSignal, removeSignal } = require('./abort-signal')
|
||||
|
||||
class ConnectHandler extends AsyncResource {
|
||||
constructor (opts, callback) {
|
||||
if (!opts || typeof opts !== 'object') {
|
||||
throw new InvalidArgumentError('invalid opts')
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
throw new InvalidArgumentError('invalid callback')
|
||||
}
|
||||
|
||||
const { signal, opaque, responseHeaders } = opts
|
||||
|
||||
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
||||
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
||||
}
|
||||
|
||||
super('UNDICI_CONNECT')
|
||||
|
||||
this.opaque = opaque || null
|
||||
this.responseHeaders = responseHeaders || null
|
||||
this.callback = callback
|
||||
this.abort = null
|
||||
|
||||
addSignal(this, signal)
|
||||
}
|
||||
|
||||
onConnect (abort, context) {
|
||||
if (!this.callback) {
|
||||
throw new RequestAbortedError()
|
||||
}
|
||||
|
||||
this.abort = abort
|
||||
this.context = context
|
||||
}
|
||||
|
||||
onHeaders () {
|
||||
throw new SocketError('bad connect', null)
|
||||
}
|
||||
|
||||
onUpgrade (statusCode, rawHeaders, socket) {
|
||||
const { callback, opaque, context } = this
|
||||
|
||||
removeSignal(this)
|
||||
|
||||
this.callback = null
|
||||
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
||||
this.runInAsyncScope(callback, null, null, {
|
||||
statusCode,
|
||||
headers,
|
||||
socket,
|
||||
opaque,
|
||||
context
|
||||
})
|
||||
}
|
||||
|
||||
onError (err) {
|
||||
const { callback, opaque } = this
|
||||
|
||||
removeSignal(this)
|
||||
|
||||
if (callback) {
|
||||
this.callback = null
|
||||
queueMicrotask(() => {
|
||||
this.runInAsyncScope(callback, null, err, { opaque })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function connect (opts, callback) {
|
||||
if (callback === undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
connect.call(this, opts, (err, data) => {
|
||||
return err ? reject(err) : resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const connectHandler = new ConnectHandler(opts, callback)
|
||||
this.dispatch({ ...opts, method: 'CONNECT' }, connectHandler)
|
||||
} catch (err) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw err
|
||||
}
|
||||
const opaque = opts && opts.opaque
|
||||
queueMicrotask(() => callback(err, { opaque }))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect
|
249
node_modules/undici/lib/api/api-pipeline.js
generated
vendored
Normal file
249
node_modules/undici/lib/api/api-pipeline.js
generated
vendored
Normal file
|
@ -0,0 +1,249 @@
|
|||
'use strict'
|
||||
|
||||
const {
|
||||
Readable,
|
||||
Duplex,
|
||||
PassThrough
|
||||
} = require('stream')
|
||||
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')
|
||||
|
||||
const kResume = Symbol('resume')
|
||||
|
||||
class PipelineRequest extends Readable {
|
||||
constructor () {
|
||||
super({ autoDestroy: true })
|
||||
|
||||
this[kResume] = null
|
||||
}
|
||||
|
||||
_read () {
|
||||
const { [kResume]: resume } = this
|
||||
|
||||
if (resume) {
|
||||
this[kResume] = null
|
||||
resume()
|
||||
}
|
||||
}
|
||||
|
||||
_destroy (err, callback) {
|
||||
this._read()
|
||||
|
||||
callback(err)
|
||||
}
|
||||
}
|
||||
|
||||
class PipelineResponse extends Readable {
|
||||
constructor (resume) {
|
||||
super({ autoDestroy: true })
|
||||
this[kResume] = resume
|
||||
}
|
||||
|
||||
_read () {
|
||||
this[kResume]()
|
||||
}
|
||||
|
||||
_destroy (err, callback) {
|
||||
if (!err && !this._readableState.endEmitted) {
|
||||
err = new RequestAbortedError()
|
||||
}
|
||||
|
||||
callback(err)
|
||||
}
|
||||
}
|
||||
|
||||
class PipelineHandler extends AsyncResource {
|
||||
constructor (opts, handler) {
|
||||
if (!opts || typeof opts !== 'object') {
|
||||
throw new InvalidArgumentError('invalid opts')
|
||||
}
|
||||
|
||||
if (typeof handler !== 'function') {
|
||||
throw new InvalidArgumentError('invalid handler')
|
||||
}
|
||||
|
||||
const { signal, method, opaque, onInfo, responseHeaders } = opts
|
||||
|
||||
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
||||
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
||||
}
|
||||
|
||||
if (method === 'CONNECT') {
|
||||
throw new InvalidArgumentError('invalid method')
|
||||
}
|
||||
|
||||
if (onInfo && typeof onInfo !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onInfo callback')
|
||||
}
|
||||
|
||||
super('UNDICI_PIPELINE')
|
||||
|
||||
this.opaque = opaque || null
|
||||
this.responseHeaders = responseHeaders || null
|
||||
this.handler = handler
|
||||
this.abort = null
|
||||
this.context = null
|
||||
this.onInfo = onInfo || null
|
||||
|
||||
this.req = new PipelineRequest().on('error', util.nop)
|
||||
|
||||
this.ret = new Duplex({
|
||||
readableObjectMode: opts.objectMode,
|
||||
autoDestroy: true,
|
||||
read: () => {
|
||||
const { body } = this
|
||||
|
||||
if (body && body.resume) {
|
||||
body.resume()
|
||||
}
|
||||
},
|
||||
write: (chunk, encoding, callback) => {
|
||||
const { req } = this
|
||||
|
||||
if (req.push(chunk, encoding) || req._readableState.destroyed) {
|
||||
callback()
|
||||
} else {
|
||||
req[kResume] = callback
|
||||
}
|
||||
},
|
||||
destroy: (err, callback) => {
|
||||
const { body, req, res, ret, abort } = this
|
||||
|
||||
if (!err && !ret._readableState.endEmitted) {
|
||||
err = new RequestAbortedError()
|
||||
}
|
||||
|
||||
if (abort && err) {
|
||||
abort()
|
||||
}
|
||||
|
||||
util.destroy(body, err)
|
||||
util.destroy(req, err)
|
||||
util.destroy(res, err)
|
||||
|
||||
removeSignal(this)
|
||||
|
||||
callback(err)
|
||||
}
|
||||
}).on('prefinish', () => {
|
||||
const { req } = this
|
||||
|
||||
// Node < 15 does not call _final in same tick.
|
||||
req.push(null)
|
||||
})
|
||||
|
||||
this.res = null
|
||||
|
||||
addSignal(this, signal)
|
||||
}
|
||||
|
||||
onConnect (abort, context) {
|
||||
const { ret, res } = this
|
||||
|
||||
assert(!res, 'pipeline cannot be retried')
|
||||
|
||||
if (ret.destroyed) {
|
||||
throw new RequestAbortedError()
|
||||
}
|
||||
|
||||
this.abort = abort
|
||||
this.context = context
|
||||
}
|
||||
|
||||
onHeaders (statusCode, rawHeaders, resume) {
|
||||
const { opaque, handler, context } = this
|
||||
|
||||
if (statusCode < 200) {
|
||||
if (this.onInfo) {
|
||||
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
||||
this.onInfo({ statusCode, headers })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.res = new PipelineResponse(resume)
|
||||
|
||||
let body
|
||||
try {
|
||||
this.handler = null
|
||||
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
||||
body = this.runInAsyncScope(handler, null, {
|
||||
statusCode,
|
||||
headers,
|
||||
opaque,
|
||||
body: this.res,
|
||||
context
|
||||
})
|
||||
} catch (err) {
|
||||
this.res.on('error', util.nop)
|
||||
throw err
|
||||
}
|
||||
|
||||
if (!body || typeof body.on !== 'function') {
|
||||
throw new InvalidReturnValueError('expected Readable')
|
||||
}
|
||||
|
||||
body
|
||||
.on('data', (chunk) => {
|
||||
const { ret, body } = this
|
||||
|
||||
if (!ret.push(chunk) && body.pause) {
|
||||
body.pause()
|
||||
}
|
||||
})
|
||||
.on('error', (err) => {
|
||||
const { ret } = this
|
||||
|
||||
util.destroy(ret, err)
|
||||
})
|
||||
.on('end', () => {
|
||||
const { ret } = this
|
||||
|
||||
ret.push(null)
|
||||
})
|
||||
.on('close', () => {
|
||||
const { ret } = this
|
||||
|
||||
if (!ret._readableState.ended) {
|
||||
util.destroy(ret, new RequestAbortedError())
|
||||
}
|
||||
})
|
||||
|
||||
this.body = body
|
||||
}
|
||||
|
||||
onData (chunk) {
|
||||
const { res } = this
|
||||
return res.push(chunk)
|
||||
}
|
||||
|
||||
onComplete (trailers) {
|
||||
const { res } = this
|
||||
res.push(null)
|
||||
}
|
||||
|
||||
onError (err) {
|
||||
const { ret } = this
|
||||
this.handler = null
|
||||
util.destroy(ret, err)
|
||||
}
|
||||
}
|
||||
|
||||
function pipeline (opts, handler) {
|
||||
try {
|
||||
const pipelineHandler = new PipelineHandler(opts, handler)
|
||||
this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler)
|
||||
return pipelineHandler.ret
|
||||
} catch (err) {
|
||||
return new PassThrough().destroy(err)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = pipeline
|
203
node_modules/undici/lib/api/api-request.js
generated
vendored
Normal file
203
node_modules/undici/lib/api/api-request.js
generated
vendored
Normal file
|
@ -0,0 +1,203 @@
|
|||
'use strict'
|
||||
|
||||
const Readable = require('./readable')
|
||||
const {
|
||||
InvalidArgumentError,
|
||||
RequestAbortedError,
|
||||
ResponseStatusCodeError
|
||||
} = require('../core/errors')
|
||||
const util = require('../core/util')
|
||||
const { AsyncResource } = require('async_hooks')
|
||||
const { addSignal, removeSignal } = require('./abort-signal')
|
||||
|
||||
class RequestHandler extends AsyncResource {
|
||||
constructor (opts, callback) {
|
||||
if (!opts || typeof opts !== 'object') {
|
||||
throw new InvalidArgumentError('invalid opts')
|
||||
}
|
||||
|
||||
const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts
|
||||
|
||||
try {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new InvalidArgumentError('invalid callback')
|
||||
}
|
||||
|
||||
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
||||
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
||||
}
|
||||
|
||||
if (method === 'CONNECT') {
|
||||
throw new InvalidArgumentError('invalid method')
|
||||
}
|
||||
|
||||
if (onInfo && typeof onInfo !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onInfo callback')
|
||||
}
|
||||
|
||||
super('UNDICI_REQUEST')
|
||||
} catch (err) {
|
||||
if (util.isStream(body)) {
|
||||
util.destroy(body.on('error', util.nop), err)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
this.responseHeaders = responseHeaders || null
|
||||
this.opaque = opaque || null
|
||||
this.callback = callback
|
||||
this.res = null
|
||||
this.abort = null
|
||||
this.body = body
|
||||
this.trailers = {}
|
||||
this.context = null
|
||||
this.onInfo = onInfo || null
|
||||
this.throwOnError = throwOnError
|
||||
|
||||
if (util.isStream(body)) {
|
||||
body.on('error', (err) => {
|
||||
this.onError(err)
|
||||
})
|
||||
}
|
||||
|
||||
addSignal(this, signal)
|
||||
}
|
||||
|
||||
onConnect (abort, context) {
|
||||
if (!this.callback) {
|
||||
throw new RequestAbortedError()
|
||||
}
|
||||
|
||||
this.abort = abort
|
||||
this.context = context
|
||||
}
|
||||
|
||||
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
||||
const { callback, opaque, abort, context } = this
|
||||
|
||||
if (statusCode < 200) {
|
||||
if (this.onInfo) {
|
||||
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
||||
this.onInfo({ statusCode, headers })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const parsedHeaders = util.parseHeaders(rawHeaders)
|
||||
const contentType = parsedHeaders['content-type']
|
||||
const body = new Readable(resume, abort, contentType)
|
||||
|
||||
this.callback = null
|
||||
this.res = body
|
||||
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
||||
|
||||
if (callback !== null) {
|
||||
if (this.throwOnError && statusCode >= 400) {
|
||||
this.runInAsyncScope(getResolveErrorBodyCallback, null,
|
||||
{ callback, body, contentType, statusCode, statusMessage, headers }
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
this.runInAsyncScope(callback, null, null, {
|
||||
statusCode,
|
||||
headers,
|
||||
trailers: this.trailers,
|
||||
opaque,
|
||||
body,
|
||||
context
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onData (chunk) {
|
||||
const { res } = this
|
||||
return res.push(chunk)
|
||||
}
|
||||
|
||||
onComplete (trailers) {
|
||||
const { res } = this
|
||||
|
||||
removeSignal(this)
|
||||
|
||||
util.parseHeaders(trailers, this.trailers)
|
||||
|
||||
res.push(null)
|
||||
}
|
||||
|
||||
onError (err) {
|
||||
const { res, callback, body, opaque } = this
|
||||
|
||||
removeSignal(this)
|
||||
|
||||
if (callback) {
|
||||
// TODO: Does this need queueMicrotask?
|
||||
this.callback = null
|
||||
queueMicrotask(() => {
|
||||
this.runInAsyncScope(callback, null, err, { opaque })
|
||||
})
|
||||
}
|
||||
|
||||
if (res) {
|
||||
this.res = null
|
||||
// Ensure all queued handlers are invoked before destroying res.
|
||||
queueMicrotask(() => {
|
||||
util.destroy(res, err)
|
||||
})
|
||||
}
|
||||
|
||||
if (body) {
|
||||
this.body = null
|
||||
util.destroy(body, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getResolveErrorBodyCallback ({ callback, body, contentType, statusCode, statusMessage, headers }) {
|
||||
if (statusCode === 204 || !contentType) {
|
||||
body.dump()
|
||||
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (contentType.startsWith('application/json')) {
|
||||
const payload = await body.json()
|
||||
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload))
|
||||
return
|
||||
}
|
||||
|
||||
if (contentType.startsWith('text/')) {
|
||||
const payload = await body.text()
|
||||
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload))
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
// Process in a fallback if error
|
||||
}
|
||||
|
||||
body.dump()
|
||||
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers))
|
||||
}
|
||||
|
||||
function request (opts, callback) {
|
||||
if (callback === undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.call(this, opts, (err, data) => {
|
||||
return err ? reject(err) : resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
this.dispatch(opts, new RequestHandler(opts, callback))
|
||||
} catch (err) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw err
|
||||
}
|
||||
const opaque = opts && opts.opaque
|
||||
queueMicrotask(() => callback(err, { opaque }))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = request
|
195
node_modules/undici/lib/api/api-stream.js
generated
vendored
Normal file
195
node_modules/undici/lib/api/api-stream.js
generated
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
'use strict'
|
||||
|
||||
const { finished } = require('stream')
|
||||
const {
|
||||
InvalidArgumentError,
|
||||
InvalidReturnValueError,
|
||||
RequestAbortedError
|
||||
} = require('../core/errors')
|
||||
const util = require('../core/util')
|
||||
const { AsyncResource } = require('async_hooks')
|
||||
const { addSignal, removeSignal } = require('./abort-signal')
|
||||
|
||||
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 } = opts
|
||||
|
||||
try {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new InvalidArgumentError('invalid callback')
|
||||
}
|
||||
|
||||
if (typeof factory !== 'function') {
|
||||
throw new InvalidArgumentError('invalid factory')
|
||||
}
|
||||
|
||||
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
||||
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
||||
}
|
||||
|
||||
if (method === 'CONNECT') {
|
||||
throw new InvalidArgumentError('invalid method')
|
||||
}
|
||||
|
||||
if (onInfo && typeof onInfo !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onInfo callback')
|
||||
}
|
||||
|
||||
super('UNDICI_STREAM')
|
||||
} catch (err) {
|
||||
if (util.isStream(body)) {
|
||||
util.destroy(body.on('error', util.nop), err)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
this.responseHeaders = responseHeaders || null
|
||||
this.opaque = opaque || null
|
||||
this.factory = factory
|
||||
this.callback = callback
|
||||
this.res = null
|
||||
this.abort = null
|
||||
this.context = null
|
||||
this.trailers = null
|
||||
this.body = body
|
||||
this.onInfo = onInfo || null
|
||||
|
||||
if (util.isStream(body)) {
|
||||
body.on('error', (err) => {
|
||||
this.onError(err)
|
||||
})
|
||||
}
|
||||
|
||||
addSignal(this, signal)
|
||||
}
|
||||
|
||||
onConnect (abort, context) {
|
||||
if (!this.callback) {
|
||||
throw new RequestAbortedError()
|
||||
}
|
||||
|
||||
this.abort = abort
|
||||
this.context = context
|
||||
}
|
||||
|
||||
onHeaders (statusCode, rawHeaders, resume) {
|
||||
const { factory, opaque, context } = this
|
||||
|
||||
if (statusCode < 200) {
|
||||
if (this.onInfo) {
|
||||
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
||||
this.onInfo({ statusCode, headers })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.factory = null
|
||||
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
||||
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')
|
||||
}
|
||||
|
||||
res.on('drain', resume)
|
||||
// TODO: Avoid finished. It registers an unecessary 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()
|
||||
}
|
||||
})
|
||||
|
||||
this.res = res
|
||||
|
||||
const needDrain = res.writableNeedDrain !== undefined
|
||||
? res.writableNeedDrain
|
||||
: res._writableState && res._writableState.needDrain
|
||||
|
||||
return needDrain !== true
|
||||
}
|
||||
|
||||
onData (chunk) {
|
||||
const { res } = this
|
||||
|
||||
return res.write(chunk)
|
||||
}
|
||||
|
||||
onComplete (trailers) {
|
||||
const { res } = this
|
||||
|
||||
removeSignal(this)
|
||||
|
||||
this.trailers = util.parseHeaders(trailers)
|
||||
|
||||
res.end()
|
||||
}
|
||||
|
||||
onError (err) {
|
||||
const { res, callback, opaque, body } = this
|
||||
|
||||
removeSignal(this)
|
||||
|
||||
this.factory = null
|
||||
|
||||
if (res) {
|
||||
this.res = null
|
||||
util.destroy(res, err)
|
||||
} else if (callback) {
|
||||
this.callback = null
|
||||
queueMicrotask(() => {
|
||||
this.runInAsyncScope(callback, null, err, { opaque })
|
||||
})
|
||||
}
|
||||
|
||||
if (body) {
|
||||
this.body = null
|
||||
util.destroy(body, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stream (opts, factory, callback) {
|
||||
if (callback === undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.call(this, opts, factory, (err, data) => {
|
||||
return err ? reject(err) : resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
this.dispatch(opts, new StreamHandler(opts, factory, callback))
|
||||
} catch (err) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw err
|
||||
}
|
||||
const opaque = opts && opts.opaque
|
||||
queueMicrotask(() => callback(err, { opaque }))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = stream
|
105
node_modules/undici/lib/api/api-upgrade.js
generated
vendored
Normal file
105
node_modules/undici/lib/api/api-upgrade.js
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
'use strict'
|
||||
|
||||
const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
|
||||
const { AsyncResource } = require('async_hooks')
|
||||
const util = require('../core/util')
|
||||
const { addSignal, removeSignal } = require('./abort-signal')
|
||||
const assert = require('assert')
|
||||
|
||||
class UpgradeHandler extends AsyncResource {
|
||||
constructor (opts, callback) {
|
||||
if (!opts || typeof opts !== 'object') {
|
||||
throw new InvalidArgumentError('invalid opts')
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
throw new InvalidArgumentError('invalid callback')
|
||||
}
|
||||
|
||||
const { signal, opaque, responseHeaders } = opts
|
||||
|
||||
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
||||
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
||||
}
|
||||
|
||||
super('UNDICI_UPGRADE')
|
||||
|
||||
this.responseHeaders = responseHeaders || null
|
||||
this.opaque = opaque || null
|
||||
this.callback = callback
|
||||
this.abort = null
|
||||
this.context = null
|
||||
|
||||
addSignal(this, signal)
|
||||
}
|
||||
|
||||
onConnect (abort, context) {
|
||||
if (!this.callback) {
|
||||
throw new RequestAbortedError()
|
||||
}
|
||||
|
||||
this.abort = abort
|
||||
this.context = null
|
||||
}
|
||||
|
||||
onHeaders () {
|
||||
throw new SocketError('bad upgrade', null)
|
||||
}
|
||||
|
||||
onUpgrade (statusCode, rawHeaders, socket) {
|
||||
const { callback, opaque, context } = this
|
||||
|
||||
assert.strictEqual(statusCode, 101)
|
||||
|
||||
removeSignal(this)
|
||||
|
||||
this.callback = null
|
||||
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
||||
this.runInAsyncScope(callback, null, null, {
|
||||
headers,
|
||||
socket,
|
||||
opaque,
|
||||
context
|
||||
})
|
||||
}
|
||||
|
||||
onError (err) {
|
||||
const { callback, opaque } = this
|
||||
|
||||
removeSignal(this)
|
||||
|
||||
if (callback) {
|
||||
this.callback = null
|
||||
queueMicrotask(() => {
|
||||
this.runInAsyncScope(callback, null, err, { opaque })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function upgrade (opts, callback) {
|
||||
if (callback === undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
upgrade.call(this, opts, (err, data) => {
|
||||
return err ? reject(err) : resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const upgradeHandler = new UpgradeHandler(opts, callback)
|
||||
this.dispatch({
|
||||
...opts,
|
||||
method: opts.method || 'GET',
|
||||
upgrade: opts.protocol || 'Websocket'
|
||||
}, upgradeHandler)
|
||||
} catch (err) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw err
|
||||
}
|
||||
const opaque = opts && opts.opaque
|
||||
queueMicrotask(() => callback(err, { opaque }))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = upgrade
|
7
node_modules/undici/lib/api/index.js
generated
vendored
Normal file
7
node_modules/undici/lib/api/index.js
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
'use strict'
|
||||
|
||||
module.exports.request = require('./api-request')
|
||||
module.exports.stream = require('./api-stream')
|
||||
module.exports.pipeline = require('./api-pipeline')
|
||||
module.exports.upgrade = require('./api-upgrade')
|
||||
module.exports.connect = require('./api-connect')
|
283
node_modules/undici/lib/api/readable.js
generated
vendored
Normal file
283
node_modules/undici/lib/api/readable.js
generated
vendored
Normal file
|
@ -0,0 +1,283 @@
|
|||
// Ported from https://github.com/nodejs/undici/pull/907
|
||||
|
||||
'use strict'
|
||||
|
||||
const assert = require('assert')
|
||||
const { Readable } = require('stream')
|
||||
const { RequestAbortedError, NotSupportedError } = require('../core/errors')
|
||||
const util = require('../core/util')
|
||||
const { ReadableStreamFrom, toUSVString } = require('../core/util')
|
||||
|
||||
let Blob
|
||||
|
||||
const kConsume = Symbol('kConsume')
|
||||
const kReading = Symbol('kReading')
|
||||
const kBody = Symbol('kBody')
|
||||
const kAbort = Symbol('abort')
|
||||
const kContentType = Symbol('kContentType')
|
||||
|
||||
module.exports = class BodyReadable extends Readable {
|
||||
constructor (resume, abort, contentType = '') {
|
||||
super({
|
||||
autoDestroy: true,
|
||||
read: resume,
|
||||
highWaterMark: 64 * 1024 // Same as nodejs fs streams.
|
||||
})
|
||||
|
||||
this._readableState.dataEmitted = false
|
||||
|
||||
this[kAbort] = abort
|
||||
this[kConsume] = null
|
||||
this[kBody] = null
|
||||
this[kContentType] = contentType
|
||||
|
||||
// Is stream being consumed through Readable API?
|
||||
// This is an optimization so that we avoid checking
|
||||
// for 'data' and 'readable' listeners in the hot path
|
||||
// inside push().
|
||||
this[kReading] = false
|
||||
}
|
||||
|
||||
destroy (err) {
|
||||
if (this.destroyed) {
|
||||
// Node < 16
|
||||
return this
|
||||
}
|
||||
|
||||
if (!err && !this._readableState.endEmitted) {
|
||||
err = new RequestAbortedError()
|
||||
}
|
||||
|
||||
if (err) {
|
||||
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
|
||||
}
|
||||
return super.emit(ev, ...args)
|
||||
}
|
||||
|
||||
on (ev, ...args) {
|
||||
if (ev === 'data' || ev === 'readable') {
|
||||
this[kReading] = true
|
||||
}
|
||||
return super.on(ev, ...args)
|
||||
}
|
||||
|
||||
addListener (ev, ...args) {
|
||||
return this.on(ev, ...args)
|
||||
}
|
||||
|
||||
off (ev, ...args) {
|
||||
const ret = super.off(ev, ...args)
|
||||
if (ev === 'data' || ev === 'readable') {
|
||||
this[kReading] = (
|
||||
this.listenerCount('data') > 0 ||
|
||||
this.listenerCount('readable') > 0
|
||||
)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
removeListener (ev, ...args) {
|
||||
return this.off(ev, ...args)
|
||||
}
|
||||
|
||||
push (chunk) {
|
||||
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 () {
|
||||
return consume(this, 'text')
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-body-json
|
||||
async json () {
|
||||
return consume(this, 'json')
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-body-blob
|
||||
async blob () {
|
||||
return consume(this, 'blob')
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
||||
async arrayBuffer () {
|
||||
return consume(this, 'arrayBuffer')
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-body-formdata
|
||||
async formData () {
|
||||
// TODO: Implement.
|
||||
throw new NotSupportedError()
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-body-bodyused
|
||||
get bodyUsed () {
|
||||
return util.isDisturbed(this)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-body-body
|
||||
get body () {
|
||||
if (!this[kBody]) {
|
||||
this[kBody] = ReadableStreamFrom(this)
|
||||
if (this[kConsume]) {
|
||||
// TODO: Is this the best way to force a lock?
|
||||
this[kBody].getReader() // Ensure stream is locked.
|
||||
assert(this[kBody].locked)
|
||||
}
|
||||
}
|
||||
return this[kBody]
|
||||
}
|
||||
|
||||
async dump (opts) {
|
||||
let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144
|
||||
try {
|
||||
for await (const chunk of this) {
|
||||
limit -= Buffer.byteLength(chunk)
|
||||
if (limit < 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Do nothing...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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')
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
})
|
||||
|
||||
process.nextTick(consumeStart, stream[kConsume])
|
||||
})
|
||||
}
|
||||
|
||||
function consumeStart (consume) {
|
||||
if (consume.body === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const { _readableState: state } = consume.stream
|
||||
|
||||
for (const chunk of state.buffer) {
|
||||
consumePush(consume, chunk)
|
||||
}
|
||||
|
||||
if (state.endEmitted) {
|
||||
consumeEnd(this[kConsume])
|
||||
} else {
|
||||
consume.stream.on('end', function () {
|
||||
consumeEnd(this[kConsume])
|
||||
})
|
||||
}
|
||||
|
||||
consume.stream.resume()
|
||||
|
||||
while (consume.stream.read() != null) {
|
||||
// Loop
|
||||
}
|
||||
}
|
||||
|
||||
function consumeEnd (consume) {
|
||||
const { type, body, resolve, stream, length } = consume
|
||||
|
||||
try {
|
||||
if (type === 'text') {
|
||||
resolve(toUSVString(Buffer.concat(body)))
|
||||
} else if (type === 'json') {
|
||||
resolve(JSON.parse(Buffer.concat(body)))
|
||||
} 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)
|
||||
} else if (type === 'blob') {
|
||||
if (!Blob) {
|
||||
Blob = require('buffer').Blob
|
||||
}
|
||||
resolve(new Blob(body, { type: stream[kContentType] }))
|
||||
}
|
||||
|
||||
consumeFinish(consume)
|
||||
} catch (err) {
|
||||
stream.destroy(err)
|
||||
}
|
||||
}
|
||||
|
||||
function consumePush (consume, chunk) {
|
||||
consume.length += chunk.length
|
||||
consume.body.push(chunk)
|
||||
}
|
||||
|
||||
function consumeFinish (consume, err) {
|
||||
if (consume.body === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (err) {
|
||||
consume.reject(err)
|
||||
} else {
|
||||
consume.resolve()
|
||||
}
|
||||
|
||||
consume.type = null
|
||||
consume.stream = null
|
||||
consume.resolve = null
|
||||
consume.reject = null
|
||||
consume.length = 0
|
||||
consume.body = null
|
||||
}
|
110
node_modules/undici/lib/balanced-pool.js
generated
vendored
Normal file
110
node_modules/undici/lib/balanced-pool.js
generated
vendored
Normal file
|
@ -0,0 +1,110 @@
|
|||
'use strict'
|
||||
|
||||
const {
|
||||
BalancedPoolMissingUpstreamError,
|
||||
InvalidArgumentError
|
||||
} = require('./core/errors')
|
||||
const {
|
||||
PoolBase,
|
||||
kClients,
|
||||
kNeedDrain,
|
||||
kAddClient,
|
||||
kRemoveClient,
|
||||
kGetDispatcher
|
||||
} = require('./pool-base')
|
||||
const Pool = require('./pool')
|
||||
const { kUrl } = require('./core/symbols')
|
||||
const { parseOrigin } = require('./core/util')
|
||||
const kFactory = Symbol('factory')
|
||||
|
||||
const kOptions = Symbol('options')
|
||||
|
||||
function defaultFactory (origin, opts) {
|
||||
return new Pool(origin, opts)
|
||||
}
|
||||
|
||||
class BalancedPool extends PoolBase {
|
||||
constructor (upstreams = [], { factory = defaultFactory, ...opts } = {}) {
|
||||
super()
|
||||
|
||||
this[kOptions] = opts
|
||||
|
||||
if (!Array.isArray(upstreams)) {
|
||||
upstreams = [upstreams]
|
||||
}
|
||||
|
||||
if (typeof factory !== 'function') {
|
||||
throw new InvalidArgumentError('factory must be a function.')
|
||||
}
|
||||
|
||||
this[kFactory] = factory
|
||||
|
||||
for (const upstream of upstreams) {
|
||||
this.addUpstream(upstream)
|
||||
}
|
||||
}
|
||||
|
||||
addUpstream (upstream) {
|
||||
const upstreamOrigin = parseOrigin(upstream).origin
|
||||
|
||||
if (this[kClients].find((pool) => (
|
||||
pool[kUrl].origin === upstreamOrigin &&
|
||||
pool.closed !== true &&
|
||||
pool.destroyed !== true
|
||||
))) {
|
||||
return this
|
||||
}
|
||||
|
||||
this[kAddClient](this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
removeUpstream (upstream) {
|
||||
const upstreamOrigin = parseOrigin(upstream).origin
|
||||
|
||||
const pool = this[kClients].find((pool) => (
|
||||
pool[kUrl].origin === upstreamOrigin &&
|
||||
pool.closed !== true &&
|
||||
pool.destroyed !== true
|
||||
))
|
||||
|
||||
if (pool) {
|
||||
this[kRemoveClient](pool)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
get upstreams () {
|
||||
return this[kClients]
|
||||
.filter(dispatcher => dispatcher.closed !== true && dispatcher.destroyed !== true)
|
||||
.map((p) => p[kUrl].origin)
|
||||
}
|
||||
|
||||
[kGetDispatcher] () {
|
||||
// We validate that pools is greater than 0,
|
||||
// otherwise we would have to wait until an upstream
|
||||
// is added, which might never happen.
|
||||
if (this[kClients].length === 0) {
|
||||
throw new BalancedPoolMissingUpstreamError()
|
||||
}
|
||||
|
||||
const dispatcher = this[kClients].find(dispatcher => (
|
||||
!dispatcher[kNeedDrain] &&
|
||||
dispatcher.closed !== true &&
|
||||
dispatcher.destroyed !== true
|
||||
))
|
||||
|
||||
if (!dispatcher) {
|
||||
return
|
||||
}
|
||||
|
||||
this[kClients].splice(this[kClients].indexOf(dispatcher), 1)
|
||||
this[kClients].push(dispatcher)
|
||||
|
||||
return dispatcher
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BalancedPool
|
1716
node_modules/undici/lib/client.js
generated
vendored
Normal file
1716
node_modules/undici/lib/client.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
38
node_modules/undici/lib/compat/dispatcher-weakref.js
generated
vendored
Normal file
38
node_modules/undici/lib/compat/dispatcher-weakref.js
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
'use strict'
|
||||
|
||||
/* istanbul ignore file: only for Node 12 */
|
||||
|
||||
const { kConnected, kSize } = require('../core/symbols')
|
||||
|
||||
class CompatWeakRef {
|
||||
constructor (value) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
deref () {
|
||||
return this.value[kConnected] === 0 && this.value[kSize] === 0
|
||||
? undefined
|
||||
: this.value
|
||||
}
|
||||
}
|
||||
|
||||
class CompatFinalizer {
|
||||
constructor (finalizer) {
|
||||
this.finalizer = finalizer
|
||||
}
|
||||
|
||||
register (dispatcher, key) {
|
||||
dispatcher.on('disconnect', () => {
|
||||
if (dispatcher[kConnected] === 0 && dispatcher[kSize] === 0) {
|
||||
this.finalizer(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
return {
|
||||
WeakRef: global.WeakRef || CompatWeakRef,
|
||||
FinalizationRegistry: global.FinalizationRegistry || CompatFinalizer
|
||||
}
|
||||
}
|
111
node_modules/undici/lib/core/connect.js
generated
vendored
Normal file
111
node_modules/undici/lib/core/connect.js
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
'use strict'
|
||||
|
||||
const net = require('net')
|
||||
const assert = require('assert')
|
||||
const util = require('./util')
|
||||
const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
|
||||
let tls // include tls conditionally since it is not always available
|
||||
|
||||
// TODO: session re-use does not wait for the first
|
||||
// connection to resolve the session and might therefore
|
||||
// resolve the same servername multiple times even when
|
||||
// re-use is enabled.
|
||||
|
||||
function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
|
||||
if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) {
|
||||
throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero')
|
||||
}
|
||||
|
||||
const options = { path: socketPath, ...opts }
|
||||
const sessionCache = new Map()
|
||||
timeout = timeout == null ? 10e3 : timeout
|
||||
maxCachedSessions = maxCachedSessions == null ? 100 : maxCachedSessions
|
||||
|
||||
return function connect ({ hostname, host, protocol, port, servername, httpSocket }, callback) {
|
||||
let socket
|
||||
if (protocol === 'https:') {
|
||||
if (!tls) {
|
||||
tls = require('tls')
|
||||
}
|
||||
servername = servername || options.servername || util.getServerName(host) || null
|
||||
|
||||
const sessionKey = servername || hostname
|
||||
const session = sessionCache.get(sessionKey) || null
|
||||
|
||||
assert(sessionKey)
|
||||
|
||||
socket = tls.connect({
|
||||
highWaterMark: 16384, // TLS in node can't have bigger HWM anyway...
|
||||
...options,
|
||||
servername,
|
||||
session,
|
||||
socket: httpSocket, // upgrade socket connection
|
||||
port: port || 443,
|
||||
host: hostname
|
||||
})
|
||||
|
||||
socket
|
||||
.on('session', function (session) {
|
||||
// cache is disabled
|
||||
if (maxCachedSessions === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (sessionCache.size >= maxCachedSessions) {
|
||||
// remove the oldest session
|
||||
const { value: oldestKey } = sessionCache.keys().next()
|
||||
sessionCache.delete(oldestKey)
|
||||
}
|
||||
|
||||
sessionCache.set(sessionKey, session)
|
||||
})
|
||||
.on('error', function (err) {
|
||||
if (sessionKey && err.code !== 'UND_ERR_INFO') {
|
||||
// TODO (fix): Only delete for session related errors.
|
||||
sessionCache.delete(sessionKey)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
assert(!httpSocket, 'httpSocket can only be sent on TLS update')
|
||||
socket = net.connect({
|
||||
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
|
||||
...options,
|
||||
port: port || 80,
|
||||
host: hostname
|
||||
})
|
||||
}
|
||||
|
||||
const timeoutId = timeout
|
||||
? setTimeout(onConnectTimeout, timeout, socket)
|
||||
: null
|
||||
|
||||
socket
|
||||
.setNoDelay(true)
|
||||
.once(protocol === 'https:' ? 'secureConnect' : 'connect', function () {
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (callback) {
|
||||
const cb = callback
|
||||
callback = null
|
||||
cb(null, this)
|
||||
}
|
||||
})
|
||||
.on('error', function (err) {
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (callback) {
|
||||
const cb = callback
|
||||
callback = null
|
||||
cb(err)
|
||||
}
|
||||
})
|
||||
|
||||
return socket
|
||||
}
|
||||
}
|
||||
|
||||
function onConnectTimeout (socket) {
|
||||
util.destroy(socket, new ConnectTimeoutError())
|
||||
}
|
||||
|
||||
module.exports = buildConnector
|
205
node_modules/undici/lib/core/errors.js
generated
vendored
Normal file
205
node_modules/undici/lib/core/errors.js
generated
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
'use strict'
|
||||
|
||||
class UndiciError extends Error {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
this.name = 'UndiciError'
|
||||
this.code = 'UND_ERR'
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectTimeoutError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, ConnectTimeoutError)
|
||||
this.name = 'ConnectTimeoutError'
|
||||
this.message = message || 'Connect Timeout Error'
|
||||
this.code = 'UND_ERR_CONNECT_TIMEOUT'
|
||||
}
|
||||
}
|
||||
|
||||
class HeadersTimeoutError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, HeadersTimeoutError)
|
||||
this.name = 'HeadersTimeoutError'
|
||||
this.message = message || 'Headers Timeout Error'
|
||||
this.code = 'UND_ERR_HEADERS_TIMEOUT'
|
||||
}
|
||||
}
|
||||
|
||||
class HeadersOverflowError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, HeadersOverflowError)
|
||||
this.name = 'HeadersOverflowError'
|
||||
this.message = message || 'Headers Overflow Error'
|
||||
this.code = 'UND_ERR_HEADERS_OVERFLOW'
|
||||
}
|
||||
}
|
||||
|
||||
class BodyTimeoutError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, BodyTimeoutError)
|
||||
this.name = 'BodyTimeoutError'
|
||||
this.message = message || 'Body Timeout Error'
|
||||
this.code = 'UND_ERR_BODY_TIMEOUT'
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseStatusCodeError extends UndiciError {
|
||||
constructor (message, statusCode, headers, body) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, ResponseStatusCodeError)
|
||||
this.name = 'ResponseStatusCodeError'
|
||||
this.message = message || 'Response Status Code Error'
|
||||
this.code = 'UND_ERR_RESPONSE_STATUS_CODE'
|
||||
this.body = body
|
||||
this.status = statusCode
|
||||
this.statusCode = statusCode
|
||||
this.headers = headers
|
||||
}
|
||||
}
|
||||
|
||||
class InvalidArgumentError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, InvalidArgumentError)
|
||||
this.name = 'InvalidArgumentError'
|
||||
this.message = message || 'Invalid Argument Error'
|
||||
this.code = 'UND_ERR_INVALID_ARG'
|
||||
}
|
||||
}
|
||||
|
||||
class InvalidReturnValueError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, InvalidReturnValueError)
|
||||
this.name = 'InvalidReturnValueError'
|
||||
this.message = message || 'Invalid Return Value Error'
|
||||
this.code = 'UND_ERR_INVALID_RETURN_VALUE'
|
||||
}
|
||||
}
|
||||
|
||||
class RequestAbortedError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, RequestAbortedError)
|
||||
this.name = 'AbortError'
|
||||
this.message = message || 'Request aborted'
|
||||
this.code = 'UND_ERR_ABORTED'
|
||||
}
|
||||
}
|
||||
|
||||
class InformationalError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, InformationalError)
|
||||
this.name = 'InformationalError'
|
||||
this.message = message || 'Request information'
|
||||
this.code = 'UND_ERR_INFO'
|
||||
}
|
||||
}
|
||||
|
||||
class RequestContentLengthMismatchError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, RequestContentLengthMismatchError)
|
||||
this.name = 'RequestContentLengthMismatchError'
|
||||
this.message = message || 'Request body length does not match content-length header'
|
||||
this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseContentLengthMismatchError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, ResponseContentLengthMismatchError)
|
||||
this.name = 'ResponseContentLengthMismatchError'
|
||||
this.message = message || 'Response body length does not match content-length header'
|
||||
this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH'
|
||||
}
|
||||
}
|
||||
|
||||
class ClientDestroyedError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, ClientDestroyedError)
|
||||
this.name = 'ClientDestroyedError'
|
||||
this.message = message || 'The client is destroyed'
|
||||
this.code = 'UND_ERR_DESTROYED'
|
||||
}
|
||||
}
|
||||
|
||||
class ClientClosedError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, ClientClosedError)
|
||||
this.name = 'ClientClosedError'
|
||||
this.message = message || 'The client is closed'
|
||||
this.code = 'UND_ERR_CLOSED'
|
||||
}
|
||||
}
|
||||
|
||||
class SocketError extends UndiciError {
|
||||
constructor (message, socket) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, SocketError)
|
||||
this.name = 'SocketError'
|
||||
this.message = message || 'Socket error'
|
||||
this.code = 'UND_ERR_SOCKET'
|
||||
this.socket = socket
|
||||
}
|
||||
}
|
||||
|
||||
class NotSupportedError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, NotSupportedError)
|
||||
this.name = 'NotSupportedError'
|
||||
this.message = message || 'Not supported error'
|
||||
this.code = 'UND_ERR_NOT_SUPPORTED'
|
||||
}
|
||||
}
|
||||
|
||||
class BalancedPoolMissingUpstreamError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, NotSupportedError)
|
||||
this.name = 'MissingUpstreamError'
|
||||
this.message = message || 'No upstream has been added to the BalancedPool'
|
||||
this.code = 'UND_ERR_BPL_MISSING_UPSTREAM'
|
||||
}
|
||||
}
|
||||
|
||||
class HTTPParserError extends Error {
|
||||
constructor (message, code, data) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, HTTPParserError)
|
||||
this.name = 'HTTPParserError'
|
||||
this.code = code ? `HPE_${code}` : undefined
|
||||
this.data = data ? data.toString() : undefined
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HTTPParserError,
|
||||
UndiciError,
|
||||
HeadersTimeoutError,
|
||||
HeadersOverflowError,
|
||||
BodyTimeoutError,
|
||||
RequestContentLengthMismatchError,
|
||||
ConnectTimeoutError,
|
||||
ResponseStatusCodeError,
|
||||
InvalidArgumentError,
|
||||
InvalidReturnValueError,
|
||||
RequestAbortedError,
|
||||
ClientDestroyedError,
|
||||
ClientClosedError,
|
||||
InformationalError,
|
||||
SocketError,
|
||||
NotSupportedError,
|
||||
ResponseContentLengthMismatchError,
|
||||
BalancedPoolMissingUpstreamError
|
||||
}
|
309
node_modules/undici/lib/core/request.js
generated
vendored
Normal file
309
node_modules/undici/lib/core/request.js
generated
vendored
Normal file
|
@ -0,0 +1,309 @@
|
|||
'use strict'
|
||||
|
||||
const {
|
||||
InvalidArgumentError,
|
||||
NotSupportedError
|
||||
} = require('./errors')
|
||||
const assert = require('assert')
|
||||
const util = require('./util')
|
||||
|
||||
const kHandler = Symbol('handler')
|
||||
|
||||
const channels = {}
|
||||
|
||||
let extractBody
|
||||
|
||||
const nodeVersion = process.versions.node.split('.')
|
||||
const nodeMajor = Number(nodeVersion[0])
|
||||
const nodeMinor = Number(nodeVersion[1])
|
||||
|
||||
try {
|
||||
const diagnosticsChannel = require('diagnostics_channel')
|
||||
channels.create = diagnosticsChannel.channel('undici:request:create')
|
||||
channels.bodySent = diagnosticsChannel.channel('undici:request:bodySent')
|
||||
channels.headers = diagnosticsChannel.channel('undici:request:headers')
|
||||
channels.trailers = diagnosticsChannel.channel('undici:request:trailers')
|
||||
channels.error = diagnosticsChannel.channel('undici:request:error')
|
||||
} catch {
|
||||
channels.create = { hasSubscribers: false }
|
||||
channels.bodySent = { hasSubscribers: false }
|
||||
channels.headers = { hasSubscribers: false }
|
||||
channels.trailers = { hasSubscribers: false }
|
||||
channels.error = { hasSubscribers: false }
|
||||
}
|
||||
|
||||
class Request {
|
||||
constructor (origin, {
|
||||
path,
|
||||
method,
|
||||
body,
|
||||
headers,
|
||||
query,
|
||||
idempotent,
|
||||
blocking,
|
||||
upgrade,
|
||||
headersTimeout,
|
||||
bodyTimeout,
|
||||
throwOnError
|
||||
}, handler) {
|
||||
if (typeof path !== 'string') {
|
||||
throw new InvalidArgumentError('path must be a string')
|
||||
} else if (
|
||||
path[0] !== '/' &&
|
||||
!(path.startsWith('http://') || path.startsWith('https://')) &&
|
||||
method !== 'CONNECT'
|
||||
) {
|
||||
throw new InvalidArgumentError('path must be an absolute URL or start with a slash')
|
||||
}
|
||||
|
||||
if (typeof method !== 'string') {
|
||||
throw new InvalidArgumentError('method must be a string')
|
||||
}
|
||||
|
||||
if (upgrade && typeof upgrade !== 'string') {
|
||||
throw new InvalidArgumentError('upgrade must be a string')
|
||||
}
|
||||
|
||||
if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) {
|
||||
throw new InvalidArgumentError('invalid headersTimeout')
|
||||
}
|
||||
|
||||
if (bodyTimeout != null && (!Number.isFinite(bodyTimeout) || bodyTimeout < 0)) {
|
||||
throw new InvalidArgumentError('invalid bodyTimeout')
|
||||
}
|
||||
|
||||
this.headersTimeout = headersTimeout
|
||||
|
||||
this.bodyTimeout = bodyTimeout
|
||||
|
||||
this.throwOnError = throwOnError === true
|
||||
|
||||
this.method = method
|
||||
|
||||
if (body == null) {
|
||||
this.body = null
|
||||
} else if (util.isStream(body)) {
|
||||
this.body = body
|
||||
} else if (util.isBuffer(body)) {
|
||||
this.body = body.byteLength ? body : null
|
||||
} else if (ArrayBuffer.isView(body)) {
|
||||
this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null
|
||||
} else if (body instanceof ArrayBuffer) {
|
||||
this.body = body.byteLength ? Buffer.from(body) : null
|
||||
} else if (typeof body === 'string') {
|
||||
this.body = body.length ? Buffer.from(body) : null
|
||||
} else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) {
|
||||
this.body = body
|
||||
} else {
|
||||
throw new InvalidArgumentError('body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable')
|
||||
}
|
||||
|
||||
this.completed = false
|
||||
|
||||
this.aborted = false
|
||||
|
||||
this.upgrade = upgrade || null
|
||||
|
||||
this.path = query ? util.buildURL(path, query) : path
|
||||
|
||||
this.origin = origin
|
||||
|
||||
this.idempotent = idempotent == null
|
||||
? method === 'HEAD' || method === 'GET'
|
||||
: idempotent
|
||||
|
||||
this.blocking = blocking == null ? false : blocking
|
||||
|
||||
this.host = null
|
||||
|
||||
this.contentLength = null
|
||||
|
||||
this.contentType = null
|
||||
|
||||
this.headers = ''
|
||||
|
||||
if (Array.isArray(headers)) {
|
||||
if (headers.length % 2 !== 0) {
|
||||
throw new InvalidArgumentError('headers array must be even')
|
||||
}
|
||||
for (let i = 0; i < headers.length; i += 2) {
|
||||
processHeader(this, headers[i], headers[i + 1])
|
||||
}
|
||||
} else if (headers && typeof headers === 'object') {
|
||||
const keys = Object.keys(headers)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
processHeader(this, key, headers[key])
|
||||
}
|
||||
} else if (headers != null) {
|
||||
throw new InvalidArgumentError('headers must be an object or an array')
|
||||
}
|
||||
|
||||
if (util.isFormDataLike(this.body)) {
|
||||
if (nodeMajor < 16 || (nodeMajor === 16 && nodeMinor < 5)) {
|
||||
throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.5 and newer.')
|
||||
}
|
||||
|
||||
if (!extractBody) {
|
||||
extractBody = require('../fetch/body.js').extractBody
|
||||
}
|
||||
|
||||
const [bodyStream, contentType] = extractBody(body)
|
||||
if (this.contentType == null) {
|
||||
this.contentType = contentType
|
||||
this.headers += `content-type: ${contentType}\r\n`
|
||||
}
|
||||
this.body = bodyStream.stream
|
||||
} else if (util.isBlobLike(body) && this.contentType == null && body.type) {
|
||||
this.contentType = body.type
|
||||
this.headers += `content-type: ${body.type}\r\n`
|
||||
}
|
||||
|
||||
util.validateHandler(handler, method, upgrade)
|
||||
|
||||
this.servername = util.getServerName(this.host)
|
||||
|
||||
this[kHandler] = handler
|
||||
|
||||
if (channels.create.hasSubscribers) {
|
||||
channels.create.publish({ request: this })
|
||||
}
|
||||
}
|
||||
|
||||
onBodySent (chunk) {
|
||||
if (this[kHandler].onBodySent) {
|
||||
try {
|
||||
this[kHandler].onBodySent(chunk)
|
||||
} catch (err) {
|
||||
this.onError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRequestSent () {
|
||||
if (channels.bodySent.hasSubscribers) {
|
||||
channels.bodySent.publish({ request: this })
|
||||
}
|
||||
}
|
||||
|
||||
onConnect (abort) {
|
||||
assert(!this.aborted)
|
||||
assert(!this.completed)
|
||||
|
||||
return this[kHandler].onConnect(abort)
|
||||
}
|
||||
|
||||
onHeaders (statusCode, headers, resume, statusText) {
|
||||
assert(!this.aborted)
|
||||
assert(!this.completed)
|
||||
|
||||
if (channels.headers.hasSubscribers) {
|
||||
channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
|
||||
}
|
||||
|
||||
return this[kHandler].onHeaders(statusCode, headers, resume, statusText)
|
||||
}
|
||||
|
||||
onData (chunk) {
|
||||
assert(!this.aborted)
|
||||
assert(!this.completed)
|
||||
|
||||
return this[kHandler].onData(chunk)
|
||||
}
|
||||
|
||||
onUpgrade (statusCode, headers, socket) {
|
||||
assert(!this.aborted)
|
||||
assert(!this.completed)
|
||||
|
||||
return this[kHandler].onUpgrade(statusCode, headers, socket)
|
||||
}
|
||||
|
||||
onComplete (trailers) {
|
||||
assert(!this.aborted)
|
||||
|
||||
this.completed = true
|
||||
if (channels.trailers.hasSubscribers) {
|
||||
channels.trailers.publish({ request: this, trailers })
|
||||
}
|
||||
return this[kHandler].onComplete(trailers)
|
||||
}
|
||||
|
||||
onError (error) {
|
||||
if (channels.error.hasSubscribers) {
|
||||
channels.error.publish({ request: this, error })
|
||||
}
|
||||
|
||||
if (this.aborted) {
|
||||
return
|
||||
}
|
||||
this.aborted = true
|
||||
return this[kHandler].onError(error)
|
||||
}
|
||||
|
||||
addHeader (key, value) {
|
||||
processHeader(this, key, value)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
function processHeader (request, key, val) {
|
||||
if (val && typeof val === 'object') {
|
||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||
} else if (val === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
request.host === null &&
|
||||
key.length === 4 &&
|
||||
key.toLowerCase() === 'host'
|
||||
) {
|
||||
// Consumed by Client
|
||||
request.host = val
|
||||
} else if (
|
||||
request.contentLength === null &&
|
||||
key.length === 14 &&
|
||||
key.toLowerCase() === 'content-length'
|
||||
) {
|
||||
request.contentLength = parseInt(val, 10)
|
||||
if (!Number.isFinite(request.contentLength)) {
|
||||
throw new InvalidArgumentError('invalid content-length header')
|
||||
}
|
||||
} else if (
|
||||
request.contentType === null &&
|
||||
key.length === 12 &&
|
||||
key.toLowerCase() === 'content-type'
|
||||
) {
|
||||
request.contentType = val
|
||||
request.headers += `${key}: ${val}\r\n`
|
||||
} else if (
|
||||
key.length === 17 &&
|
||||
key.toLowerCase() === 'transfer-encoding'
|
||||
) {
|
||||
throw new InvalidArgumentError('invalid transfer-encoding header')
|
||||
} else if (
|
||||
key.length === 10 &&
|
||||
key.toLowerCase() === 'connection'
|
||||
) {
|
||||
throw new InvalidArgumentError('invalid connection header')
|
||||
} else if (
|
||||
key.length === 10 &&
|
||||
key.toLowerCase() === 'keep-alive'
|
||||
) {
|
||||
throw new InvalidArgumentError('invalid keep-alive header')
|
||||
} else if (
|
||||
key.length === 7 &&
|
||||
key.toLowerCase() === 'upgrade'
|
||||
) {
|
||||
throw new InvalidArgumentError('invalid upgrade header')
|
||||
} else if (
|
||||
key.length === 6 &&
|
||||
key.toLowerCase() === 'expect'
|
||||
) {
|
||||
throw new NotSupportedError('expect header not supported')
|
||||
} else {
|
||||
request.headers += `${key}: ${val}\r\n`
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Request
|
52
node_modules/undici/lib/core/symbols.js
generated
vendored
Normal file
52
node_modules/undici/lib/core/symbols.js
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
module.exports = {
|
||||
kClose: Symbol('close'),
|
||||
kDestroy: Symbol('destroy'),
|
||||
kDispatch: Symbol('dispatch'),
|
||||
kUrl: Symbol('url'),
|
||||
kWriting: Symbol('writing'),
|
||||
kResuming: Symbol('resuming'),
|
||||
kQueue: Symbol('queue'),
|
||||
kConnect: Symbol('connect'),
|
||||
kConnecting: Symbol('connecting'),
|
||||
kHeadersList: Symbol('headers list'),
|
||||
kKeepAliveDefaultTimeout: Symbol('default keep alive timeout'),
|
||||
kKeepAliveMaxTimeout: Symbol('max keep alive timeout'),
|
||||
kKeepAliveTimeoutThreshold: Symbol('keep alive timeout threshold'),
|
||||
kKeepAliveTimeoutValue: Symbol('keep alive timeout'),
|
||||
kKeepAlive: Symbol('keep alive'),
|
||||
kHeadersTimeout: Symbol('headers timeout'),
|
||||
kBodyTimeout: Symbol('body timeout'),
|
||||
kServerName: Symbol('server name'),
|
||||
kHost: Symbol('host'),
|
||||
kNoRef: Symbol('no ref'),
|
||||
kBodyUsed: Symbol('used'),
|
||||
kRunning: Symbol('running'),
|
||||
kBlocking: Symbol('blocking'),
|
||||
kPending: Symbol('pending'),
|
||||
kSize: Symbol('size'),
|
||||
kBusy: Symbol('busy'),
|
||||
kQueued: Symbol('queued'),
|
||||
kFree: Symbol('free'),
|
||||
kConnected: Symbol('connected'),
|
||||
kClosed: Symbol('closed'),
|
||||
kNeedDrain: Symbol('need drain'),
|
||||
kReset: Symbol('reset'),
|
||||
kDestroyed: Symbol('destroyed'),
|
||||
kMaxHeadersSize: Symbol('max headers size'),
|
||||
kRunningIdx: Symbol('running index'),
|
||||
kPendingIdx: Symbol('pending index'),
|
||||
kError: Symbol('error'),
|
||||
kClients: Symbol('clients'),
|
||||
kClient: Symbol('client'),
|
||||
kParser: Symbol('parser'),
|
||||
kOnDestroyed: Symbol('destroy callbacks'),
|
||||
kPipelining: Symbol('pipelinig'),
|
||||
kSocket: Symbol('socket'),
|
||||
kHostHeader: Symbol('host header'),
|
||||
kConnector: Symbol('connector'),
|
||||
kStrictContentLength: Symbol('strict content length'),
|
||||
kMaxRedirections: Symbol('maxRedirections'),
|
||||
kMaxRequests: Symbol('maxRequestsPerClient'),
|
||||
kProxy: Symbol('proxy agent options'),
|
||||
kCounter: Symbol('socket request counter')
|
||||
}
|
407
node_modules/undici/lib/core/util.js
generated
vendored
Normal file
407
node_modules/undici/lib/core/util.js
generated
vendored
Normal file
|
@ -0,0 +1,407 @@
|
|||
'use strict'
|
||||
|
||||
const assert = require('assert')
|
||||
const { kDestroyed, kBodyUsed } = require('./symbols')
|
||||
const { IncomingMessage } = require('http')
|
||||
const stream = require('stream')
|
||||
const net = require('net')
|
||||
const { InvalidArgumentError } = require('./errors')
|
||||
const { Blob } = require('buffer')
|
||||
const nodeUtil = require('util')
|
||||
|
||||
function nop () {}
|
||||
|
||||
function isStream (obj) {
|
||||
return obj && typeof obj.pipe === 'function'
|
||||
}
|
||||
|
||||
// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
|
||||
function isBlobLike (object) {
|
||||
return (Blob && object instanceof Blob) || (
|
||||
object &&
|
||||
typeof object === 'object' &&
|
||||
(typeof object.stream === 'function' ||
|
||||
typeof object.arrayBuffer === 'function') &&
|
||||
/^(Blob|File)$/.test(object[Symbol.toStringTag])
|
||||
)
|
||||
}
|
||||
|
||||
function isObject (val) {
|
||||
return val !== null && typeof val === 'object'
|
||||
}
|
||||
|
||||
// this escapes all non-uri friendly characters
|
||||
function encode (val) {
|
||||
return encodeURIComponent(val)
|
||||
}
|
||||
|
||||
// based on https://github.com/axios/axios/blob/63e559fa609c40a0a460ae5d5a18c3470ffc6c9e/lib/helpers/buildURL.js (MIT license)
|
||||
function buildURL (url, queryParams) {
|
||||
if (url.includes('?') || url.includes('#')) {
|
||||
throw new Error('Query params cannot be passed when url already contains "?" or "#".')
|
||||
}
|
||||
if (!isObject(queryParams)) {
|
||||
throw new Error('Query params must be an object')
|
||||
}
|
||||
|
||||
const parts = []
|
||||
for (let [key, val] of Object.entries(queryParams)) {
|
||||
if (val === null || typeof val === 'undefined') {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!Array.isArray(val)) {
|
||||
val = [val]
|
||||
}
|
||||
|
||||
for (const v of val) {
|
||||
if (isObject(v)) {
|
||||
throw new Error('Passing object as a query param is not supported, please serialize to string up-front')
|
||||
}
|
||||
parts.push(encode(key) + '=' + encode(v))
|
||||
}
|
||||
}
|
||||
|
||||
const serializedParams = parts.join('&')
|
||||
|
||||
if (serializedParams) {
|
||||
url += '?' + serializedParams
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
function parseURL (url) {
|
||||
if (typeof url === 'string') {
|
||||
url = new URL(url)
|
||||
}
|
||||
|
||||
if (!url || typeof url !== 'object') {
|
||||
throw new InvalidArgumentError('invalid url')
|
||||
}
|
||||
|
||||
if (url.port != null && url.port !== '' && !Number.isFinite(parseInt(url.port))) {
|
||||
throw new InvalidArgumentError('invalid port')
|
||||
}
|
||||
|
||||
if (url.path != null && typeof url.path !== 'string') {
|
||||
throw new InvalidArgumentError('invalid path')
|
||||
}
|
||||
|
||||
if (url.pathname != null && typeof url.pathname !== 'string') {
|
||||
throw new InvalidArgumentError('invalid pathname')
|
||||
}
|
||||
|
||||
if (url.hostname != null && typeof url.hostname !== 'string') {
|
||||
throw new InvalidArgumentError('invalid hostname')
|
||||
}
|
||||
|
||||
if (url.origin != null && typeof url.origin !== 'string') {
|
||||
throw new InvalidArgumentError('invalid origin')
|
||||
}
|
||||
|
||||
if (!/^https?:/.test(url.origin || url.protocol)) {
|
||||
throw new InvalidArgumentError('invalid protocol')
|
||||
}
|
||||
|
||||
if (!(url instanceof URL)) {
|
||||
const port = url.port != null
|
||||
? url.port
|
||||
: (url.protocol === 'https:' ? 443 : 80)
|
||||
const origin = url.origin != null
|
||||
? url.origin
|
||||
: `${url.protocol}//${url.hostname}:${port}`
|
||||
const path = url.path != null
|
||||
? url.path
|
||||
: `${url.pathname || ''}${url.search || ''}`
|
||||
|
||||
url = new URL(path, origin)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
function parseOrigin (url) {
|
||||
url = parseURL(url)
|
||||
|
||||
if (url.pathname !== '/' || url.search || url.hash) {
|
||||
throw new InvalidArgumentError('invalid url')
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
function getHostname (host) {
|
||||
if (host[0] === '[') {
|
||||
const idx = host.indexOf(']')
|
||||
|
||||
assert(idx !== -1)
|
||||
return host.substr(1, idx - 1)
|
||||
}
|
||||
|
||||
const idx = host.indexOf(':')
|
||||
if (idx === -1) return host
|
||||
|
||||
return host.substr(0, idx)
|
||||
}
|
||||
|
||||
// IP addresses are not valid server names per RFC6066
|
||||
// > Currently, the only server names supported are DNS hostnames
|
||||
function getServerName (host) {
|
||||
if (!host) {
|
||||
return null
|
||||
}
|
||||
|
||||
assert.strictEqual(typeof host, 'string')
|
||||
|
||||
const servername = getHostname(host)
|
||||
if (net.isIP(servername)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return servername
|
||||
}
|
||||
|
||||
function deepClone (obj) {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
}
|
||||
|
||||
function isAsyncIterable (obj) {
|
||||
return !!(obj != null && typeof obj[Symbol.asyncIterator] === 'function')
|
||||
}
|
||||
|
||||
function isIterable (obj) {
|
||||
return !!(obj != null && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function'))
|
||||
}
|
||||
|
||||
function bodyLength (body) {
|
||||
if (body == null) {
|
||||
return 0
|
||||
} else if (isStream(body)) {
|
||||
const state = body._readableState
|
||||
return state && state.ended === true && Number.isFinite(state.length)
|
||||
? state.length
|
||||
: null
|
||||
} else if (isBlobLike(body)) {
|
||||
return body.size != null ? body.size : null
|
||||
} else if (isBuffer(body)) {
|
||||
return body.byteLength
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function isDestroyed (stream) {
|
||||
return !stream || !!(stream.destroyed || stream[kDestroyed])
|
||||
}
|
||||
|
||||
function isReadableAborted (stream) {
|
||||
const state = stream && stream._readableState
|
||||
return isDestroyed(stream) && state && !state.endEmitted
|
||||
}
|
||||
|
||||
function destroy (stream, err) {
|
||||
if (!isStream(stream) || isDestroyed(stream)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof stream.destroy === 'function') {
|
||||
if (Object.getPrototypeOf(stream).constructor === IncomingMessage) {
|
||||
// See: https://github.com/nodejs/node/pull/38505/files
|
||||
stream.socket = null
|
||||
}
|
||||
stream.destroy(err)
|
||||
} else if (err) {
|
||||
process.nextTick((stream, err) => {
|
||||
stream.emit('error', err)
|
||||
}, stream, err)
|
||||
}
|
||||
|
||||
if (stream.destroyed !== true) {
|
||||
stream[kDestroyed] = true
|
||||
}
|
||||
}
|
||||
|
||||
const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/
|
||||
function parseKeepAliveTimeout (val) {
|
||||
const m = val.toString().match(KEEPALIVE_TIMEOUT_EXPR)
|
||||
return m ? parseInt(m[1], 10) * 1000 : null
|
||||
}
|
||||
|
||||
function parseHeaders (headers, obj = {}) {
|
||||
for (let i = 0; i < headers.length; i += 2) {
|
||||
const key = headers[i].toString().toLowerCase()
|
||||
let val = obj[key]
|
||||
if (!val) {
|
||||
obj[key] = headers[i + 1].toString()
|
||||
} else {
|
||||
if (!Array.isArray(val)) {
|
||||
val = [val]
|
||||
obj[key] = val
|
||||
}
|
||||
val.push(headers[i + 1].toString())
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
function parseRawHeaders (headers) {
|
||||
return headers.map(header => header.toString())
|
||||
}
|
||||
|
||||
function isBuffer (buffer) {
|
||||
// See, https://github.com/mcollina/undici/pull/319
|
||||
return buffer instanceof Uint8Array || Buffer.isBuffer(buffer)
|
||||
}
|
||||
|
||||
function validateHandler (handler, method, upgrade) {
|
||||
if (!handler || typeof handler !== 'object') {
|
||||
throw new InvalidArgumentError('handler must be an object')
|
||||
}
|
||||
|
||||
if (typeof handler.onConnect !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onConnect method')
|
||||
}
|
||||
|
||||
if (typeof handler.onError !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onError method')
|
||||
}
|
||||
|
||||
if (typeof handler.onBodySent !== 'function' && handler.onBodySent !== undefined) {
|
||||
throw new InvalidArgumentError('invalid onBodySent method')
|
||||
}
|
||||
|
||||
if (upgrade || method === 'CONNECT') {
|
||||
if (typeof handler.onUpgrade !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onUpgrade method')
|
||||
}
|
||||
} else {
|
||||
if (typeof handler.onHeaders !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onHeaders method')
|
||||
}
|
||||
|
||||
if (typeof handler.onData !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onData method')
|
||||
}
|
||||
|
||||
if (typeof handler.onComplete !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onComplete method')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A body is disturbed if it has been read from and it cannot
|
||||
// be re-used without losing state or data.
|
||||
function isDisturbed (body) {
|
||||
return !!(body && (
|
||||
stream.isDisturbed
|
||||
? stream.isDisturbed(body) || body[kBodyUsed] // TODO (fix): Why is body[kBodyUsed] needed?
|
||||
: body[kBodyUsed] ||
|
||||
body.readableDidRead ||
|
||||
(body._readableState && body._readableState.dataEmitted) ||
|
||||
isReadableAborted(body)
|
||||
))
|
||||
}
|
||||
|
||||
function isErrored (body) {
|
||||
return !!(body && (
|
||||
stream.isErrored
|
||||
? stream.isErrored(body)
|
||||
: /state: 'errored'/.test(nodeUtil.inspect(body)
|
||||
)))
|
||||
}
|
||||
|
||||
function isReadable (body) {
|
||||
return !!(body && (
|
||||
stream.isReadable
|
||||
? stream.isReadable(body)
|
||||
: /state: 'readable'/.test(nodeUtil.inspect(body)
|
||||
)))
|
||||
}
|
||||
|
||||
function getSocketInfo (socket) {
|
||||
return {
|
||||
localAddress: socket.localAddress,
|
||||
localPort: socket.localPort,
|
||||
remoteAddress: socket.remoteAddress,
|
||||
remotePort: socket.remotePort,
|
||||
remoteFamily: socket.remoteFamily,
|
||||
timeout: socket.timeout,
|
||||
bytesWritten: socket.bytesWritten,
|
||||
bytesRead: socket.bytesRead
|
||||
}
|
||||
}
|
||||
|
||||
let ReadableStream
|
||||
function ReadableStreamFrom (iterable) {
|
||||
if (!ReadableStream) {
|
||||
ReadableStream = require('stream/web').ReadableStream
|
||||
}
|
||||
|
||||
if (ReadableStream.from) {
|
||||
// https://github.com/whatwg/streams/pull/1083
|
||||
return ReadableStream.from(iterable)
|
||||
}
|
||||
|
||||
let iterator
|
||||
return new ReadableStream(
|
||||
{
|
||||
async start () {
|
||||
iterator = iterable[Symbol.asyncIterator]()
|
||||
},
|
||||
async pull (controller) {
|
||||
const { done, value } = await iterator.next()
|
||||
if (done) {
|
||||
queueMicrotask(() => {
|
||||
controller.close()
|
||||
})
|
||||
} else {
|
||||
const buf = Buffer.isBuffer(value) ? value : Buffer.from(value)
|
||||
controller.enqueue(new Uint8Array(buf))
|
||||
}
|
||||
return controller.desiredSize > 0
|
||||
},
|
||||
async cancel (reason) {
|
||||
await iterator.return()
|
||||
}
|
||||
},
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
function isFormDataLike (chunk) {
|
||||
return chunk && chunk.constructor && chunk.constructor.name === 'FormData'
|
||||
}
|
||||
|
||||
const kEnumerableProperty = Object.create(null)
|
||||
kEnumerableProperty.enumerable = true
|
||||
|
||||
module.exports = {
|
||||
kEnumerableProperty,
|
||||
nop,
|
||||
isDisturbed,
|
||||
isErrored,
|
||||
isReadable,
|
||||
toUSVString: nodeUtil.toUSVString || ((val) => `${val}`),
|
||||
isReadableAborted,
|
||||
isBlobLike,
|
||||
parseOrigin,
|
||||
parseURL,
|
||||
getServerName,
|
||||
isStream,
|
||||
isIterable,
|
||||
isAsyncIterable,
|
||||
isDestroyed,
|
||||
parseRawHeaders,
|
||||
parseHeaders,
|
||||
parseKeepAliveTimeout,
|
||||
destroy,
|
||||
bodyLength,
|
||||
deepClone,
|
||||
ReadableStreamFrom,
|
||||
isBuffer,
|
||||
validateHandler,
|
||||
getSocketInfo,
|
||||
isFormDataLike,
|
||||
buildURL
|
||||
}
|
159
node_modules/undici/lib/dispatcher-base.js
generated
vendored
Normal file
159
node_modules/undici/lib/dispatcher-base.js
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
'use strict'
|
||||
|
||||
const Dispatcher = require('./dispatcher')
|
||||
const {
|
||||
ClientDestroyedError,
|
||||
ClientClosedError,
|
||||
InvalidArgumentError
|
||||
} = require('./core/errors')
|
||||
const { kDestroy, kClose, kDispatch } = require('./core/symbols')
|
||||
|
||||
const kDestroyed = Symbol('destroyed')
|
||||
const kClosed = Symbol('closed')
|
||||
const kOnDestroyed = Symbol('onDestroyed')
|
||||
const kOnClosed = Symbol('onClosed')
|
||||
|
||||
class DispatcherBase extends Dispatcher {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this[kDestroyed] = false
|
||||
this[kOnDestroyed] = []
|
||||
this[kClosed] = false
|
||||
this[kOnClosed] = []
|
||||
}
|
||||
|
||||
get destroyed () {
|
||||
return this[kDestroyed]
|
||||
}
|
||||
|
||||
get closed () {
|
||||
return this[kClosed]
|
||||
}
|
||||
|
||||
close (callback) {
|
||||
if (callback === undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.close((err, data) => {
|
||||
return err ? reject(err) : resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
throw new InvalidArgumentError('invalid callback')
|
||||
}
|
||||
|
||||
if (this[kDestroyed]) {
|
||||
queueMicrotask(() => callback(new ClientDestroyedError(), null))
|
||||
return
|
||||
}
|
||||
|
||||
if (this[kClosed]) {
|
||||
if (this[kOnClosed]) {
|
||||
this[kOnClosed].push(callback)
|
||||
} else {
|
||||
queueMicrotask(() => callback(null, null))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this[kClosed] = true
|
||||
this[kOnClosed].push(callback)
|
||||
|
||||
const onClosed = () => {
|
||||
const callbacks = this[kOnClosed]
|
||||
this[kOnClosed] = null
|
||||
for (let i = 0; i < callbacks.length; i++) {
|
||||
callbacks[i](null, null)
|
||||
}
|
||||
}
|
||||
|
||||
// Should not error.
|
||||
this[kClose]()
|
||||
.then(() => this.destroy())
|
||||
.then(() => {
|
||||
queueMicrotask(onClosed)
|
||||
})
|
||||
}
|
||||
|
||||
destroy (err, callback) {
|
||||
if (typeof err === 'function') {
|
||||
callback = err
|
||||
err = null
|
||||
}
|
||||
|
||||
if (callback === undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.destroy(err, (err, data) => {
|
||||
return err ? /* istanbul ignore next: should never error */ reject(err) : resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
throw new InvalidArgumentError('invalid callback')
|
||||
}
|
||||
|
||||
if (this[kDestroyed]) {
|
||||
if (this[kOnDestroyed]) {
|
||||
this[kOnDestroyed].push(callback)
|
||||
} else {
|
||||
queueMicrotask(() => callback(null, null))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!err) {
|
||||
err = new ClientDestroyedError()
|
||||
}
|
||||
|
||||
this[kDestroyed] = true
|
||||
this[kOnDestroyed].push(callback)
|
||||
|
||||
const onDestroyed = () => {
|
||||
const callbacks = this[kOnDestroyed]
|
||||
this[kOnDestroyed] = null
|
||||
for (let i = 0; i < callbacks.length; i++) {
|
||||
callbacks[i](null, null)
|
||||
}
|
||||
}
|
||||
|
||||
// Should not error.
|
||||
this[kDestroy](err).then(() => {
|
||||
queueMicrotask(onDestroyed)
|
||||
})
|
||||
}
|
||||
|
||||
dispatch (opts, handler) {
|
||||
if (!handler || typeof handler !== 'object') {
|
||||
throw new InvalidArgumentError('handler must be an object')
|
||||
}
|
||||
|
||||
try {
|
||||
if (!opts || typeof opts !== 'object') {
|
||||
throw new InvalidArgumentError('opts must be an object.')
|
||||
}
|
||||
|
||||
if (this[kDestroyed]) {
|
||||
throw new ClientDestroyedError()
|
||||
}
|
||||
|
||||
if (this[kClosed]) {
|
||||
throw new ClientClosedError()
|
||||
}
|
||||
|
||||
return this[kDispatch](opts, handler)
|
||||
} catch (err) {
|
||||
if (typeof handler.onError !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onError method')
|
||||
}
|
||||
|
||||
handler.onError(err)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DispatcherBase
|
19
node_modules/undici/lib/dispatcher.js
generated
vendored
Normal file
19
node_modules/undici/lib/dispatcher.js
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
'use strict'
|
||||
|
||||
const EventEmitter = require('events')
|
||||
|
||||
class Dispatcher extends EventEmitter {
|
||||
dispatch () {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
|
||||
close () {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
|
||||
destroy () {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Dispatcher
|
21
node_modules/undici/lib/fetch/LICENSE
generated
vendored
Normal file
21
node_modules/undici/lib/fetch/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Ethan Arrowood
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
402
node_modules/undici/lib/fetch/body.js
generated
vendored
Normal file
402
node_modules/undici/lib/fetch/body.js
generated
vendored
Normal file
|
@ -0,0 +1,402 @@
|
|||
'use strict'
|
||||
|
||||
const util = require('../core/util')
|
||||
const { ReadableStreamFrom, toUSVString, isBlobLike } = require('./util')
|
||||
const { FormData } = require('./formdata')
|
||||
const { kState } = require('./symbols')
|
||||
const { webidl } = require('./webidl')
|
||||
const { Blob } = require('buffer')
|
||||
const { kBodyUsed } = require('../core/symbols')
|
||||
const assert = require('assert')
|
||||
const { NotSupportedError } = require('../core/errors')
|
||||
const { isErrored } = require('../core/util')
|
||||
const { isUint8Array, isArrayBuffer } = require('util/types')
|
||||
|
||||
let ReadableStream
|
||||
|
||||
async function * blobGen (blob) {
|
||||
if (blob.stream) {
|
||||
yield * blob.stream()
|
||||
} else {
|
||||
// istanbul ignore next: node < 16.7
|
||||
yield await blob.arrayBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
||||
function extractBody (object, keepalive = false) {
|
||||
if (!ReadableStream) {
|
||||
ReadableStream = require('stream/web').ReadableStream
|
||||
}
|
||||
|
||||
// 1. Let stream be object if object is a ReadableStream object.
|
||||
// Otherwise, let stream be a new ReadableStream, and set up stream.
|
||||
let stream = null
|
||||
|
||||
// 2. Let action be null.
|
||||
let action = null
|
||||
|
||||
// 3. Let source be null.
|
||||
let source = null
|
||||
|
||||
// 4. Let length be null.
|
||||
let length = null
|
||||
|
||||
// 5. Let Content-Type be null.
|
||||
let contentType = null
|
||||
|
||||
// 6. Switch on object:
|
||||
if (object == null) {
|
||||
// Note: The IDL processor cannot handle this situation. See
|
||||
// https://crbug.com/335871.
|
||||
} else if (object instanceof URLSearchParams) {
|
||||
// URLSearchParams
|
||||
|
||||
// spec says to run application/x-www-form-urlencoded on body.list
|
||||
// this is implemented in Node.js as apart of an URLSearchParams instance toString method
|
||||
// See: https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L490
|
||||
// and https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L1100
|
||||
|
||||
// Set source to the result of running the application/x-www-form-urlencoded serializer with object’s list.
|
||||
source = object.toString()
|
||||
|
||||
// Set Content-Type to `application/x-www-form-urlencoded;charset=UTF-8`.
|
||||
contentType = 'application/x-www-form-urlencoded;charset=UTF-8'
|
||||
} else if (isArrayBuffer(object) || ArrayBuffer.isView(object)) {
|
||||
// BufferSource
|
||||
|
||||
if (object instanceof DataView) {
|
||||
// TODO: Blob doesn't seem to work with DataView?
|
||||
object = object.buffer
|
||||
}
|
||||
|
||||
// Set source to a copy of the bytes held by object.
|
||||
source = new Uint8Array(object)
|
||||
} else if (util.isFormDataLike(object)) {
|
||||
const boundary = '----formdata-undici-' + Math.random()
|
||||
const prefix = `--${boundary}\r\nContent-Disposition: form-data`
|
||||
|
||||
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||
const escape = (str) =>
|
||||
str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')
|
||||
const normalizeLinefeeds = (value) => value.replace(/\r?\n|\r/g, '\r\n')
|
||||
|
||||
// Set action to this step: run the multipart/form-data
|
||||
// encoding algorithm, with object’s entry list and UTF-8.
|
||||
action = async function * (object) {
|
||||
const enc = new TextEncoder()
|
||||
|
||||
for (const [name, value] of object) {
|
||||
if (typeof value === 'string') {
|
||||
yield enc.encode(
|
||||
prefix +
|
||||
`; name="${escape(normalizeLinefeeds(name))}"` +
|
||||
`\r\n\r\n${normalizeLinefeeds(value)}\r\n`
|
||||
)
|
||||
} else {
|
||||
yield enc.encode(
|
||||
prefix +
|
||||
`; name="${escape(normalizeLinefeeds(name))}"` +
|
||||
(value.name ? `; filename="${escape(value.name)}"` : '') +
|
||||
'\r\n' +
|
||||
`Content-Type: ${
|
||||
value.type || 'application/octet-stream'
|
||||
}\r\n\r\n`
|
||||
)
|
||||
|
||||
yield * blobGen(value)
|
||||
|
||||
yield enc.encode('\r\n')
|
||||
}
|
||||
}
|
||||
|
||||
yield enc.encode(`--${boundary}--`)
|
||||
}
|
||||
|
||||
// Set source to object.
|
||||
source = object
|
||||
|
||||
// Set length to unclear, see html/6424 for improving this.
|
||||
// TODO
|
||||
|
||||
// Set Content-Type to `multipart/form-data; boundary=`,
|
||||
// followed by the multipart/form-data boundary string generated
|
||||
// by the multipart/form-data encoding algorithm.
|
||||
contentType = 'multipart/form-data; boundary=' + boundary
|
||||
} else if (isBlobLike(object)) {
|
||||
// Blob
|
||||
|
||||
// Set action to this step: read object.
|
||||
action = blobGen
|
||||
|
||||
// Set source to object.
|
||||
source = object
|
||||
|
||||
// Set length to object’s size.
|
||||
length = object.size
|
||||
|
||||
// If object’s type attribute is not the empty byte sequence, set
|
||||
// Content-Type to its value.
|
||||
if (object.type) {
|
||||
contentType = object.type
|
||||
}
|
||||
} else if (typeof object[Symbol.asyncIterator] === 'function') {
|
||||
// If keepalive is true, then throw a TypeError.
|
||||
if (keepalive) {
|
||||
throw new TypeError('keepalive')
|
||||
}
|
||||
|
||||
// If object is disturbed or locked, then throw a TypeError.
|
||||
if (util.isDisturbed(object) || object.locked) {
|
||||
throw new TypeError(
|
||||
'Response body object should not be disturbed or locked'
|
||||
)
|
||||
}
|
||||
|
||||
stream =
|
||||
object instanceof ReadableStream ? object : ReadableStreamFrom(object)
|
||||
} else {
|
||||
// TODO: byte sequence?
|
||||
// TODO: scalar value string?
|
||||
// TODO: else?
|
||||
source = toUSVString(object)
|
||||
contentType = 'text/plain;charset=UTF-8'
|
||||
}
|
||||
|
||||
// 7. If source is a byte sequence, then set action to a
|
||||
// step that returns source and length to source’s length.
|
||||
// TODO: What is a "byte sequence?"
|
||||
if (typeof source === 'string' || util.isBuffer(source)) {
|
||||
length = Buffer.byteLength(source)
|
||||
}
|
||||
|
||||
// 8. If action is non-null, then run these steps in in parallel:
|
||||
if (action != null) {
|
||||
// Run action.
|
||||
let iterator
|
||||
stream = new ReadableStream({
|
||||
async start () {
|
||||
iterator = action(object)[Symbol.asyncIterator]()
|
||||
},
|
||||
async pull (controller) {
|
||||
const { value, done } = await iterator.next()
|
||||
if (done) {
|
||||
// When running action is done, close stream.
|
||||
queueMicrotask(() => {
|
||||
controller.close()
|
||||
})
|
||||
} else {
|
||||
// Whenever one or more bytes are available and stream is not errored,
|
||||
// enqueue a Uint8Array wrapping an ArrayBuffer containing the available
|
||||
// bytes into stream.
|
||||
if (!isErrored(stream)) {
|
||||
controller.enqueue(new Uint8Array(value))
|
||||
}
|
||||
}
|
||||
return controller.desiredSize > 0
|
||||
},
|
||||
async cancel (reason) {
|
||||
await iterator.return()
|
||||
}
|
||||
})
|
||||
} else if (!stream) {
|
||||
// TODO: Spec doesn't say anything about this?
|
||||
stream = new ReadableStream({
|
||||
async pull (controller) {
|
||||
controller.enqueue(
|
||||
typeof source === 'string' ? new TextEncoder().encode(source) : source
|
||||
)
|
||||
queueMicrotask(() => {
|
||||
controller.close()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 9. Let body be a body whose stream is stream, source is source,
|
||||
// and length is length.
|
||||
const body = { stream, source, length }
|
||||
|
||||
// 10. Return body and Content-Type.
|
||||
return [body, contentType]
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#bodyinit-safely-extract
|
||||
function safelyExtractBody (object, keepalive = false) {
|
||||
if (!ReadableStream) {
|
||||
// istanbul ignore next
|
||||
ReadableStream = require('stream/web').ReadableStream
|
||||
}
|
||||
|
||||
// To safely extract a body and a `Content-Type` value from
|
||||
// a byte sequence or BodyInit object object, run these steps:
|
||||
|
||||
// 1. If object is a ReadableStream object, then:
|
||||
if (object instanceof ReadableStream) {
|
||||
// Assert: object is neither disturbed nor locked.
|
||||
// istanbul ignore next
|
||||
assert(!util.isDisturbed(object), 'disturbed')
|
||||
// istanbul ignore next
|
||||
assert(!object.locked, 'locked')
|
||||
}
|
||||
|
||||
// 2. Return the results of extracting object.
|
||||
return extractBody(object, keepalive)
|
||||
}
|
||||
|
||||
function cloneBody (body) {
|
||||
// To clone a body body, run these steps:
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-body-clone
|
||||
|
||||
// 1. Let « out1, out2 » be the result of teeing body’s stream.
|
||||
const [out1, out2] = body.stream.tee()
|
||||
|
||||
// 2. Set body’s stream to out1.
|
||||
body.stream = out1
|
||||
|
||||
// 3. Return a body whose stream is out2 and other members are copied from body.
|
||||
return {
|
||||
stream: out2,
|
||||
length: body.length,
|
||||
source: body.source
|
||||
}
|
||||
}
|
||||
|
||||
function bodyMixinMethods (instance) {
|
||||
const methods = {
|
||||
async blob () {
|
||||
if (!(this instanceof instance)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
const chunks = []
|
||||
|
||||
if (this[kState].body) {
|
||||
if (isUint8Array(this[kState].body)) {
|
||||
chunks.push(this[kState].body)
|
||||
} else {
|
||||
const stream = this[kState].body.stream
|
||||
|
||||
if (util.isDisturbed(stream)) {
|
||||
throw new TypeError('disturbed')
|
||||
}
|
||||
|
||||
if (stream.locked) {
|
||||
throw new TypeError('locked')
|
||||
}
|
||||
|
||||
// Compat.
|
||||
stream[kBodyUsed] = true
|
||||
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Blob(chunks, { type: this.headers.get('Content-Type') || '' })
|
||||
},
|
||||
|
||||
async arrayBuffer () {
|
||||
if (!(this instanceof instance)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
const blob = await this.blob()
|
||||
return await blob.arrayBuffer()
|
||||
},
|
||||
|
||||
async text () {
|
||||
if (!(this instanceof instance)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
const blob = await this.blob()
|
||||
return toUSVString(await blob.text())
|
||||
},
|
||||
|
||||
async json () {
|
||||
if (!(this instanceof instance)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return JSON.parse(await this.text())
|
||||
},
|
||||
|
||||
async formData () {
|
||||
if (!(this instanceof instance)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
const contentType = this.headers.get('Content-Type')
|
||||
|
||||
// If mimeType’s essence is "multipart/form-data", then:
|
||||
if (/multipart\/form-data/.test(contentType)) {
|
||||
throw new NotSupportedError('multipart/form-data not supported')
|
||||
} else if (/application\/x-www-form-urlencoded/.test(contentType)) {
|
||||
// Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then:
|
||||
|
||||
// 1. Let entries be the result of parsing bytes.
|
||||
let entries
|
||||
try {
|
||||
entries = new URLSearchParams(await this.text())
|
||||
} catch (err) {
|
||||
// istanbul ignore next: Unclear when new URLSearchParams can fail on a string.
|
||||
// 2. If entries is failure, then throw a TypeError.
|
||||
throw Object.assign(new TypeError(), { cause: err })
|
||||
}
|
||||
|
||||
// 3. Return a new FormData object whose entries are entries.
|
||||
const formData = new FormData()
|
||||
for (const [name, value] of entries) {
|
||||
formData.append(name, value)
|
||||
}
|
||||
return formData
|
||||
} else {
|
||||
// Otherwise, throw a TypeError.
|
||||
webidl.errors.exception({
|
||||
header: `${instance.name}.formData`,
|
||||
value: 'Could not parse content as FormData.'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
const properties = {
|
||||
body: {
|
||||
enumerable: true,
|
||||
get () {
|
||||
if (!this || !this[kState]) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].body ? this[kState].body.stream : null
|
||||
}
|
||||
},
|
||||
bodyUsed: {
|
||||
enumerable: true,
|
||||
get () {
|
||||
if (!this || !this[kState]) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return !!this[kState].body && util.isDisturbed(this[kState].body.stream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mixinBody (prototype) {
|
||||
Object.assign(prototype.prototype, bodyMixinMethods(prototype))
|
||||
Object.defineProperties(prototype.prototype, properties)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extractBody,
|
||||
safelyExtractBody,
|
||||
cloneBody,
|
||||
mixinBody
|
||||
}
|
88
node_modules/undici/lib/fetch/constants.js
generated
vendored
Normal file
88
node_modules/undici/lib/fetch/constants.js
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
'use strict'
|
||||
|
||||
const corsSafeListedMethods = ['GET', 'HEAD', 'POST']
|
||||
|
||||
const nullBodyStatus = [101, 204, 205, 304]
|
||||
|
||||
const redirectStatus = [301, 302, 303, 307, 308]
|
||||
|
||||
const referrerPolicy = [
|
||||
'',
|
||||
'no-referrer',
|
||||
'no-referrer-when-downgrade',
|
||||
'same-origin',
|
||||
'origin',
|
||||
'strict-origin',
|
||||
'origin-when-cross-origin',
|
||||
'strict-origin-when-cross-origin',
|
||||
'unsafe-url'
|
||||
]
|
||||
|
||||
const requestRedirect = ['follow', 'manual', 'error']
|
||||
|
||||
const safeMethods = ['GET', 'HEAD', 'OPTIONS', 'TRACE']
|
||||
|
||||
const requestMode = ['navigate', 'same-origin', 'no-cors', 'cors']
|
||||
|
||||
const requestCredentials = ['omit', 'same-origin', 'include']
|
||||
|
||||
const requestCache = [
|
||||
'default',
|
||||
'no-store',
|
||||
'reload',
|
||||
'no-cache',
|
||||
'force-cache',
|
||||
'only-if-cached'
|
||||
]
|
||||
|
||||
const requestBodyHeader = [
|
||||
'content-encoding',
|
||||
'content-language',
|
||||
'content-location',
|
||||
'content-type'
|
||||
]
|
||||
|
||||
// http://fetch.spec.whatwg.org/#forbidden-method
|
||||
const forbiddenMethods = ['CONNECT', 'TRACE', 'TRACK']
|
||||
|
||||
const subresource = [
|
||||
'audio',
|
||||
'audioworklet',
|
||||
'font',
|
||||
'image',
|
||||
'manifest',
|
||||
'paintworklet',
|
||||
'script',
|
||||
'style',
|
||||
'track',
|
||||
'video',
|
||||
'xslt',
|
||||
''
|
||||
]
|
||||
|
||||
/** @type {globalThis['DOMException']} */
|
||||
const DOMException = globalThis.DOMException ?? (() => {
|
||||
// DOMException was only made a global in Node v17.0.0,
|
||||
// but fetch supports >= v16.5.
|
||||
try {
|
||||
atob('~')
|
||||
} catch (err) {
|
||||
return Object.getPrototypeOf(err).constructor
|
||||
}
|
||||
})()
|
||||
|
||||
module.exports = {
|
||||
DOMException,
|
||||
subresource,
|
||||
forbiddenMethods,
|
||||
requestBodyHeader,
|
||||
referrerPolicy,
|
||||
requestRedirect,
|
||||
requestMode,
|
||||
requestCredentials,
|
||||
requestCache,
|
||||
redirectStatus,
|
||||
corsSafeListedMethods,
|
||||
nullBodyStatus,
|
||||
safeMethods
|
||||
}
|
557
node_modules/undici/lib/fetch/dataURL.js
generated
vendored
Normal file
557
node_modules/undici/lib/fetch/dataURL.js
generated
vendored
Normal file
|
@ -0,0 +1,557 @@
|
|||
const assert = require('assert')
|
||||
const { atob } = require('buffer')
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
// https://fetch.spec.whatwg.org/#data-url-processor
|
||||
/** @param {URL} dataURL */
|
||||
function dataURLProcessor (dataURL) {
|
||||
// 1. Assert: dataURL’s scheme is "data".
|
||||
assert(dataURL.protocol === 'data:')
|
||||
|
||||
// 2. Let input be the result of running the URL
|
||||
// serializer on dataURL with exclude fragment
|
||||
// set to true.
|
||||
let input = URLSerializer(dataURL, true)
|
||||
|
||||
// 3. Remove the leading "data:" string from input.
|
||||
input = input.slice(5)
|
||||
|
||||
// 4. Let position point at the start of input.
|
||||
const position = { position: 0 }
|
||||
|
||||
// 5. Let mimeType be the result of collecting a
|
||||
// sequence of code points that are not equal
|
||||
// to U+002C (,), given position.
|
||||
let mimeType = collectASequenceOfCodePoints(
|
||||
(char) => char !== ',',
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 6. Strip leading and trailing ASCII whitespace
|
||||
// from mimeType.
|
||||
// Note: This will only remove U+0020 SPACE code
|
||||
// points, if any.
|
||||
// Undici implementation note: we need to store the
|
||||
// length because if the mimetype has spaces removed,
|
||||
// the wrong amount will be sliced from the input in
|
||||
// step #9
|
||||
const mimeTypeLength = mimeType.length
|
||||
mimeType = mimeType.replace(/^(\u0020)+|(\u0020)+$/g, '')
|
||||
|
||||
// 7. If position is past the end of input, then
|
||||
// return failure
|
||||
if (position.position >= input.length) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 8. Advance position by 1.
|
||||
position.position++
|
||||
|
||||
// 9. Let encodedBody be the remainder of input.
|
||||
const encodedBody = input.slice(mimeTypeLength + 1)
|
||||
|
||||
// 10. Let body be the percent-decoding of encodedBody.
|
||||
/** @type {Uint8Array|string} */
|
||||
let body = stringPercentDecode(encodedBody)
|
||||
|
||||
// 11. If mimeType ends with U+003B (;), followed by
|
||||
// zero or more U+0020 SPACE, followed by an ASCII
|
||||
// case-insensitive match for "base64", then:
|
||||
if (/;(\u0020){0,}base64$/i.test(mimeType)) {
|
||||
// 1. Let stringBody be the isomorphic decode of body.
|
||||
const stringBody = decodeURIComponent(new TextDecoder('utf-8').decode(body))
|
||||
// 2. Set body to the forgiving-base64 decode of
|
||||
// stringBody.
|
||||
body = forgivingBase64(stringBody)
|
||||
|
||||
// 3. If body is failure, then return failure.
|
||||
if (body === 'failure') {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 4. Remove the last 6 code points from mimeType.
|
||||
mimeType = mimeType.slice(0, -6)
|
||||
|
||||
// 5. Remove trailing U+0020 SPACE code points from mimeType,
|
||||
// if any.
|
||||
mimeType = mimeType.replace(/(\u0020)+$/, '')
|
||||
|
||||
// 6. Remove the last U+003B (;) code point from mimeType.
|
||||
mimeType = mimeType.slice(0, -1)
|
||||
}
|
||||
|
||||
// 12. If mimeType starts with U+003B (;), then prepend
|
||||
// "text/plain" to mimeType.
|
||||
if (mimeType.startsWith(';')) {
|
||||
mimeType = 'text/plain' + mimeType
|
||||
}
|
||||
|
||||
// 13. Let mimeTypeRecord be the result of parsing
|
||||
// mimeType.
|
||||
let mimeTypeRecord = parseMIMEType(mimeType)
|
||||
|
||||
// 14. If mimeTypeRecord is failure, then set
|
||||
// mimeTypeRecord to text/plain;charset=US-ASCII.
|
||||
if (mimeTypeRecord === 'failure') {
|
||||
mimeTypeRecord = parseMIMEType('text/plain;charset=US-ASCII')
|
||||
}
|
||||
|
||||
// 15. Return a new data: URL struct whose MIME
|
||||
// type is mimeTypeRecord and body is body.
|
||||
// https://fetch.spec.whatwg.org/#data-url-struct
|
||||
return { mimeType: mimeTypeRecord, body }
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#concept-url-serializer
|
||||
/**
|
||||
* @param {URL} url
|
||||
* @param {boolean} excludeFragment
|
||||
*/
|
||||
function URLSerializer (url, excludeFragment = false) {
|
||||
// 1. Let output be url’s scheme and U+003A (:) concatenated.
|
||||
let output = url.protocol
|
||||
|
||||
// 2. If url’s host is non-null:
|
||||
if (url.host.length > 0) {
|
||||
// 1. Append "//" to output.
|
||||
output += '//'
|
||||
|
||||
// 2. If url includes credentials, then:
|
||||
if (url.username.length > 0 || url.password.length > 0) {
|
||||
// 1. Append url’s username to output.
|
||||
output += url.username
|
||||
|
||||
// 2. If url’s password is not the empty string, then append U+003A (:),
|
||||
// followed by url’s password, to output.
|
||||
if (url.password.length > 0) {
|
||||
output += ':' + url.password
|
||||
}
|
||||
|
||||
// 3. Append U+0040 (@) to output.
|
||||
output += '@'
|
||||
}
|
||||
|
||||
// 3. Append url’s host, serialized, to output.
|
||||
output += decodeURIComponent(url.host)
|
||||
|
||||
// 4. If url’s port is non-null, append U+003A (:) followed by url’s port,
|
||||
// serialized, to output.
|
||||
if (url.port.length > 0) {
|
||||
output += ':' + url.port
|
||||
}
|
||||
}
|
||||
|
||||
// 3. If url’s host is null, url does not have an opaque path,
|
||||
// url’s path’s size is greater than 1, and url’s path[0]
|
||||
// is the empty string, then append U+002F (/) followed by
|
||||
// U+002E (.) to output.
|
||||
// Note: This prevents web+demo:/.//not-a-host/ or web+demo:/path/..//not-a-host/,
|
||||
// when parsed and then serialized, from ending up as web+demo://not-a-host/
|
||||
// (they end up as web+demo:/.//not-a-host/).
|
||||
// Undici implementation note: url's path[0] can never be an
|
||||
// empty string, so we have to slightly alter what the spec says.
|
||||
if (
|
||||
url.host.length === 0 &&
|
||||
url.pathname.length > 1 &&
|
||||
url.href.slice(url.protocol.length + 1)[0] === '.'
|
||||
) {
|
||||
output += '/.'
|
||||
}
|
||||
|
||||
// 4. Append the result of URL path serializing url to output.
|
||||
output += url.pathname
|
||||
|
||||
// 5. If url’s query is non-null, append U+003F (?),
|
||||
// followed by url’s query, to output.
|
||||
if (url.search.length > 0) {
|
||||
output += url.search
|
||||
}
|
||||
|
||||
// 6. If exclude fragment is false and url’s fragment is non-null,
|
||||
// then append U+0023 (#), followed by url’s fragment, to output.
|
||||
if (excludeFragment === false && url.hash.length > 0) {
|
||||
output += url.hash
|
||||
}
|
||||
|
||||
// 7. Return output.
|
||||
return output
|
||||
}
|
||||
|
||||
// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
||||
/**
|
||||
* @param {(char: string) => boolean} condition
|
||||
* @param {string} input
|
||||
* @param {{ position: number }} position
|
||||
*/
|
||||
function collectASequenceOfCodePoints (condition, input, position) {
|
||||
// 1. Let result be the empty string.
|
||||
let result = ''
|
||||
|
||||
// 2. While position doesn’t point past the end of input and the
|
||||
// code point at position within input meets the condition condition:
|
||||
while (position.position < input.length && condition(input[position.position])) {
|
||||
// 1. Append that code point to the end of result.
|
||||
result += input[position.position]
|
||||
|
||||
// 2. Advance position by 1.
|
||||
position.position++
|
||||
}
|
||||
|
||||
// 3. Return result.
|
||||
return result
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#string-percent-decode
|
||||
/** @param {string} input */
|
||||
function stringPercentDecode (input) {
|
||||
// 1. Let bytes be the UTF-8 encoding of input.
|
||||
const bytes = encoder.encode(input)
|
||||
|
||||
// 2. Return the percent-decoding of bytes.
|
||||
return percentDecode(bytes)
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#percent-decode
|
||||
/** @param {Uint8Array} input */
|
||||
function percentDecode (input) {
|
||||
// 1. Let output be an empty byte sequence.
|
||||
/** @type {number[]} */
|
||||
const output = []
|
||||
|
||||
// 2. For each byte byte in input:
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const byte = input[i]
|
||||
|
||||
// 1. If byte is not 0x25 (%), then append byte to output.
|
||||
if (byte !== 0x25) {
|
||||
output.push(byte)
|
||||
|
||||
// 2. Otherwise, if byte is 0x25 (%) and the next two bytes
|
||||
// after byte in input are not in the ranges
|
||||
// 0x30 (0) to 0x39 (9), 0x41 (A) to 0x46 (F),
|
||||
// and 0x61 (a) to 0x66 (f), all inclusive, append byte
|
||||
// to output.
|
||||
} else if (
|
||||
byte === 0x25 &&
|
||||
!/^[0-9A-Fa-f]{2}$/i.test(String.fromCharCode(input[i + 1], input[i + 2]))
|
||||
) {
|
||||
output.push(0x25)
|
||||
|
||||
// 3. Otherwise:
|
||||
} else {
|
||||
// 1. Let bytePoint be the two bytes after byte in input,
|
||||
// decoded, and then interpreted as hexadecimal number.
|
||||
const nextTwoBytes = String.fromCharCode(input[i + 1], input[i + 2])
|
||||
const bytePoint = Number.parseInt(nextTwoBytes, 16)
|
||||
|
||||
// 2. Append a byte whose value is bytePoint to output.
|
||||
output.push(bytePoint)
|
||||
|
||||
// 3. Skip the next two bytes in input.
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return output.
|
||||
return Uint8Array.of(...output)
|
||||
}
|
||||
|
||||
// https://mimesniff.spec.whatwg.org/#parse-a-mime-type
|
||||
/** @param {string} input */
|
||||
function parseMIMEType (input) {
|
||||
// 1. Remove any leading and trailing HTTP whitespace
|
||||
// from input.
|
||||
input = input.trim()
|
||||
|
||||
// 2. Let position be a position variable for input,
|
||||
// initially pointing at the start of input.
|
||||
const position = { position: 0 }
|
||||
|
||||
// 3. Let type be the result of collecting a sequence
|
||||
// of code points that are not U+002F (/) from
|
||||
// input, given position.
|
||||
const type = collectASequenceOfCodePoints(
|
||||
(char) => char !== '/',
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 4. If type is the empty string or does not solely
|
||||
// contain HTTP token code points, then return failure.
|
||||
// https://mimesniff.spec.whatwg.org/#http-token-code-point
|
||||
if (type.length === 0 || !/^[!#$%&'*+-.^_|~A-z0-9]+$/.test(type)) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 5. If position is past the end of input, then return
|
||||
// failure
|
||||
if (position.position > input.length) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 6. Advance position by 1. (This skips past U+002F (/).)
|
||||
position.position++
|
||||
|
||||
// 7. Let subtype be the result of collecting a sequence of
|
||||
// code points that are not U+003B (;) from input, given
|
||||
// position.
|
||||
let subtype = collectASequenceOfCodePoints(
|
||||
(char) => char !== ';',
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 8. Remove any trailing HTTP whitespace from subtype.
|
||||
subtype = subtype.trim()
|
||||
|
||||
// 9. If subtype is the empty string or does not solely
|
||||
// contain HTTP token code points, then return failure.
|
||||
if (subtype.length === 0 || !/^[!#$%&'*+-.^_|~A-z0-9]+$/.test(subtype)) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 10. Let mimeType be a new MIME type record whose type
|
||||
// is type, in ASCII lowercase, and subtype is subtype,
|
||||
// in ASCII lowercase.
|
||||
// https://mimesniff.spec.whatwg.org/#mime-type
|
||||
const mimeType = {
|
||||
type: type.toLowerCase(),
|
||||
subtype: subtype.toLowerCase(),
|
||||
/** @type {Map<string, string>} */
|
||||
parameters: new Map()
|
||||
}
|
||||
|
||||
// 11. While position is not past the end of input:
|
||||
while (position.position < input.length) {
|
||||
// 1. Advance position by 1. (This skips past U+003B (;).)
|
||||
position.position++
|
||||
|
||||
// 2. Collect a sequence of code points that are HTTP
|
||||
// whitespace from input given position.
|
||||
collectASequenceOfCodePoints(
|
||||
// https://fetch.spec.whatwg.org/#http-whitespace
|
||||
(char) => /(\u000A|\u000D|\u0009|\u0020)/.test(char), // eslint-disable-line
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 3. Let parameterName be the result of collecting a
|
||||
// sequence of code points that are not U+003B (;)
|
||||
// or U+003D (=) from input, given position.
|
||||
let parameterName = collectASequenceOfCodePoints(
|
||||
(char) => char !== ';' && char !== '=',
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 4. Set parameterName to parameterName, in ASCII
|
||||
// lowercase.
|
||||
parameterName = parameterName.toLowerCase()
|
||||
|
||||
// 5. If position is not past the end of input, then:
|
||||
if (position.position < input.length) {
|
||||
// 1. If the code point at position within input is
|
||||
// U+003B (;), then continue.
|
||||
if (input[position.position] === ';') {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. Advance position by 1. (This skips past U+003D (=).)
|
||||
position.position++
|
||||
}
|
||||
|
||||
// 6. If position is past the end of input, then break.
|
||||
if (position.position > input.length) {
|
||||
break
|
||||
}
|
||||
|
||||
// 7. Let parameterValue be null.
|
||||
let parameterValue = null
|
||||
|
||||
// 8. If the code point at position within input is
|
||||
// U+0022 ("), then:
|
||||
if (input[position.position] === '"') {
|
||||
// 1. Set parameterValue to the result of collecting
|
||||
// an HTTP quoted string from input, given position
|
||||
// and the extract-value flag.
|
||||
// Undici implementation note: extract-value is never
|
||||
// defined or mentioned anywhere.
|
||||
parameterValue = collectAnHTTPQuotedString(input, position/*, extractValue */)
|
||||
|
||||
// 2. Collect a sequence of code points that are not
|
||||
// U+003B (;) from input, given position.
|
||||
collectASequenceOfCodePoints(
|
||||
(char) => char !== ';',
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 9. Otherwise:
|
||||
} else {
|
||||
// 1. Set parameterValue to the result of collecting
|
||||
// a sequence of code points that are not U+003B (;)
|
||||
// from input, given position.
|
||||
parameterValue = collectASequenceOfCodePoints(
|
||||
(char) => char !== ';',
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 2. Remove any trailing HTTP whitespace from parameterValue.
|
||||
parameterValue = parameterValue.trim()
|
||||
|
||||
// 3. If parameterValue is the empty string, then continue.
|
||||
if (parameterValue.length === 0) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 10. If all of the following are true
|
||||
// - parameterName is not the empty string
|
||||
// - parameterName solely contains HTTP token code points
|
||||
// - parameterValue solely contains HTTP quoted-string token code points
|
||||
// - mimeType’s parameters[parameterName] does not exist
|
||||
// then set mimeType’s parameters[parameterName] to parameterValue.
|
||||
if (
|
||||
parameterName.length !== 0 &&
|
||||
/^[!#$%&'*+-.^_|~A-z0-9]+$/.test(parameterName) &&
|
||||
// https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
|
||||
!/^(\u0009|\x{0020}-\x{007E}|\x{0080}-\x{00FF})+$/.test(parameterValue) && // eslint-disable-line
|
||||
!mimeType.parameters.has(parameterName)
|
||||
) {
|
||||
mimeType.parameters.set(parameterName, parameterValue)
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Return mimeType.
|
||||
return mimeType
|
||||
}
|
||||
|
||||
// https://infra.spec.whatwg.org/#forgiving-base64-decode
|
||||
/** @param {string} data */
|
||||
function forgivingBase64 (data) {
|
||||
// 1. Remove all ASCII whitespace from data.
|
||||
data = data.replace(/[\u0009\u000A\u000C\u000D\u0020]/g, '') // eslint-disable-line
|
||||
|
||||
// 2. If data’s code point length divides by 4 leaving
|
||||
// no remainder, then:
|
||||
if (data.length % 4 === 0) {
|
||||
// 1. If data ends with one or two U+003D (=) code points,
|
||||
// then remove them from data.
|
||||
data = data.replace(/=?=$/, '')
|
||||
}
|
||||
|
||||
// 3. If data’s code point length divides by 4 leaving
|
||||
// a remainder of 1, then return failure.
|
||||
if (data.length % 4 === 1) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// 4. If data contains a code point that is not one of
|
||||
// U+002B (+)
|
||||
// U+002F (/)
|
||||
// ASCII alphanumeric
|
||||
// then return failure.
|
||||
if (/[^+/0-9A-Za-z]/.test(data)) {
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
const binary = atob(data)
|
||||
const bytes = new Uint8Array(binary.length)
|
||||
|
||||
for (let byte = 0; byte < binary.length; byte++) {
|
||||
bytes[byte] = binary.charCodeAt(byte)
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
|
||||
// tests: https://fetch.spec.whatwg.org/#example-http-quoted-string
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {{ position: number }} position
|
||||
* @param {boolean?} extractValue
|
||||
*/
|
||||
function collectAnHTTPQuotedString (input, position, extractValue) {
|
||||
// 1. Let positionStart be position.
|
||||
const positionStart = position.position
|
||||
|
||||
// 2. Let value be the empty string.
|
||||
let value = ''
|
||||
|
||||
// 3. Assert: the code point at position within input
|
||||
// is U+0022 (").
|
||||
assert(input[position.position] === '"')
|
||||
|
||||
// 4. Advance position by 1.
|
||||
position.position++
|
||||
|
||||
// 5. While true:
|
||||
while (true) {
|
||||
// 1. Append the result of collecting a sequence of code points
|
||||
// that are not U+0022 (") or U+005C (\) from input, given
|
||||
// position, to value.
|
||||
value += collectASequenceOfCodePoints(
|
||||
(char) => char !== '"' && char !== '\\',
|
||||
input,
|
||||
position
|
||||
)
|
||||
|
||||
// 2. If position is past the end of input, then break.
|
||||
if (position.position >= input.length) {
|
||||
break
|
||||
}
|
||||
|
||||
// 3. Let quoteOrBackslash be the code point at position within
|
||||
// input.
|
||||
const quoteOrBackslash = input[position.position]
|
||||
|
||||
// 4. Advance position by 1.
|
||||
position.position++
|
||||
|
||||
// 5. If quoteOrBackslash is U+005C (\), then:
|
||||
if (quoteOrBackslash === '\\') {
|
||||
// 1. If position is past the end of input, then append
|
||||
// U+005C (\) to value and break.
|
||||
if (position.position >= input.length) {
|
||||
value += '\\'
|
||||
break
|
||||
}
|
||||
|
||||
// 2. Append the code point at position within input to value.
|
||||
value += input[position.position]
|
||||
|
||||
// 3. Advance position by 1.
|
||||
position.position++
|
||||
|
||||
// 6. Otherwise:
|
||||
} else {
|
||||
// 1. Assert: quoteOrBackslash is U+0022 (").
|
||||
assert(quoteOrBackslash === '"')
|
||||
|
||||
// 2. Break.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 6. If the extract-value flag is set, then return value.
|
||||
if (extractValue) {
|
||||
return value
|
||||
}
|
||||
|
||||
// 7. Return the code points from positionStart to position,
|
||||
// inclusive, within input.
|
||||
return input.slice(positionStart, position.position)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dataURLProcessor,
|
||||
URLSerializer,
|
||||
collectASequenceOfCodePoints,
|
||||
stringPercentDecode,
|
||||
parseMIMEType,
|
||||
collectAnHTTPQuotedString
|
||||
}
|
313
node_modules/undici/lib/fetch/file.js
generated
vendored
Normal file
313
node_modules/undici/lib/fetch/file.js
generated
vendored
Normal file
|
@ -0,0 +1,313 @@
|
|||
'use strict'
|
||||
|
||||
const { Blob } = require('buffer')
|
||||
const { types } = require('util')
|
||||
const { kState } = require('./symbols')
|
||||
const { isBlobLike } = require('./util')
|
||||
const { webidl } = require('./webidl')
|
||||
|
||||
class File extends Blob {
|
||||
constructor (fileBits, fileName, options = {}) {
|
||||
// The File constructor is invoked with two or three parameters, depending
|
||||
// on whether the optional dictionary parameter is used. When the File()
|
||||
// constructor is invoked, user agents must run the following steps:
|
||||
if (arguments.length < 2) {
|
||||
throw new TypeError('2 arguments required')
|
||||
}
|
||||
|
||||
fileBits = webidl.converters['sequence<BlobPart>'](fileBits)
|
||||
fileName = webidl.converters.USVString(fileName)
|
||||
options = webidl.converters.FilePropertyBag(options)
|
||||
|
||||
// 1. Let bytes be the result of processing blob parts given fileBits and
|
||||
// options.
|
||||
// Note: Blob handles this for us
|
||||
|
||||
// 2. Let n be the fileName argument to the constructor.
|
||||
const n = fileName
|
||||
|
||||
// 3. Process FilePropertyBag dictionary argument by running the following
|
||||
// substeps:
|
||||
|
||||
// 1. If the type member is provided and is not the empty string, let t
|
||||
// be set to the type dictionary member. If t contains any characters
|
||||
// outside the range U+0020 to U+007E, then set t to the empty string
|
||||
// and return from these substeps.
|
||||
// 2. Convert every character in t to ASCII lowercase.
|
||||
// Note: Blob handles both of these steps for us
|
||||
|
||||
// 3. If the lastModified member is provided, let d be set to the
|
||||
// lastModified dictionary member. If it is not provided, set d to the
|
||||
// current date and time represented as the number of milliseconds since
|
||||
// the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]).
|
||||
const d = options.lastModified
|
||||
|
||||
// 4. Return a new File object F such that:
|
||||
// F refers to the bytes byte sequence.
|
||||
// F.size is set to the number of total bytes in bytes.
|
||||
// F.name is set to n.
|
||||
// F.type is set to t.
|
||||
// F.lastModified is set to d.
|
||||
|
||||
super(processBlobParts(fileBits, options), { type: options.type })
|
||||
this[kState] = {
|
||||
name: n,
|
||||
lastModified: d
|
||||
}
|
||||
}
|
||||
|
||||
get name () {
|
||||
if (!(this instanceof File)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].name
|
||||
}
|
||||
|
||||
get lastModified () {
|
||||
if (!(this instanceof File)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].lastModified
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return this.constructor.name
|
||||
}
|
||||
}
|
||||
|
||||
class FileLike {
|
||||
constructor (blobLike, fileName, options = {}) {
|
||||
// TODO: argument idl type check
|
||||
|
||||
// The File constructor is invoked with two or three parameters, depending
|
||||
// on whether the optional dictionary parameter is used. When the File()
|
||||
// constructor is invoked, user agents must run the following steps:
|
||||
|
||||
// 1. Let bytes be the result of processing blob parts given fileBits and
|
||||
// options.
|
||||
|
||||
// 2. Let n be the fileName argument to the constructor.
|
||||
const n = fileName
|
||||
|
||||
// 3. Process FilePropertyBag dictionary argument by running the following
|
||||
// substeps:
|
||||
|
||||
// 1. If the type member is provided and is not the empty string, let t
|
||||
// be set to the type dictionary member. If t contains any characters
|
||||
// outside the range U+0020 to U+007E, then set t to the empty string
|
||||
// and return from these substeps.
|
||||
// TODO
|
||||
const t = options.type
|
||||
|
||||
// 2. Convert every character in t to ASCII lowercase.
|
||||
// TODO
|
||||
|
||||
// 3. If the lastModified member is provided, let d be set to the
|
||||
// lastModified dictionary member. If it is not provided, set d to the
|
||||
// current date and time represented as the number of milliseconds since
|
||||
// the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]).
|
||||
const d = options.lastModified ?? Date.now()
|
||||
|
||||
// 4. Return a new File object F such that:
|
||||
// F refers to the bytes byte sequence.
|
||||
// F.size is set to the number of total bytes in bytes.
|
||||
// F.name is set to n.
|
||||
// F.type is set to t.
|
||||
// F.lastModified is set to d.
|
||||
|
||||
this[kState] = {
|
||||
blobLike,
|
||||
name: n,
|
||||
type: t,
|
||||
lastModified: d
|
||||
}
|
||||
}
|
||||
|
||||
stream (...args) {
|
||||
if (!(this instanceof FileLike)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].blobLike.stream(...args)
|
||||
}
|
||||
|
||||
arrayBuffer (...args) {
|
||||
if (!(this instanceof FileLike)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].blobLike.arrayBuffer(...args)
|
||||
}
|
||||
|
||||
slice (...args) {
|
||||
if (!(this instanceof FileLike)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].blobLike.slice(...args)
|
||||
}
|
||||
|
||||
text (...args) {
|
||||
if (!(this instanceof FileLike)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].blobLike.text(...args)
|
||||
}
|
||||
|
||||
get size () {
|
||||
if (!(this instanceof FileLike)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].blobLike.size
|
||||
}
|
||||
|
||||
get type () {
|
||||
if (!(this instanceof FileLike)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].blobLike.type
|
||||
}
|
||||
|
||||
get name () {
|
||||
if (!(this instanceof FileLike)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].name
|
||||
}
|
||||
|
||||
get lastModified () {
|
||||
if (!(this instanceof FileLike)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kState].lastModified
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return 'File'
|
||||
}
|
||||
}
|
||||
|
||||
webidl.converters.Blob = webidl.interfaceConverter(Blob)
|
||||
|
||||
webidl.converters.BlobPart = function (V, opts) {
|
||||
if (webidl.util.Type(V) === 'Object') {
|
||||
if (isBlobLike(V)) {
|
||||
return webidl.converters.Blob(V, { strict: false })
|
||||
}
|
||||
|
||||
return webidl.converters.BufferSource(V, opts)
|
||||
} else {
|
||||
return webidl.converters.USVString(V, opts)
|
||||
}
|
||||
}
|
||||
|
||||
webidl.converters['sequence<BlobPart>'] = webidl.sequenceConverter(
|
||||
webidl.converters.BlobPart
|
||||
)
|
||||
|
||||
// https://www.w3.org/TR/FileAPI/#dfn-FilePropertyBag
|
||||
webidl.converters.FilePropertyBag = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'lastModified',
|
||||
converter: webidl.converters['long long'],
|
||||
get defaultValue () {
|
||||
return Date.now()
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
converter: webidl.converters.DOMString,
|
||||
defaultValue: ''
|
||||
},
|
||||
{
|
||||
key: 'endings',
|
||||
converter: (value) => {
|
||||
value = webidl.converters.DOMString(value)
|
||||
value = value.toLowerCase()
|
||||
|
||||
if (value !== 'native') {
|
||||
value = 'transparent'
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
defaultValue: 'transparent'
|
||||
}
|
||||
])
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/FileAPI/#process-blob-parts
|
||||
* @param {(NodeJS.TypedArray|Blob|string)[]} parts
|
||||
* @param {{ type: string, endings: string }} options
|
||||
*/
|
||||
function processBlobParts (parts, options) {
|
||||
// 1. Let bytes be an empty sequence of bytes.
|
||||
/** @type {NodeJS.TypedArray[]} */
|
||||
const bytes = []
|
||||
|
||||
// 2. For each element in parts:
|
||||
for (const element of parts) {
|
||||
// 1. If element is a USVString, run the following substeps:
|
||||
if (typeof element === 'string') {
|
||||
// 1. Let s be element.
|
||||
let s = element
|
||||
|
||||
// 2. If the endings member of options is "native", set s
|
||||
// to the result of converting line endings to native
|
||||
// of element.
|
||||
if (options.endings === 'native') {
|
||||
s = convertLineEndingsNative(s)
|
||||
}
|
||||
|
||||
// 3. Append the result of UTF-8 encoding s to bytes.
|
||||
bytes.push(new TextEncoder().encode(s))
|
||||
} else if (
|
||||
types.isAnyArrayBuffer(element) ||
|
||||
types.isTypedArray(element)
|
||||
) {
|
||||
// 2. If element is a BufferSource, get a copy of the
|
||||
// bytes held by the buffer source, and append those
|
||||
// bytes to bytes.
|
||||
if (!element.buffer) { // ArrayBuffer
|
||||
bytes.push(new Uint8Array(element))
|
||||
} else {
|
||||
bytes.push(element.buffer)
|
||||
}
|
||||
} else if (isBlobLike(element)) {
|
||||
// 3. If element is a Blob, append the bytes it represents
|
||||
// to bytes.
|
||||
bytes.push(element)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return bytes.
|
||||
return bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/FileAPI/#convert-line-endings-to-native
|
||||
* @param {string} s
|
||||
*/
|
||||
function convertLineEndingsNative (s) {
|
||||
// 1. Let native line ending be be the code point U+000A LF.
|
||||
let nativeLineEnding = '\n'
|
||||
|
||||
// 2. If the underlying platform’s conventions are to
|
||||
// represent newlines as a carriage return and line feed
|
||||
// sequence, set native line ending to the code point
|
||||
// U+000D CR followed by the code point U+000A LF.
|
||||
if (process.platform === 'win32') {
|
||||
nativeLineEnding = '\r\n'
|
||||
}
|
||||
|
||||
return s.replace(/\r?\n/g, nativeLineEnding)
|
||||
}
|
||||
|
||||
module.exports = { File, FileLike }
|
321
node_modules/undici/lib/fetch/formdata.js
generated
vendored
Normal file
321
node_modules/undici/lib/fetch/formdata.js
generated
vendored
Normal file
|
@ -0,0 +1,321 @@
|
|||
'use strict'
|
||||
|
||||
const { isBlobLike, isFileLike, toUSVString, makeIterator } = require('./util')
|
||||
const { kState } = require('./symbols')
|
||||
const { File, FileLike } = require('./file')
|
||||
const { webidl } = require('./webidl')
|
||||
const { Blob } = require('buffer')
|
||||
|
||||
// https://xhr.spec.whatwg.org/#formdata
|
||||
class FormData {
|
||||
static name = 'FormData'
|
||||
|
||||
constructor (form) {
|
||||
if (arguments.length > 0 && form != null) {
|
||||
webidl.errors.conversionFailed({
|
||||
prefix: 'FormData constructor',
|
||||
argument: 'Argument 1',
|
||||
types: ['null']
|
||||
})
|
||||
}
|
||||
|
||||
this[kState] = []
|
||||
}
|
||||
|
||||
append (name, value, filename = undefined) {
|
||||
if (!(this instanceof FormData)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 2) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'append' on 'FormData': 2 arguments required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
if (arguments.length === 3 && !isBlobLike(value)) {
|
||||
throw new TypeError(
|
||||
"Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'"
|
||||
)
|
||||
}
|
||||
|
||||
// 1. Let value be value if given; otherwise blobValue.
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
value = isBlobLike(value)
|
||||
? webidl.converters.Blob(value, { strict: false })
|
||||
: webidl.converters.USVString(value)
|
||||
filename = arguments.length === 3
|
||||
? webidl.converters.USVString(filename)
|
||||
: undefined
|
||||
|
||||
// 2. Let entry be the result of creating an entry with
|
||||
// name, value, and filename if given.
|
||||
const entry = makeEntry(name, value, filename)
|
||||
|
||||
// 3. Append entry to this’s entry list.
|
||||
this[kState].push(entry)
|
||||
}
|
||||
|
||||
delete (name) {
|
||||
if (!(this instanceof FormData)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'delete' on 'FormData': 1 arguments required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
|
||||
// The delete(name) method steps are to remove all entries whose name
|
||||
// is name from this’s entry list.
|
||||
const next = []
|
||||
for (const entry of this[kState]) {
|
||||
if (entry.name !== name) {
|
||||
next.push(entry)
|
||||
}
|
||||
}
|
||||
|
||||
this[kState] = next
|
||||
}
|
||||
|
||||
get (name) {
|
||||
if (!(this instanceof FormData)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'get' on 'FormData': 1 arguments required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
|
||||
// 1. If there is no entry whose name is name in this’s entry list,
|
||||
// then return null.
|
||||
const idx = this[kState].findIndex((entry) => entry.name === name)
|
||||
if (idx === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 2. Return the value of the first entry whose name is name from
|
||||
// this’s entry list.
|
||||
return this[kState][idx].value
|
||||
}
|
||||
|
||||
getAll (name) {
|
||||
if (!(this instanceof FormData)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'getAll' on 'FormData': 1 arguments required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
|
||||
// 1. If there is no entry whose name is name in this’s entry list,
|
||||
// then return the empty list.
|
||||
// 2. Return the values of all entries whose name is name, in order,
|
||||
// from this’s entry list.
|
||||
return this[kState]
|
||||
.filter((entry) => entry.name === name)
|
||||
.map((entry) => entry.value)
|
||||
}
|
||||
|
||||
has (name) {
|
||||
if (!(this instanceof FormData)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'has' on 'FormData': 1 arguments required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
|
||||
// The has(name) method steps are to return true if there is an entry
|
||||
// whose name is name in this’s entry list; otherwise false.
|
||||
return this[kState].findIndex((entry) => entry.name === name) !== -1
|
||||
}
|
||||
|
||||
set (name, value, filename = undefined) {
|
||||
if (!(this instanceof FormData)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 2) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'set' on 'FormData': 2 arguments required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
if (arguments.length === 3 && !isBlobLike(value)) {
|
||||
throw new TypeError(
|
||||
"Failed to execute 'set' on 'FormData': parameter 2 is not of type 'Blob'"
|
||||
)
|
||||
}
|
||||
|
||||
// The set(name, value) and set(name, blobValue, filename) method steps
|
||||
// are:
|
||||
|
||||
// 1. Let value be value if given; otherwise blobValue.
|
||||
|
||||
name = webidl.converters.USVString(name)
|
||||
value = isBlobLike(value)
|
||||
? webidl.converters.Blob(value, { strict: false })
|
||||
: webidl.converters.USVString(value)
|
||||
filename = arguments.length === 3
|
||||
? toUSVString(filename)
|
||||
: undefined
|
||||
|
||||
// 2. Let entry be the result of creating an entry with name, value, and
|
||||
// filename if given.
|
||||
const entry = makeEntry(name, value, filename)
|
||||
|
||||
// 3. If there are entries in this’s entry list whose name is name, then
|
||||
// replace the first such entry with entry and remove the others.
|
||||
const idx = this[kState].findIndex((entry) => entry.name === name)
|
||||
if (idx !== -1) {
|
||||
this[kState] = [
|
||||
...this[kState].slice(0, idx),
|
||||
entry,
|
||||
...this[kState].slice(idx + 1).filter((entry) => entry.name !== name)
|
||||
]
|
||||
} else {
|
||||
// 4. Otherwise, append entry to this’s entry list.
|
||||
this[kState].push(entry)
|
||||
}
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return this.constructor.name
|
||||
}
|
||||
|
||||
entries () {
|
||||
if (!(this instanceof FormData)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return makeIterator(
|
||||
makeIterable(this[kState], 'entries'),
|
||||
'FormData'
|
||||
)
|
||||
}
|
||||
|
||||
keys () {
|
||||
if (!(this instanceof FormData)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return makeIterator(
|
||||
makeIterable(this[kState], 'keys'),
|
||||
'FormData'
|
||||
)
|
||||
}
|
||||
|
||||
values () {
|
||||
if (!(this instanceof FormData)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return makeIterator(
|
||||
makeIterable(this[kState], 'values'),
|
||||
'FormData'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: string, key: string, self: FormData) => void} callbackFn
|
||||
* @param {unknown} thisArg
|
||||
*/
|
||||
forEach (callbackFn, thisArg = globalThis) {
|
||||
if (!(this instanceof FormData)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'forEach' on 'FormData': 1 argument required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof callbackFn !== 'function') {
|
||||
throw new TypeError(
|
||||
"Failed to execute 'forEach' on 'FormData': parameter 1 is not of type 'Function'."
|
||||
)
|
||||
}
|
||||
|
||||
for (const [key, value] of this) {
|
||||
callbackFn.apply(thisArg, [value, key, this])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormData.prototype[Symbol.iterator] = FormData.prototype.entries
|
||||
|
||||
/**
|
||||
* @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry
|
||||
* @param {string} name
|
||||
* @param {string|Blob} value
|
||||
* @param {?string} filename
|
||||
* @returns
|
||||
*/
|
||||
function makeEntry (name, value, filename) {
|
||||
// 1. Set name to the result of converting name into a scalar value string.
|
||||
// "To convert a string into a scalar value string, replace any surrogates
|
||||
// with U+FFFD."
|
||||
// see: https://nodejs.org/dist/latest-v18.x/docs/api/buffer.html#buftostringencoding-start-end
|
||||
name = Buffer.from(name).toString('utf8')
|
||||
|
||||
// 2. If value is a string, then set value to the result of converting
|
||||
// value into a scalar value string.
|
||||
if (typeof value === 'string') {
|
||||
value = Buffer.from(value).toString('utf8')
|
||||
} else {
|
||||
// 3. Otherwise:
|
||||
|
||||
// 1. If value is not a File object, then set value to a new File object,
|
||||
// representing the same bytes, whose name attribute value is "blob"
|
||||
if (!isFileLike(value)) {
|
||||
value = value instanceof Blob
|
||||
? new File([value], 'blob', { type: value.type })
|
||||
: new FileLike(value, 'blob', { type: value.type })
|
||||
}
|
||||
|
||||
// 2. If filename is given, then set value to a new File object,
|
||||
// representing the same bytes, whose name attribute is filename.
|
||||
if (filename !== undefined) {
|
||||
value = value instanceof File
|
||||
? new File([value], filename, { type: value.type })
|
||||
: new FileLike(value, filename, { type: value.type })
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Return an entry whose name is name and whose value is value.
|
||||
return { name, value }
|
||||
}
|
||||
|
||||
function * makeIterable (entries, type) {
|
||||
// The value pairs to iterate over are this’s entry list’s entries
|
||||
// with the key being the name and the value being the value.
|
||||
for (const { name, value } of entries) {
|
||||
if (type === 'entries') {
|
||||
yield [name, value]
|
||||
} else if (type === 'values') {
|
||||
yield value
|
||||
} else {
|
||||
yield name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { FormData }
|
501
node_modules/undici/lib/fetch/headers.js
generated
vendored
Normal file
501
node_modules/undici/lib/fetch/headers.js
generated
vendored
Normal file
|
@ -0,0 +1,501 @@
|
|||
// https://github.com/Ethan-Arrowood/undici-fetch
|
||||
|
||||
'use strict'
|
||||
|
||||
const { kHeadersList } = require('../core/symbols')
|
||||
const { kGuard } = require('./symbols')
|
||||
const { kEnumerableProperty } = require('../core/util')
|
||||
const {
|
||||
makeIterator,
|
||||
isValidHeaderName,
|
||||
isValidHeaderValue
|
||||
} = require('./util')
|
||||
const { webidl } = require('./webidl')
|
||||
|
||||
const kHeadersMap = Symbol('headers map')
|
||||
const kHeadersSortedMap = Symbol('headers map sorted')
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#concept-header-value-normalize
|
||||
* @param {string} potentialValue
|
||||
*/
|
||||
function headerValueNormalize (potentialValue) {
|
||||
// To normalize a byte sequence potentialValue, remove
|
||||
// any leading and trailing HTTP whitespace bytes from
|
||||
// potentialValue.
|
||||
return potentialValue.replace(
|
||||
/^[\r\n\t ]+|[\r\n\t ]+$/g,
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
function fill (headers, object) {
|
||||
// To fill a Headers object headers with a given object object, run these steps:
|
||||
|
||||
// 1. If object is a sequence, then for each header in object:
|
||||
// Note: webidl conversion to array has already been done.
|
||||
if (Array.isArray(object)) {
|
||||
for (const header of object) {
|
||||
// 1. If header does not contain exactly two items, then throw a TypeError.
|
||||
if (header.length !== 2) {
|
||||
webidl.errors.exception({
|
||||
header: 'Headers constructor',
|
||||
message: `expected name/value pair to be length 2, found ${header.length}.`
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Append (header’s first item, header’s second item) to headers.
|
||||
headers.append(header[0], header[1])
|
||||
}
|
||||
} else if (typeof object === 'object' && object !== null) {
|
||||
// Note: null should throw
|
||||
|
||||
// 2. Otherwise, object is a record, then for each key → value in object,
|
||||
// append (key, value) to headers
|
||||
for (const [key, value] of Object.entries(object)) {
|
||||
headers.append(key, value)
|
||||
}
|
||||
} else {
|
||||
webidl.errors.conversionFailed({
|
||||
prefix: 'Headers constructor',
|
||||
argument: 'Argument 1',
|
||||
types: ['sequence<sequence<ByteString>>', 'record<ByteString, ByteString>']
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class HeadersList {
|
||||
constructor (init) {
|
||||
if (init instanceof HeadersList) {
|
||||
this[kHeadersMap] = new Map(init[kHeadersMap])
|
||||
this[kHeadersSortedMap] = init[kHeadersSortedMap]
|
||||
} else {
|
||||
this[kHeadersMap] = new Map(init)
|
||||
this[kHeadersSortedMap] = null
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#header-list-contains
|
||||
contains (name) {
|
||||
// A header list list contains a header name name if list
|
||||
// contains a header whose name is a byte-case-insensitive
|
||||
// match for name.
|
||||
name = name.toLowerCase()
|
||||
|
||||
return this[kHeadersMap].has(name)
|
||||
}
|
||||
|
||||
clear () {
|
||||
this[kHeadersMap].clear()
|
||||
this[kHeadersSortedMap] = null
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-append
|
||||
append (name, value) {
|
||||
this[kHeadersSortedMap] = null
|
||||
|
||||
// 1. If list contains name, then set name to the first such
|
||||
// header’s name.
|
||||
name = name.toLowerCase()
|
||||
const exists = this[kHeadersMap].get(name)
|
||||
|
||||
// 2. Append (name, value) to list.
|
||||
if (exists) {
|
||||
this[kHeadersMap].set(name, `${exists}, ${value}`)
|
||||
} else {
|
||||
this[kHeadersMap].set(name, `${value}`)
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-set
|
||||
set (name, value) {
|
||||
this[kHeadersSortedMap] = null
|
||||
|
||||
// 1. If list contains name, then set the value of
|
||||
// the first such header to value and remove the
|
||||
// others.
|
||||
// 2. Otherwise, append header (name, value) to list.
|
||||
return this[kHeadersMap].set(name, value)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-delete
|
||||
delete (name) {
|
||||
this[kHeadersSortedMap] = null
|
||||
|
||||
name = name.toLowerCase()
|
||||
return this[kHeadersMap].delete(name)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-get
|
||||
get (name) {
|
||||
name = name.toLowerCase()
|
||||
|
||||
// 1. If list does not contain name, then return null.
|
||||
if (!this.contains(name)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 2. Return the values of all headers in list whose name
|
||||
// is a byte-case-insensitive match for name,
|
||||
// separated from each other by 0x2C 0x20, in order.
|
||||
return this[kHeadersMap].get(name) ?? null
|
||||
}
|
||||
|
||||
has (name) {
|
||||
name = name.toLowerCase()
|
||||
return this[kHeadersMap].has(name)
|
||||
}
|
||||
|
||||
keys () {
|
||||
return this[kHeadersMap].keys()
|
||||
}
|
||||
|
||||
values () {
|
||||
return this[kHeadersMap].values()
|
||||
}
|
||||
|
||||
entries () {
|
||||
return this[kHeadersMap].entries()
|
||||
}
|
||||
|
||||
[Symbol.iterator] () {
|
||||
return this[kHeadersMap][Symbol.iterator]()
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#headers-class
|
||||
class Headers {
|
||||
constructor (init = undefined) {
|
||||
this[kHeadersList] = new HeadersList()
|
||||
|
||||
// The new Headers(init) constructor steps are:
|
||||
|
||||
// 1. Set this’s guard to "none".
|
||||
this[kGuard] = 'none'
|
||||
|
||||
// 2. If init is given, then fill this with init.
|
||||
if (init !== undefined) {
|
||||
init = webidl.converters.HeadersInit(init)
|
||||
fill(this, init)
|
||||
}
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return this.constructor.name
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-append
|
||||
append (name, value) {
|
||||
if (!(this instanceof Headers)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 2) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'append' on 'Headers': 2 arguments required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
name = webidl.converters.ByteString(name)
|
||||
value = webidl.converters.ByteString(value)
|
||||
|
||||
// 1. Normalize value.
|
||||
value = headerValueNormalize(value)
|
||||
|
||||
// 2. If name is not a header name or value is not a
|
||||
// header value, then throw a TypeError.
|
||||
if (!isValidHeaderName(name)) {
|
||||
webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.append',
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
} else if (!isValidHeaderValue(value)) {
|
||||
webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.append',
|
||||
value,
|
||||
type: 'header value'
|
||||
})
|
||||
}
|
||||
|
||||
// 3. If headers’s guard is "immutable", then throw a TypeError.
|
||||
// 4. Otherwise, if headers’s guard is "request" and name is a
|
||||
// forbidden header name, return.
|
||||
// Note: undici does not implement forbidden header names
|
||||
if (this[kGuard] === 'immutable') {
|
||||
throw new TypeError('immutable')
|
||||
} else if (this[kGuard] === 'request-no-cors') {
|
||||
// 5. Otherwise, if headers’s guard is "request-no-cors":
|
||||
// TODO
|
||||
}
|
||||
|
||||
// 6. Otherwise, if headers’s guard is "response" and name is a
|
||||
// forbidden response-header name, return.
|
||||
|
||||
// 7. Append (name, value) to headers’s header list.
|
||||
// 8. If headers’s guard is "request-no-cors", then remove
|
||||
// privileged no-CORS request headers from headers
|
||||
return this[kHeadersList].append(name, value)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-delete
|
||||
delete (name) {
|
||||
if (!(this instanceof Headers)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'delete' on 'Headers': 1 argument required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
name = webidl.converters.ByteString(name)
|
||||
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
if (!isValidHeaderName(name)) {
|
||||
webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.delete',
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
}
|
||||
|
||||
// 2. If this’s guard is "immutable", then throw a TypeError.
|
||||
// 3. Otherwise, if this’s guard is "request" and name is a
|
||||
// forbidden header name, return.
|
||||
// 4. Otherwise, if this’s guard is "request-no-cors", name
|
||||
// is not a no-CORS-safelisted request-header name, and
|
||||
// name is not a privileged no-CORS request-header name,
|
||||
// return.
|
||||
// 5. Otherwise, if this’s guard is "response" and name is
|
||||
// a forbidden response-header name, return.
|
||||
// Note: undici does not implement forbidden header names
|
||||
if (this[kGuard] === 'immutable') {
|
||||
throw new TypeError('immutable')
|
||||
} else if (this[kGuard] === 'request-no-cors') {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// 6. If this’s header list does not contain name, then
|
||||
// return.
|
||||
if (!this[kHeadersList].contains(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 7. Delete name from this’s header list.
|
||||
// 8. If this’s guard is "request-no-cors", then remove
|
||||
// privileged no-CORS request headers from this.
|
||||
return this[kHeadersList].delete(name)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-get
|
||||
get (name) {
|
||||
if (!(this instanceof Headers)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'get' on 'Headers': 1 argument required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
name = webidl.converters.ByteString(name)
|
||||
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
if (!isValidHeaderName(name)) {
|
||||
webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.get',
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Return the result of getting name from this’s header
|
||||
// list.
|
||||
return this[kHeadersList].get(name)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-has
|
||||
has (name) {
|
||||
if (!(this instanceof Headers)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'has' on 'Headers': 1 argument required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
name = webidl.converters.ByteString(name)
|
||||
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
if (!isValidHeaderName(name)) {
|
||||
webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.has',
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Return true if this’s header list contains name;
|
||||
// otherwise false.
|
||||
return this[kHeadersList].contains(name)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-set
|
||||
set (name, value) {
|
||||
if (!(this instanceof Headers)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 2) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'set' on 'Headers': 2 arguments required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
name = webidl.converters.ByteString(name)
|
||||
value = webidl.converters.ByteString(value)
|
||||
|
||||
// 1. Normalize value.
|
||||
value = headerValueNormalize(value)
|
||||
|
||||
// 2. If name is not a header name or value is not a
|
||||
// header value, then throw a TypeError.
|
||||
if (!isValidHeaderName(name)) {
|
||||
webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.set',
|
||||
value: name,
|
||||
type: 'header name'
|
||||
})
|
||||
} else if (!isValidHeaderValue(value)) {
|
||||
webidl.errors.invalidArgument({
|
||||
prefix: 'Headers.set',
|
||||
value,
|
||||
type: 'header value'
|
||||
})
|
||||
}
|
||||
|
||||
// 3. If this’s guard is "immutable", then throw a TypeError.
|
||||
// 4. Otherwise, if this’s guard is "request" and name is a
|
||||
// forbidden header name, return.
|
||||
// 5. Otherwise, if this’s guard is "request-no-cors" and
|
||||
// name/value is not a no-CORS-safelisted request-header,
|
||||
// return.
|
||||
// 6. Otherwise, if this’s guard is "response" and name is a
|
||||
// forbidden response-header name, return.
|
||||
// Note: undici does not implement forbidden header names
|
||||
if (this[kGuard] === 'immutable') {
|
||||
throw new TypeError('immutable')
|
||||
} else if (this[kGuard] === 'request-no-cors') {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// 7. Set (name, value) in this’s header list.
|
||||
// 8. If this’s guard is "request-no-cors", then remove
|
||||
// privileged no-CORS request headers from this
|
||||
return this[kHeadersList].set(name, value)
|
||||
}
|
||||
|
||||
get [kHeadersSortedMap] () {
|
||||
this[kHeadersList][kHeadersSortedMap] ??= new Map([...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1))
|
||||
return this[kHeadersList][kHeadersSortedMap]
|
||||
}
|
||||
|
||||
keys () {
|
||||
if (!(this instanceof Headers)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return makeIterator(this[kHeadersSortedMap].keys(), 'Headers')
|
||||
}
|
||||
|
||||
values () {
|
||||
if (!(this instanceof Headers)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return makeIterator(this[kHeadersSortedMap].values(), 'Headers')
|
||||
}
|
||||
|
||||
entries () {
|
||||
if (!(this instanceof Headers)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return makeIterator(this[kHeadersSortedMap].entries(), 'Headers')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: string, key: string, self: Headers) => void} callbackFn
|
||||
* @param {unknown} thisArg
|
||||
*/
|
||||
forEach (callbackFn, thisArg = globalThis) {
|
||||
if (!(this instanceof Headers)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'forEach' on 'Headers': 1 argument required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof callbackFn !== 'function') {
|
||||
throw new TypeError(
|
||||
"Failed to execute 'forEach' on 'Headers': parameter 1 is not of type 'Function'."
|
||||
)
|
||||
}
|
||||
|
||||
for (const [key, value] of this) {
|
||||
callbackFn.apply(thisArg, [value, key, this])
|
||||
}
|
||||
}
|
||||
|
||||
[Symbol.for('nodejs.util.inspect.custom')] () {
|
||||
if (!(this instanceof Headers)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
return this[kHeadersList]
|
||||
}
|
||||
}
|
||||
|
||||
Headers.prototype[Symbol.iterator] = Headers.prototype.entries
|
||||
|
||||
Object.defineProperties(Headers.prototype, {
|
||||
append: kEnumerableProperty,
|
||||
delete: kEnumerableProperty,
|
||||
get: kEnumerableProperty,
|
||||
has: kEnumerableProperty,
|
||||
set: kEnumerableProperty,
|
||||
keys: kEnumerableProperty,
|
||||
values: kEnumerableProperty,
|
||||
entries: kEnumerableProperty,
|
||||
forEach: kEnumerableProperty
|
||||
})
|
||||
|
||||
webidl.converters.HeadersInit = function (V) {
|
||||
if (webidl.util.Type(V) === 'Object') {
|
||||
if (V[Symbol.iterator]) {
|
||||
return webidl.converters['sequence<sequence<ByteString>>'](V)
|
||||
}
|
||||
|
||||
return webidl.converters['record<ByteString, ByteString>'](V)
|
||||
}
|
||||
|
||||
webidl.errors.conversionFailed({
|
||||
prefix: 'Headers constructor',
|
||||
argument: 'Argument 1',
|
||||
types: ['sequence<sequence<ByteString>>', 'record<ByteString, ByteString>']
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fill,
|
||||
Headers,
|
||||
HeadersList
|
||||
}
|
2039
node_modules/undici/lib/fetch/index.js
generated
vendored
Normal file
2039
node_modules/undici/lib/fetch/index.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
926
node_modules/undici/lib/fetch/request.js
generated
vendored
Normal file
926
node_modules/undici/lib/fetch/request.js
generated
vendored
Normal file
|
@ -0,0 +1,926 @@
|
|||
/* globals AbortController */
|
||||
|
||||
'use strict'
|
||||
|
||||
const { extractBody, mixinBody, cloneBody } = require('./body')
|
||||
const { Headers, fill: fillHeaders, HeadersList } = require('./headers')
|
||||
const util = require('../core/util')
|
||||
const {
|
||||
isValidHTTPToken,
|
||||
sameOrigin,
|
||||
normalizeMethod
|
||||
} = require('./util')
|
||||
const {
|
||||
forbiddenMethods,
|
||||
corsSafeListedMethods,
|
||||
referrerPolicy,
|
||||
requestRedirect,
|
||||
requestMode,
|
||||
requestCredentials,
|
||||
requestCache
|
||||
} = require('./constants')
|
||||
const { kEnumerableProperty } = util
|
||||
const { kHeaders, kSignal, kState, kGuard, kRealm } = require('./symbols')
|
||||
const { webidl } = require('./webidl')
|
||||
const { kHeadersList } = require('../core/symbols')
|
||||
const assert = require('assert')
|
||||
|
||||
let TransformStream
|
||||
|
||||
const kInit = Symbol('init')
|
||||
|
||||
const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
|
||||
signal.removeEventListener('abort', abort)
|
||||
})
|
||||
|
||||
// https://fetch.spec.whatwg.org/#request-class
|
||||
class Request {
|
||||
// https://fetch.spec.whatwg.org/#dom-request
|
||||
constructor (input, init = {}) {
|
||||
if (input === kInit) {
|
||||
return
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to construct 'Request': 1 argument required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
input = webidl.converters.RequestInfo(input)
|
||||
init = webidl.converters.RequestInit(init)
|
||||
|
||||
// TODO
|
||||
this[kRealm] = { settingsObject: {} }
|
||||
|
||||
// 1. Let request be null.
|
||||
let request = null
|
||||
|
||||
// 2. Let fallbackMode be null.
|
||||
let fallbackMode = null
|
||||
|
||||
// 3. Let baseURL be this’s relevant settings object’s API base URL.
|
||||
const baseUrl = this[kRealm].settingsObject.baseUrl
|
||||
|
||||
// 4. Let signal be null.
|
||||
let signal = null
|
||||
|
||||
// 5. If input is a string, then:
|
||||
if (typeof input === 'string') {
|
||||
// 1. Let parsedURL be the result of parsing input with baseURL.
|
||||
// 2. If parsedURL is failure, then throw a TypeError.
|
||||
let parsedURL
|
||||
try {
|
||||
parsedURL = new URL(input, baseUrl)
|
||||
} catch (err) {
|
||||
throw new TypeError('Failed to parse URL from ' + input, { cause: err })
|
||||
}
|
||||
|
||||
// 3. If parsedURL includes credentials, then throw a TypeError.
|
||||
if (parsedURL.username || parsedURL.password) {
|
||||
throw new TypeError(
|
||||
'Request cannot be constructed from a URL that includes credentials: ' +
|
||||
input
|
||||
)
|
||||
}
|
||||
|
||||
// 4. Set request to a new request whose URL is parsedURL.
|
||||
request = makeRequest({ urlList: [parsedURL] })
|
||||
|
||||
// 5. Set fallbackMode to "cors".
|
||||
fallbackMode = 'cors'
|
||||
} else {
|
||||
// 6. Otherwise:
|
||||
|
||||
// 7. Assert: input is a Request object.
|
||||
assert(input instanceof Request)
|
||||
|
||||
// 8. Set request to input’s request.
|
||||
request = input[kState]
|
||||
|
||||
// 9. Set signal to input’s signal.
|
||||
signal = input[kSignal]
|
||||
}
|
||||
|
||||
// 7. Let origin be this’s relevant settings object’s origin.
|
||||
const origin = this[kRealm].settingsObject.origin
|
||||
|
||||
// 8. Let window be "client".
|
||||
let window = 'client'
|
||||
|
||||
// 9. If request’s window is an environment settings object and its origin
|
||||
// is same origin with origin, then set window to request’s window.
|
||||
if (
|
||||
request.window?.constructor?.name === 'EnvironmentSettingsObject' &&
|
||||
sameOrigin(request.window, origin)
|
||||
) {
|
||||
window = request.window
|
||||
}
|
||||
|
||||
// 10. If init["window"] exists and is non-null, then throw a TypeError.
|
||||
if (init.window !== undefined && init.window != null) {
|
||||
throw new TypeError(`'window' option '${window}' must be null`)
|
||||
}
|
||||
|
||||
// 11. If init["window"] exists, then set window to "no-window".
|
||||
if (init.window !== undefined) {
|
||||
window = 'no-window'
|
||||
}
|
||||
|
||||
// 12. Set request to a new request with the following properties:
|
||||
request = makeRequest({
|
||||
// URL request’s URL.
|
||||
// undici implementation note: this is set as the first item in request's urlList in makeRequest
|
||||
// method request’s method.
|
||||
method: request.method,
|
||||
// header list A copy of request’s header list.
|
||||
// undici implementation note: headersList is cloned in makeRequest
|
||||
headersList: request.headersList,
|
||||
// unsafe-request flag Set.
|
||||
unsafeRequest: request.unsafeRequest,
|
||||
// client This’s relevant settings object.
|
||||
client: this[kRealm].settingsObject,
|
||||
// window window.
|
||||
window,
|
||||
// priority request’s priority.
|
||||
priority: request.priority,
|
||||
// origin request’s origin. The propagation of the origin is only significant for navigation requests
|
||||
// being handled by a service worker. In this scenario a request can have an origin that is different
|
||||
// from the current client.
|
||||
origin: request.origin,
|
||||
// referrer request’s referrer.
|
||||
referrer: request.referrer,
|
||||
// referrer policy request’s referrer policy.
|
||||
referrerPolicy: request.referrerPolicy,
|
||||
// mode request’s mode.
|
||||
mode: request.mode,
|
||||
// credentials mode request’s credentials mode.
|
||||
credentials: request.credentials,
|
||||
// cache mode request’s cache mode.
|
||||
cache: request.cache,
|
||||
// redirect mode request’s redirect mode.
|
||||
redirect: request.redirect,
|
||||
// integrity metadata request’s integrity metadata.
|
||||
integrity: request.integrity,
|
||||
// keepalive request’s keepalive.
|
||||
keepalive: request.keepalive,
|
||||
// reload-navigation flag request’s reload-navigation flag.
|
||||
reloadNavigation: request.reloadNavigation,
|
||||
// history-navigation flag request’s history-navigation flag.
|
||||
historyNavigation: request.historyNavigation,
|
||||
// URL list A clone of request’s URL list.
|
||||
urlList: [...request.urlList]
|
||||
})
|
||||
|
||||
// 13. If init is not empty, then:
|
||||
if (Object.keys(init).length > 0) {
|
||||
// 1. If request’s mode is "navigate", then set it to "same-origin".
|
||||
if (request.mode === 'navigate') {
|
||||
request.mode = 'same-origin'
|
||||
}
|
||||
|
||||
// 2. Unset request’s reload-navigation flag.
|
||||
request.reloadNavigation = false
|
||||
|
||||
// 3. Unset request’s history-navigation flag.
|
||||
request.historyNavigation = false
|
||||
|
||||
// 4. Set request’s origin to "client".
|
||||
request.origin = 'client'
|
||||
|
||||
// 5. Set request’s referrer to "client"
|
||||
request.referrer = 'client'
|
||||
|
||||
// 6. Set request’s referrer policy to the empty string.
|
||||
request.referrerPolicy = ''
|
||||
|
||||
// 7. Set request’s URL to request’s current URL.
|
||||
request.url = request.urlList[request.urlList.length - 1]
|
||||
|
||||
// 8. Set request’s URL list to « request’s URL ».
|
||||
request.urlList = [request.url]
|
||||
}
|
||||
|
||||
// 14. If init["referrer"] exists, then:
|
||||
if (init.referrer !== undefined) {
|
||||
// 1. Let referrer be init["referrer"].
|
||||
const referrer = init.referrer
|
||||
|
||||
// 2. If referrer is the empty string, then set request’s referrer to "no-referrer".
|
||||
if (referrer === '') {
|
||||
request.referrer = 'no-referrer'
|
||||
} else {
|
||||
// 1. Let parsedReferrer be the result of parsing referrer with
|
||||
// baseURL.
|
||||
// 2. If parsedReferrer is failure, then throw a TypeError.
|
||||
let parsedReferrer
|
||||
try {
|
||||
parsedReferrer = new URL(referrer, baseUrl)
|
||||
} catch (err) {
|
||||
throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err })
|
||||
}
|
||||
|
||||
// 3. If one of the following is true
|
||||
// parsedReferrer’s cannot-be-a-base-URL is true, scheme is "about",
|
||||
// and path contains a single string "client"
|
||||
// parsedReferrer’s origin is not same origin with origin
|
||||
// then set request’s referrer to "client".
|
||||
// TODO
|
||||
|
||||
// 4. Otherwise, set request’s referrer to parsedReferrer.
|
||||
request.referrer = parsedReferrer
|
||||
}
|
||||
}
|
||||
|
||||
// 15. If init["referrerPolicy"] exists, then set request’s referrer policy
|
||||
// to it.
|
||||
if (init.referrerPolicy !== undefined) {
|
||||
request.referrerPolicy = init.referrerPolicy
|
||||
if (!referrerPolicy.includes(request.referrerPolicy)) {
|
||||
throw new TypeError(
|
||||
`Failed to construct 'Request': The provided value '${request.referrerPolicy}' is not a valid enum value of type ReferrerPolicy.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise.
|
||||
let mode
|
||||
if (init.mode !== undefined) {
|
||||
mode = init.mode
|
||||
if (!requestMode.includes(mode)) {
|
||||
throw new TypeError(
|
||||
`Failed to construct 'Request': The provided value '${request.mode}' is not a valid enum value of type RequestMode.`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
mode = fallbackMode
|
||||
}
|
||||
|
||||
// 17. If mode is "navigate", then throw a TypeError.
|
||||
if (mode === 'navigate') {
|
||||
webidl.errors.exception({
|
||||
header: 'Request constructor',
|
||||
message: 'invalid request mode navigate.'
|
||||
})
|
||||
}
|
||||
|
||||
// 18. If mode is non-null, set request’s mode to mode.
|
||||
if (mode != null) {
|
||||
request.mode = mode
|
||||
}
|
||||
|
||||
// 19. If init["credentials"] exists, then set request’s credentials mode
|
||||
// to it.
|
||||
if (init.credentials !== undefined) {
|
||||
request.credentials = init.credentials
|
||||
if (!requestCredentials.includes(request.credentials)) {
|
||||
throw new TypeError(
|
||||
`Failed to construct 'Request': The provided value '${request.credentials}' is not a valid enum value of type RequestCredentials.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 18. If init["cache"] exists, then set request’s cache mode to it.
|
||||
if (init.cache !== undefined) {
|
||||
request.cache = init.cache
|
||||
if (!requestCache.includes(request.cache)) {
|
||||
throw new TypeError(
|
||||
`Failed to construct 'Request': The provided value '${request.cache}' is not a valid enum value of type RequestCache.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 21. If request’s cache mode is "only-if-cached" and request’s mode is
|
||||
// not "same-origin", then throw a TypeError.
|
||||
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
|
||||
throw new TypeError(
|
||||
"'only-if-cached' can be set only with 'same-origin' mode"
|
||||
)
|
||||
}
|
||||
|
||||
// 22. If init["redirect"] exists, then set request’s redirect mode to it.
|
||||
if (init.redirect !== undefined) {
|
||||
request.redirect = init.redirect
|
||||
if (!requestRedirect.includes(request.redirect)) {
|
||||
throw new TypeError(
|
||||
`Failed to construct 'Request': The provided value '${request.redirect}' is not a valid enum value of type RequestRedirect.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 23. If init["integrity"] exists, then set request’s integrity metadata to it.
|
||||
if (init.integrity !== undefined && init.integrity != null) {
|
||||
request.integrity = String(init.integrity)
|
||||
}
|
||||
|
||||
// 24. If init["keepalive"] exists, then set request’s keepalive to it.
|
||||
if (init.keepalive !== undefined) {
|
||||
request.keepalive = Boolean(init.keepalive)
|
||||
}
|
||||
|
||||
// 25. If init["method"] exists, then:
|
||||
if (init.method !== undefined) {
|
||||
// 1. Let method be init["method"].
|
||||
let method = init.method
|
||||
|
||||
// 2. If method is not a method or method is a forbidden method, then
|
||||
// throw a TypeError.
|
||||
if (!isValidHTTPToken(init.method)) {
|
||||
throw TypeError(`'${init.method}' is not a valid HTTP method.`)
|
||||
}
|
||||
|
||||
if (forbiddenMethods.indexOf(method.toUpperCase()) !== -1) {
|
||||
throw TypeError(`'${init.method}' HTTP method is unsupported.`)
|
||||
}
|
||||
|
||||
// 3. Normalize method.
|
||||
method = normalizeMethod(init.method)
|
||||
|
||||
// 4. Set request’s method to method.
|
||||
request.method = method
|
||||
}
|
||||
|
||||
// 26. If init["signal"] exists, then set signal to it.
|
||||
if (init.signal !== undefined) {
|
||||
signal = init.signal
|
||||
}
|
||||
|
||||
// 27. Set this’s request to request.
|
||||
this[kState] = request
|
||||
|
||||
// 28. Set this’s signal to a new AbortSignal object with this’s relevant
|
||||
// Realm.
|
||||
const ac = new AbortController()
|
||||
this[kSignal] = ac.signal
|
||||
this[kSignal][kRealm] = this[kRealm]
|
||||
|
||||
// 29. If signal is not null, then make this’s signal follow signal.
|
||||
if (signal != null) {
|
||||
if (
|
||||
!signal ||
|
||||
typeof signal.aborted !== 'boolean' ||
|
||||
typeof signal.addEventListener !== 'function'
|
||||
) {
|
||||
throw new TypeError(
|
||||
"Failed to construct 'Request': member signal is not of type AbortSignal."
|
||||
)
|
||||
}
|
||||
|
||||
if (signal.aborted) {
|
||||
ac.abort()
|
||||
} else {
|
||||
const abort = () => ac.abort()
|
||||
signal.addEventListener('abort', abort, { once: true })
|
||||
requestFinalizer.register(this, { signal, abort })
|
||||
}
|
||||
}
|
||||
|
||||
// 30. Set this’s headers to a new Headers object with this’s relevant
|
||||
// Realm, whose header list is request’s header list and guard is
|
||||
// "request".
|
||||
this[kHeaders] = new Headers()
|
||||
this[kHeaders][kHeadersList] = request.headersList
|
||||
this[kHeaders][kGuard] = 'request'
|
||||
this[kHeaders][kRealm] = this[kRealm]
|
||||
|
||||
// 31. If this’s request’s mode is "no-cors", then:
|
||||
if (mode === 'no-cors') {
|
||||
// 1. If this’s request’s method is not a CORS-safelisted method,
|
||||
// then throw a TypeError.
|
||||
if (!corsSafeListedMethods.includes(request.method)) {
|
||||
throw new TypeError(
|
||||
`'${request.method} is unsupported in no-cors mode.`
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Set this’s headers’s guard to "request-no-cors".
|
||||
this[kHeaders][kGuard] = 'request-no-cors'
|
||||
}
|
||||
|
||||
// 32. If init is not empty, then:
|
||||
if (Object.keys(init).length !== 0) {
|
||||
// 1. Let headers be a copy of this’s headers and its associated header
|
||||
// list.
|
||||
let headers = new Headers(this[kHeaders])
|
||||
|
||||
// 2. If init["headers"] exists, then set headers to init["headers"].
|
||||
if (init.headers !== undefined) {
|
||||
headers = init.headers
|
||||
}
|
||||
|
||||
// 3. Empty this’s headers’s header list.
|
||||
this[kHeaders][kHeadersList].clear()
|
||||
|
||||
// 4. If headers is a Headers object, then for each header in its header
|
||||
// list, append header’s name/header’s value to this’s headers.
|
||||
if (headers.constructor.name === 'Headers') {
|
||||
for (const [key, val] of headers) {
|
||||
this[kHeaders].append(key, val)
|
||||
}
|
||||
} else {
|
||||
// 5. Otherwise, fill this’s headers with headers.
|
||||
fillHeaders(this[kHeaders], headers)
|
||||
}
|
||||
}
|
||||
|
||||
// 33. Let inputBody be input’s request’s body if input is a Request
|
||||
// object; otherwise null.
|
||||
const inputBody = input instanceof Request ? input[kState].body : null
|
||||
|
||||
// 34. If either init["body"] exists and is non-null or inputBody is
|
||||
// non-null, and request’s method is `GET` or `HEAD`, then throw a
|
||||
// TypeError.
|
||||
if (
|
||||
((init.body !== undefined && init.body != null) || inputBody != null) &&
|
||||
(request.method === 'GET' || request.method === 'HEAD')
|
||||
) {
|
||||
throw new TypeError('Request with GET/HEAD method cannot have body.')
|
||||
}
|
||||
|
||||
// 35. Let initBody be null.
|
||||
let initBody = null
|
||||
|
||||
// 36. If init["body"] exists and is non-null, then:
|
||||
if (init.body !== undefined && init.body != null) {
|
||||
// 1. Let Content-Type be null.
|
||||
// 2. Set initBody and Content-Type to the result of extracting
|
||||
// init["body"], with keepalive set to request’s keepalive.
|
||||
const [extractedBody, contentType] = extractBody(
|
||||
init.body,
|
||||
request.keepalive
|
||||
)
|
||||
initBody = extractedBody
|
||||
|
||||
// 3, If Content-Type is non-null and this’s headers’s header list does
|
||||
// not contain `Content-Type`, then append `Content-Type`/Content-Type to
|
||||
// this’s headers.
|
||||
if (contentType && !this[kHeaders].has('content-type')) {
|
||||
this[kHeaders].append('content-type', contentType)
|
||||
}
|
||||
}
|
||||
|
||||
// 37. Let inputOrInitBody be initBody if it is non-null; otherwise
|
||||
// inputBody.
|
||||
const inputOrInitBody = initBody ?? inputBody
|
||||
|
||||
// 38. If inputOrInitBody is non-null and inputOrInitBody’s source is
|
||||
// null, then:
|
||||
if (inputOrInitBody != null && inputOrInitBody.source == null) {
|
||||
// 1. If this’s request’s mode is neither "same-origin" nor "cors",
|
||||
// then throw a TypeError.
|
||||
if (request.mode !== 'same-origin' && request.mode !== 'cors') {
|
||||
throw new TypeError(
|
||||
'If request is made from ReadableStream, mode should be "same-origin" or "cors"'
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Set this’s request’s use-CORS-preflight flag.
|
||||
request.useCORSPreflightFlag = true
|
||||
}
|
||||
|
||||
// 39. Let finalBody be inputOrInitBody.
|
||||
let finalBody = inputOrInitBody
|
||||
|
||||
// 40. If initBody is null and inputBody is non-null, then:
|
||||
if (initBody == null && inputBody != null) {
|
||||
// 1. If input is unusable, then throw a TypeError.
|
||||
if (util.isDisturbed(inputBody.stream) || inputBody.stream.locked) {
|
||||
throw new TypeError(
|
||||
'Cannot construct a Request with a Request object that has already been used.'
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Set finalBody to the result of creating a proxy for inputBody.
|
||||
if (!TransformStream) {
|
||||
TransformStream = require('stream/web').TransformStream
|
||||
}
|
||||
|
||||
// https://streams.spec.whatwg.org/#readablestream-create-a-proxy
|
||||
const identityTransform = new TransformStream()
|
||||
inputBody.stream.pipeThrough(identityTransform)
|
||||
finalBody = {
|
||||
source: inputBody.source,
|
||||
length: inputBody.length,
|
||||
stream: identityTransform.readable
|
||||
}
|
||||
}
|
||||
|
||||
// 41. Set this’s request’s body to finalBody.
|
||||
this[kState].body = finalBody
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return this.constructor.name
|
||||
}
|
||||
|
||||
// Returns request’s HTTP method, which is "GET" by default.
|
||||
get method () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The method getter steps are to return this’s request’s method.
|
||||
return this[kState].method
|
||||
}
|
||||
|
||||
// Returns the URL of request as a string.
|
||||
get url () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The url getter steps are to return this’s request’s URL, serialized.
|
||||
return this[kState].url.toString()
|
||||
}
|
||||
|
||||
// Returns a Headers object consisting of the headers associated with request.
|
||||
// Note that headers added in the network layer by the user agent will not
|
||||
// be accounted for in this object, e.g., the "Host" header.
|
||||
get headers () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The headers getter steps are to return this’s headers.
|
||||
return this[kHeaders]
|
||||
}
|
||||
|
||||
// Returns the kind of resource requested by request, e.g., "document"
|
||||
// or "script".
|
||||
get destination () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The destination getter are to return this’s request’s destination.
|
||||
return this[kState].destination
|
||||
}
|
||||
|
||||
// Returns the referrer of request. Its value can be a same-origin URL if
|
||||
// explicitly set in init, the empty string to indicate no referrer, and
|
||||
// "about:client" when defaulting to the global’s default. This is used
|
||||
// during fetching to determine the value of the `Referer` header of the
|
||||
// request being made.
|
||||
get referrer () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// 1. If this’s request’s referrer is "no-referrer", then return the
|
||||
// empty string.
|
||||
if (this[kState].referrer === 'no-referrer') {
|
||||
return ''
|
||||
}
|
||||
|
||||
// 2. If this’s request’s referrer is "client", then return
|
||||
// "about:client".
|
||||
if (this[kState].referrer === 'client') {
|
||||
return 'about:client'
|
||||
}
|
||||
|
||||
// Return this’s request’s referrer, serialized.
|
||||
return this[kState].referrer.toString()
|
||||
}
|
||||
|
||||
// Returns the referrer policy associated with request.
|
||||
// This is used during fetching to compute the value of the request’s
|
||||
// referrer.
|
||||
get referrerPolicy () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The referrerPolicy getter steps are to return this’s request’s referrer policy.
|
||||
return this[kState].referrerPolicy
|
||||
}
|
||||
|
||||
// Returns the mode associated with request, which is a string indicating
|
||||
// whether the request will use CORS, or will be restricted to same-origin
|
||||
// URLs.
|
||||
get mode () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The mode getter steps are to return this’s request’s mode.
|
||||
return this[kState].mode
|
||||
}
|
||||
|
||||
// Returns the credentials mode associated with request,
|
||||
// which is a string indicating whether credentials will be sent with the
|
||||
// request always, never, or only when sent to a same-origin URL.
|
||||
get credentials () {
|
||||
// The credentials getter steps are to return this’s request’s credentials mode.
|
||||
return this[kState].credentials
|
||||
}
|
||||
|
||||
// Returns the cache mode associated with request,
|
||||
// which is a string indicating how the request will
|
||||
// interact with the browser’s cache when fetching.
|
||||
get cache () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The cache getter steps are to return this’s request’s cache mode.
|
||||
return this[kState].cache
|
||||
}
|
||||
|
||||
// Returns the redirect mode associated with request,
|
||||
// which is a string indicating how redirects for the
|
||||
// request will be handled during fetching. A request
|
||||
// will follow redirects by default.
|
||||
get redirect () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The redirect getter steps are to return this’s request’s redirect mode.
|
||||
return this[kState].redirect
|
||||
}
|
||||
|
||||
// Returns request’s subresource integrity metadata, which is a
|
||||
// cryptographic hash of the resource being fetched. Its value
|
||||
// consists of multiple hashes separated by whitespace. [SRI]
|
||||
get integrity () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The integrity getter steps are to return this’s request’s integrity
|
||||
// metadata.
|
||||
return this[kState].integrity
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether or not request can outlive the
|
||||
// global in which it was created.
|
||||
get keepalive () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The keepalive getter steps are to return this’s request’s keepalive.
|
||||
return this[kState].keepalive
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether or not request is for a reload
|
||||
// navigation.
|
||||
get isReloadNavigation () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The isReloadNavigation getter steps are to return true if this’s
|
||||
// request’s reload-navigation flag is set; otherwise false.
|
||||
return this[kState].reloadNavigation
|
||||
}
|
||||
|
||||
// Returns a boolean indicating whether or not request is for a history
|
||||
// navigation (a.k.a. back-foward navigation).
|
||||
get isHistoryNavigation () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The isHistoryNavigation getter steps are to return true if this’s request’s
|
||||
// history-navigation flag is set; otherwise false.
|
||||
return this[kState].historyNavigation
|
||||
}
|
||||
|
||||
// Returns the signal associated with request, which is an AbortSignal
|
||||
// object indicating whether or not request has been aborted, and its
|
||||
// abort event handler.
|
||||
get signal () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The signal getter steps are to return this’s signal.
|
||||
return this[kSignal]
|
||||
}
|
||||
|
||||
// Returns a clone of request.
|
||||
clone () {
|
||||
if (!(this instanceof Request)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// 1. If this is unusable, then throw a TypeError.
|
||||
if (this.bodyUsed || this.body?.locked) {
|
||||
throw new TypeError('unusable')
|
||||
}
|
||||
|
||||
// 2. Let clonedRequest be the result of cloning this’s request.
|
||||
const clonedRequest = cloneRequest(this[kState])
|
||||
|
||||
// 3. Let clonedRequestObject be the result of creating a Request object,
|
||||
// given clonedRequest, this’s headers’s guard, and this’s relevant Realm.
|
||||
const clonedRequestObject = new Request(kInit)
|
||||
clonedRequestObject[kState] = clonedRequest
|
||||
clonedRequestObject[kRealm] = this[kRealm]
|
||||
clonedRequestObject[kHeaders] = new Headers()
|
||||
clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList
|
||||
clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard]
|
||||
clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm]
|
||||
|
||||
// 4. Make clonedRequestObject’s signal follow this’s signal.
|
||||
const ac = new AbortController()
|
||||
if (this.signal.aborted) {
|
||||
ac.abort()
|
||||
} else {
|
||||
this.signal.addEventListener(
|
||||
'abort',
|
||||
function () {
|
||||
ac.abort()
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
}
|
||||
clonedRequestObject[kSignal] = ac.signal
|
||||
|
||||
// 4. Return clonedRequestObject.
|
||||
return clonedRequestObject
|
||||
}
|
||||
}
|
||||
|
||||
mixinBody(Request)
|
||||
|
||||
function makeRequest (init) {
|
||||
// https://fetch.spec.whatwg.org/#requests
|
||||
const request = {
|
||||
method: 'GET',
|
||||
localURLsOnly: false,
|
||||
unsafeRequest: false,
|
||||
body: null,
|
||||
client: null,
|
||||
reservedClient: null,
|
||||
replacesClientId: '',
|
||||
window: 'client',
|
||||
keepalive: false,
|
||||
serviceWorkers: 'all',
|
||||
initiator: '',
|
||||
destination: '',
|
||||
priority: null,
|
||||
origin: 'client',
|
||||
policyContainer: 'client',
|
||||
referrer: 'client',
|
||||
referrerPolicy: '',
|
||||
mode: 'no-cors',
|
||||
useCORSPreflightFlag: false,
|
||||
credentials: 'same-origin',
|
||||
useCredentials: false,
|
||||
cache: 'default',
|
||||
redirect: 'follow',
|
||||
integrity: '',
|
||||
cryptoGraphicsNonceMetadata: '',
|
||||
parserMetadata: '',
|
||||
reloadNavigation: false,
|
||||
historyNavigation: false,
|
||||
userActivation: false,
|
||||
taintedOrigin: false,
|
||||
redirectCount: 0,
|
||||
responseTainting: 'basic',
|
||||
preventNoCacheCacheControlHeaderModification: false,
|
||||
done: false,
|
||||
timingAllowFailed: false,
|
||||
...init,
|
||||
headersList: init.headersList
|
||||
? new HeadersList(init.headersList)
|
||||
: new HeadersList()
|
||||
}
|
||||
request.url = request.urlList[0]
|
||||
return request
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-request-clone
|
||||
function cloneRequest (request) {
|
||||
// To clone a request request, run these steps:
|
||||
|
||||
// 1. Let newRequest be a copy of request, except for its body.
|
||||
const newRequest = makeRequest({ ...request, body: null })
|
||||
|
||||
// 2. If request’s body is non-null, set newRequest’s body to the
|
||||
// result of cloning request’s body.
|
||||
if (request.body != null) {
|
||||
newRequest.body = cloneBody(request.body)
|
||||
}
|
||||
|
||||
// 3. Return newRequest.
|
||||
return newRequest
|
||||
}
|
||||
|
||||
Object.defineProperties(Request.prototype, {
|
||||
method: kEnumerableProperty,
|
||||
url: kEnumerableProperty,
|
||||
headers: kEnumerableProperty,
|
||||
redirect: kEnumerableProperty,
|
||||
clone: kEnumerableProperty,
|
||||
signal: kEnumerableProperty
|
||||
})
|
||||
|
||||
webidl.converters.Request = webidl.interfaceConverter(
|
||||
Request
|
||||
)
|
||||
|
||||
// https://fetch.spec.whatwg.org/#requestinfo
|
||||
webidl.converters.RequestInfo = function (V) {
|
||||
if (typeof V === 'string') {
|
||||
return webidl.converters.USVString(V)
|
||||
}
|
||||
|
||||
if (V instanceof Request) {
|
||||
return webidl.converters.Request(V)
|
||||
}
|
||||
|
||||
return webidl.converters.USVString(V)
|
||||
}
|
||||
|
||||
webidl.converters.AbortSignal = webidl.interfaceConverter(
|
||||
AbortSignal
|
||||
)
|
||||
|
||||
// https://fetch.spec.whatwg.org/#requestinit
|
||||
webidl.converters.RequestInit = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'method',
|
||||
converter: webidl.converters.ByteString
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
converter: webidl.converters.HeadersInit
|
||||
},
|
||||
{
|
||||
key: 'body',
|
||||
converter: webidl.nullableConverter(
|
||||
webidl.converters.BodyInit
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'referrer',
|
||||
converter: webidl.converters.USVString
|
||||
},
|
||||
{
|
||||
key: 'referrerPolicy',
|
||||
converter: webidl.converters.DOMString,
|
||||
// https://w3c.github.io/webappsec-referrer-policy/#referrer-policy
|
||||
allowedValues: [
|
||||
'', 'no-referrer', 'no-referrer-when-downgrade',
|
||||
'same-origin', 'origin', 'strict-origin',
|
||||
'origin-when-cross-origin', 'strict-origin-when-cross-origin',
|
||||
'unsafe-url'
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'mode',
|
||||
converter: webidl.converters.DOMString,
|
||||
// https://fetch.spec.whatwg.org/#concept-request-mode
|
||||
allowedValues: [
|
||||
'same-origin', 'cors', 'no-cors', 'navigate', 'websocket'
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'credentials',
|
||||
converter: webidl.converters.DOMString,
|
||||
// https://fetch.spec.whatwg.org/#requestcredentials
|
||||
allowedValues: [
|
||||
'omit', 'same-origin', 'include'
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'cache',
|
||||
converter: webidl.converters.DOMString,
|
||||
// https://fetch.spec.whatwg.org/#requestcache
|
||||
allowedValues: [
|
||||
'default', 'no-store', 'reload', 'no-cache', 'force-cache',
|
||||
'only-if-cached'
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'redirect',
|
||||
converter: webidl.converters.DOMString,
|
||||
// https://fetch.spec.whatwg.org/#requestredirect
|
||||
allowedValues: [
|
||||
'follow', 'error', 'manual'
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'integrity',
|
||||
converter: webidl.converters.DOMString
|
||||
},
|
||||
{
|
||||
key: 'keepalive',
|
||||
converter: webidl.converters.boolean
|
||||
},
|
||||
{
|
||||
key: 'signal',
|
||||
converter: webidl.nullableConverter(
|
||||
webidl.converters.AbortSignal
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'window',
|
||||
converter: webidl.converters.any
|
||||
}
|
||||
])
|
||||
|
||||
module.exports = { Request, makeRequest }
|
580
node_modules/undici/lib/fetch/response.js
generated
vendored
Normal file
580
node_modules/undici/lib/fetch/response.js
generated
vendored
Normal file
|
@ -0,0 +1,580 @@
|
|||
'use strict'
|
||||
|
||||
const { Headers, HeadersList, fill } = require('./headers')
|
||||
const { extractBody, cloneBody, mixinBody } = require('./body')
|
||||
const util = require('../core/util')
|
||||
const { kEnumerableProperty } = util
|
||||
const {
|
||||
responseURL,
|
||||
isValidReasonPhrase,
|
||||
isCancelled,
|
||||
isAborted,
|
||||
isBlobLike,
|
||||
serializeJavascriptValueToJSONString
|
||||
} = require('./util')
|
||||
const {
|
||||
redirectStatus,
|
||||
nullBodyStatus,
|
||||
DOMException
|
||||
} = require('./constants')
|
||||
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
|
||||
const { webidl } = require('./webidl')
|
||||
const { FormData } = require('./formdata')
|
||||
const { kHeadersList } = require('../core/symbols')
|
||||
const assert = require('assert')
|
||||
const { types } = require('util')
|
||||
|
||||
const ReadableStream = globalThis.ReadableStream || require('stream/web').ReadableStream
|
||||
|
||||
// https://fetch.spec.whatwg.org/#response-class
|
||||
class Response {
|
||||
// Creates network error Response.
|
||||
static error () {
|
||||
// TODO
|
||||
const relevantRealm = { settingsObject: {} }
|
||||
|
||||
// The static error() method steps are to return the result of creating a
|
||||
// Response object, given a new network error, "immutable", and this’s
|
||||
// relevant Realm.
|
||||
const responseObject = new Response()
|
||||
responseObject[kState] = makeNetworkError()
|
||||
responseObject[kRealm] = relevantRealm
|
||||
responseObject[kHeaders][kHeadersList] = responseObject[kState].headersList
|
||||
responseObject[kHeaders][kGuard] = 'immutable'
|
||||
responseObject[kHeaders][kRealm] = relevantRealm
|
||||
return responseObject
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-response-json
|
||||
static json (data, init = {}) {
|
||||
if (arguments.length === 0) {
|
||||
throw new TypeError(
|
||||
'Failed to execute \'json\' on \'Response\': 1 argument required, but 0 present.'
|
||||
)
|
||||
}
|
||||
|
||||
if (init !== null) {
|
||||
init = webidl.converters.ResponseInit(init)
|
||||
}
|
||||
|
||||
// 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
|
||||
const bytes = new TextEncoder('utf-8').encode(
|
||||
serializeJavascriptValueToJSONString(data)
|
||||
)
|
||||
|
||||
// 2. Let body be the result of extracting bytes.
|
||||
const body = extractBody(bytes)
|
||||
|
||||
// 3. Let responseObject be the result of creating a Response object, given a new response,
|
||||
// "response", and this’s relevant Realm.
|
||||
const relevantRealm = { settingsObject: {} }
|
||||
const responseObject = new Response()
|
||||
responseObject[kRealm] = relevantRealm
|
||||
responseObject[kHeaders][kGuard] = 'response'
|
||||
responseObject[kHeaders][kRealm] = relevantRealm
|
||||
|
||||
// 4. Perform initialize a response given responseObject, init, and (body, "application/json").
|
||||
initializeResponse(responseObject, init, { body: body[0], type: 'application/json' })
|
||||
|
||||
// 5. Return responseObject.
|
||||
return responseObject
|
||||
}
|
||||
|
||||
// Creates a redirect Response that redirects to url with status status.
|
||||
static redirect (url, status = 302) {
|
||||
const relevantRealm = { settingsObject: {} }
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'redirect' on 'Response': 1 argument required, but only ${arguments.length} present.`
|
||||
)
|
||||
}
|
||||
|
||||
url = webidl.converters.USVString(url)
|
||||
status = webidl.converters['unsigned short'](status)
|
||||
|
||||
// 1. Let parsedURL be the result of parsing url with current settings
|
||||
// object’s API base URL.
|
||||
// 2. If parsedURL is failure, then throw a TypeError.
|
||||
// TODO: base-URL?
|
||||
let parsedURL
|
||||
try {
|
||||
parsedURL = new URL(url)
|
||||
} catch (err) {
|
||||
throw Object.assign(new TypeError('Failed to parse URL from ' + url), {
|
||||
cause: err
|
||||
})
|
||||
}
|
||||
|
||||
// 3. If status is not a redirect status, then throw a RangeError.
|
||||
if (!redirectStatus.includes(status)) {
|
||||
throw new RangeError('Invalid status code')
|
||||
}
|
||||
|
||||
// 4. Let responseObject be the result of creating a Response object,
|
||||
// given a new response, "immutable", and this’s relevant Realm.
|
||||
const responseObject = new Response()
|
||||
responseObject[kRealm] = relevantRealm
|
||||
responseObject[kHeaders][kGuard] = 'immutable'
|
||||
responseObject[kHeaders][kRealm] = relevantRealm
|
||||
|
||||
// 5. Set responseObject’s response’s status to status.
|
||||
responseObject[kState].status = status
|
||||
|
||||
// 6. Let value be parsedURL, serialized and isomorphic encoded.
|
||||
// TODO: isomorphic encoded?
|
||||
const value = parsedURL.toString()
|
||||
|
||||
// 7. Append `Location`/value to responseObject’s response’s header list.
|
||||
responseObject[kState].headersList.append('location', value)
|
||||
|
||||
// 8. Return responseObject.
|
||||
return responseObject
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-response
|
||||
constructor (body = null, init = {}) {
|
||||
if (body !== null) {
|
||||
body = webidl.converters.BodyInit(body)
|
||||
}
|
||||
|
||||
init = webidl.converters.ResponseInit(init)
|
||||
|
||||
// TODO
|
||||
this[kRealm] = { settingsObject: {} }
|
||||
|
||||
// 1. Set this’s response to a new response.
|
||||
this[kState] = makeResponse({})
|
||||
|
||||
// 2. Set this’s headers to a new Headers object with this’s relevant
|
||||
// Realm, whose header list is this’s response’s header list and guard
|
||||
// is "response".
|
||||
this[kHeaders] = new Headers()
|
||||
this[kHeaders][kGuard] = 'response'
|
||||
this[kHeaders][kHeadersList] = this[kState].headersList
|
||||
this[kHeaders][kRealm] = this[kRealm]
|
||||
|
||||
// 3. Let bodyWithType be null.
|
||||
let bodyWithType = null
|
||||
|
||||
// 4. If body is non-null, then set bodyWithType to the result of extracting body.
|
||||
if (body != null) {
|
||||
const [extractedBody, type] = extractBody(body)
|
||||
bodyWithType = { body: extractedBody, type }
|
||||
}
|
||||
|
||||
// 5. Perform initialize a response given this, init, and bodyWithType.
|
||||
initializeResponse(this, init, bodyWithType)
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return this.constructor.name
|
||||
}
|
||||
|
||||
// Returns response’s type, e.g., "cors".
|
||||
get type () {
|
||||
if (!(this instanceof Response)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The type getter steps are to return this’s response’s type.
|
||||
return this[kState].type
|
||||
}
|
||||
|
||||
// Returns response’s URL, if it has one; otherwise the empty string.
|
||||
get url () {
|
||||
if (!(this instanceof Response)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The url getter steps are to return the empty string if this’s
|
||||
// response’s URL is null; otherwise this’s response’s URL,
|
||||
// serialized with exclude fragment set to true.
|
||||
let url = responseURL(this[kState])
|
||||
|
||||
if (url == null) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (url.hash) {
|
||||
url = new URL(url)
|
||||
url.hash = ''
|
||||
}
|
||||
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
// Returns whether response was obtained through a redirect.
|
||||
get redirected () {
|
||||
if (!(this instanceof Response)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The redirected getter steps are to return true if this’s response’s URL
|
||||
// list has more than one item; otherwise false.
|
||||
return this[kState].urlList.length > 1
|
||||
}
|
||||
|
||||
// Returns response’s status.
|
||||
get status () {
|
||||
if (!(this instanceof Response)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The status getter steps are to return this’s response’s status.
|
||||
return this[kState].status
|
||||
}
|
||||
|
||||
// Returns whether response’s status is an ok status.
|
||||
get ok () {
|
||||
if (!(this instanceof Response)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The ok getter steps are to return true if this’s response’s status is an
|
||||
// ok status; otherwise false.
|
||||
return this[kState].status >= 200 && this[kState].status <= 299
|
||||
}
|
||||
|
||||
// Returns response’s status message.
|
||||
get statusText () {
|
||||
if (!(this instanceof Response)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The statusText getter steps are to return this’s response’s status
|
||||
// message.
|
||||
return this[kState].statusText
|
||||
}
|
||||
|
||||
// Returns response’s headers as Headers.
|
||||
get headers () {
|
||||
if (!(this instanceof Response)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// The headers getter steps are to return this’s headers.
|
||||
return this[kHeaders]
|
||||
}
|
||||
|
||||
// Returns a clone of response.
|
||||
clone () {
|
||||
if (!(this instanceof Response)) {
|
||||
throw new TypeError('Illegal invocation')
|
||||
}
|
||||
|
||||
// 1. If this is unusable, then throw a TypeError.
|
||||
if (this.bodyUsed || (this.body && this.body.locked)) {
|
||||
webidl.errors.exception({
|
||||
header: 'Response.clone',
|
||||
message: 'Body has already been consumed.'
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Let clonedResponse be the result of cloning this’s response.
|
||||
const clonedResponse = cloneResponse(this[kState])
|
||||
|
||||
// 3. Return the result of creating a Response object, given
|
||||
// clonedResponse, this’s headers’s guard, and this’s relevant Realm.
|
||||
const clonedResponseObject = new Response()
|
||||
clonedResponseObject[kState] = clonedResponse
|
||||
clonedResponseObject[kRealm] = this[kRealm]
|
||||
clonedResponseObject[kHeaders][kHeadersList] = clonedResponse.headersList
|
||||
clonedResponseObject[kHeaders][kGuard] = this[kHeaders][kGuard]
|
||||
clonedResponseObject[kHeaders][kRealm] = this[kHeaders][kRealm]
|
||||
|
||||
return clonedResponseObject
|
||||
}
|
||||
}
|
||||
|
||||
mixinBody(Response)
|
||||
|
||||
Object.defineProperties(Response.prototype, {
|
||||
type: kEnumerableProperty,
|
||||
url: kEnumerableProperty,
|
||||
status: kEnumerableProperty,
|
||||
ok: kEnumerableProperty,
|
||||
redirected: kEnumerableProperty,
|
||||
statusText: kEnumerableProperty,
|
||||
headers: kEnumerableProperty,
|
||||
clone: kEnumerableProperty
|
||||
})
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-response-clone
|
||||
function cloneResponse (response) {
|
||||
// To clone a response response, run these steps:
|
||||
|
||||
// 1. If response is a filtered response, then return a new identical
|
||||
// filtered response whose internal response is a clone of response’s
|
||||
// internal response.
|
||||
if (response.internalResponse) {
|
||||
return filterResponse(
|
||||
cloneResponse(response.internalResponse),
|
||||
response.type
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Let newResponse be a copy of response, except for its body.
|
||||
const newResponse = makeResponse({ ...response, body: null })
|
||||
|
||||
// 3. If response’s body is non-null, then set newResponse’s body to the
|
||||
// result of cloning response’s body.
|
||||
if (response.body != null) {
|
||||
newResponse.body = cloneBody(response.body)
|
||||
}
|
||||
|
||||
// 4. Return newResponse.
|
||||
return newResponse
|
||||
}
|
||||
|
||||
function makeResponse (init) {
|
||||
return {
|
||||
aborted: false,
|
||||
rangeRequested: false,
|
||||
timingAllowPassed: false,
|
||||
requestIncludesCredentials: false,
|
||||
type: 'default',
|
||||
status: 200,
|
||||
timingInfo: null,
|
||||
cacheState: '',
|
||||
statusText: '',
|
||||
...init,
|
||||
headersList: init.headersList
|
||||
? new HeadersList(init.headersList)
|
||||
: new HeadersList(),
|
||||
urlList: init.urlList ? [...init.urlList] : []
|
||||
}
|
||||
}
|
||||
|
||||
function makeNetworkError (reason) {
|
||||
return makeResponse({
|
||||
type: 'error',
|
||||
status: 0,
|
||||
error:
|
||||
reason instanceof Error
|
||||
? reason
|
||||
: new Error(reason ? String(reason) : reason, {
|
||||
cause: reason instanceof Error ? reason : undefined
|
||||
}),
|
||||
aborted: reason && reason.name === 'AbortError'
|
||||
})
|
||||
}
|
||||
|
||||
function makeFilteredResponse (response, state) {
|
||||
state = {
|
||||
internalResponse: response,
|
||||
...state
|
||||
}
|
||||
|
||||
return new Proxy(response, {
|
||||
get (target, p) {
|
||||
return p in state ? state[p] : target[p]
|
||||
},
|
||||
set (target, p, value) {
|
||||
assert(!(p in state))
|
||||
target[p] = value
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-filtered-response
|
||||
function filterResponse (response, type) {
|
||||
// Set response to the following filtered response with response as its
|
||||
// internal response, depending on request’s response tainting:
|
||||
if (type === 'basic') {
|
||||
// A basic filtered response is a filtered response whose type is "basic"
|
||||
// and header list excludes any headers in internal response’s header list
|
||||
// whose name is a forbidden response-header name.
|
||||
|
||||
// Note: undici does not implement forbidden response-header names
|
||||
return makeFilteredResponse(response, {
|
||||
type: 'basic',
|
||||
headersList: response.headersList
|
||||
})
|
||||
} else if (type === 'cors') {
|
||||
// A CORS filtered response is a filtered response whose type is "cors"
|
||||
// and header list excludes any headers in internal response’s header
|
||||
// list whose name is not a CORS-safelisted response-header name, given
|
||||
// internal response’s CORS-exposed header-name list.
|
||||
|
||||
// Note: undici does not implement CORS-safelisted response-header names
|
||||
return makeFilteredResponse(response, {
|
||||
type: 'cors',
|
||||
headersList: response.headersList
|
||||
})
|
||||
} else if (type === 'opaque') {
|
||||
// An opaque filtered response is a filtered response whose type is
|
||||
// "opaque", URL list is the empty list, status is 0, status message
|
||||
// is the empty byte sequence, header list is empty, and body is null.
|
||||
|
||||
return makeFilteredResponse(response, {
|
||||
type: 'opaque',
|
||||
urlList: Object.freeze([]),
|
||||
status: 0,
|
||||
statusText: '',
|
||||
body: null
|
||||
})
|
||||
} else if (type === 'opaqueredirect') {
|
||||
// An opaque-redirect filtered response is a filtered response whose type
|
||||
// is "opaqueredirect", status is 0, status message is the empty byte
|
||||
// sequence, header list is empty, and body is null.
|
||||
|
||||
return makeFilteredResponse(response, {
|
||||
type: 'opaqueredirect',
|
||||
status: 0,
|
||||
statusText: '',
|
||||
headersList: [],
|
||||
body: null
|
||||
})
|
||||
} else {
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#appropriate-network-error
|
||||
function makeAppropriateNetworkError (fetchParams) {
|
||||
// 1. Assert: fetchParams is canceled.
|
||||
assert(isCancelled(fetchParams))
|
||||
|
||||
// 2. Return an aborted network error if fetchParams is aborted;
|
||||
// otherwise return a network error.
|
||||
return isAborted(fetchParams)
|
||||
? makeNetworkError(new DOMException('The operation was aborted.', 'AbortError'))
|
||||
: makeNetworkError(fetchParams.controller.terminated.reason)
|
||||
}
|
||||
|
||||
// https://whatpr.org/fetch/1392.html#initialize-a-response
|
||||
function initializeResponse (response, init, body) {
|
||||
// 1. If init["status"] is not in the range 200 to 599, inclusive, then
|
||||
// throw a RangeError.
|
||||
if (init.status !== null && (init.status < 200 || init.status > 599)) {
|
||||
throw new RangeError('init["status"] must be in the range of 200 to 599, inclusive.')
|
||||
}
|
||||
|
||||
// 2. If init["statusText"] does not match the reason-phrase token production,
|
||||
// then throw a TypeError.
|
||||
if ('statusText' in init && init.statusText != null) {
|
||||
// See, https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2:
|
||||
// reason-phrase = *( HTAB / SP / VCHAR / obs-text )
|
||||
if (!isValidReasonPhrase(String(init.statusText))) {
|
||||
throw new TypeError('Invalid statusText')
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Set response’s response’s status to init["status"].
|
||||
if ('status' in init && init.status != null) {
|
||||
response[kState].status = init.status
|
||||
}
|
||||
|
||||
// 4. Set response’s response’s status message to init["statusText"].
|
||||
if ('statusText' in init && init.statusText != null) {
|
||||
response[kState].statusText = init.statusText
|
||||
}
|
||||
|
||||
// 5. If init["headers"] exists, then fill response’s headers with init["headers"].
|
||||
if ('headers' in init && init.headers != null) {
|
||||
fill(response[kState].headersList, init.headers)
|
||||
}
|
||||
|
||||
// 6. If body was given, then:
|
||||
if (body) {
|
||||
// 1. If response's status is a null body status, then throw a TypeError.
|
||||
if (nullBodyStatus.includes(response.status)) {
|
||||
webidl.errors.exception({
|
||||
header: 'Response constructor',
|
||||
message: 'Invalid response status code.'
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Set response's body to body's body.
|
||||
response[kState].body = body.body
|
||||
|
||||
// 3. If body's type is non-null and response's header list does not contain
|
||||
// `Content-Type`, then append (`Content-Type`, body's type) to response's header list.
|
||||
if (body.type != null && !response[kState].headersList.has('Content-Type')) {
|
||||
response[kState].headersList.append('content-type', body.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
webidl.converters.ReadableStream = webidl.interfaceConverter(
|
||||
ReadableStream
|
||||
)
|
||||
|
||||
webidl.converters.FormData = webidl.interfaceConverter(
|
||||
FormData
|
||||
)
|
||||
|
||||
webidl.converters.URLSearchParams = webidl.interfaceConverter(
|
||||
URLSearchParams
|
||||
)
|
||||
|
||||
// https://fetch.spec.whatwg.org/#typedefdef-xmlhttprequestbodyinit
|
||||
webidl.converters.XMLHttpRequestBodyInit = function (V) {
|
||||
if (typeof V === 'string') {
|
||||
return webidl.converters.USVString(V)
|
||||
}
|
||||
|
||||
if (isBlobLike(V)) {
|
||||
return webidl.converters.Blob(V)
|
||||
}
|
||||
|
||||
if (
|
||||
types.isAnyArrayBuffer(V) ||
|
||||
types.isTypedArray(V) ||
|
||||
types.isDataView(V)
|
||||
) {
|
||||
return webidl.converters.BufferSource(V)
|
||||
}
|
||||
|
||||
if (V instanceof FormData) {
|
||||
return webidl.converters.FormData(V)
|
||||
}
|
||||
|
||||
if (V instanceof URLSearchParams) {
|
||||
return webidl.converters.URLSearchParams(V)
|
||||
}
|
||||
|
||||
return webidl.converters.DOMString(V)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#bodyinit
|
||||
webidl.converters.BodyInit = function (V) {
|
||||
if (V instanceof ReadableStream) {
|
||||
return webidl.converters.ReadableStream(V)
|
||||
}
|
||||
|
||||
// Note: the spec doesn't include async iterables,
|
||||
// this is an undici extension.
|
||||
if (V?.[Symbol.asyncIterator]) {
|
||||
return V
|
||||
}
|
||||
|
||||
return webidl.converters.XMLHttpRequestBodyInit(V)
|
||||
}
|
||||
|
||||
webidl.converters.ResponseInit = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'status',
|
||||
converter: webidl.converters['unsigned short'],
|
||||
defaultValue: 200
|
||||
},
|
||||
{
|
||||
key: 'statusText',
|
||||
converter: webidl.converters.ByteString,
|
||||
defaultValue: ''
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
converter: webidl.converters.HeadersInit
|
||||
}
|
||||
])
|
||||
|
||||
module.exports = {
|
||||
makeNetworkError,
|
||||
makeResponse,
|
||||
makeAppropriateNetworkError,
|
||||
filterResponse,
|
||||
Response
|
||||
}
|
10
node_modules/undici/lib/fetch/symbols.js
generated
vendored
Normal file
10
node_modules/undici/lib/fetch/symbols.js
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
kUrl: Symbol('url'),
|
||||
kHeaders: Symbol('headers'),
|
||||
kSignal: Symbol('signal'),
|
||||
kState: Symbol('state'),
|
||||
kGuard: Symbol('guard'),
|
||||
kRealm: Symbol('realm')
|
||||
}
|
467
node_modules/undici/lib/fetch/util.js
generated
vendored
Normal file
467
node_modules/undici/lib/fetch/util.js
generated
vendored
Normal file
|
@ -0,0 +1,467 @@
|
|||
'use strict'
|
||||
|
||||
const { redirectStatus } = require('./constants')
|
||||
const { performance } = require('perf_hooks')
|
||||
const { isBlobLike, toUSVString, ReadableStreamFrom } = require('../core/util')
|
||||
const assert = require('assert')
|
||||
|
||||
let File
|
||||
|
||||
// https://fetch.spec.whatwg.org/#block-bad-port
|
||||
const badPorts = [
|
||||
'1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79',
|
||||
'87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137',
|
||||
'139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532',
|
||||
'540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723',
|
||||
'2049', '3659', '4045', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6697',
|
||||
'10080'
|
||||
]
|
||||
|
||||
function responseURL (response) {
|
||||
// https://fetch.spec.whatwg.org/#responses
|
||||
// A response has an associated URL. It is a pointer to the last URL
|
||||
// in response’s URL list and null if response’s URL list is empty.
|
||||
const urlList = response.urlList
|
||||
const length = urlList.length
|
||||
return length === 0 ? null : urlList[length - 1].toString()
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-response-location-url
|
||||
function responseLocationURL (response, requestFragment) {
|
||||
// 1. If response’s status is not a redirect status, then return null.
|
||||
if (!redirectStatus.includes(response.status)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 2. Let location be the result of extracting header list values given
|
||||
// `Location` and response’s header list.
|
||||
let location = response.headersList.get('location')
|
||||
|
||||
// 3. If location is a value, then set location to the result of parsing
|
||||
// location with response’s URL.
|
||||
location = location ? new URL(location, responseURL(response)) : null
|
||||
|
||||
// 4. If location is a URL whose fragment is null, then set location’s
|
||||
// fragment to requestFragment.
|
||||
if (location && !location.hash) {
|
||||
location.hash = requestFragment
|
||||
}
|
||||
|
||||
// 5. Return location.
|
||||
return location
|
||||
}
|
||||
|
||||
/** @returns {URL} */
|
||||
function requestCurrentURL (request) {
|
||||
return request.urlList[request.urlList.length - 1]
|
||||
}
|
||||
|
||||
function requestBadPort (request) {
|
||||
// 1. Let url be request’s current URL.
|
||||
const url = requestCurrentURL(request)
|
||||
|
||||
// 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port,
|
||||
// then return blocked.
|
||||
if (/^https?:/.test(url.protocol) && badPorts.includes(url.port)) {
|
||||
return 'blocked'
|
||||
}
|
||||
|
||||
// 3. Return allowed.
|
||||
return 'allowed'
|
||||
}
|
||||
|
||||
function isFileLike (object) {
|
||||
if (!File) {
|
||||
File = require('./file').File
|
||||
}
|
||||
return object instanceof File || (
|
||||
object &&
|
||||
(typeof object.stream === 'function' ||
|
||||
typeof object.arrayBuffer === 'function') &&
|
||||
/^(File)$/.test(object[Symbol.toStringTag])
|
||||
)
|
||||
}
|
||||
|
||||
// Check whether |statusText| is a ByteString and
|
||||
// matches the Reason-Phrase token production.
|
||||
// RFC 2616: https://tools.ietf.org/html/rfc2616
|
||||
// RFC 7230: https://tools.ietf.org/html/rfc7230
|
||||
// "reason-phrase = *( HTAB / SP / VCHAR / obs-text )"
|
||||
// https://github.com/chromium/chromium/blob/94.0.4604.1/third_party/blink/renderer/core/fetch/response.cc#L116
|
||||
function isValidReasonPhrase (statusText) {
|
||||
for (let i = 0; i < statusText.length; ++i) {
|
||||
const c = statusText.charCodeAt(i)
|
||||
if (
|
||||
!(
|
||||
(
|
||||
c === 0x09 || // HTAB
|
||||
(c >= 0x20 && c <= 0x7e) || // SP / VCHAR
|
||||
(c >= 0x80 && c <= 0xff)
|
||||
) // obs-text
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function isTokenChar (c) {
|
||||
return !(
|
||||
c >= 0x7f ||
|
||||
c <= 0x20 ||
|
||||
c === '(' ||
|
||||
c === ')' ||
|
||||
c === '<' ||
|
||||
c === '>' ||
|
||||
c === '@' ||
|
||||
c === ',' ||
|
||||
c === ';' ||
|
||||
c === ':' ||
|
||||
c === '\\' ||
|
||||
c === '"' ||
|
||||
c === '/' ||
|
||||
c === '[' ||
|
||||
c === ']' ||
|
||||
c === '?' ||
|
||||
c === '=' ||
|
||||
c === '{' ||
|
||||
c === '}'
|
||||
)
|
||||
}
|
||||
|
||||
// See RFC 7230, Section 3.2.6.
|
||||
// https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/third_party/blink/renderer/platform/network/http_parsers.cc#L321
|
||||
function isValidHTTPToken (characters) {
|
||||
if (!characters || typeof characters !== 'string') {
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < characters.length; ++i) {
|
||||
const c = characters.charCodeAt(i)
|
||||
if (c > 0x7f || !isTokenChar(c)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#header-name
|
||||
// https://github.com/chromium/chromium/blob/b3d37e6f94f87d59e44662d6078f6a12de845d17/net/http/http_util.cc#L342
|
||||
function isValidHeaderName (potentialValue) {
|
||||
if (potentialValue.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (const char of potentialValue) {
|
||||
if (!isValidHTTPToken(char)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://fetch.spec.whatwg.org/#header-value
|
||||
* @param {string} potentialValue
|
||||
*/
|
||||
function isValidHeaderValue (potentialValue) {
|
||||
// - Has no leading or trailing HTTP tab or space bytes.
|
||||
// - Contains no 0x00 (NUL) or HTTP newline bytes.
|
||||
if (
|
||||
potentialValue.startsWith('\t') ||
|
||||
potentialValue.startsWith(' ') ||
|
||||
potentialValue.endsWith('\t') ||
|
||||
potentialValue.endsWith(' ')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
potentialValue.includes('\0') ||
|
||||
potentialValue.includes('\r') ||
|
||||
potentialValue.includes('\n')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
|
||||
function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
|
||||
// Given a request request and a response actualResponse, this algorithm
|
||||
// updates request’s referrer policy according to the Referrer-Policy
|
||||
// header (if any) in actualResponse.
|
||||
|
||||
// 1. Let policy be the result of executing § 8.1 Parse a referrer policy
|
||||
// from a Referrer-Policy header on actualResponse.
|
||||
// TODO: https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header
|
||||
const policy = ''
|
||||
|
||||
// 2. If policy is not the empty string, then set request’s referrer policy to policy.
|
||||
if (policy !== '') {
|
||||
request.referrerPolicy = policy
|
||||
}
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check
|
||||
function crossOriginResourcePolicyCheck () {
|
||||
// TODO
|
||||
return 'allowed'
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-cors-check
|
||||
function corsCheck () {
|
||||
// TODO
|
||||
return 'success'
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-tao-check
|
||||
function TAOCheck () {
|
||||
// TODO
|
||||
return 'success'
|
||||
}
|
||||
|
||||
function appendFetchMetadata (httpRequest) {
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-dest-header
|
||||
// TODO
|
||||
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-mode-header
|
||||
|
||||
// 1. Assert: r’s url is a potentially trustworthy URL.
|
||||
// TODO
|
||||
|
||||
// 2. Let header be a Structured Header whose value is a token.
|
||||
let header = null
|
||||
|
||||
// 3. Set header’s value to r’s mode.
|
||||
header = httpRequest.mode
|
||||
|
||||
// 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list.
|
||||
httpRequest.headersList.set('sec-fetch-mode', header)
|
||||
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header
|
||||
// TODO
|
||||
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-user-header
|
||||
// TODO
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#append-a-request-origin-header
|
||||
function appendRequestOriginHeader (request) {
|
||||
// 1. Let serializedOrigin be the result of byte-serializing a request origin with request.
|
||||
let serializedOrigin = request.origin
|
||||
|
||||
// 2. If request’s response tainting is "cors" or request’s mode is "websocket", then append (`Origin`, serializedOrigin) to request’s header list.
|
||||
if (request.responseTainting === 'cors' || request.mode === 'websocket') {
|
||||
if (serializedOrigin) {
|
||||
request.headersList.append('Origin', serializedOrigin)
|
||||
}
|
||||
|
||||
// 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
|
||||
} else if (request.method !== 'GET' && request.method !== 'HEAD') {
|
||||
// 1. Switch on request’s referrer policy:
|
||||
switch (request.referrerPolicy) {
|
||||
case 'no-referrer':
|
||||
// Set serializedOrigin to `null`.
|
||||
serializedOrigin = null
|
||||
break
|
||||
case 'no-referrer-when-downgrade':
|
||||
case 'strict-origin':
|
||||
case 'strict-origin-when-cross-origin':
|
||||
// If request’s origin is a tuple origin, its scheme is "https", and request’s current URL’s scheme is not "https", then set serializedOrigin to `null`.
|
||||
if (/^https:/.test(request.origin) && !/^https:/.test(requestCurrentURL(request))) {
|
||||
serializedOrigin = null
|
||||
}
|
||||
break
|
||||
case 'same-origin':
|
||||
// If request’s origin is not same origin with request’s current URL’s origin, then set serializedOrigin to `null`.
|
||||
if (!sameOrigin(request, requestCurrentURL(request))) {
|
||||
serializedOrigin = null
|
||||
}
|
||||
break
|
||||
default:
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
if (serializedOrigin) {
|
||||
// 2. Append (`Origin`, serializedOrigin) to request’s header list.
|
||||
request.headersList.append('Origin', serializedOrigin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) {
|
||||
// TODO
|
||||
return performance.now()
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#create-an-opaque-timing-info
|
||||
function createOpaqueTimingInfo (timingInfo) {
|
||||
return {
|
||||
startTime: timingInfo.startTime ?? 0,
|
||||
redirectStartTime: 0,
|
||||
redirectEndTime: 0,
|
||||
postRedirectStartTime: timingInfo.startTime ?? 0,
|
||||
finalServiceWorkerStartTime: 0,
|
||||
finalNetworkResponseStartTime: 0,
|
||||
finalNetworkRequestStartTime: 0,
|
||||
endTime: 0,
|
||||
encodedBodySize: 0,
|
||||
decodedBodySize: 0,
|
||||
finalConnectionTimingInfo: null
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#policy-container
|
||||
function makePolicyContainer () {
|
||||
// TODO
|
||||
return {}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container
|
||||
function clonePolicyContainer () {
|
||||
// TODO
|
||||
return {}
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
|
||||
function determineRequestsReferrer (request) {
|
||||
// TODO
|
||||
return 'no-referrer'
|
||||
}
|
||||
|
||||
function matchRequestIntegrity (request, bytes) {
|
||||
return false
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
|
||||
function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin}
|
||||
* @param {URL} A
|
||||
* @param {URL} B
|
||||
*/
|
||||
function sameOrigin (A, B) {
|
||||
// 1. If A and B are the same opaque origin, then return true.
|
||||
// "opaque origin" is an internal value we cannot access, ignore.
|
||||
|
||||
// 2. If A and B are both tuple origins and their schemes,
|
||||
// hosts, and port are identical, then return true.
|
||||
if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 3. Return false.
|
||||
return false
|
||||
}
|
||||
|
||||
function createDeferredPromise () {
|
||||
let res
|
||||
let rej
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
res = resolve
|
||||
rej = reject
|
||||
})
|
||||
|
||||
return { promise, resolve: res, reject: rej }
|
||||
}
|
||||
|
||||
function isAborted (fetchParams) {
|
||||
return fetchParams.controller.state === 'aborted'
|
||||
}
|
||||
|
||||
function isCancelled (fetchParams) {
|
||||
return fetchParams.controller.state === 'aborted' ||
|
||||
fetchParams.controller.state === 'terminated'
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-method-normalize
|
||||
function normalizeMethod (method) {
|
||||
return /^(DELETE|GET|HEAD|OPTIONS|POST|PUT)$/i.test(method)
|
||||
? method.toUpperCase()
|
||||
: method
|
||||
}
|
||||
|
||||
// https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
|
||||
function serializeJavascriptValueToJSONString (value) {
|
||||
// 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
|
||||
const result = JSON.stringify(value)
|
||||
|
||||
// 2. If result is undefined, then throw a TypeError.
|
||||
if (result === undefined) {
|
||||
throw new TypeError('Value is not JSON serializable')
|
||||
}
|
||||
|
||||
// 3. Assert: result is a string.
|
||||
assert(typeof result === 'string')
|
||||
|
||||
// 4. Return result.
|
||||
return result
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
|
||||
const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
|
||||
|
||||
// https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
|
||||
function makeIterator (iterator, name) {
|
||||
const i = {
|
||||
next () {
|
||||
if (Object.getPrototypeOf(this) !== i) {
|
||||
throw new TypeError(
|
||||
`'next' called on an object that does not implement interface ${name} Iterator.`
|
||||
)
|
||||
}
|
||||
|
||||
return iterator.next()
|
||||
},
|
||||
// The class string of an iterator prototype object for a given interface is the
|
||||
// result of concatenating the identifier of the interface and the string " Iterator".
|
||||
[Symbol.toStringTag]: `${name} Iterator`
|
||||
}
|
||||
|
||||
// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
|
||||
Object.setPrototypeOf(i, esIteratorPrototype)
|
||||
// esIteratorPrototype needs to be the prototype of i
|
||||
// which is the prototype of an empty object. Yes, it's confusing.
|
||||
return Object.setPrototypeOf({}, i)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isAborted,
|
||||
isCancelled,
|
||||
createDeferredPromise,
|
||||
ReadableStreamFrom,
|
||||
toUSVString,
|
||||
tryUpgradeRequestToAPotentiallyTrustworthyURL,
|
||||
coarsenedSharedCurrentTime,
|
||||
matchRequestIntegrity,
|
||||
determineRequestsReferrer,
|
||||
makePolicyContainer,
|
||||
clonePolicyContainer,
|
||||
appendFetchMetadata,
|
||||
appendRequestOriginHeader,
|
||||
TAOCheck,
|
||||
corsCheck,
|
||||
crossOriginResourcePolicyCheck,
|
||||
createOpaqueTimingInfo,
|
||||
setRequestReferrerPolicyOnRedirect,
|
||||
isValidHTTPToken,
|
||||
requestBadPort,
|
||||
requestCurrentURL,
|
||||
responseURL,
|
||||
responseLocationURL,
|
||||
isBlobLike,
|
||||
isFileLike,
|
||||
isValidReasonPhrase,
|
||||
sameOrigin,
|
||||
normalizeMethod,
|
||||
serializeJavascriptValueToJSONString,
|
||||
makeIterator,
|
||||
isValidHeaderName,
|
||||
isValidHeaderValue
|
||||
}
|
594
node_modules/undici/lib/fetch/webidl.js
generated
vendored
Normal file
594
node_modules/undici/lib/fetch/webidl.js
generated
vendored
Normal file
|
@ -0,0 +1,594 @@
|
|||
'use strict'
|
||||
|
||||
const { toUSVString, types } = require('util')
|
||||
|
||||
const webidl = {}
|
||||
webidl.converters = {}
|
||||
webidl.util = {}
|
||||
webidl.errors = {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{
|
||||
* header: string
|
||||
* message: string
|
||||
* }} message
|
||||
*/
|
||||
webidl.errors.exception = function (message) {
|
||||
throw new TypeError(`${message.header}: ${message.message}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an error when conversion from one type to another has failed
|
||||
* @param {{
|
||||
* prefix: string
|
||||
* argument: string
|
||||
* types: string[]
|
||||
* }} context
|
||||
*/
|
||||
webidl.errors.conversionFailed = function (context) {
|
||||
const plural = context.types.length === 1 ? '' : ' one of'
|
||||
const message =
|
||||
`${context.argument} could not be converted to` +
|
||||
`${plural}: ${context.types.join(', ')}.`
|
||||
|
||||
return webidl.errors.exception({
|
||||
header: context.prefix,
|
||||
message
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an error when an invalid argument is provided
|
||||
* @param {{
|
||||
* prefix: string
|
||||
* value: string
|
||||
* type: string
|
||||
* }} context
|
||||
*/
|
||||
webidl.errors.invalidArgument = function (context) {
|
||||
return webidl.errors.exception({
|
||||
header: context.prefix,
|
||||
message: `"${context.value}" is an invalid ${context.type}.`
|
||||
})
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values
|
||||
webidl.util.Type = function (V) {
|
||||
switch (typeof V) {
|
||||
case 'undefined': return 'Undefined'
|
||||
case 'boolean': return 'Boolean'
|
||||
case 'string': return 'String'
|
||||
case 'symbol': return 'Symbol'
|
||||
case 'number': return 'Number'
|
||||
case 'bigint': return 'BigInt'
|
||||
case 'function':
|
||||
case 'object': {
|
||||
if (V === null) {
|
||||
return 'Null'
|
||||
}
|
||||
|
||||
return 'Object'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
|
||||
webidl.util.ConvertToInt = function (V, bitLength, signedness, opts = {}) {
|
||||
let upperBound
|
||||
let lowerBound
|
||||
|
||||
// 1. If bitLength is 64, then:
|
||||
if (bitLength === 64) {
|
||||
// 1. Let upperBound be 2^53 − 1.
|
||||
upperBound = Math.pow(2, 53) - 1
|
||||
|
||||
// 2. If signedness is "unsigned", then let lowerBound be 0.
|
||||
if (signedness === 'unsigned') {
|
||||
lowerBound = 0
|
||||
} else {
|
||||
// 3. Otherwise let lowerBound be −2^53 + 1.
|
||||
lowerBound = Math.pow(-2, 53) + 1
|
||||
}
|
||||
} else if (signedness === 'unsigned') {
|
||||
// 2. Otherwise, if signedness is "unsigned", then:
|
||||
|
||||
// 1. Let lowerBound be 0.
|
||||
lowerBound = 0
|
||||
|
||||
// 2. Let upperBound be 2^bitLength − 1.
|
||||
upperBound = Math.pow(2, bitLength) - 1
|
||||
} else {
|
||||
// 3. Otherwise:
|
||||
|
||||
// 1. Let lowerBound be -2^bitLength − 1.
|
||||
lowerBound = Math.pow(-2, bitLength) - 1
|
||||
|
||||
// 2. Let upperBound be 2^bitLength − 1 − 1.
|
||||
upperBound = Math.pow(2, bitLength - 1) - 1
|
||||
}
|
||||
|
||||
// 4. Let x be ? ToNumber(V).
|
||||
let x = Number(V)
|
||||
|
||||
// 5. If x is −0, then set x to +0.
|
||||
if (Object.is(-0, x)) {
|
||||
x = 0
|
||||
}
|
||||
|
||||
// 6. If the conversion is to an IDL type associated
|
||||
// with the [EnforceRange] extended attribute, then:
|
||||
if (opts.enforceRange === true) {
|
||||
// 1. If x is NaN, +∞, or −∞, then throw a TypeError.
|
||||
if (
|
||||
Number.isNaN(x) ||
|
||||
x === Number.POSITIVE_INFINITY ||
|
||||
x === Number.NEGATIVE_INFINITY
|
||||
) {
|
||||
webidl.errors.exception({
|
||||
header: 'Integer conversion',
|
||||
message: `Could not convert ${V} to an integer.`
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Set x to IntegerPart(x).
|
||||
x = webidl.util.IntegerPart(x)
|
||||
|
||||
// 3. If x < lowerBound or x > upperBound, then
|
||||
// throw a TypeError.
|
||||
if (x < lowerBound || x > upperBound) {
|
||||
webidl.errors.exception({
|
||||
header: 'Integer conversion',
|
||||
message: `Value must be between ${lowerBound}-${upperBound}, got ${x}.`
|
||||
})
|
||||
}
|
||||
|
||||
// 4. Return x.
|
||||
return x
|
||||
}
|
||||
|
||||
// 7. If x is not NaN and the conversion is to an IDL
|
||||
// type associated with the [Clamp] extended
|
||||
// attribute, then:
|
||||
if (!Number.isNaN(x) && opts.clamp === true) {
|
||||
// 1. Set x to min(max(x, lowerBound), upperBound).
|
||||
x = Math.min(Math.max(x, lowerBound), upperBound)
|
||||
|
||||
// 2. Round x to the nearest integer, choosing the
|
||||
// even integer if it lies halfway between two,
|
||||
// and choosing +0 rather than −0.
|
||||
if (Math.floor(x) % 2 === 0) {
|
||||
x = Math.floor(x)
|
||||
} else {
|
||||
x = Math.ceil(x)
|
||||
}
|
||||
|
||||
// 3. Return x.
|
||||
return x
|
||||
}
|
||||
|
||||
// 8. If x is NaN, +0, +∞, or −∞, then return +0.
|
||||
if (
|
||||
Number.isNaN(x) ||
|
||||
Object.is(0, x) ||
|
||||
x === Number.POSITIVE_INFINITY ||
|
||||
x === Number.NEGATIVE_INFINITY
|
||||
) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 9. Set x to IntegerPart(x).
|
||||
x = webidl.util.IntegerPart(x)
|
||||
|
||||
// 10. Set x to x modulo 2^bitLength.
|
||||
x = x % Math.pow(2, bitLength)
|
||||
|
||||
// 11. If signedness is "signed" and x ≥ 2^bitLength − 1,
|
||||
// then return x − 2^bitLength.
|
||||
if (signedness === 'signed' && x >= Math.pow(2, bitLength) - 1) {
|
||||
return x - Math.pow(2, bitLength)
|
||||
}
|
||||
|
||||
// 12. Otherwise, return x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart
|
||||
webidl.util.IntegerPart = function (n) {
|
||||
// 1. Let r be floor(abs(n)).
|
||||
const r = Math.floor(Math.abs(n))
|
||||
|
||||
// 2. If n < 0, then return -1 × r.
|
||||
if (n < 0) {
|
||||
return -1 * r
|
||||
}
|
||||
|
||||
// 3. Otherwise, return r.
|
||||
return r
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-sequence
|
||||
webidl.sequenceConverter = function (converter) {
|
||||
return (V) => {
|
||||
// 1. If Type(V) is not Object, throw a TypeError.
|
||||
if (webidl.util.Type(V) !== 'Object') {
|
||||
webidl.errors.exception({
|
||||
header: 'Sequence',
|
||||
message: `Value of type ${webidl.util.Type(V)} is not an Object.`
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Let method be ? GetMethod(V, @@iterator).
|
||||
/** @type {Generator} */
|
||||
const method = V?.[Symbol.iterator]?.()
|
||||
const seq = []
|
||||
|
||||
// 3. If method is undefined, throw a TypeError.
|
||||
if (
|
||||
method === undefined ||
|
||||
typeof method.next !== 'function'
|
||||
) {
|
||||
webidl.errors.exception({
|
||||
header: 'Sequence',
|
||||
message: 'Object is not an iterator.'
|
||||
})
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#create-sequence-from-iterable
|
||||
while (true) {
|
||||
const { done, value } = method.next()
|
||||
|
||||
if (done) {
|
||||
break
|
||||
}
|
||||
|
||||
seq.push(converter(value))
|
||||
}
|
||||
|
||||
return seq
|
||||
}
|
||||
}
|
||||
|
||||
webidl.recordConverter = function (keyConverter, valueConverter) {
|
||||
return (V) => {
|
||||
const record = {}
|
||||
const type = webidl.util.Type(V)
|
||||
|
||||
if (type === 'Undefined' || type === 'Null') {
|
||||
return record
|
||||
}
|
||||
|
||||
if (type !== 'Object') {
|
||||
webidl.errors.exception({
|
||||
header: 'Record',
|
||||
message: `Expected ${V} to be an Object type.`
|
||||
})
|
||||
}
|
||||
|
||||
for (let [key, value] of Object.entries(V)) {
|
||||
key = keyConverter(key)
|
||||
value = valueConverter(value)
|
||||
|
||||
record[key] = value
|
||||
}
|
||||
|
||||
return record
|
||||
}
|
||||
}
|
||||
|
||||
webidl.interfaceConverter = function (i) {
|
||||
return (V, opts = {}) => {
|
||||
if (opts.strict !== false && !(V instanceof i)) {
|
||||
webidl.errors.exception({
|
||||
header: i.name,
|
||||
message: `Expected ${V} to be an instance of ${i.name}.`
|
||||
})
|
||||
}
|
||||
|
||||
return V
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* key: string,
|
||||
* defaultValue?: any,
|
||||
* required?: boolean,
|
||||
* converter: (...args: unknown[]) => unknown,
|
||||
* allowedValues?: any[]
|
||||
* }[]} converters
|
||||
* @returns
|
||||
*/
|
||||
webidl.dictionaryConverter = function (converters) {
|
||||
return (dictionary) => {
|
||||
const type = webidl.util.Type(dictionary)
|
||||
const dict = {}
|
||||
|
||||
if (type !== 'Null' && type !== 'Undefined' && type !== 'Object') {
|
||||
webidl.errors.exception({
|
||||
header: 'Dictionary',
|
||||
message: `Expected ${dictionary} to be one of: Null, Undefined, Object.`
|
||||
})
|
||||
}
|
||||
|
||||
for (const options of converters) {
|
||||
const { key, defaultValue, required, converter } = options
|
||||
|
||||
if (required === true) {
|
||||
if (!Object.hasOwn(dictionary, key)) {
|
||||
webidl.errors.exception({
|
||||
header: 'Dictionary',
|
||||
message: `Missing required key "${key}".`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let value = dictionary[key]
|
||||
const hasDefault = Object.hasOwn(options, 'defaultValue')
|
||||
|
||||
// Only use defaultValue if value is undefined and
|
||||
// a defaultValue options was provided.
|
||||
if (hasDefault && value !== null) {
|
||||
value = value ?? defaultValue
|
||||
}
|
||||
|
||||
// A key can be optional and have no default value.
|
||||
// When this happens, do not perform a conversion,
|
||||
// and do not assign the key a value.
|
||||
if (required || hasDefault || value !== undefined) {
|
||||
value = converter(value)
|
||||
|
||||
if (
|
||||
options.allowedValues &&
|
||||
!options.allowedValues.includes(value)
|
||||
) {
|
||||
webidl.errors.exception({
|
||||
header: 'Dictionary',
|
||||
message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(', ')}.`
|
||||
})
|
||||
}
|
||||
|
||||
dict[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return dict
|
||||
}
|
||||
}
|
||||
|
||||
webidl.nullableConverter = function (converter) {
|
||||
return (V) => {
|
||||
if (V === null) {
|
||||
return V
|
||||
}
|
||||
|
||||
return converter(V)
|
||||
}
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-DOMString
|
||||
webidl.converters.DOMString = function (V, opts = {}) {
|
||||
// 1. If V is null and the conversion is to an IDL type
|
||||
// associated with the [LegacyNullToEmptyString]
|
||||
// extended attribute, then return the DOMString value
|
||||
// that represents the empty string.
|
||||
if (V === null && opts.legacyNullToEmptyString) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// 2. Let x be ? ToString(V).
|
||||
if (typeof V === 'symbol') {
|
||||
throw new TypeError('Could not convert argument of type symbol to string.')
|
||||
}
|
||||
|
||||
// 3. Return the IDL DOMString value that represents the
|
||||
// same sequence of code units as the one the
|
||||
// ECMAScript String value x represents.
|
||||
return String(V)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const isNotLatin1 = /[^\u0000-\u00ff]/
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-ByteString
|
||||
webidl.converters.ByteString = function (V) {
|
||||
// 1. Let x be ? ToString(V).
|
||||
// Note: DOMString converter perform ? ToString(V)
|
||||
const x = webidl.converters.DOMString(V)
|
||||
|
||||
// 2. If the value of any element of x is greater than
|
||||
// 255, then throw a TypeError.
|
||||
if (isNotLatin1.test(x)) {
|
||||
throw new TypeError('Argument is not a ByteString')
|
||||
}
|
||||
|
||||
// 3. Return an IDL ByteString value whose length is the
|
||||
// length of x, and where the value of each element is
|
||||
// the value of the corresponding element of x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-USVString
|
||||
// TODO: ensure that util.toUSVString follows webidl spec
|
||||
webidl.converters.USVString = toUSVString
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-boolean
|
||||
webidl.converters.boolean = function (V) {
|
||||
// 1. Let x be the result of computing ToBoolean(V).
|
||||
const x = Boolean(V)
|
||||
|
||||
// 2. Return the IDL boolean value that is the one that represents
|
||||
// the same truth value as the ECMAScript Boolean value x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-any
|
||||
webidl.converters.any = function (V) {
|
||||
return V
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-long-long
|
||||
webidl.converters['long long'] = function (V, opts) {
|
||||
// 1. Let x be ? ConvertToInt(V, 64, "signed").
|
||||
const x = webidl.util.ConvertToInt(V, 64, 'signed', opts)
|
||||
|
||||
// 2. Return the IDL long long value that represents
|
||||
// the same numeric value as x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-unsigned-short
|
||||
webidl.converters['unsigned short'] = function (V) {
|
||||
// 1. Let x be ? ConvertToInt(V, 16, "unsigned").
|
||||
const x = webidl.util.ConvertToInt(V, 16, 'unsigned')
|
||||
|
||||
// 2. Return the IDL unsigned short value that represents
|
||||
// the same numeric value as x.
|
||||
return x
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#idl-ArrayBuffer
|
||||
webidl.converters.ArrayBuffer = function (V, opts = {}) {
|
||||
// 1. If Type(V) is not Object, or V does not have an
|
||||
// [[ArrayBufferData]] internal slot, then throw a
|
||||
// TypeError.
|
||||
// see: https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-instances
|
||||
// see: https://tc39.es/ecma262/#sec-properties-of-the-sharedarraybuffer-instances
|
||||
if (
|
||||
webidl.util.Type(V) !== 'Object' ||
|
||||
!types.isAnyArrayBuffer(V)
|
||||
) {
|
||||
webidl.errors.conversionFailed({
|
||||
prefix: `${V}`,
|
||||
argument: `${V}`,
|
||||
types: ['ArrayBuffer']
|
||||
})
|
||||
}
|
||||
|
||||
// 2. If the conversion is not to an IDL type associated
|
||||
// with the [AllowShared] extended attribute, and
|
||||
// IsSharedArrayBuffer(V) is true, then throw a
|
||||
// TypeError.
|
||||
if (opts.allowShared === false && types.isSharedArrayBuffer(V)) {
|
||||
webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'SharedArrayBuffer is not allowed.'
|
||||
})
|
||||
}
|
||||
|
||||
// 3. If the conversion is not to an IDL type associated
|
||||
// with the [AllowResizable] extended attribute, and
|
||||
// IsResizableArrayBuffer(V) is true, then throw a
|
||||
// TypeError.
|
||||
// Note: resizable ArrayBuffers are currently a proposal.
|
||||
|
||||
// 4. Return the IDL ArrayBuffer value that is a
|
||||
// reference to the same object as V.
|
||||
return V
|
||||
}
|
||||
|
||||
webidl.converters.TypedArray = function (V, T, opts = {}) {
|
||||
// 1. Let T be the IDL type V is being converted to.
|
||||
|
||||
// 2. If Type(V) is not Object, or V does not have a
|
||||
// [[TypedArrayName]] internal slot with a value
|
||||
// equal to T’s name, then throw a TypeError.
|
||||
if (
|
||||
webidl.util.Type(V) !== 'Object' ||
|
||||
!types.isTypedArray(V) ||
|
||||
V.constructor.name !== T.name
|
||||
) {
|
||||
webidl.errors.conversionFailed({
|
||||
prefix: `${T.name}`,
|
||||
argument: `${V}`,
|
||||
types: [T.name]
|
||||
})
|
||||
}
|
||||
|
||||
// 3. If the conversion is not to an IDL type associated
|
||||
// with the [AllowShared] extended attribute, and
|
||||
// IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is
|
||||
// true, then throw a TypeError.
|
||||
if (opts.allowShared === false && types.isSharedArrayBuffer(V.buffer)) {
|
||||
webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'SharedArrayBuffer is not allowed.'
|
||||
})
|
||||
}
|
||||
|
||||
// 4. If the conversion is not to an IDL type associated
|
||||
// with the [AllowResizable] extended attribute, and
|
||||
// IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is
|
||||
// true, then throw a TypeError.
|
||||
// Note: resizable array buffers are currently a proposal
|
||||
|
||||
// 5. Return the IDL value of type T that is a reference
|
||||
// to the same object as V.
|
||||
return V
|
||||
}
|
||||
|
||||
webidl.converters.DataView = function (V, opts = {}) {
|
||||
// 1. If Type(V) is not Object, or V does not have a
|
||||
// [[DataView]] internal slot, then throw a TypeError.
|
||||
if (webidl.util.Type(V) !== 'Object' || !types.isDataView(V)) {
|
||||
webidl.errors.exception({
|
||||
header: 'DataView',
|
||||
message: 'Object is not a DataView.'
|
||||
})
|
||||
}
|
||||
|
||||
// 2. If the conversion is not to an IDL type associated
|
||||
// with the [AllowShared] extended attribute, and
|
||||
// IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is true,
|
||||
// then throw a TypeError.
|
||||
if (opts.allowShared === false && types.isSharedArrayBuffer(V.buffer)) {
|
||||
webidl.errors.exception({
|
||||
header: 'ArrayBuffer',
|
||||
message: 'SharedArrayBuffer is not allowed.'
|
||||
})
|
||||
}
|
||||
|
||||
// 3. If the conversion is not to an IDL type associated
|
||||
// with the [AllowResizable] extended attribute, and
|
||||
// IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is
|
||||
// true, then throw a TypeError.
|
||||
// Note: resizable ArrayBuffers are currently a proposal
|
||||
|
||||
// 4. Return the IDL DataView value that is a reference
|
||||
// to the same object as V.
|
||||
return V
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#BufferSource
|
||||
webidl.converters.BufferSource = function (V, opts = {}) {
|
||||
if (types.isAnyArrayBuffer(V)) {
|
||||
return webidl.converters.ArrayBuffer(V, opts)
|
||||
}
|
||||
|
||||
if (types.isTypedArray(V)) {
|
||||
return webidl.converters.TypedArray(V, V.constructor)
|
||||
}
|
||||
|
||||
if (types.isDataView(V)) {
|
||||
return webidl.converters.DataView(V, opts)
|
||||
}
|
||||
|
||||
throw new TypeError(`Could not convert ${V} to a BufferSource.`)
|
||||
}
|
||||
|
||||
webidl.converters['sequence<ByteString>'] = webidl.sequenceConverter(
|
||||
webidl.converters.ByteString
|
||||
)
|
||||
|
||||
webidl.converters['sequence<sequence<ByteString>>'] = webidl.sequenceConverter(
|
||||
webidl.converters['sequence<ByteString>']
|
||||
)
|
||||
|
||||
webidl.converters['record<ByteString, ByteString>'] = webidl.recordConverter(
|
||||
webidl.converters.ByteString,
|
||||
webidl.converters.ByteString
|
||||
)
|
||||
|
||||
module.exports = {
|
||||
webidl
|
||||
}
|
32
node_modules/undici/lib/global.js
generated
vendored
Normal file
32
node_modules/undici/lib/global.js
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
'use strict'
|
||||
|
||||
// We include a version number for the Dispatcher API. In case of breaking changes,
|
||||
// this version number must be increased to avoid conflicts.
|
||||
const globalDispatcher = Symbol.for('undici.globalDispatcher.1')
|
||||
const { InvalidArgumentError } = require('./core/errors')
|
||||
const Agent = require('./agent')
|
||||
|
||||
if (getGlobalDispatcher() === undefined) {
|
||||
setGlobalDispatcher(new Agent())
|
||||
}
|
||||
|
||||
function setGlobalDispatcher (agent) {
|
||||
if (!agent || typeof agent.dispatch !== 'function') {
|
||||
throw new InvalidArgumentError('Argument agent must implement Agent')
|
||||
}
|
||||
Object.defineProperty(globalThis, globalDispatcher, {
|
||||
value: agent,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
})
|
||||
}
|
||||
|
||||
function getGlobalDispatcher () {
|
||||
return globalThis[globalDispatcher]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setGlobalDispatcher,
|
||||
getGlobalDispatcher
|
||||
}
|
214
node_modules/undici/lib/handler/redirect.js
generated
vendored
Normal file
214
node_modules/undici/lib/handler/redirect.js
generated
vendored
Normal file
|
@ -0,0 +1,214 @@
|
|||
'use strict'
|
||||
|
||||
const util = require('../core/util')
|
||||
const { kBodyUsed } = require('../core/symbols')
|
||||
const assert = require('assert')
|
||||
const { InvalidArgumentError } = require('../core/errors')
|
||||
const EE = require('events')
|
||||
|
||||
const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
|
||||
|
||||
const kBody = Symbol('body')
|
||||
|
||||
class BodyAsyncIterable {
|
||||
constructor (body) {
|
||||
this[kBody] = body
|
||||
this[kBodyUsed] = false
|
||||
}
|
||||
|
||||
async * [Symbol.asyncIterator] () {
|
||||
assert(!this[kBodyUsed], 'disturbed')
|
||||
this[kBodyUsed] = true
|
||||
yield * this[kBody]
|
||||
}
|
||||
}
|
||||
|
||||
class RedirectHandler {
|
||||
constructor (dispatcher, maxRedirections, opts, handler) {
|
||||
if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
|
||||
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
||||
}
|
||||
|
||||
util.validateHandler(handler, opts.method, opts.upgrade)
|
||||
|
||||
this.dispatcher = dispatcher
|
||||
this.location = null
|
||||
this.abort = null
|
||||
this.opts = { ...opts, maxRedirections: 0 } // opts must be a copy
|
||||
this.maxRedirections = maxRedirections
|
||||
this.handler = handler
|
||||
this.history = []
|
||||
|
||||
if (util.isStream(this.opts.body)) {
|
||||
// TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
|
||||
// so that it can be dispatched again?
|
||||
// TODO (fix): Do we need 100-expect support to provide a way to do this properly?
|
||||
if (util.bodyLength(this.opts.body) === 0) {
|
||||
this.opts.body
|
||||
.on('data', function () {
|
||||
assert(false)
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof this.opts.body.readableDidRead !== 'boolean') {
|
||||
this.opts.body[kBodyUsed] = false
|
||||
EE.prototype.on.call(this.opts.body, 'data', function () {
|
||||
this[kBodyUsed] = true
|
||||
})
|
||||
}
|
||||
} else if (this.opts.body && typeof this.opts.body.pipeTo === 'function') {
|
||||
// TODO (fix): We can't access ReadableStream internal state
|
||||
// to determine whether or not it has been disturbed. This is just
|
||||
// a workaround.
|
||||
this.opts.body = new BodyAsyncIterable(this.opts.body)
|
||||
} else if (
|
||||
this.opts.body &&
|
||||
typeof this.opts.body !== 'string' &&
|
||||
!ArrayBuffer.isView(this.opts.body) &&
|
||||
util.isIterable(this.opts.body)
|
||||
) {
|
||||
// TODO: Should we allow re-using iterable if !this.opts.idempotent
|
||||
// or through some other flag?
|
||||
this.opts.body = new BodyAsyncIterable(this.opts.body)
|
||||
}
|
||||
}
|
||||
|
||||
onConnect (abort) {
|
||||
this.abort = abort
|
||||
this.handler.onConnect(abort, { history: this.history })
|
||||
}
|
||||
|
||||
onUpgrade (statusCode, headers, socket) {
|
||||
this.handler.onUpgrade(statusCode, headers, socket)
|
||||
}
|
||||
|
||||
onError (error) {
|
||||
this.handler.onError(error)
|
||||
}
|
||||
|
||||
onHeaders (statusCode, headers, resume, statusText) {
|
||||
this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body)
|
||||
? null
|
||||
: parseLocation(statusCode, headers)
|
||||
|
||||
if (this.opts.origin) {
|
||||
this.history.push(new URL(this.opts.path, this.opts.origin))
|
||||
}
|
||||
|
||||
if (!this.location) {
|
||||
return this.handler.onHeaders(statusCode, headers, resume, statusText)
|
||||
}
|
||||
|
||||
const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)))
|
||||
const path = search ? `${pathname}${search}` : pathname
|
||||
|
||||
// Remove headers referring to the original URL.
|
||||
// By default it is Host only, unless it's a 303 (see below), which removes also all Content-* headers.
|
||||
// https://tools.ietf.org/html/rfc7231#section-6.4
|
||||
this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin)
|
||||
this.opts.path = path
|
||||
this.opts.origin = origin
|
||||
this.opts.maxRedirections = 0
|
||||
|
||||
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
||||
// In case of HTTP 303, always replace method to be either HEAD or GET
|
||||
if (statusCode === 303 && this.opts.method !== 'HEAD') {
|
||||
this.opts.method = 'GET'
|
||||
this.opts.body = null
|
||||
}
|
||||
}
|
||||
|
||||
onData (chunk) {
|
||||
if (this.location) {
|
||||
/*
|
||||
https://tools.ietf.org/html/rfc7231#section-6.4
|
||||
|
||||
TLDR: undici always ignores 3xx response bodies.
|
||||
|
||||
Redirection is used to serve the requested resource from another URL, so it is assumes that
|
||||
no body is generated (and thus can be ignored). Even though generating a body is not prohibited.
|
||||
|
||||
For status 301, 302, 303, 307 and 308 (the latter from RFC 7238), the specs mention that the body usually
|
||||
(which means it's optional and not mandated) contain just an hyperlink to the value of
|
||||
the Location response header, so the body can be ignored safely.
|
||||
|
||||
For status 300, which is "Multiple Choices", the spec mentions both generating a Location
|
||||
response header AND a response body with the other possible location to follow.
|
||||
Since the spec explicitily chooses not to specify a format for such body and leave it to
|
||||
servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it.
|
||||
*/
|
||||
} else {
|
||||
return this.handler.onData(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
onComplete (trailers) {
|
||||
if (this.location) {
|
||||
/*
|
||||
https://tools.ietf.org/html/rfc7231#section-6.4
|
||||
|
||||
TLDR: undici always ignores 3xx response trailers as they are not expected in case of redirections
|
||||
and neither are useful if present.
|
||||
|
||||
See comment on onData method above for more detailed informations.
|
||||
*/
|
||||
|
||||
this.location = null
|
||||
this.abort = null
|
||||
|
||||
this.dispatcher.dispatch(this.opts, this)
|
||||
} else {
|
||||
this.handler.onComplete(trailers)
|
||||
}
|
||||
}
|
||||
|
||||
onBodySent (chunk) {
|
||||
if (this.handler.onBodySent) {
|
||||
this.handler.onBodySent(chunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseLocation (statusCode, headers) {
|
||||
if (redirectableStatusCodes.indexOf(statusCode) === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (let i = 0; i < headers.length; i += 2) {
|
||||
if (headers[i].toString().toLowerCase() === 'location') {
|
||||
return headers[i + 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
||||
function shouldRemoveHeader (header, removeContent, unknownOrigin) {
|
||||
return (
|
||||
(header.length === 4 && header.toString().toLowerCase() === 'host') ||
|
||||
(removeContent && header.toString().toLowerCase().indexOf('content-') === 0) ||
|
||||
(unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization')
|
||||
)
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7231#section-6.4
|
||||
function cleanRequestHeaders (headers, removeContent, unknownOrigin) {
|
||||
const ret = []
|
||||
if (Array.isArray(headers)) {
|
||||
for (let i = 0; i < headers.length; i += 2) {
|
||||
if (!shouldRemoveHeader(headers[i], removeContent, unknownOrigin)) {
|
||||
ret.push(headers[i], headers[i + 1])
|
||||
}
|
||||
}
|
||||
} else if (headers && typeof headers === 'object') {
|
||||
for (const key of Object.keys(headers)) {
|
||||
if (!shouldRemoveHeader(key, removeContent, unknownOrigin)) {
|
||||
ret.push(key, headers[key])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(headers == null, 'headers must be an object or an array')
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
module.exports = RedirectHandler
|
199
node_modules/undici/lib/llhttp/constants.d.ts
generated
vendored
Normal file
199
node_modules/undici/lib/llhttp/constants.d.ts
generated
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
import { IEnumMap } from './utils';
|
||||
export declare type HTTPMode = 'loose' | 'strict';
|
||||
export declare enum ERROR {
|
||||
OK = 0,
|
||||
INTERNAL = 1,
|
||||
STRICT = 2,
|
||||
LF_EXPECTED = 3,
|
||||
UNEXPECTED_CONTENT_LENGTH = 4,
|
||||
CLOSED_CONNECTION = 5,
|
||||
INVALID_METHOD = 6,
|
||||
INVALID_URL = 7,
|
||||
INVALID_CONSTANT = 8,
|
||||
INVALID_VERSION = 9,
|
||||
INVALID_HEADER_TOKEN = 10,
|
||||
INVALID_CONTENT_LENGTH = 11,
|
||||
INVALID_CHUNK_SIZE = 12,
|
||||
INVALID_STATUS = 13,
|
||||
INVALID_EOF_STATE = 14,
|
||||
INVALID_TRANSFER_ENCODING = 15,
|
||||
CB_MESSAGE_BEGIN = 16,
|
||||
CB_HEADERS_COMPLETE = 17,
|
||||
CB_MESSAGE_COMPLETE = 18,
|
||||
CB_CHUNK_HEADER = 19,
|
||||
CB_CHUNK_COMPLETE = 20,
|
||||
PAUSED = 21,
|
||||
PAUSED_UPGRADE = 22,
|
||||
PAUSED_H2_UPGRADE = 23,
|
||||
USER = 24
|
||||
}
|
||||
export declare enum TYPE {
|
||||
BOTH = 0,
|
||||
REQUEST = 1,
|
||||
RESPONSE = 2
|
||||
}
|
||||
export declare enum FLAGS {
|
||||
CONNECTION_KEEP_ALIVE = 1,
|
||||
CONNECTION_CLOSE = 2,
|
||||
CONNECTION_UPGRADE = 4,
|
||||
CHUNKED = 8,
|
||||
UPGRADE = 16,
|
||||
CONTENT_LENGTH = 32,
|
||||
SKIPBODY = 64,
|
||||
TRAILING = 128,
|
||||
TRANSFER_ENCODING = 512
|
||||
}
|
||||
export declare enum LENIENT_FLAGS {
|
||||
HEADERS = 1,
|
||||
CHUNKED_LENGTH = 2,
|
||||
KEEP_ALIVE = 4
|
||||
}
|
||||
export declare enum METHODS {
|
||||
DELETE = 0,
|
||||
GET = 1,
|
||||
HEAD = 2,
|
||||
POST = 3,
|
||||
PUT = 4,
|
||||
CONNECT = 5,
|
||||
OPTIONS = 6,
|
||||
TRACE = 7,
|
||||
COPY = 8,
|
||||
LOCK = 9,
|
||||
MKCOL = 10,
|
||||
MOVE = 11,
|
||||
PROPFIND = 12,
|
||||
PROPPATCH = 13,
|
||||
SEARCH = 14,
|
||||
UNLOCK = 15,
|
||||
BIND = 16,
|
||||
REBIND = 17,
|
||||
UNBIND = 18,
|
||||
ACL = 19,
|
||||
REPORT = 20,
|
||||
MKACTIVITY = 21,
|
||||
CHECKOUT = 22,
|
||||
MERGE = 23,
|
||||
'M-SEARCH' = 24,
|
||||
NOTIFY = 25,
|
||||
SUBSCRIBE = 26,
|
||||
UNSUBSCRIBE = 27,
|
||||
PATCH = 28,
|
||||
PURGE = 29,
|
||||
MKCALENDAR = 30,
|
||||
LINK = 31,
|
||||
UNLINK = 32,
|
||||
SOURCE = 33,
|
||||
PRI = 34,
|
||||
DESCRIBE = 35,
|
||||
ANNOUNCE = 36,
|
||||
SETUP = 37,
|
||||
PLAY = 38,
|
||||
PAUSE = 39,
|
||||
TEARDOWN = 40,
|
||||
GET_PARAMETER = 41,
|
||||
SET_PARAMETER = 42,
|
||||
REDIRECT = 43,
|
||||
RECORD = 44,
|
||||
FLUSH = 45
|
||||
}
|
||||
export declare const METHODS_HTTP: METHODS[];
|
||||
export declare const METHODS_ICE: METHODS[];
|
||||
export declare const METHODS_RTSP: METHODS[];
|
||||
export declare const METHOD_MAP: IEnumMap;
|
||||
export declare const H_METHOD_MAP: IEnumMap;
|
||||
export declare enum FINISH {
|
||||
SAFE = 0,
|
||||
SAFE_WITH_CB = 1,
|
||||
UNSAFE = 2
|
||||
}
|
||||
export declare type CharList = Array<string | number>;
|
||||
export declare const ALPHA: CharList;
|
||||
export declare const NUM_MAP: {
|
||||
0: number;
|
||||
1: number;
|
||||
2: number;
|
||||
3: number;
|
||||
4: number;
|
||||
5: number;
|
||||
6: number;
|
||||
7: number;
|
||||
8: number;
|
||||
9: number;
|
||||
};
|
||||
export declare const HEX_MAP: {
|
||||
0: number;
|
||||
1: number;
|
||||
2: number;
|
||||
3: number;
|
||||
4: number;
|
||||
5: number;
|
||||
6: number;
|
||||
7: number;
|
||||
8: number;
|
||||
9: number;
|
||||
A: number;
|
||||
B: number;
|
||||
C: number;
|
||||
D: number;
|
||||
E: number;
|
||||
F: number;
|
||||
a: number;
|
||||
b: number;
|
||||
c: number;
|
||||
d: number;
|
||||
e: number;
|
||||
f: number;
|
||||
};
|
||||
export declare const NUM: CharList;
|
||||
export declare const ALPHANUM: CharList;
|
||||
export declare const MARK: CharList;
|
||||
export declare const USERINFO_CHARS: CharList;
|
||||
export declare const STRICT_URL_CHAR: CharList;
|
||||
export declare const URL_CHAR: CharList;
|
||||
export declare const HEX: CharList;
|
||||
export declare const STRICT_TOKEN: CharList;
|
||||
export declare const TOKEN: CharList;
|
||||
export declare const HEADER_CHARS: CharList;
|
||||
export declare const CONNECTION_TOKEN_CHARS: CharList;
|
||||
export declare const MAJOR: {
|
||||
0: number;
|
||||
1: number;
|
||||
2: number;
|
||||
3: number;
|
||||
4: number;
|
||||
5: number;
|
||||
6: number;
|
||||
7: number;
|
||||
8: number;
|
||||
9: number;
|
||||
};
|
||||
export declare const MINOR: {
|
||||
0: number;
|
||||
1: number;
|
||||
2: number;
|
||||
3: number;
|
||||
4: number;
|
||||
5: number;
|
||||
6: number;
|
||||
7: number;
|
||||
8: number;
|
||||
9: number;
|
||||
};
|
||||
export declare enum HEADER_STATE {
|
||||
GENERAL = 0,
|
||||
CONNECTION = 1,
|
||||
CONTENT_LENGTH = 2,
|
||||
TRANSFER_ENCODING = 3,
|
||||
UPGRADE = 4,
|
||||
CONNECTION_KEEP_ALIVE = 5,
|
||||
CONNECTION_CLOSE = 6,
|
||||
CONNECTION_UPGRADE = 7,
|
||||
TRANSFER_ENCODING_CHUNKED = 8
|
||||
}
|
||||
export declare const SPECIAL_HEADERS: {
|
||||
connection: HEADER_STATE;
|
||||
'content-length': HEADER_STATE;
|
||||
'proxy-connection': HEADER_STATE;
|
||||
'transfer-encoding': HEADER_STATE;
|
||||
upgrade: HEADER_STATE;
|
||||
};
|
278
node_modules/undici/lib/llhttp/constants.js
generated
vendored
Normal file
278
node_modules/undici/lib/llhttp/constants.js
generated
vendored
Normal file
|
@ -0,0 +1,278 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.SPECIAL_HEADERS = exports.HEADER_STATE = exports.MINOR = exports.MAJOR = exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS = exports.TOKEN = exports.STRICT_TOKEN = exports.HEX = exports.URL_CHAR = exports.STRICT_URL_CHAR = exports.USERINFO_CHARS = exports.MARK = exports.ALPHANUM = exports.NUM = exports.HEX_MAP = exports.NUM_MAP = exports.ALPHA = exports.FINISH = exports.H_METHOD_MAP = exports.METHOD_MAP = exports.METHODS_RTSP = exports.METHODS_ICE = exports.METHODS_HTTP = exports.METHODS = exports.LENIENT_FLAGS = exports.FLAGS = exports.TYPE = exports.ERROR = void 0;
|
||||
const utils_1 = require("./utils");
|
||||
// C headers
|
||||
var ERROR;
|
||||
(function (ERROR) {
|
||||
ERROR[ERROR["OK"] = 0] = "OK";
|
||||
ERROR[ERROR["INTERNAL"] = 1] = "INTERNAL";
|
||||
ERROR[ERROR["STRICT"] = 2] = "STRICT";
|
||||
ERROR[ERROR["LF_EXPECTED"] = 3] = "LF_EXPECTED";
|
||||
ERROR[ERROR["UNEXPECTED_CONTENT_LENGTH"] = 4] = "UNEXPECTED_CONTENT_LENGTH";
|
||||
ERROR[ERROR["CLOSED_CONNECTION"] = 5] = "CLOSED_CONNECTION";
|
||||
ERROR[ERROR["INVALID_METHOD"] = 6] = "INVALID_METHOD";
|
||||
ERROR[ERROR["INVALID_URL"] = 7] = "INVALID_URL";
|
||||
ERROR[ERROR["INVALID_CONSTANT"] = 8] = "INVALID_CONSTANT";
|
||||
ERROR[ERROR["INVALID_VERSION"] = 9] = "INVALID_VERSION";
|
||||
ERROR[ERROR["INVALID_HEADER_TOKEN"] = 10] = "INVALID_HEADER_TOKEN";
|
||||
ERROR[ERROR["INVALID_CONTENT_LENGTH"] = 11] = "INVALID_CONTENT_LENGTH";
|
||||
ERROR[ERROR["INVALID_CHUNK_SIZE"] = 12] = "INVALID_CHUNK_SIZE";
|
||||
ERROR[ERROR["INVALID_STATUS"] = 13] = "INVALID_STATUS";
|
||||
ERROR[ERROR["INVALID_EOF_STATE"] = 14] = "INVALID_EOF_STATE";
|
||||
ERROR[ERROR["INVALID_TRANSFER_ENCODING"] = 15] = "INVALID_TRANSFER_ENCODING";
|
||||
ERROR[ERROR["CB_MESSAGE_BEGIN"] = 16] = "CB_MESSAGE_BEGIN";
|
||||
ERROR[ERROR["CB_HEADERS_COMPLETE"] = 17] = "CB_HEADERS_COMPLETE";
|
||||
ERROR[ERROR["CB_MESSAGE_COMPLETE"] = 18] = "CB_MESSAGE_COMPLETE";
|
||||
ERROR[ERROR["CB_CHUNK_HEADER"] = 19] = "CB_CHUNK_HEADER";
|
||||
ERROR[ERROR["CB_CHUNK_COMPLETE"] = 20] = "CB_CHUNK_COMPLETE";
|
||||
ERROR[ERROR["PAUSED"] = 21] = "PAUSED";
|
||||
ERROR[ERROR["PAUSED_UPGRADE"] = 22] = "PAUSED_UPGRADE";
|
||||
ERROR[ERROR["PAUSED_H2_UPGRADE"] = 23] = "PAUSED_H2_UPGRADE";
|
||||
ERROR[ERROR["USER"] = 24] = "USER";
|
||||
})(ERROR = exports.ERROR || (exports.ERROR = {}));
|
||||
var TYPE;
|
||||
(function (TYPE) {
|
||||
TYPE[TYPE["BOTH"] = 0] = "BOTH";
|
||||
TYPE[TYPE["REQUEST"] = 1] = "REQUEST";
|
||||
TYPE[TYPE["RESPONSE"] = 2] = "RESPONSE";
|
||||
})(TYPE = exports.TYPE || (exports.TYPE = {}));
|
||||
var FLAGS;
|
||||
(function (FLAGS) {
|
||||
FLAGS[FLAGS["CONNECTION_KEEP_ALIVE"] = 1] = "CONNECTION_KEEP_ALIVE";
|
||||
FLAGS[FLAGS["CONNECTION_CLOSE"] = 2] = "CONNECTION_CLOSE";
|
||||
FLAGS[FLAGS["CONNECTION_UPGRADE"] = 4] = "CONNECTION_UPGRADE";
|
||||
FLAGS[FLAGS["CHUNKED"] = 8] = "CHUNKED";
|
||||
FLAGS[FLAGS["UPGRADE"] = 16] = "UPGRADE";
|
||||
FLAGS[FLAGS["CONTENT_LENGTH"] = 32] = "CONTENT_LENGTH";
|
||||
FLAGS[FLAGS["SKIPBODY"] = 64] = "SKIPBODY";
|
||||
FLAGS[FLAGS["TRAILING"] = 128] = "TRAILING";
|
||||
// 1 << 8 is unused
|
||||
FLAGS[FLAGS["TRANSFER_ENCODING"] = 512] = "TRANSFER_ENCODING";
|
||||
})(FLAGS = exports.FLAGS || (exports.FLAGS = {}));
|
||||
var LENIENT_FLAGS;
|
||||
(function (LENIENT_FLAGS) {
|
||||
LENIENT_FLAGS[LENIENT_FLAGS["HEADERS"] = 1] = "HEADERS";
|
||||
LENIENT_FLAGS[LENIENT_FLAGS["CHUNKED_LENGTH"] = 2] = "CHUNKED_LENGTH";
|
||||
LENIENT_FLAGS[LENIENT_FLAGS["KEEP_ALIVE"] = 4] = "KEEP_ALIVE";
|
||||
})(LENIENT_FLAGS = exports.LENIENT_FLAGS || (exports.LENIENT_FLAGS = {}));
|
||||
var METHODS;
|
||||
(function (METHODS) {
|
||||
METHODS[METHODS["DELETE"] = 0] = "DELETE";
|
||||
METHODS[METHODS["GET"] = 1] = "GET";
|
||||
METHODS[METHODS["HEAD"] = 2] = "HEAD";
|
||||
METHODS[METHODS["POST"] = 3] = "POST";
|
||||
METHODS[METHODS["PUT"] = 4] = "PUT";
|
||||
/* pathological */
|
||||
METHODS[METHODS["CONNECT"] = 5] = "CONNECT";
|
||||
METHODS[METHODS["OPTIONS"] = 6] = "OPTIONS";
|
||||
METHODS[METHODS["TRACE"] = 7] = "TRACE";
|
||||
/* WebDAV */
|
||||
METHODS[METHODS["COPY"] = 8] = "COPY";
|
||||
METHODS[METHODS["LOCK"] = 9] = "LOCK";
|
||||
METHODS[METHODS["MKCOL"] = 10] = "MKCOL";
|
||||
METHODS[METHODS["MOVE"] = 11] = "MOVE";
|
||||
METHODS[METHODS["PROPFIND"] = 12] = "PROPFIND";
|
||||
METHODS[METHODS["PROPPATCH"] = 13] = "PROPPATCH";
|
||||
METHODS[METHODS["SEARCH"] = 14] = "SEARCH";
|
||||
METHODS[METHODS["UNLOCK"] = 15] = "UNLOCK";
|
||||
METHODS[METHODS["BIND"] = 16] = "BIND";
|
||||
METHODS[METHODS["REBIND"] = 17] = "REBIND";
|
||||
METHODS[METHODS["UNBIND"] = 18] = "UNBIND";
|
||||
METHODS[METHODS["ACL"] = 19] = "ACL";
|
||||
/* subversion */
|
||||
METHODS[METHODS["REPORT"] = 20] = "REPORT";
|
||||
METHODS[METHODS["MKACTIVITY"] = 21] = "MKACTIVITY";
|
||||
METHODS[METHODS["CHECKOUT"] = 22] = "CHECKOUT";
|
||||
METHODS[METHODS["MERGE"] = 23] = "MERGE";
|
||||
/* upnp */
|
||||
METHODS[METHODS["M-SEARCH"] = 24] = "M-SEARCH";
|
||||
METHODS[METHODS["NOTIFY"] = 25] = "NOTIFY";
|
||||
METHODS[METHODS["SUBSCRIBE"] = 26] = "SUBSCRIBE";
|
||||
METHODS[METHODS["UNSUBSCRIBE"] = 27] = "UNSUBSCRIBE";
|
||||
/* RFC-5789 */
|
||||
METHODS[METHODS["PATCH"] = 28] = "PATCH";
|
||||
METHODS[METHODS["PURGE"] = 29] = "PURGE";
|
||||
/* CalDAV */
|
||||
METHODS[METHODS["MKCALENDAR"] = 30] = "MKCALENDAR";
|
||||
/* RFC-2068, section 19.6.1.2 */
|
||||
METHODS[METHODS["LINK"] = 31] = "LINK";
|
||||
METHODS[METHODS["UNLINK"] = 32] = "UNLINK";
|
||||
/* icecast */
|
||||
METHODS[METHODS["SOURCE"] = 33] = "SOURCE";
|
||||
/* RFC-7540, section 11.6 */
|
||||
METHODS[METHODS["PRI"] = 34] = "PRI";
|
||||
/* RFC-2326 RTSP */
|
||||
METHODS[METHODS["DESCRIBE"] = 35] = "DESCRIBE";
|
||||
METHODS[METHODS["ANNOUNCE"] = 36] = "ANNOUNCE";
|
||||
METHODS[METHODS["SETUP"] = 37] = "SETUP";
|
||||
METHODS[METHODS["PLAY"] = 38] = "PLAY";
|
||||
METHODS[METHODS["PAUSE"] = 39] = "PAUSE";
|
||||
METHODS[METHODS["TEARDOWN"] = 40] = "TEARDOWN";
|
||||
METHODS[METHODS["GET_PARAMETER"] = 41] = "GET_PARAMETER";
|
||||
METHODS[METHODS["SET_PARAMETER"] = 42] = "SET_PARAMETER";
|
||||
METHODS[METHODS["REDIRECT"] = 43] = "REDIRECT";
|
||||
METHODS[METHODS["RECORD"] = 44] = "RECORD";
|
||||
/* RAOP */
|
||||
METHODS[METHODS["FLUSH"] = 45] = "FLUSH";
|
||||
})(METHODS = exports.METHODS || (exports.METHODS = {}));
|
||||
exports.METHODS_HTTP = [
|
||||
METHODS.DELETE,
|
||||
METHODS.GET,
|
||||
METHODS.HEAD,
|
||||
METHODS.POST,
|
||||
METHODS.PUT,
|
||||
METHODS.CONNECT,
|
||||
METHODS.OPTIONS,
|
||||
METHODS.TRACE,
|
||||
METHODS.COPY,
|
||||
METHODS.LOCK,
|
||||
METHODS.MKCOL,
|
||||
METHODS.MOVE,
|
||||
METHODS.PROPFIND,
|
||||
METHODS.PROPPATCH,
|
||||
METHODS.SEARCH,
|
||||
METHODS.UNLOCK,
|
||||
METHODS.BIND,
|
||||
METHODS.REBIND,
|
||||
METHODS.UNBIND,
|
||||
METHODS.ACL,
|
||||
METHODS.REPORT,
|
||||
METHODS.MKACTIVITY,
|
||||
METHODS.CHECKOUT,
|
||||
METHODS.MERGE,
|
||||
METHODS['M-SEARCH'],
|
||||
METHODS.NOTIFY,
|
||||
METHODS.SUBSCRIBE,
|
||||
METHODS.UNSUBSCRIBE,
|
||||
METHODS.PATCH,
|
||||
METHODS.PURGE,
|
||||
METHODS.MKCALENDAR,
|
||||
METHODS.LINK,
|
||||
METHODS.UNLINK,
|
||||
METHODS.PRI,
|
||||
// TODO(indutny): should we allow it with HTTP?
|
||||
METHODS.SOURCE,
|
||||
];
|
||||
exports.METHODS_ICE = [
|
||||
METHODS.SOURCE,
|
||||
];
|
||||
exports.METHODS_RTSP = [
|
||||
METHODS.OPTIONS,
|
||||
METHODS.DESCRIBE,
|
||||
METHODS.ANNOUNCE,
|
||||
METHODS.SETUP,
|
||||
METHODS.PLAY,
|
||||
METHODS.PAUSE,
|
||||
METHODS.TEARDOWN,
|
||||
METHODS.GET_PARAMETER,
|
||||
METHODS.SET_PARAMETER,
|
||||
METHODS.REDIRECT,
|
||||
METHODS.RECORD,
|
||||
METHODS.FLUSH,
|
||||
// For AirPlay
|
||||
METHODS.GET,
|
||||
METHODS.POST,
|
||||
];
|
||||
exports.METHOD_MAP = utils_1.enumToMap(METHODS);
|
||||
exports.H_METHOD_MAP = {};
|
||||
Object.keys(exports.METHOD_MAP).forEach((key) => {
|
||||
if (/^H/.test(key)) {
|
||||
exports.H_METHOD_MAP[key] = exports.METHOD_MAP[key];
|
||||
}
|
||||
});
|
||||
var FINISH;
|
||||
(function (FINISH) {
|
||||
FINISH[FINISH["SAFE"] = 0] = "SAFE";
|
||||
FINISH[FINISH["SAFE_WITH_CB"] = 1] = "SAFE_WITH_CB";
|
||||
FINISH[FINISH["UNSAFE"] = 2] = "UNSAFE";
|
||||
})(FINISH = exports.FINISH || (exports.FINISH = {}));
|
||||
exports.ALPHA = [];
|
||||
for (let i = 'A'.charCodeAt(0); i <= 'Z'.charCodeAt(0); i++) {
|
||||
// Upper case
|
||||
exports.ALPHA.push(String.fromCharCode(i));
|
||||
// Lower case
|
||||
exports.ALPHA.push(String.fromCharCode(i + 0x20));
|
||||
}
|
||||
exports.NUM_MAP = {
|
||||
0: 0, 1: 1, 2: 2, 3: 3, 4: 4,
|
||||
5: 5, 6: 6, 7: 7, 8: 8, 9: 9,
|
||||
};
|
||||
exports.HEX_MAP = {
|
||||
0: 0, 1: 1, 2: 2, 3: 3, 4: 4,
|
||||
5: 5, 6: 6, 7: 7, 8: 8, 9: 9,
|
||||
A: 0XA, B: 0XB, C: 0XC, D: 0XD, E: 0XE, F: 0XF,
|
||||
a: 0xa, b: 0xb, c: 0xc, d: 0xd, e: 0xe, f: 0xf,
|
||||
};
|
||||
exports.NUM = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
];
|
||||
exports.ALPHANUM = exports.ALPHA.concat(exports.NUM);
|
||||
exports.MARK = ['-', '_', '.', '!', '~', '*', '\'', '(', ')'];
|
||||
exports.USERINFO_CHARS = exports.ALPHANUM
|
||||
.concat(exports.MARK)
|
||||
.concat(['%', ';', ':', '&', '=', '+', '$', ',']);
|
||||
// TODO(indutny): use RFC
|
||||
exports.STRICT_URL_CHAR = [
|
||||
'!', '"', '$', '%', '&', '\'',
|
||||
'(', ')', '*', '+', ',', '-', '.', '/',
|
||||
':', ';', '<', '=', '>',
|
||||
'@', '[', '\\', ']', '^', '_',
|
||||
'`',
|
||||
'{', '|', '}', '~',
|
||||
].concat(exports.ALPHANUM);
|
||||
exports.URL_CHAR = exports.STRICT_URL_CHAR
|
||||
.concat(['\t', '\f']);
|
||||
// All characters with 0x80 bit set to 1
|
||||
for (let i = 0x80; i <= 0xff; i++) {
|
||||
exports.URL_CHAR.push(i);
|
||||
}
|
||||
exports.HEX = exports.NUM.concat(['a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F']);
|
||||
/* Tokens as defined by rfc 2616. Also lowercases them.
|
||||
* token = 1*<any CHAR except CTLs or separators>
|
||||
* separators = "(" | ")" | "<" | ">" | "@"
|
||||
* | "," | ";" | ":" | "\" | <">
|
||||
* | "/" | "[" | "]" | "?" | "="
|
||||
* | "{" | "}" | SP | HT
|
||||
*/
|
||||
exports.STRICT_TOKEN = [
|
||||
'!', '#', '$', '%', '&', '\'',
|
||||
'*', '+', '-', '.',
|
||||
'^', '_', '`',
|
||||
'|', '~',
|
||||
].concat(exports.ALPHANUM);
|
||||
exports.TOKEN = exports.STRICT_TOKEN.concat([' ']);
|
||||
/*
|
||||
* Verify that a char is a valid visible (printable) US-ASCII
|
||||
* character or %x80-FF
|
||||
*/
|
||||
exports.HEADER_CHARS = ['\t'];
|
||||
for (let i = 32; i <= 255; i++) {
|
||||
if (i !== 127) {
|
||||
exports.HEADER_CHARS.push(i);
|
||||
}
|
||||
}
|
||||
// ',' = \x44
|
||||
exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS.filter((c) => c !== 44);
|
||||
exports.MAJOR = exports.NUM_MAP;
|
||||
exports.MINOR = exports.MAJOR;
|
||||
var HEADER_STATE;
|
||||
(function (HEADER_STATE) {
|
||||
HEADER_STATE[HEADER_STATE["GENERAL"] = 0] = "GENERAL";
|
||||
HEADER_STATE[HEADER_STATE["CONNECTION"] = 1] = "CONNECTION";
|
||||
HEADER_STATE[HEADER_STATE["CONTENT_LENGTH"] = 2] = "CONTENT_LENGTH";
|
||||
HEADER_STATE[HEADER_STATE["TRANSFER_ENCODING"] = 3] = "TRANSFER_ENCODING";
|
||||
HEADER_STATE[HEADER_STATE["UPGRADE"] = 4] = "UPGRADE";
|
||||
HEADER_STATE[HEADER_STATE["CONNECTION_KEEP_ALIVE"] = 5] = "CONNECTION_KEEP_ALIVE";
|
||||
HEADER_STATE[HEADER_STATE["CONNECTION_CLOSE"] = 6] = "CONNECTION_CLOSE";
|
||||
HEADER_STATE[HEADER_STATE["CONNECTION_UPGRADE"] = 7] = "CONNECTION_UPGRADE";
|
||||
HEADER_STATE[HEADER_STATE["TRANSFER_ENCODING_CHUNKED"] = 8] = "TRANSFER_ENCODING_CHUNKED";
|
||||
})(HEADER_STATE = exports.HEADER_STATE || (exports.HEADER_STATE = {}));
|
||||
exports.SPECIAL_HEADERS = {
|
||||
'connection': HEADER_STATE.CONNECTION,
|
||||
'content-length': HEADER_STATE.CONTENT_LENGTH,
|
||||
'proxy-connection': HEADER_STATE.CONNECTION,
|
||||
'transfer-encoding': HEADER_STATE.TRANSFER_ENCODING,
|
||||
'upgrade': HEADER_STATE.UPGRADE,
|
||||
};
|
||||
//# sourceMappingURL=constants.js.map
|
1
node_modules/undici/lib/llhttp/constants.js.map
generated
vendored
Normal file
1
node_modules/undici/lib/llhttp/constants.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
node_modules/undici/lib/llhttp/llhttp.wasm
generated
vendored
Executable file
BIN
node_modules/undici/lib/llhttp/llhttp.wasm
generated
vendored
Executable file
Binary file not shown.
1
node_modules/undici/lib/llhttp/llhttp.wasm.js
generated
vendored
Normal file
1
node_modules/undici/lib/llhttp/llhttp.wasm.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
node_modules/undici/lib/llhttp/llhttp_simd.wasm
generated
vendored
Executable file
BIN
node_modules/undici/lib/llhttp/llhttp_simd.wasm
generated
vendored
Executable file
Binary file not shown.
1
node_modules/undici/lib/llhttp/llhttp_simd.wasm.js
generated
vendored
Normal file
1
node_modules/undici/lib/llhttp/llhttp_simd.wasm.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
4
node_modules/undici/lib/llhttp/utils.d.ts
generated
vendored
Normal file
4
node_modules/undici/lib/llhttp/utils.d.ts
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
export interface IEnumMap {
|
||||
[key: string]: number;
|
||||
}
|
||||
export declare function enumToMap(obj: any): IEnumMap;
|
15
node_modules/undici/lib/llhttp/utils.js
generated
vendored
Normal file
15
node_modules/undici/lib/llhttp/utils.js
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.enumToMap = void 0;
|
||||
function enumToMap(obj) {
|
||||
const res = {};
|
||||
Object.keys(obj).forEach((key) => {
|
||||
const value = obj[key];
|
||||
if (typeof value === 'number') {
|
||||
res[key] = value;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
exports.enumToMap = enumToMap;
|
||||
//# sourceMappingURL=utils.js.map
|
1
node_modules/undici/lib/llhttp/utils.js.map
generated
vendored
Normal file
1
node_modules/undici/lib/llhttp/utils.js.map
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/llhttp/utils.ts"],"names":[],"mappings":";;;AAIA,SAAgB,SAAS,CAAC,GAAQ;IAChC,MAAM,GAAG,GAAa,EAAE,CAAC;IAEzB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAC/B,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;YAC7B,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;SAClB;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC;AAXD,8BAWC"}
|
171
node_modules/undici/lib/mock/mock-agent.js
generated
vendored
Normal file
171
node_modules/undici/lib/mock/mock-agent.js
generated
vendored
Normal file
|
@ -0,0 +1,171 @@
|
|||
'use strict'
|
||||
|
||||
const { kClients } = require('../core/symbols')
|
||||
const Agent = require('../agent')
|
||||
const {
|
||||
kAgent,
|
||||
kMockAgentSet,
|
||||
kMockAgentGet,
|
||||
kDispatches,
|
||||
kIsMockActive,
|
||||
kNetConnect,
|
||||
kGetNetConnect,
|
||||
kOptions,
|
||||
kFactory
|
||||
} = require('./mock-symbols')
|
||||
const MockClient = require('./mock-client')
|
||||
const MockPool = require('./mock-pool')
|
||||
const { matchValue, buildMockOptions } = require('./mock-utils')
|
||||
const { InvalidArgumentError, UndiciError } = require('../core/errors')
|
||||
const Dispatcher = require('../dispatcher')
|
||||
const Pluralizer = require('./pluralizer')
|
||||
const PendingInterceptorsFormatter = require('./pending-interceptors-formatter')
|
||||
|
||||
class FakeWeakRef {
|
||||
constructor (value) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
deref () {
|
||||
return this.value
|
||||
}
|
||||
}
|
||||
|
||||
class MockAgent extends Dispatcher {
|
||||
constructor (opts) {
|
||||
super(opts)
|
||||
|
||||
this[kNetConnect] = true
|
||||
this[kIsMockActive] = true
|
||||
|
||||
// Instantiate Agent and encapsulate
|
||||
if ((opts && opts.agent && typeof opts.agent.dispatch !== 'function')) {
|
||||
throw new InvalidArgumentError('Argument opts.agent must implement Agent')
|
||||
}
|
||||
const agent = opts && opts.agent ? opts.agent : new Agent(opts)
|
||||
this[kAgent] = agent
|
||||
|
||||
this[kClients] = agent[kClients]
|
||||
this[kOptions] = buildMockOptions(opts)
|
||||
}
|
||||
|
||||
get (origin) {
|
||||
let dispatcher = this[kMockAgentGet](origin)
|
||||
|
||||
if (!dispatcher) {
|
||||
dispatcher = this[kFactory](origin)
|
||||
this[kMockAgentSet](origin, dispatcher)
|
||||
}
|
||||
return dispatcher
|
||||
}
|
||||
|
||||
dispatch (opts, handler) {
|
||||
// Call MockAgent.get to perform additional setup before dispatching as normal
|
||||
this.get(opts.origin)
|
||||
return this[kAgent].dispatch(opts, handler)
|
||||
}
|
||||
|
||||
async close () {
|
||||
await this[kAgent].close()
|
||||
this[kClients].clear()
|
||||
}
|
||||
|
||||
deactivate () {
|
||||
this[kIsMockActive] = false
|
||||
}
|
||||
|
||||
activate () {
|
||||
this[kIsMockActive] = true
|
||||
}
|
||||
|
||||
enableNetConnect (matcher) {
|
||||
if (typeof matcher === 'string' || typeof matcher === 'function' || matcher instanceof RegExp) {
|
||||
if (Array.isArray(this[kNetConnect])) {
|
||||
this[kNetConnect].push(matcher)
|
||||
} else {
|
||||
this[kNetConnect] = [matcher]
|
||||
}
|
||||
} else if (typeof matcher === 'undefined') {
|
||||
this[kNetConnect] = true
|
||||
} else {
|
||||
throw new InvalidArgumentError('Unsupported matcher. Must be one of String|Function|RegExp.')
|
||||
}
|
||||
}
|
||||
|
||||
disableNetConnect () {
|
||||
this[kNetConnect] = false
|
||||
}
|
||||
|
||||
// This is required to bypass issues caused by using global symbols - see:
|
||||
// https://github.com/nodejs/undici/issues/1447
|
||||
get isMockActive () {
|
||||
return this[kIsMockActive]
|
||||
}
|
||||
|
||||
[kMockAgentSet] (origin, dispatcher) {
|
||||
this[kClients].set(origin, new FakeWeakRef(dispatcher))
|
||||
}
|
||||
|
||||
[kFactory] (origin) {
|
||||
const mockOptions = Object.assign({ agent: this }, this[kOptions])
|
||||
return this[kOptions] && this[kOptions].connections === 1
|
||||
? new MockClient(origin, mockOptions)
|
||||
: new MockPool(origin, mockOptions)
|
||||
}
|
||||
|
||||
[kMockAgentGet] (origin) {
|
||||
// First check if we can immediately find it
|
||||
const ref = this[kClients].get(origin)
|
||||
if (ref) {
|
||||
return ref.deref()
|
||||
}
|
||||
|
||||
// If the origin is not a string create a dummy parent pool and return to user
|
||||
if (typeof origin !== 'string') {
|
||||
const dispatcher = this[kFactory]('http://localhost:9999')
|
||||
this[kMockAgentSet](origin, dispatcher)
|
||||
return dispatcher
|
||||
}
|
||||
|
||||
// If we match, create a pool and assign the same dispatches
|
||||
for (const [keyMatcher, nonExplicitRef] of Array.from(this[kClients])) {
|
||||
const nonExplicitDispatcher = nonExplicitRef.deref()
|
||||
if (nonExplicitDispatcher && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) {
|
||||
const dispatcher = this[kFactory](origin)
|
||||
this[kMockAgentSet](origin, dispatcher)
|
||||
dispatcher[kDispatches] = nonExplicitDispatcher[kDispatches]
|
||||
return dispatcher
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[kGetNetConnect] () {
|
||||
return this[kNetConnect]
|
||||
}
|
||||
|
||||
pendingInterceptors () {
|
||||
const mockAgentClients = this[kClients]
|
||||
|
||||
return Array.from(mockAgentClients.entries())
|
||||
.flatMap(([origin, scope]) => scope.deref()[kDispatches].map(dispatch => ({ ...dispatch, origin })))
|
||||
.filter(({ pending }) => pending)
|
||||
}
|
||||
|
||||
assertNoPendingInterceptors ({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) {
|
||||
const pending = this.pendingInterceptors()
|
||||
|
||||
if (pending.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const pluralizer = new Pluralizer('interceptor', 'interceptors').pluralize(pending.length)
|
||||
|
||||
throw new UndiciError(`
|
||||
${pluralizer.count} ${pluralizer.noun} ${pluralizer.is} pending:
|
||||
|
||||
${pendingInterceptorsFormatter.format(pending)}
|
||||
`.trim())
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MockAgent
|
59
node_modules/undici/lib/mock/mock-client.js
generated
vendored
Normal file
59
node_modules/undici/lib/mock/mock-client.js
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
'use strict'
|
||||
|
||||
const { promisify } = require('util')
|
||||
const Client = require('../client')
|
||||
const { buildMockDispatch } = require('./mock-utils')
|
||||
const {
|
||||
kDispatches,
|
||||
kMockAgent,
|
||||
kClose,
|
||||
kOriginalClose,
|
||||
kOrigin,
|
||||
kOriginalDispatch,
|
||||
kConnected
|
||||
} = require('./mock-symbols')
|
||||
const { MockInterceptor } = require('./mock-interceptor')
|
||||
const Symbols = require('../core/symbols')
|
||||
const { InvalidArgumentError } = require('../core/errors')
|
||||
|
||||
/**
|
||||
* MockClient provides an API that extends the Client to influence the mockDispatches.
|
||||
*/
|
||||
class MockClient extends Client {
|
||||
constructor (origin, opts) {
|
||||
super(origin, opts)
|
||||
|
||||
if (!opts || !opts.agent || typeof opts.agent.dispatch !== 'function') {
|
||||
throw new InvalidArgumentError('Argument opts.agent must implement Agent')
|
||||
}
|
||||
|
||||
this[kMockAgent] = opts.agent
|
||||
this[kOrigin] = origin
|
||||
this[kDispatches] = []
|
||||
this[kConnected] = 1
|
||||
this[kOriginalDispatch] = this.dispatch
|
||||
this[kOriginalClose] = this.close.bind(this)
|
||||
|
||||
this.dispatch = buildMockDispatch.call(this)
|
||||
this.close = this[kClose]
|
||||
}
|
||||
|
||||
get [Symbols.kConnected] () {
|
||||
return this[kConnected]
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the base interceptor for mocking replies from undici.
|
||||
*/
|
||||
intercept (opts) {
|
||||
return new MockInterceptor(opts, this[kDispatches])
|
||||
}
|
||||
|
||||
async [kClose] () {
|
||||
await promisify(this[kOriginalClose])()
|
||||
this[kConnected] = 0
|
||||
this[kMockAgent][Symbols.kClients].delete(this[kOrigin])
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MockClient
|
17
node_modules/undici/lib/mock/mock-errors.js
generated
vendored
Normal file
17
node_modules/undici/lib/mock/mock-errors.js
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
'use strict'
|
||||
|
||||
const { UndiciError } = require('../core/errors')
|
||||
|
||||
class MockNotMatchedError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, MockNotMatchedError)
|
||||
this.name = 'MockNotMatchedError'
|
||||
this.message = message || 'The request does not match any registered mock dispatches'
|
||||
this.code = 'UND_MOCK_ERR_MOCK_NOT_MATCHED'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MockNotMatchedError
|
||||
}
|
206
node_modules/undici/lib/mock/mock-interceptor.js
generated
vendored
Normal file
206
node_modules/undici/lib/mock/mock-interceptor.js
generated
vendored
Normal file
|
@ -0,0 +1,206 @@
|
|||
'use strict'
|
||||
|
||||
const { getResponseData, buildKey, addMockDispatch } = require('./mock-utils')
|
||||
const {
|
||||
kDispatches,
|
||||
kDispatchKey,
|
||||
kDefaultHeaders,
|
||||
kDefaultTrailers,
|
||||
kContentLength,
|
||||
kMockDispatch
|
||||
} = require('./mock-symbols')
|
||||
const { InvalidArgumentError } = require('../core/errors')
|
||||
const { buildURL } = require('../core/util')
|
||||
|
||||
/**
|
||||
* Defines the scope API for an interceptor reply
|
||||
*/
|
||||
class MockScope {
|
||||
constructor (mockDispatch) {
|
||||
this[kMockDispatch] = mockDispatch
|
||||
}
|
||||
|
||||
/**
|
||||
* Delay a reply by a set amount in ms.
|
||||
*/
|
||||
delay (waitInMs) {
|
||||
if (typeof waitInMs !== 'number' || !Number.isInteger(waitInMs) || waitInMs <= 0) {
|
||||
throw new InvalidArgumentError('waitInMs must be a valid integer > 0')
|
||||
}
|
||||
|
||||
this[kMockDispatch].delay = waitInMs
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* For a defined reply, never mark as consumed.
|
||||
*/
|
||||
persist () {
|
||||
this[kMockDispatch].persist = true
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow one to define a reply for a set amount of matching requests.
|
||||
*/
|
||||
times (repeatTimes) {
|
||||
if (typeof repeatTimes !== 'number' || !Number.isInteger(repeatTimes) || repeatTimes <= 0) {
|
||||
throw new InvalidArgumentError('repeatTimes must be a valid integer > 0')
|
||||
}
|
||||
|
||||
this[kMockDispatch].times = repeatTimes
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an interceptor for a Mock
|
||||
*/
|
||||
class MockInterceptor {
|
||||
constructor (opts, mockDispatches) {
|
||||
if (typeof opts !== 'object') {
|
||||
throw new InvalidArgumentError('opts must be an object')
|
||||
}
|
||||
if (typeof opts.path === 'undefined') {
|
||||
throw new InvalidArgumentError('opts.path must be defined')
|
||||
}
|
||||
if (typeof opts.method === 'undefined') {
|
||||
opts.method = 'GET'
|
||||
}
|
||||
// See https://github.com/nodejs/undici/issues/1245
|
||||
// As per RFC 3986, clients are not supposed to send URI
|
||||
// fragments to servers when they retrieve a document,
|
||||
if (typeof opts.path === 'string') {
|
||||
if (opts.query) {
|
||||
opts.path = buildURL(opts.path, opts.query)
|
||||
} else {
|
||||
// Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811
|
||||
const parsedURL = new URL(opts.path, 'data://')
|
||||
opts.path = parsedURL.pathname + parsedURL.search
|
||||
}
|
||||
}
|
||||
if (typeof opts.method === 'string') {
|
||||
opts.method = opts.method.toUpperCase()
|
||||
}
|
||||
|
||||
this[kDispatchKey] = buildKey(opts)
|
||||
this[kDispatches] = mockDispatches
|
||||
this[kDefaultHeaders] = {}
|
||||
this[kDefaultTrailers] = {}
|
||||
this[kContentLength] = false
|
||||
}
|
||||
|
||||
createMockScopeDispatchData (statusCode, data, responseOptions = {}) {
|
||||
const responseData = getResponseData(data)
|
||||
const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {}
|
||||
const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers }
|
||||
const trailers = { ...this[kDefaultTrailers], ...responseOptions.trailers }
|
||||
|
||||
return { statusCode, data, headers, trailers }
|
||||
}
|
||||
|
||||
validateReplyParameters (statusCode, data, responseOptions) {
|
||||
if (typeof statusCode === 'undefined') {
|
||||
throw new InvalidArgumentError('statusCode must be defined')
|
||||
}
|
||||
if (typeof data === 'undefined') {
|
||||
throw new InvalidArgumentError('data must be defined')
|
||||
}
|
||||
if (typeof responseOptions !== 'object') {
|
||||
throw new InvalidArgumentError('responseOptions must be an object')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock an undici request with a defined reply.
|
||||
*/
|
||||
reply (replyData) {
|
||||
// Values of reply aren't available right now as they
|
||||
// can only be available when the reply callback is invoked.
|
||||
if (typeof replyData === 'function') {
|
||||
// We'll first wrap the provided callback in another function,
|
||||
// this function will properly resolve the data from the callback
|
||||
// when invoked.
|
||||
const wrappedDefaultsCallback = (opts) => {
|
||||
// Our reply options callback contains the parameter for statusCode, data and options.
|
||||
const resolvedData = replyData(opts)
|
||||
|
||||
// Check if it is in the right format
|
||||
if (typeof resolvedData !== 'object') {
|
||||
throw new InvalidArgumentError('reply options callback must return an object')
|
||||
}
|
||||
|
||||
const { statusCode, data, responseOptions = {} } = resolvedData
|
||||
this.validateReplyParameters(statusCode, data, responseOptions)
|
||||
// Since the values can be obtained immediately we return them
|
||||
// from this higher order function that will be resolved later.
|
||||
return {
|
||||
...this.createMockScopeDispatchData(statusCode, data, responseOptions)
|
||||
}
|
||||
}
|
||||
|
||||
// Add usual dispatch data, but this time set the data parameter to function that will eventually provide data.
|
||||
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback)
|
||||
return new MockScope(newMockDispatch)
|
||||
}
|
||||
|
||||
// We can have either one or three parameters, if we get here,
|
||||
// we should have 2-3 parameters. So we spread the arguments of
|
||||
// this function to obtain the parameters, since replyData will always
|
||||
// just be the statusCode.
|
||||
const [statusCode, data, responseOptions = {}] = [...arguments]
|
||||
this.validateReplyParameters(statusCode, data, responseOptions)
|
||||
|
||||
// Send in-already provided data like usual
|
||||
const dispatchData = this.createMockScopeDispatchData(statusCode, data, responseOptions)
|
||||
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData)
|
||||
return new MockScope(newMockDispatch)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock an undici request with a defined error.
|
||||
*/
|
||||
replyWithError (error) {
|
||||
if (typeof error === 'undefined') {
|
||||
throw new InvalidArgumentError('error must be defined')
|
||||
}
|
||||
|
||||
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { error })
|
||||
return new MockScope(newMockDispatch)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default reply headers on the interceptor for subsequent replies
|
||||
*/
|
||||
defaultReplyHeaders (headers) {
|
||||
if (typeof headers === 'undefined') {
|
||||
throw new InvalidArgumentError('headers must be defined')
|
||||
}
|
||||
|
||||
this[kDefaultHeaders] = headers
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default reply trailers on the interceptor for subsequent replies
|
||||
*/
|
||||
defaultReplyTrailers (trailers) {
|
||||
if (typeof trailers === 'undefined') {
|
||||
throw new InvalidArgumentError('trailers must be defined')
|
||||
}
|
||||
|
||||
this[kDefaultTrailers] = trailers
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set reply content length header for replies on the interceptor
|
||||
*/
|
||||
replyContentLength () {
|
||||
this[kContentLength] = true
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.MockInterceptor = MockInterceptor
|
||||
module.exports.MockScope = MockScope
|
59
node_modules/undici/lib/mock/mock-pool.js
generated
vendored
Normal file
59
node_modules/undici/lib/mock/mock-pool.js
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
'use strict'
|
||||
|
||||
const { promisify } = require('util')
|
||||
const Pool = require('../pool')
|
||||
const { buildMockDispatch } = require('./mock-utils')
|
||||
const {
|
||||
kDispatches,
|
||||
kMockAgent,
|
||||
kClose,
|
||||
kOriginalClose,
|
||||
kOrigin,
|
||||
kOriginalDispatch,
|
||||
kConnected
|
||||
} = require('./mock-symbols')
|
||||
const { MockInterceptor } = require('./mock-interceptor')
|
||||
const Symbols = require('../core/symbols')
|
||||
const { InvalidArgumentError } = require('../core/errors')
|
||||
|
||||
/**
|
||||
* MockPool provides an API that extends the Pool to influence the mockDispatches.
|
||||
*/
|
||||
class MockPool extends Pool {
|
||||
constructor (origin, opts) {
|
||||
super(origin, opts)
|
||||
|
||||
if (!opts || !opts.agent || typeof opts.agent.dispatch !== 'function') {
|
||||
throw new InvalidArgumentError('Argument opts.agent must implement Agent')
|
||||
}
|
||||
|
||||
this[kMockAgent] = opts.agent
|
||||
this[kOrigin] = origin
|
||||
this[kDispatches] = []
|
||||
this[kConnected] = 1
|
||||
this[kOriginalDispatch] = this.dispatch
|
||||
this[kOriginalClose] = this.close.bind(this)
|
||||
|
||||
this.dispatch = buildMockDispatch.call(this)
|
||||
this.close = this[kClose]
|
||||
}
|
||||
|
||||
get [Symbols.kConnected] () {
|
||||
return this[kConnected]
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the base interceptor for mocking replies from undici.
|
||||
*/
|
||||
intercept (opts) {
|
||||
return new MockInterceptor(opts, this[kDispatches])
|
||||
}
|
||||
|
||||
async [kClose] () {
|
||||
await promisify(this[kOriginalClose])()
|
||||
this[kConnected] = 0
|
||||
this[kMockAgent][Symbols.kClients].delete(this[kOrigin])
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MockPool
|
23
node_modules/undici/lib/mock/mock-symbols.js
generated
vendored
Normal file
23
node_modules/undici/lib/mock/mock-symbols.js
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
kAgent: Symbol('agent'),
|
||||
kOptions: Symbol('options'),
|
||||
kFactory: Symbol('factory'),
|
||||
kDispatches: Symbol('dispatches'),
|
||||
kDispatchKey: Symbol('dispatch key'),
|
||||
kDefaultHeaders: Symbol('default headers'),
|
||||
kDefaultTrailers: Symbol('default trailers'),
|
||||
kContentLength: Symbol('content length'),
|
||||
kMockAgent: Symbol('mock agent'),
|
||||
kMockAgentSet: Symbol('mock agent set'),
|
||||
kMockAgentGet: Symbol('mock agent get'),
|
||||
kMockDispatch: Symbol('mock dispatch'),
|
||||
kClose: Symbol('close'),
|
||||
kOriginalClose: Symbol('original agent close'),
|
||||
kOrigin: Symbol('origin'),
|
||||
kIsMockActive: Symbol('is mock active'),
|
||||
kNetConnect: Symbol('net connect'),
|
||||
kGetNetConnect: Symbol('get net connect'),
|
||||
kConnected: Symbol('connected')
|
||||
}
|
364
node_modules/undici/lib/mock/mock-utils.js
generated
vendored
Normal file
364
node_modules/undici/lib/mock/mock-utils.js
generated
vendored
Normal file
|
@ -0,0 +1,364 @@
|
|||
'use strict'
|
||||
|
||||
const { MockNotMatchedError } = require('./mock-errors')
|
||||
const {
|
||||
kDispatches,
|
||||
kMockAgent,
|
||||
kOriginalDispatch,
|
||||
kOrigin,
|
||||
kGetNetConnect
|
||||
} = require('./mock-symbols')
|
||||
const { buildURL } = require('../core/util')
|
||||
|
||||
function matchValue (match, value) {
|
||||
if (typeof match === 'string') {
|
||||
return match === value
|
||||
}
|
||||
if (match instanceof RegExp) {
|
||||
return match.test(value)
|
||||
}
|
||||
if (typeof match === 'function') {
|
||||
return match(value) === true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function lowerCaseEntries (headers) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(headers).map(([headerName, headerValue]) => {
|
||||
return [headerName.toLocaleLowerCase(), headerValue]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('../../index').Headers|string[]|Record<string, string>} headers
|
||||
* @param {string} key
|
||||
*/
|
||||
function getHeaderByName (headers, key) {
|
||||
if (Array.isArray(headers)) {
|
||||
for (let i = 0; i < headers.length; i += 2) {
|
||||
if (headers[i] === key) {
|
||||
return headers[i + 1]
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
} else if (typeof headers.get === 'function') {
|
||||
return headers.get(key)
|
||||
} else {
|
||||
return headers[key]
|
||||
}
|
||||
}
|
||||
|
||||
function matchHeaders (mockDispatch, headers) {
|
||||
if (typeof mockDispatch.headers === 'function') {
|
||||
if (Array.isArray(headers)) { // fetch HeadersList
|
||||
const clone = headers.slice()
|
||||
const entries = []
|
||||
for (let index = 0; index < clone.length; index += 2) {
|
||||
entries.push([clone[index], clone[index + 1]])
|
||||
}
|
||||
headers = Object.fromEntries(entries)
|
||||
}
|
||||
return mockDispatch.headers(headers ? lowerCaseEntries(headers) : {})
|
||||
}
|
||||
if (typeof mockDispatch.headers === 'undefined') {
|
||||
return true
|
||||
}
|
||||
if (typeof headers !== 'object' || typeof mockDispatch.headers !== 'object') {
|
||||
return false
|
||||
}
|
||||
|
||||
for (const [matchHeaderName, matchHeaderValue] of Object.entries(mockDispatch.headers)) {
|
||||
const headerValue = getHeaderByName(headers, matchHeaderName)
|
||||
|
||||
if (!matchValue(matchHeaderValue, headerValue)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function matchKey (mockDispatch, { path, method, body, headers }) {
|
||||
const pathMatch = matchValue(mockDispatch.path, path)
|
||||
const methodMatch = matchValue(mockDispatch.method, method)
|
||||
const bodyMatch = typeof mockDispatch.body !== 'undefined' ? matchValue(mockDispatch.body, body) : true
|
||||
const headersMatch = matchHeaders(mockDispatch, headers)
|
||||
return pathMatch && methodMatch && bodyMatch && headersMatch
|
||||
}
|
||||
|
||||
function getResponseData (data) {
|
||||
if (Buffer.isBuffer(data)) {
|
||||
return data
|
||||
} else if (typeof data === 'object') {
|
||||
return JSON.stringify(data)
|
||||
} else {
|
||||
return data.toString()
|
||||
}
|
||||
}
|
||||
|
||||
function getMockDispatch (mockDispatches, key) {
|
||||
const resolvedPath = key.query ? buildURL(key.path, key.query) : key.path
|
||||
|
||||
// Match path
|
||||
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(path, resolvedPath))
|
||||
if (matchedMockDispatches.length === 0) {
|
||||
throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`)
|
||||
}
|
||||
|
||||
// Match method
|
||||
matchedMockDispatches = matchedMockDispatches.filter(({ method }) => matchValue(method, key.method))
|
||||
if (matchedMockDispatches.length === 0) {
|
||||
throw new MockNotMatchedError(`Mock dispatch not matched for method '${key.method}'`)
|
||||
}
|
||||
|
||||
// Match body
|
||||
matchedMockDispatches = matchedMockDispatches.filter(({ body }) => typeof body !== 'undefined' ? matchValue(body, key.body) : true)
|
||||
if (matchedMockDispatches.length === 0) {
|
||||
throw new MockNotMatchedError(`Mock dispatch not matched for body '${key.body}'`)
|
||||
}
|
||||
|
||||
// Match headers
|
||||
matchedMockDispatches = matchedMockDispatches.filter((mockDispatch) => matchHeaders(mockDispatch, key.headers))
|
||||
if (matchedMockDispatches.length === 0) {
|
||||
throw new MockNotMatchedError(`Mock dispatch not matched for headers '${typeof key.headers === 'object' ? JSON.stringify(key.headers) : key.headers}'`)
|
||||
}
|
||||
|
||||
return matchedMockDispatches[0]
|
||||
}
|
||||
|
||||
function addMockDispatch (mockDispatches, key, data) {
|
||||
const baseData = { timesInvoked: 0, times: 1, persist: false, consumed: false }
|
||||
const replyData = typeof data === 'function' ? { callback: data } : { ...data }
|
||||
const newMockDispatch = { ...baseData, ...key, pending: true, data: { error: null, ...replyData } }
|
||||
mockDispatches.push(newMockDispatch)
|
||||
return newMockDispatch
|
||||
}
|
||||
|
||||
function deleteMockDispatch (mockDispatches, key) {
|
||||
const index = mockDispatches.findIndex(dispatch => {
|
||||
if (!dispatch.consumed) {
|
||||
return false
|
||||
}
|
||||
return matchKey(dispatch, key)
|
||||
})
|
||||
if (index !== -1) {
|
||||
mockDispatches.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
function buildKey (opts) {
|
||||
const { path, method, body, headers, query } = opts
|
||||
return {
|
||||
path,
|
||||
method,
|
||||
body,
|
||||
headers,
|
||||
query
|
||||
}
|
||||
}
|
||||
|
||||
function generateKeyValues (data) {
|
||||
return Object.entries(data).reduce((keyValuePairs, [key, value]) => [...keyValuePairs, key, value], [])
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
||||
* @param {number} statusCode
|
||||
*/
|
||||
function getStatusText (statusCode) {
|
||||
switch (statusCode) {
|
||||
case 100: return 'Continue'
|
||||
case 101: return 'Switching Protocols'
|
||||
case 102: return 'Processing'
|
||||
case 103: return 'Early Hints'
|
||||
case 200: return 'OK'
|
||||
case 201: return 'Created'
|
||||
case 202: return 'Accepted'
|
||||
case 203: return 'Non-Authoritative Information'
|
||||
case 204: return 'No Content'
|
||||
case 205: return 'Reset Content'
|
||||
case 206: return 'Partial Content'
|
||||
case 207: return 'Multi-Status'
|
||||
case 208: return 'Already Reported'
|
||||
case 226: return 'IM Used'
|
||||
case 300: return 'Multiple Choice'
|
||||
case 301: return 'Moved Permanently'
|
||||
case 302: return 'Found'
|
||||
case 303: return 'See Other'
|
||||
case 304: return 'Not Modified'
|
||||
case 305: return 'Use Proxy'
|
||||
case 306: return 'unused'
|
||||
case 307: return 'Temporary Redirect'
|
||||
case 308: return 'Permanent Redirect'
|
||||
case 400: return 'Bad Request'
|
||||
case 401: return 'Unauthorized'
|
||||
case 402: return 'Payment Required'
|
||||
case 403: return 'Forbidden'
|
||||
case 404: return 'Not Found'
|
||||
case 405: return 'Method Not Allowed'
|
||||
case 406: return 'Not Acceptable'
|
||||
case 407: return 'Proxy Authentication Required'
|
||||
case 408: return 'Request Timeout'
|
||||
case 409: return 'Conflict'
|
||||
case 410: return 'Gone'
|
||||
case 411: return 'Length Required'
|
||||
case 412: return 'Precondition Failed'
|
||||
case 413: return 'Payload Too Large'
|
||||
case 414: return 'URI Too Large'
|
||||
case 415: return 'Unsupported Media Type'
|
||||
case 416: return 'Range Not Satisfiable'
|
||||
case 417: return 'Expectation Failed'
|
||||
case 418: return 'I\'m a teapot'
|
||||
case 421: return 'Misdirected Request'
|
||||
case 422: return 'Unprocessable Entity'
|
||||
case 423: return 'Locked'
|
||||
case 424: return 'Failed Dependency'
|
||||
case 425: return 'Too Early'
|
||||
case 426: return 'Upgrade Required'
|
||||
case 428: return 'Precondition Required'
|
||||
case 429: return 'Too Many Requests'
|
||||
case 431: return 'Request Header Fields Too Large'
|
||||
case 451: return 'Unavailable For Legal Reasons'
|
||||
case 500: return 'Internal Server Error'
|
||||
case 501: return 'Not Implemented'
|
||||
case 502: return 'Bad Gateway'
|
||||
case 503: return 'Service Unavailable'
|
||||
case 504: return 'Gateway Timeout'
|
||||
case 505: return 'HTTP Version Not Supported'
|
||||
case 506: return 'Variant Also Negotiates'
|
||||
case 507: return 'Insufficient Storage'
|
||||
case 508: return 'Loop Detected'
|
||||
case 510: return 'Not Extended'
|
||||
case 511: return 'Network Authentication Required'
|
||||
default: return 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
async function getResponse (body) {
|
||||
const buffers = []
|
||||
for await (const data of body) {
|
||||
buffers.push(data)
|
||||
}
|
||||
return Buffer.concat(buffers).toString('utf8')
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock dispatch function used to simulate undici dispatches
|
||||
*/
|
||||
function mockDispatch (opts, handler) {
|
||||
// Get mock dispatch from built key
|
||||
const key = buildKey(opts)
|
||||
const mockDispatch = getMockDispatch(this[kDispatches], key)
|
||||
|
||||
mockDispatch.timesInvoked++
|
||||
|
||||
// Here's where we resolve a callback if a callback is present for the dispatch data.
|
||||
if (mockDispatch.data.callback) {
|
||||
mockDispatch.data = { ...mockDispatch.data, ...mockDispatch.data.callback(opts) }
|
||||
}
|
||||
|
||||
// Parse mockDispatch data
|
||||
const { data: { statusCode, data, headers, trailers, error }, delay, persist } = mockDispatch
|
||||
const { timesInvoked, times } = mockDispatch
|
||||
|
||||
// If it's used up and not persistent, mark as consumed
|
||||
mockDispatch.consumed = !persist && timesInvoked >= times
|
||||
mockDispatch.pending = timesInvoked < times
|
||||
|
||||
// If specified, trigger dispatch error
|
||||
if (error !== null) {
|
||||
deleteMockDispatch(this[kDispatches], key)
|
||||
handler.onError(error)
|
||||
return true
|
||||
}
|
||||
|
||||
// Handle the request with a delay if necessary
|
||||
if (typeof delay === 'number' && delay > 0) {
|
||||
setTimeout(() => {
|
||||
handleReply(this[kDispatches])
|
||||
}, delay)
|
||||
} else {
|
||||
handleReply(this[kDispatches])
|
||||
}
|
||||
|
||||
function handleReply (mockDispatches) {
|
||||
const responseData = getResponseData(typeof data === 'function' ? data(opts) : data)
|
||||
const responseHeaders = generateKeyValues(headers)
|
||||
const responseTrailers = generateKeyValues(trailers)
|
||||
|
||||
handler.onHeaders(statusCode, responseHeaders, resume, getStatusText(statusCode))
|
||||
handler.onData(Buffer.from(responseData))
|
||||
handler.onComplete(responseTrailers)
|
||||
deleteMockDispatch(mockDispatches, key)
|
||||
}
|
||||
|
||||
function resume () {}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function buildMockDispatch () {
|
||||
const agent = this[kMockAgent]
|
||||
const origin = this[kOrigin]
|
||||
const originalDispatch = this[kOriginalDispatch]
|
||||
|
||||
return function dispatch (opts, handler) {
|
||||
if (agent.isMockActive) {
|
||||
try {
|
||||
mockDispatch.call(this, opts, handler)
|
||||
} catch (error) {
|
||||
if (error instanceof MockNotMatchedError) {
|
||||
const netConnect = agent[kGetNetConnect]()
|
||||
if (netConnect === false) {
|
||||
throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)`)
|
||||
}
|
||||
if (checkNetConnect(netConnect, origin)) {
|
||||
originalDispatch.call(this, opts, handler)
|
||||
} else {
|
||||
throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect is not enabled for this origin)`)
|
||||
}
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
} else {
|
||||
originalDispatch.call(this, opts, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkNetConnect (netConnect, origin) {
|
||||
const url = new URL(origin)
|
||||
if (netConnect === true) {
|
||||
return true
|
||||
} else if (Array.isArray(netConnect) && netConnect.some((matcher) => matchValue(matcher, url.host))) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function buildMockOptions (opts) {
|
||||
if (opts) {
|
||||
const { agent, ...mockOptions } = opts
|
||||
return mockOptions
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getResponseData,
|
||||
getMockDispatch,
|
||||
addMockDispatch,
|
||||
deleteMockDispatch,
|
||||
buildKey,
|
||||
generateKeyValues,
|
||||
matchValue,
|
||||
getResponse,
|
||||
getStatusText,
|
||||
mockDispatch,
|
||||
buildMockDispatch,
|
||||
checkNetConnect,
|
||||
buildMockOptions,
|
||||
getHeaderByName
|
||||
}
|
40
node_modules/undici/lib/mock/pending-interceptors-formatter.js
generated
vendored
Normal file
40
node_modules/undici/lib/mock/pending-interceptors-formatter.js
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
'use strict'
|
||||
|
||||
const { Transform } = require('stream')
|
||||
const { Console } = require('console')
|
||||
|
||||
/**
|
||||
* Gets the output of `console.table(…)` as a string.
|
||||
*/
|
||||
module.exports = class PendingInterceptorsFormatter {
|
||||
constructor ({ disableColors } = {}) {
|
||||
this.transform = new Transform({
|
||||
transform (chunk, _enc, cb) {
|
||||
cb(null, chunk)
|
||||
}
|
||||
})
|
||||
|
||||
this.logger = new Console({
|
||||
stdout: this.transform,
|
||||
inspectOptions: {
|
||||
colors: !disableColors && !process.env.CI
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
format (pendingInterceptors) {
|
||||
const withPrettyHeaders = pendingInterceptors.map(
|
||||
({ method, path, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
|
||||
Method: method,
|
||||
Origin: origin,
|
||||
Path: path,
|
||||
'Status code': statusCode,
|
||||
Persistent: persist ? '✅' : '❌',
|
||||
Invocations: timesInvoked,
|
||||
Remaining: persist ? Infinity : times - timesInvoked
|
||||
}))
|
||||
|
||||
this.logger.table(withPrettyHeaders)
|
||||
return this.transform.read().toString()
|
||||
}
|
||||
}
|
29
node_modules/undici/lib/mock/pluralizer.js
generated
vendored
Normal file
29
node_modules/undici/lib/mock/pluralizer.js
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
'use strict'
|
||||
|
||||
const singulars = {
|
||||
pronoun: 'it',
|
||||
is: 'is',
|
||||
was: 'was',
|
||||
this: 'this'
|
||||
}
|
||||
|
||||
const plurals = {
|
||||
pronoun: 'they',
|
||||
is: 'are',
|
||||
was: 'were',
|
||||
this: 'these'
|
||||
}
|
||||
|
||||
module.exports = class Pluralizer {
|
||||
constructor (singular, plural) {
|
||||
this.singular = singular
|
||||
this.plural = plural
|
||||
}
|
||||
|
||||
pluralize (count) {
|
||||
const one = count === 1
|
||||
const keys = one ? singulars : plurals
|
||||
const noun = one ? this.singular : this.plural
|
||||
return { ...keys, count, noun }
|
||||
}
|
||||
}
|
117
node_modules/undici/lib/node/fixed-queue.js
generated
vendored
Normal file
117
node_modules/undici/lib/node/fixed-queue.js
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
/* eslint-disable */
|
||||
|
||||
'use strict'
|
||||
|
||||
// Extracted from node/lib/internal/fixed_queue.js
|
||||
|
||||
// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
|
||||
const kSize = 2048;
|
||||
const kMask = kSize - 1;
|
||||
|
||||
// The FixedQueue is implemented as a singly-linked list of fixed-size
|
||||
// circular buffers. It looks something like this:
|
||||
//
|
||||
// head tail
|
||||
// | |
|
||||
// v v
|
||||
// +-----------+ <-----\ +-----------+ <------\ +-----------+
|
||||
// | [null] | \----- | next | \------- | next |
|
||||
// +-----------+ +-----------+ +-----------+
|
||||
// | item | <-- bottom | item | <-- bottom | [empty] |
|
||||
// | item | | item | | [empty] |
|
||||
// | item | | item | | [empty] |
|
||||
// | item | | item | | [empty] |
|
||||
// | item | | item | bottom --> | item |
|
||||
// | item | | item | | item |
|
||||
// | ... | | ... | | ... |
|
||||
// | item | | item | | item |
|
||||
// | item | | item | | item |
|
||||
// | [empty] | <-- top | item | | item |
|
||||
// | [empty] | | item | | item |
|
||||
// | [empty] | | [empty] | <-- top top --> | [empty] |
|
||||
// +-----------+ +-----------+ +-----------+
|
||||
//
|
||||
// Or, if there is only one circular buffer, it looks something
|
||||
// like either of these:
|
||||
//
|
||||
// head tail head tail
|
||||
// | | | |
|
||||
// v v v v
|
||||
// +-----------+ +-----------+
|
||||
// | [null] | | [null] |
|
||||
// +-----------+ +-----------+
|
||||
// | [empty] | | item |
|
||||
// | [empty] | | item |
|
||||
// | item | <-- bottom top --> | [empty] |
|
||||
// | item | | [empty] |
|
||||
// | [empty] | <-- top bottom --> | item |
|
||||
// | [empty] | | item |
|
||||
// +-----------+ +-----------+
|
||||
//
|
||||
// Adding a value means moving `top` forward by one, removing means
|
||||
// moving `bottom` forward by one. After reaching the end, the queue
|
||||
// wraps around.
|
||||
//
|
||||
// When `top === bottom` the current queue is empty and when
|
||||
// `top + 1 === bottom` it's full. This wastes a single space of storage
|
||||
// but allows much quicker checks.
|
||||
|
||||
class FixedCircularBuffer {
|
||||
constructor() {
|
||||
this.bottom = 0;
|
||||
this.top = 0;
|
||||
this.list = new Array(kSize);
|
||||
this.next = null;
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.top === this.bottom;
|
||||
}
|
||||
|
||||
isFull() {
|
||||
return ((this.top + 1) & kMask) === this.bottom;
|
||||
}
|
||||
|
||||
push(data) {
|
||||
this.list[this.top] = data;
|
||||
this.top = (this.top + 1) & kMask;
|
||||
}
|
||||
|
||||
shift() {
|
||||
const nextItem = this.list[this.bottom];
|
||||
if (nextItem === undefined)
|
||||
return null;
|
||||
this.list[this.bottom] = undefined;
|
||||
this.bottom = (this.bottom + 1) & kMask;
|
||||
return nextItem;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = class FixedQueue {
|
||||
constructor() {
|
||||
this.head = this.tail = new FixedCircularBuffer();
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.head.isEmpty();
|
||||
}
|
||||
|
||||
push(data) {
|
||||
if (this.head.isFull()) {
|
||||
// Head is full: Creates a new queue, sets the old queue's `.next` to it,
|
||||
// and sets it as the new main queue.
|
||||
this.head = this.head.next = new FixedCircularBuffer();
|
||||
}
|
||||
this.head.push(data);
|
||||
}
|
||||
|
||||
shift() {
|
||||
const tail = this.tail;
|
||||
const next = tail.shift();
|
||||
if (tail.isEmpty() && tail.next !== null) {
|
||||
// If there is another queue, it forms the new tail.
|
||||
this.tail = tail.next;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
};
|
194
node_modules/undici/lib/pool-base.js
generated
vendored
Normal file
194
node_modules/undici/lib/pool-base.js
generated
vendored
Normal file
|
@ -0,0 +1,194 @@
|
|||
'use strict'
|
||||
|
||||
const DispatcherBase = require('./dispatcher-base')
|
||||
const FixedQueue = require('./node/fixed-queue')
|
||||
const { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl, kClose, kDestroy, kDispatch } = require('./core/symbols')
|
||||
const PoolStats = require('./pool-stats')
|
||||
|
||||
const kClients = Symbol('clients')
|
||||
const kNeedDrain = Symbol('needDrain')
|
||||
const kQueue = Symbol('queue')
|
||||
const kClosedResolve = Symbol('closed resolve')
|
||||
const kOnDrain = Symbol('onDrain')
|
||||
const kOnConnect = Symbol('onConnect')
|
||||
const kOnDisconnect = Symbol('onDisconnect')
|
||||
const kOnConnectionError = Symbol('onConnectionError')
|
||||
const kGetDispatcher = Symbol('get dispatcher')
|
||||
const kAddClient = Symbol('add client')
|
||||
const kRemoveClient = Symbol('remove client')
|
||||
const kStats = Symbol('stats')
|
||||
|
||||
class PoolBase extends DispatcherBase {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this[kQueue] = new FixedQueue()
|
||||
this[kClients] = []
|
||||
this[kQueued] = 0
|
||||
|
||||
const pool = this
|
||||
|
||||
this[kOnDrain] = function onDrain (origin, targets) {
|
||||
const queue = pool[kQueue]
|
||||
|
||||
let needDrain = false
|
||||
|
||||
while (!needDrain) {
|
||||
const item = queue.shift()
|
||||
if (!item) {
|
||||
break
|
||||
}
|
||||
pool[kQueued]--
|
||||
needDrain = !this.dispatch(item.opts, item.handler)
|
||||
}
|
||||
|
||||
this[kNeedDrain] = needDrain
|
||||
|
||||
if (!this[kNeedDrain] && pool[kNeedDrain]) {
|
||||
pool[kNeedDrain] = false
|
||||
pool.emit('drain', origin, [pool, ...targets])
|
||||
}
|
||||
|
||||
if (pool[kClosedResolve] && queue.isEmpty()) {
|
||||
Promise
|
||||
.all(pool[kClients].map(c => c.close()))
|
||||
.then(pool[kClosedResolve])
|
||||
}
|
||||
}
|
||||
|
||||
this[kOnConnect] = (origin, targets) => {
|
||||
pool.emit('connect', origin, [pool, ...targets])
|
||||
}
|
||||
|
||||
this[kOnDisconnect] = (origin, targets, err) => {
|
||||
pool.emit('disconnect', origin, [pool, ...targets], err)
|
||||
}
|
||||
|
||||
this[kOnConnectionError] = (origin, targets, err) => {
|
||||
pool.emit('connectionError', origin, [pool, ...targets], err)
|
||||
}
|
||||
|
||||
this[kStats] = new PoolStats(this)
|
||||
}
|
||||
|
||||
get [kBusy] () {
|
||||
return this[kNeedDrain]
|
||||
}
|
||||
|
||||
get [kConnected] () {
|
||||
return this[kClients].filter(client => client[kConnected]).length
|
||||
}
|
||||
|
||||
get [kFree] () {
|
||||
return this[kClients].filter(client => client[kConnected] && !client[kNeedDrain]).length
|
||||
}
|
||||
|
||||
get [kPending] () {
|
||||
let ret = this[kQueued]
|
||||
for (const { [kPending]: pending } of this[kClients]) {
|
||||
ret += pending
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
get [kRunning] () {
|
||||
let ret = 0
|
||||
for (const { [kRunning]: running } of this[kClients]) {
|
||||
ret += running
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
get [kSize] () {
|
||||
let ret = this[kQueued]
|
||||
for (const { [kSize]: size } of this[kClients]) {
|
||||
ret += size
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
get stats () {
|
||||
return this[kStats]
|
||||
}
|
||||
|
||||
async [kClose] () {
|
||||
if (this[kQueue].isEmpty()) {
|
||||
return Promise.all(this[kClients].map(c => c.close()))
|
||||
} else {
|
||||
return new Promise((resolve) => {
|
||||
this[kClosedResolve] = resolve
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async [kDestroy] (err) {
|
||||
while (true) {
|
||||
const item = this[kQueue].shift()
|
||||
if (!item) {
|
||||
break
|
||||
}
|
||||
item.handler.onError(err)
|
||||
}
|
||||
|
||||
return Promise.all(this[kClients].map(c => c.destroy(err)))
|
||||
}
|
||||
|
||||
[kDispatch] (opts, handler) {
|
||||
const dispatcher = this[kGetDispatcher]()
|
||||
|
||||
if (!dispatcher) {
|
||||
this[kNeedDrain] = true
|
||||
this[kQueue].push({ opts, handler })
|
||||
this[kQueued]++
|
||||
} else if (!dispatcher.dispatch(opts, handler)) {
|
||||
dispatcher[kNeedDrain] = true
|
||||
this[kNeedDrain] = !this[kGetDispatcher]()
|
||||
}
|
||||
|
||||
return !this[kNeedDrain]
|
||||
}
|
||||
|
||||
[kAddClient] (client) {
|
||||
client
|
||||
.on('drain', this[kOnDrain])
|
||||
.on('connect', this[kOnConnect])
|
||||
.on('disconnect', this[kOnDisconnect])
|
||||
.on('connectionError', this[kOnConnectionError])
|
||||
|
||||
this[kClients].push(client)
|
||||
|
||||
if (this[kNeedDrain]) {
|
||||
process.nextTick(() => {
|
||||
if (this[kNeedDrain]) {
|
||||
this[kOnDrain](client[kUrl], [this, client])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
[kRemoveClient] (client) {
|
||||
client.close(() => {
|
||||
const idx = this[kClients].indexOf(client)
|
||||
if (idx !== -1) {
|
||||
this[kClients].splice(idx, 1)
|
||||
}
|
||||
})
|
||||
|
||||
this[kNeedDrain] = this[kClients].some(dispatcher => (
|
||||
!dispatcher[kNeedDrain] &&
|
||||
dispatcher.closed !== true &&
|
||||
dispatcher.destroyed !== true
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PoolBase,
|
||||
kClients,
|
||||
kNeedDrain,
|
||||
kAddClient,
|
||||
kRemoveClient,
|
||||
kGetDispatcher
|
||||
}
|
34
node_modules/undici/lib/pool-stats.js
generated
vendored
Normal file
34
node_modules/undici/lib/pool-stats.js
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
const { kFree, kConnected, kPending, kQueued, kRunning, kSize } = require('./core/symbols')
|
||||
const kPool = Symbol('pool')
|
||||
|
||||
class PoolStats {
|
||||
constructor (pool) {
|
||||
this[kPool] = pool
|
||||
}
|
||||
|
||||
get connected () {
|
||||
return this[kPool][kConnected]
|
||||
}
|
||||
|
||||
get free () {
|
||||
return this[kPool][kFree]
|
||||
}
|
||||
|
||||
get pending () {
|
||||
return this[kPool][kPending]
|
||||
}
|
||||
|
||||
get queued () {
|
||||
return this[kPool][kQueued]
|
||||
}
|
||||
|
||||
get running () {
|
||||
return this[kPool][kRunning]
|
||||
}
|
||||
|
||||
get size () {
|
||||
return this[kPool][kSize]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PoolStats
|
83
node_modules/undici/lib/pool.js
generated
vendored
Normal file
83
node_modules/undici/lib/pool.js
generated
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
'use strict'
|
||||
|
||||
const {
|
||||
PoolBase,
|
||||
kClients,
|
||||
kNeedDrain,
|
||||
kAddClient,
|
||||
kGetDispatcher
|
||||
} = require('./pool-base')
|
||||
const Client = require('./client')
|
||||
const {
|
||||
InvalidArgumentError
|
||||
} = require('./core/errors')
|
||||
const util = require('./core/util')
|
||||
const { kUrl } = require('./core/symbols')
|
||||
const buildConnector = require('./core/connect')
|
||||
|
||||
const kOptions = Symbol('options')
|
||||
const kConnections = Symbol('connections')
|
||||
const kFactory = Symbol('factory')
|
||||
|
||||
function defaultFactory (origin, opts) {
|
||||
return new Client(origin, opts)
|
||||
}
|
||||
|
||||
class Pool extends PoolBase {
|
||||
constructor (origin, {
|
||||
connections,
|
||||
factory = defaultFactory,
|
||||
connect,
|
||||
connectTimeout,
|
||||
tls,
|
||||
maxCachedSessions,
|
||||
socketPath,
|
||||
...options
|
||||
} = {}) {
|
||||
super()
|
||||
|
||||
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
||||
throw new InvalidArgumentError('invalid connections')
|
||||
}
|
||||
|
||||
if (typeof factory !== 'function') {
|
||||
throw new InvalidArgumentError('factory must be a function.')
|
||||
}
|
||||
|
||||
if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') {
|
||||
throw new InvalidArgumentError('connect must be a function or an object')
|
||||
}
|
||||
|
||||
if (typeof connect !== 'function') {
|
||||
connect = buildConnector({
|
||||
...tls,
|
||||
maxCachedSessions,
|
||||
socketPath,
|
||||
timeout: connectTimeout == null ? 10e3 : connectTimeout,
|
||||
...connect
|
||||
})
|
||||
}
|
||||
|
||||
this[kConnections] = connections || null
|
||||
this[kUrl] = util.parseOrigin(origin)
|
||||
this[kOptions] = { ...util.deepClone(options), connect }
|
||||
this[kFactory] = factory
|
||||
}
|
||||
|
||||
[kGetDispatcher] () {
|
||||
let dispatcher = this[kClients].find(dispatcher => !dispatcher[kNeedDrain])
|
||||
|
||||
if (dispatcher) {
|
||||
return dispatcher
|
||||
}
|
||||
|
||||
if (!this[kConnections] || this[kClients].length < this[kConnections]) {
|
||||
dispatcher = this[kFactory](this[kUrl], this[kOptions])
|
||||
this[kAddClient](dispatcher)
|
||||
}
|
||||
|
||||
return dispatcher
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Pool
|
150
node_modules/undici/lib/proxy-agent.js
generated
vendored
Normal file
150
node_modules/undici/lib/proxy-agent.js
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
'use strict'
|
||||
|
||||
const { kClose, kDestroy } = require('./core/symbols')
|
||||
const Client = require('./agent')
|
||||
const Agent = require('./agent')
|
||||
const DispatcherBase = require('./dispatcher-base')
|
||||
const { InvalidArgumentError, RequestAbortedError } = require('./core/errors')
|
||||
const buildConnector = require('./core/connect')
|
||||
|
||||
const kAgent = Symbol('proxy agent')
|
||||
const kClient = Symbol('proxy client')
|
||||
const kProxyHeaders = Symbol('proxy headers')
|
||||
const kRequestTls = Symbol('request tls settings')
|
||||
const kProxyTls = Symbol('proxy tls settings')
|
||||
const kConnectEndpoint = Symbol('connect endpoint function')
|
||||
|
||||
function defaultProtocolPort (protocol) {
|
||||
return protocol === 'https:' ? 443 : 80
|
||||
}
|
||||
|
||||
class ProxyAgent extends DispatcherBase {
|
||||
constructor (opts) {
|
||||
super(opts)
|
||||
|
||||
if (typeof opts === 'string') {
|
||||
opts = { uri: opts }
|
||||
}
|
||||
|
||||
if (!opts || !opts.uri) {
|
||||
throw new InvalidArgumentError('Proxy opts.uri is mandatory')
|
||||
}
|
||||
|
||||
this[kRequestTls] = opts.requestTls
|
||||
this[kProxyTls] = opts.proxyTls
|
||||
this[kProxyHeaders] = {}
|
||||
|
||||
if (opts.auth) {
|
||||
this[kProxyHeaders]['proxy-authorization'] = `Basic ${opts.auth}`
|
||||
}
|
||||
|
||||
const { origin, port } = new URL(opts.uri)
|
||||
|
||||
const connect = buildConnector({ ...opts.proxyTls })
|
||||
this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
|
||||
this[kClient] = new Client({ origin: opts.origin, connect })
|
||||
this[kAgent] = new Agent({
|
||||
...opts,
|
||||
connect: async (opts, callback) => {
|
||||
let requestedHost = opts.host
|
||||
if (!opts.port) {
|
||||
requestedHost += `:${defaultProtocolPort(opts.protocol)}`
|
||||
}
|
||||
try {
|
||||
const { socket, statusCode } = await this[kClient].connect({
|
||||
origin,
|
||||
port,
|
||||
path: requestedHost,
|
||||
signal: opts.signal,
|
||||
headers: {
|
||||
...this[kProxyHeaders],
|
||||
host: opts.host
|
||||
}
|
||||
})
|
||||
if (statusCode !== 200) {
|
||||
socket.on('error', () => {}).destroy()
|
||||
callback(new RequestAbortedError('Proxy response !== 200 when HTTP Tunneling'))
|
||||
}
|
||||
if (opts.protocol !== 'https:') {
|
||||
callback(null, socket)
|
||||
return
|
||||
}
|
||||
let servername
|
||||
if (this[kRequestTls]) {
|
||||
servername = this[kRequestTls].servername
|
||||
} else {
|
||||
servername = opts.servername
|
||||
}
|
||||
this[kConnectEndpoint]({ ...opts, servername, httpSocket: socket }, callback)
|
||||
} catch (err) {
|
||||
callback(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
dispatch (opts, handler) {
|
||||
const { host } = new URL(opts.origin)
|
||||
const headers = buildHeaders(opts.headers)
|
||||
throwIfProxyAuthIsSent(headers)
|
||||
return this[kAgent].dispatch(
|
||||
{
|
||||
...opts,
|
||||
headers: {
|
||||
...headers,
|
||||
host
|
||||
}
|
||||
},
|
||||
handler
|
||||
)
|
||||
}
|
||||
|
||||
async [kClose] () {
|
||||
await this[kAgent].close()
|
||||
await this[kClient].close()
|
||||
}
|
||||
|
||||
async [kDestroy] () {
|
||||
await this[kAgent].destroy()
|
||||
await this[kClient].destroy()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[] | Record<string, string>} headers
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function buildHeaders (headers) {
|
||||
// When using undici.fetch, the headers list is stored
|
||||
// as an array.
|
||||
if (Array.isArray(headers)) {
|
||||
/** @type {Record<string, string>} */
|
||||
const headersPair = {}
|
||||
|
||||
for (let i = 0; i < headers.length; i += 2) {
|
||||
headersPair[headers[i]] = headers[i + 1]
|
||||
}
|
||||
|
||||
return headersPair
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, string>} headers
|
||||
*
|
||||
* Previous versions of ProxyAgent suggests the Proxy-Authorization in request headers
|
||||
* Nevertheless, it was changed and to avoid a security vulnerability by end users
|
||||
* this check was created.
|
||||
* It should be removed in the next major version for performance reasons
|
||||
*/
|
||||
function throwIfProxyAuthIsSent (headers) {
|
||||
const existProxyAuth = headers && Object.keys(headers)
|
||||
.find((key) => key.toLowerCase() === 'proxy-authorization')
|
||||
if (existProxyAuth) {
|
||||
throw new InvalidArgumentError('Proxy-Authorization should be sent in ProxyAgent constructor')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProxyAgent
|
Loading…
Add table
Add a link
Reference in a new issue