chore: update deps
This commit is contained in:
125
node_modules/undici/lib/core/connect.js
generated
vendored
125
node_modules/undici/lib/core/connect.js
generated
vendored
@ -1,9 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const net = require('net')
|
||||
const assert = require('assert')
|
||||
const net = require('node:net')
|
||||
const assert = require('node:assert')
|
||||
const util = require('./util')
|
||||
const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
|
||||
const timers = require('../util/timers')
|
||||
|
||||
function noop () {}
|
||||
|
||||
let tls // include tls conditionally since it is not always available
|
||||
|
||||
@ -15,7 +18,7 @@ let tls // include tls conditionally since it is not always available
|
||||
let SessionCache
|
||||
// FIXME: remove workaround when the Node bug is fixed
|
||||
// https://github.com/nodejs/node/issues/49344#issuecomment-1741776308
|
||||
if (global.FinalizationRegistry && !process.env.NODE_V8_COVERAGE) {
|
||||
if (global.FinalizationRegistry && !(process.env.NODE_V8_COVERAGE || process.env.UNDICI_NO_FG)) {
|
||||
SessionCache = class WeakSessionCache {
|
||||
constructor (maxCachedSessions) {
|
||||
this._maxCachedSessions = maxCachedSessions
|
||||
@ -73,7 +76,7 @@ if (global.FinalizationRegistry && !process.env.NODE_V8_COVERAGE) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...opts }) {
|
||||
function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, session: customSession, ...opts }) {
|
||||
if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) {
|
||||
throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero')
|
||||
}
|
||||
@ -86,15 +89,17 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...o
|
||||
let socket
|
||||
if (protocol === 'https:') {
|
||||
if (!tls) {
|
||||
tls = require('tls')
|
||||
tls = require('node:tls')
|
||||
}
|
||||
servername = servername || options.servername || util.getServerName(host) || null
|
||||
|
||||
const sessionKey = servername || hostname
|
||||
const session = sessionCache.get(sessionKey) || null
|
||||
|
||||
assert(sessionKey)
|
||||
|
||||
const session = customSession || sessionCache.get(sessionKey) || null
|
||||
|
||||
port = port || 443
|
||||
|
||||
socket = tls.connect({
|
||||
highWaterMark: 16384, // TLS in node can't have bigger HWM anyway...
|
||||
...options,
|
||||
@ -104,7 +109,7 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...o
|
||||
// TODO(HTTP/2): Add support for h2c
|
||||
ALPNProtocols: allowH2 ? ['http/1.1', 'h2'] : ['http/1.1'],
|
||||
socket: httpSocket, // upgrade socket connection
|
||||
port: port || 443,
|
||||
port,
|
||||
host: hostname
|
||||
})
|
||||
|
||||
@ -115,11 +120,14 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...o
|
||||
})
|
||||
} else {
|
||||
assert(!httpSocket, 'httpSocket can only be sent on TLS update')
|
||||
|
||||
port = port || 80
|
||||
|
||||
socket = net.connect({
|
||||
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
|
||||
...options,
|
||||
localAddress,
|
||||
port: port || 80,
|
||||
port,
|
||||
host: hostname
|
||||
})
|
||||
}
|
||||
@ -130,12 +138,12 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...o
|
||||
socket.setKeepAlive(true, keepAliveInitialDelay)
|
||||
}
|
||||
|
||||
const cancelTimeout = setupTimeout(() => onConnectTimeout(socket), timeout)
|
||||
const clearConnectTimeout = setupConnectTimeout(new WeakRef(socket), { timeout, hostname, port })
|
||||
|
||||
socket
|
||||
.setNoDelay(true)
|
||||
.once(protocol === 'https:' ? 'secureConnect' : 'connect', function () {
|
||||
cancelTimeout()
|
||||
queueMicrotask(clearConnectTimeout)
|
||||
|
||||
if (callback) {
|
||||
const cb = callback
|
||||
@ -144,7 +152,7 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...o
|
||||
}
|
||||
})
|
||||
.on('error', function (err) {
|
||||
cancelTimeout()
|
||||
queueMicrotask(clearConnectTimeout)
|
||||
|
||||
if (callback) {
|
||||
const cb = callback
|
||||
@ -157,33 +165,76 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...o
|
||||
}
|
||||
}
|
||||
|
||||
function setupTimeout (onConnectTimeout, timeout) {
|
||||
if (!timeout) {
|
||||
return () => {}
|
||||
}
|
||||
|
||||
let s1 = null
|
||||
let s2 = null
|
||||
const timeoutId = setTimeout(() => {
|
||||
// setImmediate is added to make sure that we priotorise socket error events over timeouts
|
||||
s1 = setImmediate(() => {
|
||||
if (process.platform === 'win32') {
|
||||
// Windows needs an extra setImmediate probably due to implementation differences in the socket logic
|
||||
s2 = setImmediate(() => onConnectTimeout())
|
||||
} else {
|
||||
onConnectTimeout()
|
||||
/**
|
||||
* @param {WeakRef<net.Socket>} socketWeakRef
|
||||
* @param {object} opts
|
||||
* @param {number} opts.timeout
|
||||
* @param {string} opts.hostname
|
||||
* @param {number} opts.port
|
||||
* @returns {() => void}
|
||||
*/
|
||||
const setupConnectTimeout = process.platform === 'win32'
|
||||
? (socketWeakRef, opts) => {
|
||||
if (!opts.timeout) {
|
||||
return noop
|
||||
}
|
||||
})
|
||||
}, timeout)
|
||||
return () => {
|
||||
clearTimeout(timeoutId)
|
||||
clearImmediate(s1)
|
||||
clearImmediate(s2)
|
||||
}
|
||||
}
|
||||
|
||||
function onConnectTimeout (socket) {
|
||||
util.destroy(socket, new ConnectTimeoutError())
|
||||
let s1 = null
|
||||
let s2 = null
|
||||
const fastTimer = timers.setFastTimeout(() => {
|
||||
// setImmediate is added to make sure that we prioritize socket error events over timeouts
|
||||
s1 = setImmediate(() => {
|
||||
// Windows needs an extra setImmediate probably due to implementation differences in the socket logic
|
||||
s2 = setImmediate(() => onConnectTimeout(socketWeakRef.deref(), opts))
|
||||
})
|
||||
}, opts.timeout)
|
||||
return () => {
|
||||
timers.clearFastTimeout(fastTimer)
|
||||
clearImmediate(s1)
|
||||
clearImmediate(s2)
|
||||
}
|
||||
}
|
||||
: (socketWeakRef, opts) => {
|
||||
if (!opts.timeout) {
|
||||
return noop
|
||||
}
|
||||
|
||||
let s1 = null
|
||||
const fastTimer = timers.setFastTimeout(() => {
|
||||
// setImmediate is added to make sure that we prioritize socket error events over timeouts
|
||||
s1 = setImmediate(() => {
|
||||
onConnectTimeout(socketWeakRef.deref(), opts)
|
||||
})
|
||||
}, opts.timeout)
|
||||
return () => {
|
||||
timers.clearFastTimeout(fastTimer)
|
||||
clearImmediate(s1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {net.Socket} socket
|
||||
* @param {object} opts
|
||||
* @param {number} opts.timeout
|
||||
* @param {string} opts.hostname
|
||||
* @param {number} opts.port
|
||||
*/
|
||||
function onConnectTimeout (socket, opts) {
|
||||
// The socket could be already garbage collected
|
||||
if (socket == null) {
|
||||
return
|
||||
}
|
||||
|
||||
let message = 'Connect Timeout Error'
|
||||
if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
|
||||
message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')},`
|
||||
} else {
|
||||
message += ` (attempted address: ${opts.hostname}:${opts.port},`
|
||||
}
|
||||
|
||||
message += ` timeout: ${opts.timeout}ms)`
|
||||
|
||||
util.destroy(socket, new ConnectTimeoutError(message))
|
||||
}
|
||||
|
||||
module.exports = buildConnector
|
||||
|
45
node_modules/undici/lib/core/constants.js
generated
vendored
45
node_modules/undici/lib/core/constants.js
generated
vendored
@ -1,10 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
/** @type {Record<string, string | undefined>} */
|
||||
const headerNameLowerCasedRecord = {}
|
||||
|
||||
// https://developer.mozilla.org/docs/Web/HTTP/Headers
|
||||
const wellknownHeaderNames = [
|
||||
/**
|
||||
* @see https://developer.mozilla.org/docs/Web/HTTP/Headers
|
||||
*/
|
||||
const wellknownHeaderNames = /** @type {const} */ ([
|
||||
'Accept',
|
||||
'Accept-Encoding',
|
||||
'Accept-Language',
|
||||
@ -100,7 +99,35 @@ const wellknownHeaderNames = [
|
||||
'X-Powered-By',
|
||||
'X-Requested-With',
|
||||
'X-XSS-Protection'
|
||||
]
|
||||
])
|
||||
|
||||
/** @type {Record<typeof wellknownHeaderNames[number]|Lowercase<typeof wellknownHeaderNames[number]>, string>} */
|
||||
const headerNameLowerCasedRecord = {}
|
||||
|
||||
// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
|
||||
Object.setPrototypeOf(headerNameLowerCasedRecord, null)
|
||||
|
||||
/**
|
||||
* @type {Record<Lowercase<typeof wellknownHeaderNames[number]>, Buffer>}
|
||||
*/
|
||||
const wellknownHeaderNameBuffers = {}
|
||||
|
||||
// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
|
||||
Object.setPrototypeOf(wellknownHeaderNameBuffers, null)
|
||||
|
||||
/**
|
||||
* @param {string} header Lowercased header
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
function getHeaderNameAsBuffer (header) {
|
||||
let buffer = wellknownHeaderNameBuffers[header]
|
||||
|
||||
if (buffer === undefined) {
|
||||
buffer = Buffer.from(header)
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
for (let i = 0; i < wellknownHeaderNames.length; ++i) {
|
||||
const key = wellknownHeaderNames[i]
|
||||
@ -109,10 +136,8 @@ for (let i = 0; i < wellknownHeaderNames.length; ++i) {
|
||||
lowerCasedKey
|
||||
}
|
||||
|
||||
// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
|
||||
Object.setPrototypeOf(headerNameLowerCasedRecord, null)
|
||||
|
||||
module.exports = {
|
||||
wellknownHeaderNames,
|
||||
headerNameLowerCasedRecord
|
||||
headerNameLowerCasedRecord,
|
||||
getHeaderNameAsBuffer
|
||||
}
|
||||
|
196
node_modules/undici/lib/core/diagnostics.js
generated
vendored
Normal file
196
node_modules/undici/lib/core/diagnostics.js
generated
vendored
Normal file
@ -0,0 +1,196 @@
|
||||
'use strict'
|
||||
|
||||
const diagnosticsChannel = require('node:diagnostics_channel')
|
||||
const util = require('node:util')
|
||||
|
||||
const undiciDebugLog = util.debuglog('undici')
|
||||
const fetchDebuglog = util.debuglog('fetch')
|
||||
const websocketDebuglog = util.debuglog('websocket')
|
||||
|
||||
const channels = {
|
||||
// Client
|
||||
beforeConnect: diagnosticsChannel.channel('undici:client:beforeConnect'),
|
||||
connected: diagnosticsChannel.channel('undici:client:connected'),
|
||||
connectError: diagnosticsChannel.channel('undici:client:connectError'),
|
||||
sendHeaders: diagnosticsChannel.channel('undici:client:sendHeaders'),
|
||||
// Request
|
||||
create: diagnosticsChannel.channel('undici:request:create'),
|
||||
bodySent: diagnosticsChannel.channel('undici:request:bodySent'),
|
||||
headers: diagnosticsChannel.channel('undici:request:headers'),
|
||||
trailers: diagnosticsChannel.channel('undici:request:trailers'),
|
||||
error: diagnosticsChannel.channel('undici:request:error'),
|
||||
// WebSocket
|
||||
open: diagnosticsChannel.channel('undici:websocket:open'),
|
||||
close: diagnosticsChannel.channel('undici:websocket:close'),
|
||||
socketError: diagnosticsChannel.channel('undici:websocket:socket_error'),
|
||||
ping: diagnosticsChannel.channel('undici:websocket:ping'),
|
||||
pong: diagnosticsChannel.channel('undici:websocket:pong')
|
||||
}
|
||||
|
||||
let isTrackingClientEvents = false
|
||||
|
||||
function trackClientEvents (debugLog = undiciDebugLog) {
|
||||
if (isTrackingClientEvents) {
|
||||
return
|
||||
}
|
||||
|
||||
isTrackingClientEvents = true
|
||||
|
||||
diagnosticsChannel.subscribe('undici:client:beforeConnect',
|
||||
evt => {
|
||||
const {
|
||||
connectParams: { version, protocol, port, host }
|
||||
} = evt
|
||||
debugLog(
|
||||
'connecting to %s%s using %s%s',
|
||||
host,
|
||||
port ? `:${port}` : '',
|
||||
protocol,
|
||||
version
|
||||
)
|
||||
})
|
||||
|
||||
diagnosticsChannel.subscribe('undici:client:connected',
|
||||
evt => {
|
||||
const {
|
||||
connectParams: { version, protocol, port, host }
|
||||
} = evt
|
||||
debugLog(
|
||||
'connected to %s%s using %s%s',
|
||||
host,
|
||||
port ? `:${port}` : '',
|
||||
protocol,
|
||||
version
|
||||
)
|
||||
})
|
||||
|
||||
diagnosticsChannel.subscribe('undici:client:connectError',
|
||||
evt => {
|
||||
const {
|
||||
connectParams: { version, protocol, port, host },
|
||||
error
|
||||
} = evt
|
||||
debugLog(
|
||||
'connection to %s%s using %s%s errored - %s',
|
||||
host,
|
||||
port ? `:${port}` : '',
|
||||
protocol,
|
||||
version,
|
||||
error.message
|
||||
)
|
||||
})
|
||||
|
||||
diagnosticsChannel.subscribe('undici:client:sendHeaders',
|
||||
evt => {
|
||||
const {
|
||||
request: { method, path, origin }
|
||||
} = evt
|
||||
debugLog('sending request to %s %s/%s', method, origin, path)
|
||||
})
|
||||
}
|
||||
|
||||
let isTrackingRequestEvents = false
|
||||
|
||||
function trackRequestEvents (debugLog = undiciDebugLog) {
|
||||
if (isTrackingRequestEvents) {
|
||||
return
|
||||
}
|
||||
|
||||
isTrackingRequestEvents = true
|
||||
|
||||
diagnosticsChannel.subscribe('undici:request:headers',
|
||||
evt => {
|
||||
const {
|
||||
request: { method, path, origin },
|
||||
response: { statusCode }
|
||||
} = evt
|
||||
debugLog(
|
||||
'received response to %s %s/%s - HTTP %d',
|
||||
method,
|
||||
origin,
|
||||
path,
|
||||
statusCode
|
||||
)
|
||||
})
|
||||
|
||||
diagnosticsChannel.subscribe('undici:request:trailers',
|
||||
evt => {
|
||||
const {
|
||||
request: { method, path, origin }
|
||||
} = evt
|
||||
debugLog('trailers received from %s %s/%s', method, origin, path)
|
||||
})
|
||||
|
||||
diagnosticsChannel.subscribe('undici:request:error',
|
||||
evt => {
|
||||
const {
|
||||
request: { method, path, origin },
|
||||
error
|
||||
} = evt
|
||||
debugLog(
|
||||
'request to %s %s/%s errored - %s',
|
||||
method,
|
||||
origin,
|
||||
path,
|
||||
error.message
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let isTrackingWebSocketEvents = false
|
||||
|
||||
function trackWebSocketEvents (debugLog = websocketDebuglog) {
|
||||
if (isTrackingWebSocketEvents) {
|
||||
return
|
||||
}
|
||||
|
||||
isTrackingWebSocketEvents = true
|
||||
|
||||
diagnosticsChannel.subscribe('undici:websocket:open',
|
||||
evt => {
|
||||
const {
|
||||
address: { address, port }
|
||||
} = evt
|
||||
debugLog('connection opened %s%s', address, port ? `:${port}` : '')
|
||||
})
|
||||
|
||||
diagnosticsChannel.subscribe('undici:websocket:close',
|
||||
evt => {
|
||||
const { websocket, code, reason } = evt
|
||||
debugLog(
|
||||
'closed connection to %s - %s %s',
|
||||
websocket.url,
|
||||
code,
|
||||
reason
|
||||
)
|
||||
})
|
||||
|
||||
diagnosticsChannel.subscribe('undici:websocket:socket_error',
|
||||
err => {
|
||||
debugLog('connection errored - %s', err.message)
|
||||
})
|
||||
|
||||
diagnosticsChannel.subscribe('undici:websocket:ping',
|
||||
evt => {
|
||||
debugLog('ping received')
|
||||
})
|
||||
|
||||
diagnosticsChannel.subscribe('undici:websocket:pong',
|
||||
evt => {
|
||||
debugLog('pong received')
|
||||
})
|
||||
}
|
||||
|
||||
if (undiciDebugLog.enabled || fetchDebuglog.enabled) {
|
||||
trackClientEvents(fetchDebuglog.enabled ? fetchDebuglog : undiciDebugLog)
|
||||
trackRequestEvents(fetchDebuglog.enabled ? fetchDebuglog : undiciDebugLog)
|
||||
}
|
||||
|
||||
if (websocketDebuglog.enabled) {
|
||||
trackClientEvents(undiciDebugLog.enabled ? undiciDebugLog : websocketDebuglog)
|
||||
trackWebSocketEvents(websocketDebuglog)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
channels
|
||||
}
|
60
node_modules/undici/lib/core/errors.js
generated
vendored
60
node_modules/undici/lib/core/errors.js
generated
vendored
@ -1,8 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
class UndiciError extends Error {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
constructor (message, options) {
|
||||
super(message, options)
|
||||
this.name = 'UndiciError'
|
||||
this.code = 'UND_ERR'
|
||||
}
|
||||
@ -11,7 +11,6 @@ class UndiciError extends Error {
|
||||
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'
|
||||
@ -21,7 +20,6 @@ class ConnectTimeoutError extends UndiciError {
|
||||
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'
|
||||
@ -31,7 +29,6 @@ class HeadersTimeoutError extends UndiciError {
|
||||
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'
|
||||
@ -41,7 +38,6 @@ class HeadersOverflowError extends UndiciError {
|
||||
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'
|
||||
@ -51,7 +47,6 @@ class BodyTimeoutError extends UndiciError {
|
||||
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'
|
||||
@ -65,7 +60,6 @@ class ResponseStatusCodeError extends UndiciError {
|
||||
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'
|
||||
@ -75,17 +69,23 @@ class InvalidArgumentError extends UndiciError {
|
||||
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 {
|
||||
class AbortError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
this.name = 'AbortError'
|
||||
this.message = message || 'The operation was aborted'
|
||||
}
|
||||
}
|
||||
|
||||
class RequestAbortedError extends AbortError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, RequestAbortedError)
|
||||
this.name = 'AbortError'
|
||||
this.message = message || 'Request aborted'
|
||||
this.code = 'UND_ERR_ABORTED'
|
||||
@ -95,7 +95,6 @@ class RequestAbortedError extends UndiciError {
|
||||
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'
|
||||
@ -105,7 +104,6 @@ class InformationalError extends UndiciError {
|
||||
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'
|
||||
@ -115,7 +113,6 @@ class RequestContentLengthMismatchError extends UndiciError {
|
||||
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'
|
||||
@ -125,7 +122,6 @@ class ResponseContentLengthMismatchError extends UndiciError {
|
||||
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'
|
||||
@ -135,7 +131,6 @@ class ClientDestroyedError extends UndiciError {
|
||||
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'
|
||||
@ -145,7 +140,6 @@ class ClientClosedError extends UndiciError {
|
||||
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'
|
||||
@ -156,7 +150,6 @@ class SocketError extends UndiciError {
|
||||
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'
|
||||
@ -166,7 +159,6 @@ class NotSupportedError extends UndiciError {
|
||||
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'
|
||||
@ -176,7 +168,6 @@ class BalancedPoolMissingUpstreamError extends UndiciError {
|
||||
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
|
||||
@ -186,7 +177,6 @@ class HTTPParserError extends Error {
|
||||
class ResponseExceededMaxSizeError extends UndiciError {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, ResponseExceededMaxSizeError)
|
||||
this.name = 'ResponseExceededMaxSizeError'
|
||||
this.message = message || 'Response content exceeded max size'
|
||||
this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE'
|
||||
@ -196,7 +186,6 @@ class ResponseExceededMaxSizeError extends UndiciError {
|
||||
class RequestRetryError extends UndiciError {
|
||||
constructor (message, code, { headers, data }) {
|
||||
super(message)
|
||||
Error.captureStackTrace(this, RequestRetryError)
|
||||
this.name = 'RequestRetryError'
|
||||
this.message = message || 'Request retry error'
|
||||
this.code = 'UND_ERR_REQ_RETRY'
|
||||
@ -206,7 +195,30 @@ class RequestRetryError extends UndiciError {
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseError extends UndiciError {
|
||||
constructor (message, code, { headers, body }) {
|
||||
super(message)
|
||||
this.name = 'ResponseError'
|
||||
this.message = message || 'Response error'
|
||||
this.code = 'UND_ERR_RESPONSE'
|
||||
this.statusCode = code
|
||||
this.body = body
|
||||
this.headers = headers
|
||||
}
|
||||
}
|
||||
|
||||
class SecureProxyConnectionError extends UndiciError {
|
||||
constructor (cause, message, options = {}) {
|
||||
super(message, { cause, ...options })
|
||||
this.name = 'SecureProxyConnectionError'
|
||||
this.message = message || 'Secure Proxy Connection failed'
|
||||
this.code = 'UND_ERR_PRX_TLS'
|
||||
this.cause = cause
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AbortError,
|
||||
HTTPParserError,
|
||||
UndiciError,
|
||||
HeadersTimeoutError,
|
||||
@ -226,5 +238,7 @@ module.exports = {
|
||||
ResponseContentLengthMismatchError,
|
||||
BalancedPoolMissingUpstreamError,
|
||||
ResponseExceededMaxSizeError,
|
||||
RequestRetryError
|
||||
RequestRetryError,
|
||||
ResponseError,
|
||||
SecureProxyConnectionError
|
||||
}
|
||||
|
306
node_modules/undici/lib/core/request.js
generated
vendored
306
node_modules/undici/lib/core/request.js
generated
vendored
@ -4,52 +4,29 @@ const {
|
||||
InvalidArgumentError,
|
||||
NotSupportedError
|
||||
} = require('./errors')
|
||||
const assert = require('assert')
|
||||
const { kHTTP2BuildRequest, kHTTP2CopyHeaders, kHTTP1BuildRequest } = require('./symbols')
|
||||
const util = require('./util')
|
||||
|
||||
// tokenRegExp and headerCharRegex have been lifted from
|
||||
// https://github.com/nodejs/node/blob/main/lib/_http_common.js
|
||||
|
||||
/**
|
||||
* Verifies that the given val is a valid HTTP token
|
||||
* per the rules defined in RFC 7230
|
||||
* See https://tools.ietf.org/html/rfc7230#section-3.2.6
|
||||
*/
|
||||
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/
|
||||
|
||||
/**
|
||||
* Matches if val contains an invalid field-vchar
|
||||
* field-value = *( field-content / obs-fold )
|
||||
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
||||
* field-vchar = VCHAR / obs-text
|
||||
*/
|
||||
const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
|
||||
const assert = require('node:assert')
|
||||
const {
|
||||
isValidHTTPToken,
|
||||
isValidHeaderValue,
|
||||
isStream,
|
||||
destroy,
|
||||
isBuffer,
|
||||
isFormDataLike,
|
||||
isIterable,
|
||||
isBlobLike,
|
||||
serializePathWithQuery,
|
||||
assertRequestHandler,
|
||||
getServerName,
|
||||
normalizedMethodRecords
|
||||
} = require('./util')
|
||||
const { channels } = require('./diagnostics.js')
|
||||
const { headerNameLowerCasedRecord } = require('./constants')
|
||||
|
||||
// Verifies that a given path is valid does not contain control chars \x00 to \x20
|
||||
const invalidPathRegex = /[^\u0021-\u00ff]/
|
||||
|
||||
const kHandler = Symbol('handler')
|
||||
|
||||
const channels = {}
|
||||
|
||||
let extractBody
|
||||
|
||||
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,
|
||||
@ -63,8 +40,9 @@ class Request {
|
||||
headersTimeout,
|
||||
bodyTimeout,
|
||||
reset,
|
||||
throwOnError,
|
||||
expectContinue
|
||||
expectContinue,
|
||||
servername,
|
||||
throwOnError
|
||||
}, handler) {
|
||||
if (typeof path !== 'string') {
|
||||
throw new InvalidArgumentError('path must be a string')
|
||||
@ -74,13 +52,13 @@ class Request {
|
||||
method !== 'CONNECT'
|
||||
) {
|
||||
throw new InvalidArgumentError('path must be an absolute URL or start with a slash')
|
||||
} else if (invalidPathRegex.exec(path) !== null) {
|
||||
} else if (invalidPathRegex.test(path)) {
|
||||
throw new InvalidArgumentError('invalid request path')
|
||||
}
|
||||
|
||||
if (typeof method !== 'string') {
|
||||
throw new InvalidArgumentError('method must be a string')
|
||||
} else if (tokenRegExp.exec(method) === null) {
|
||||
} else if (normalizedMethodRecords[method] === undefined && !isValidHTTPToken(method)) {
|
||||
throw new InvalidArgumentError('invalid request method')
|
||||
}
|
||||
|
||||
@ -104,25 +82,27 @@ class Request {
|
||||
throw new InvalidArgumentError('invalid expectContinue')
|
||||
}
|
||||
|
||||
if (throwOnError != null) {
|
||||
throw new InvalidArgumentError('invalid throwOnError')
|
||||
}
|
||||
|
||||
this.headersTimeout = headersTimeout
|
||||
|
||||
this.bodyTimeout = bodyTimeout
|
||||
|
||||
this.throwOnError = throwOnError === true
|
||||
|
||||
this.method = method
|
||||
|
||||
this.abort = null
|
||||
|
||||
if (body == null) {
|
||||
this.body = null
|
||||
} else if (util.isStream(body)) {
|
||||
} else if (isStream(body)) {
|
||||
this.body = body
|
||||
|
||||
const rState = this.body._readableState
|
||||
if (!rState || !rState.autoDestroy) {
|
||||
this.endHandler = function autoDestroy () {
|
||||
util.destroy(this)
|
||||
destroy(this)
|
||||
}
|
||||
this.body.on('end', this.endHandler)
|
||||
}
|
||||
@ -135,7 +115,7 @@ class Request {
|
||||
}
|
||||
}
|
||||
this.body.on('error', this.errorHandler)
|
||||
} else if (util.isBuffer(body)) {
|
||||
} else if (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
|
||||
@ -143,19 +123,18 @@ class Request {
|
||||
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)) {
|
||||
} else if (isFormDataLike(body) || isIterable(body) || 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.path = query ? serializePathWithQuery(path, query) : path
|
||||
|
||||
this.origin = origin
|
||||
|
||||
@ -163,7 +142,7 @@ class Request {
|
||||
? method === 'HEAD' || method === 'GET'
|
||||
: idempotent
|
||||
|
||||
this.blocking = blocking == null ? false : blocking
|
||||
this.blocking = blocking ?? this.method !== 'HEAD'
|
||||
|
||||
this.reset = reset == null ? null : reset
|
||||
|
||||
@ -173,7 +152,7 @@ class Request {
|
||||
|
||||
this.contentType = null
|
||||
|
||||
this.headers = ''
|
||||
this.headers = []
|
||||
|
||||
// Only for H2
|
||||
this.expectContinue = expectContinue != null ? expectContinue : false
|
||||
@ -186,39 +165,26 @@ class Request {
|
||||
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])
|
||||
if (headers[Symbol.iterator]) {
|
||||
for (const header of headers) {
|
||||
if (!Array.isArray(header) || header.length !== 2) {
|
||||
throw new InvalidArgumentError('headers must be in key-value pair format')
|
||||
}
|
||||
processHeader(this, header[0], header[1])
|
||||
}
|
||||
} else {
|
||||
const keys = Object.keys(headers)
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
processHeader(this, keys[i], headers[keys[i]])
|
||||
}
|
||||
}
|
||||
} else if (headers != null) {
|
||||
throw new InvalidArgumentError('headers must be an object or an array')
|
||||
}
|
||||
|
||||
if (util.isFormDataLike(this.body)) {
|
||||
if (util.nodeMajor < 16 || (util.nodeMajor === 16 && util.nodeMinor < 8)) {
|
||||
throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.8 and newer.')
|
||||
}
|
||||
assertRequestHandler(handler, method, upgrade)
|
||||
|
||||
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
|
||||
this.contentLength = bodyStream.length
|
||||
} 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.servername = servername || getServerName(this.host) || null
|
||||
|
||||
this[kHandler] = handler
|
||||
|
||||
@ -263,6 +229,10 @@ class Request {
|
||||
}
|
||||
}
|
||||
|
||||
onResponseStarted () {
|
||||
return this[kHandler].onResponseStarted?.()
|
||||
}
|
||||
|
||||
onHeaders (statusCode, headers, resume, statusText) {
|
||||
assert(!this.aborted)
|
||||
assert(!this.completed)
|
||||
@ -301,6 +271,7 @@ class Request {
|
||||
this.onFinally()
|
||||
|
||||
assert(!this.aborted)
|
||||
assert(!this.completed)
|
||||
|
||||
this.completed = true
|
||||
if (channels.trailers.hasSubscribers) {
|
||||
@ -342,157 +313,84 @@ class Request {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: adjust to support H2
|
||||
addHeader (key, value) {
|
||||
processHeader(this, key, value)
|
||||
return this
|
||||
}
|
||||
|
||||
static [kHTTP1BuildRequest] (origin, opts, handler) {
|
||||
// TODO: Migrate header parsing here, to make Requests
|
||||
// HTTP agnostic
|
||||
return new Request(origin, opts, handler)
|
||||
}
|
||||
|
||||
static [kHTTP2BuildRequest] (origin, opts, handler) {
|
||||
const headers = opts.headers
|
||||
opts = { ...opts, headers: null }
|
||||
|
||||
const request = new Request(origin, opts, handler)
|
||||
|
||||
request.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(request, headers[i], headers[i + 1], true)
|
||||
}
|
||||
} else if (headers && typeof headers === 'object') {
|
||||
const keys = Object.keys(headers)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
processHeader(request, key, headers[key], true)
|
||||
}
|
||||
} else if (headers != null) {
|
||||
throw new InvalidArgumentError('headers must be an object or an array')
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
static [kHTTP2CopyHeaders] (raw) {
|
||||
const rawHeaders = raw.split('\r\n')
|
||||
const headers = {}
|
||||
|
||||
for (const header of rawHeaders) {
|
||||
const [key, value] = header.split(': ')
|
||||
|
||||
if (value == null || value.length === 0) continue
|
||||
|
||||
if (headers[key]) headers[key] += `,${value}`
|
||||
else headers[key] = value
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
}
|
||||
|
||||
function processHeaderValue (key, val, skipAppend) {
|
||||
if (val && typeof val === 'object') {
|
||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||
}
|
||||
|
||||
val = val != null ? `${val}` : ''
|
||||
|
||||
if (headerCharRegex.exec(val) !== null) {
|
||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||
}
|
||||
|
||||
return skipAppend ? val : `${key}: ${val}\r\n`
|
||||
}
|
||||
|
||||
function processHeader (request, key, val, skipAppend = false) {
|
||||
function processHeader (request, key, val) {
|
||||
if (val && (typeof val === 'object' && !Array.isArray(val))) {
|
||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||
} else if (val === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
request.host === null &&
|
||||
key.length === 4 &&
|
||||
key.toLowerCase() === 'host'
|
||||
) {
|
||||
if (headerCharRegex.exec(val) !== null) {
|
||||
let headerName = headerNameLowerCasedRecord[key]
|
||||
|
||||
if (headerName === undefined) {
|
||||
headerName = key.toLowerCase()
|
||||
if (headerNameLowerCasedRecord[headerName] === undefined && !isValidHTTPToken(headerName)) {
|
||||
throw new InvalidArgumentError('invalid header key')
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
const arr = []
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
if (typeof val[i] === 'string') {
|
||||
if (!isValidHeaderValue(val[i])) {
|
||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||
}
|
||||
arr.push(val[i])
|
||||
} else if (val[i] === null) {
|
||||
arr.push('')
|
||||
} else if (typeof val[i] === 'object') {
|
||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||
} else {
|
||||
arr.push(`${val[i]}`)
|
||||
}
|
||||
}
|
||||
val = arr
|
||||
} else if (typeof val === 'string') {
|
||||
if (!isValidHeaderValue(val)) {
|
||||
throw new InvalidArgumentError(`invalid ${key} header`)
|
||||
}
|
||||
} else if (val === null) {
|
||||
val = ''
|
||||
} else {
|
||||
val = `${val}`
|
||||
}
|
||||
|
||||
if (request.host === null && headerName === 'host') {
|
||||
if (typeof val !== 'string') {
|
||||
throw new InvalidArgumentError('invalid host header')
|
||||
}
|
||||
// Consumed by Client
|
||||
request.host = val
|
||||
} else if (
|
||||
request.contentLength === null &&
|
||||
key.length === 14 &&
|
||||
key.toLowerCase() === 'content-length'
|
||||
) {
|
||||
} else if (request.contentLength === null && headerName === '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'
|
||||
) {
|
||||
} else if (request.contentType === null && headerName === 'content-type') {
|
||||
request.contentType = val
|
||||
if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend)
|
||||
else request.headers += processHeaderValue(key, val)
|
||||
} else if (
|
||||
key.length === 17 &&
|
||||
key.toLowerCase() === 'transfer-encoding'
|
||||
) {
|
||||
throw new InvalidArgumentError('invalid transfer-encoding header')
|
||||
} else if (
|
||||
key.length === 10 &&
|
||||
key.toLowerCase() === 'connection'
|
||||
) {
|
||||
request.headers.push(key, val)
|
||||
} else if (headerName === 'transfer-encoding' || headerName === 'keep-alive' || headerName === 'upgrade') {
|
||||
throw new InvalidArgumentError(`invalid ${headerName} header`)
|
||||
} else if (headerName === 'connection') {
|
||||
const value = typeof val === 'string' ? val.toLowerCase() : null
|
||||
if (value !== 'close' && value !== 'keep-alive') {
|
||||
throw new InvalidArgumentError('invalid connection header')
|
||||
} else if (value === 'close') {
|
||||
}
|
||||
|
||||
if (value === 'close') {
|
||||
request.reset = true
|
||||
}
|
||||
} 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'
|
||||
) {
|
||||
} else if (headerName === 'expect') {
|
||||
throw new NotSupportedError('expect header not supported')
|
||||
} else if (tokenRegExp.exec(key) === null) {
|
||||
throw new InvalidArgumentError('invalid header key')
|
||||
} else {
|
||||
if (Array.isArray(val)) {
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
if (skipAppend) {
|
||||
if (request.headers[key]) request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}`
|
||||
else request.headers[key] = processHeaderValue(key, val[i], skipAppend)
|
||||
} else {
|
||||
request.headers += processHeaderValue(key, val[i])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend)
|
||||
else request.headers += processHeaderValue(key, val)
|
||||
}
|
||||
request.headers.push(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
|
19
node_modules/undici/lib/core/symbols.js
generated
vendored
19
node_modules/undici/lib/core/symbols.js
generated
vendored
@ -1,3 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
kClose: Symbol('close'),
|
||||
kDestroy: Symbol('destroy'),
|
||||
@ -8,7 +10,6 @@ module.exports = {
|
||||
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'),
|
||||
@ -21,6 +22,7 @@ module.exports = {
|
||||
kHost: Symbol('host'),
|
||||
kNoRef: Symbol('no ref'),
|
||||
kBodyUsed: Symbol('used'),
|
||||
kBody: Symbol('abstracted request body'),
|
||||
kRunning: Symbol('running'),
|
||||
kBlocking: Symbol('blocking'),
|
||||
kPending: Symbol('pending'),
|
||||
@ -33,6 +35,8 @@ module.exports = {
|
||||
kNeedDrain: Symbol('need drain'),
|
||||
kReset: Symbol('reset'),
|
||||
kDestroyed: Symbol.for('nodejs.stream.destroyed'),
|
||||
kResume: Symbol('resume'),
|
||||
kOnError: Symbol('on error'),
|
||||
kMaxHeadersSize: Symbol('max headers size'),
|
||||
kRunningIdx: Symbol('running index'),
|
||||
kPendingIdx: Symbol('pending index'),
|
||||
@ -50,14 +54,15 @@ module.exports = {
|
||||
kMaxRequests: Symbol('maxRequestsPerClient'),
|
||||
kProxy: Symbol('proxy agent options'),
|
||||
kCounter: Symbol('socket request counter'),
|
||||
kInterceptors: Symbol('dispatch interceptors'),
|
||||
kMaxResponseSize: Symbol('max response size'),
|
||||
kHTTP2Session: Symbol('http2Session'),
|
||||
kHTTP2SessionState: Symbol('http2Session state'),
|
||||
kHTTP2BuildRequest: Symbol('http2 build request'),
|
||||
kHTTP1BuildRequest: Symbol('http1 build request'),
|
||||
kHTTP2CopyHeaders: Symbol('http2 copy headers'),
|
||||
kHTTPConnVersion: Symbol('http connection version'),
|
||||
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
|
||||
kConstruct: Symbol('constructable')
|
||||
kConstruct: Symbol('constructable'),
|
||||
kListeners: Symbol('listeners'),
|
||||
kHTTPContext: Symbol('http context'),
|
||||
kMaxConcurrentStreams: Symbol('max concurrent streams'),
|
||||
kNoProxyAgent: Symbol('no proxy agent'),
|
||||
kHttpProxyAgent: Symbol('http proxy agent'),
|
||||
kHttpsProxyAgent: Symbol('https proxy agent')
|
||||
}
|
||||
|
160
node_modules/undici/lib/core/tree.js
generated
vendored
Normal file
160
node_modules/undici/lib/core/tree.js
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
wellknownHeaderNames,
|
||||
headerNameLowerCasedRecord
|
||||
} = require('./constants')
|
||||
|
||||
class TstNode {
|
||||
/** @type {any} */
|
||||
value = null
|
||||
/** @type {null | TstNode} */
|
||||
left = null
|
||||
/** @type {null | TstNode} */
|
||||
middle = null
|
||||
/** @type {null | TstNode} */
|
||||
right = null
|
||||
/** @type {number} */
|
||||
code
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {any} value
|
||||
* @param {number} index
|
||||
*/
|
||||
constructor (key, value, index) {
|
||||
if (index === undefined || index >= key.length) {
|
||||
throw new TypeError('Unreachable')
|
||||
}
|
||||
const code = this.code = key.charCodeAt(index)
|
||||
// check code is ascii string
|
||||
if (code > 0x7F) {
|
||||
throw new TypeError('key must be ascii string')
|
||||
}
|
||||
if (key.length !== ++index) {
|
||||
this.middle = new TstNode(key, value, index)
|
||||
} else {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {any} value
|
||||
* @returns {void}
|
||||
*/
|
||||
add (key, value) {
|
||||
const length = key.length
|
||||
if (length === 0) {
|
||||
throw new TypeError('Unreachable')
|
||||
}
|
||||
let index = 0
|
||||
/**
|
||||
* @type {TstNode}
|
||||
*/
|
||||
let node = this
|
||||
while (true) {
|
||||
const code = key.charCodeAt(index)
|
||||
// check code is ascii string
|
||||
if (code > 0x7F) {
|
||||
throw new TypeError('key must be ascii string')
|
||||
}
|
||||
if (node.code === code) {
|
||||
if (length === ++index) {
|
||||
node.value = value
|
||||
break
|
||||
} else if (node.middle !== null) {
|
||||
node = node.middle
|
||||
} else {
|
||||
node.middle = new TstNode(key, value, index)
|
||||
break
|
||||
}
|
||||
} else if (node.code < code) {
|
||||
if (node.left !== null) {
|
||||
node = node.left
|
||||
} else {
|
||||
node.left = new TstNode(key, value, index)
|
||||
break
|
||||
}
|
||||
} else if (node.right !== null) {
|
||||
node = node.right
|
||||
} else {
|
||||
node.right = new TstNode(key, value, index)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} key
|
||||
* @return {TstNode | null}
|
||||
*/
|
||||
search (key) {
|
||||
const keylength = key.length
|
||||
let index = 0
|
||||
/**
|
||||
* @type {TstNode|null}
|
||||
*/
|
||||
let node = this
|
||||
while (node !== null && index < keylength) {
|
||||
let code = key[index]
|
||||
// A-Z
|
||||
// First check if it is bigger than 0x5a.
|
||||
// Lowercase letters have higher char codes than uppercase ones.
|
||||
// Also we assume that headers will mostly contain lowercase characters.
|
||||
if (code <= 0x5a && code >= 0x41) {
|
||||
// Lowercase for uppercase.
|
||||
code |= 32
|
||||
}
|
||||
while (node !== null) {
|
||||
if (code === node.code) {
|
||||
if (keylength === ++index) {
|
||||
// Returns Node since it is the last key.
|
||||
return node
|
||||
}
|
||||
node = node.middle
|
||||
break
|
||||
}
|
||||
node = node.code < code ? node.left : node.right
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class TernarySearchTree {
|
||||
/** @type {TstNode | null} */
|
||||
node = null
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {any} value
|
||||
* @returns {void}
|
||||
* */
|
||||
insert (key, value) {
|
||||
if (this.node === null) {
|
||||
this.node = new TstNode(key, value, 0)
|
||||
} else {
|
||||
this.node.add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} key
|
||||
* @returns {any}
|
||||
*/
|
||||
lookup (key) {
|
||||
return this.node?.search(key)?.value ?? null
|
||||
}
|
||||
}
|
||||
|
||||
const tree = new TernarySearchTree()
|
||||
|
||||
for (let i = 0; i < wellknownHeaderNames.length; ++i) {
|
||||
const key = headerNameLowerCasedRecord[wellknownHeaderNames[i]]
|
||||
tree.insert(key, key)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
TernarySearchTree,
|
||||
tree
|
||||
}
|
704
node_modules/undici/lib/core/util.js
generated
vendored
704
node_modules/undici/lib/core/util.js
generated
vendored
@ -1,36 +1,112 @@
|
||||
'use strict'
|
||||
|
||||
const assert = require('assert')
|
||||
const { kDestroyed, kBodyUsed } = require('./symbols')
|
||||
const { IncomingMessage } = require('http')
|
||||
const stream = require('stream')
|
||||
const net = require('net')
|
||||
const assert = require('node:assert')
|
||||
const { kDestroyed, kBodyUsed, kListeners, kBody } = require('./symbols')
|
||||
const { IncomingMessage } = require('node:http')
|
||||
const stream = require('node:stream')
|
||||
const net = require('node:net')
|
||||
const { Blob } = require('node:buffer')
|
||||
const nodeUtil = require('node:util')
|
||||
const { stringify } = require('node:querystring')
|
||||
const { EventEmitter: EE } = require('node:events')
|
||||
const { InvalidArgumentError } = require('./errors')
|
||||
const { Blob } = require('buffer')
|
||||
const nodeUtil = require('util')
|
||||
const { stringify } = require('querystring')
|
||||
const { headerNameLowerCasedRecord } = require('./constants')
|
||||
const { tree } = require('./tree')
|
||||
|
||||
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
|
||||
const [nodeMajor, nodeMinor] = process.versions.node.split('.', 2).map(v => Number(v))
|
||||
|
||||
function nop () {}
|
||||
class BodyAsyncIterable {
|
||||
constructor (body) {
|
||||
this[kBody] = body
|
||||
this[kBodyUsed] = false
|
||||
}
|
||||
|
||||
async * [Symbol.asyncIterator] () {
|
||||
assert(!this[kBodyUsed], 'disturbed')
|
||||
this[kBodyUsed] = true
|
||||
yield * this[kBody]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} body
|
||||
* @returns {*}
|
||||
*/
|
||||
function wrapRequestBody (body) {
|
||||
if (isStream(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 (bodyLength(body) === 0) {
|
||||
body
|
||||
.on('data', function () {
|
||||
assert(false)
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof body.readableDidRead !== 'boolean') {
|
||||
body[kBodyUsed] = false
|
||||
EE.prototype.on.call(body, 'data', function () {
|
||||
this[kBodyUsed] = true
|
||||
})
|
||||
}
|
||||
|
||||
return body
|
||||
} else if (body && typeof 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.
|
||||
return new BodyAsyncIterable(body)
|
||||
} else if (
|
||||
body &&
|
||||
typeof body !== 'string' &&
|
||||
!ArrayBuffer.isView(body) &&
|
||||
isIterable(body)
|
||||
) {
|
||||
// TODO: Should we allow re-using iterable if !this.opts.idempotent
|
||||
// or through some other flag?
|
||||
return new BodyAsyncIterable(body)
|
||||
} else {
|
||||
return body
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} obj
|
||||
* @returns {obj is import('node:stream').Stream}
|
||||
*/
|
||||
function isStream (obj) {
|
||||
return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function'
|
||||
}
|
||||
|
||||
// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
|
||||
/**
|
||||
* @param {*} object
|
||||
* @returns {object is Blob}
|
||||
* 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])
|
||||
)
|
||||
if (object === null) {
|
||||
return false
|
||||
} else if (object instanceof Blob) {
|
||||
return true
|
||||
} else if (typeof object !== 'object') {
|
||||
return false
|
||||
} else {
|
||||
const sTag = object[Symbol.toStringTag]
|
||||
|
||||
return (sTag === 'Blob' || sTag === 'File') && (
|
||||
('stream' in object && typeof object.stream === 'function') ||
|
||||
('arrayBuffer' in object && typeof object.arrayBuffer === 'function')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function buildURL (url, queryParams) {
|
||||
/**
|
||||
* @param {string} url The URL to add the query params to
|
||||
* @param {import('node:querystring').ParsedUrlQueryInput} queryParams The object to serialize into a URL query string
|
||||
* @returns {string} The URL with the query params added
|
||||
*/
|
||||
function serializePathWithQuery (url, queryParams) {
|
||||
if (url.includes('?') || url.includes('#')) {
|
||||
throw new Error('Query params cannot be passed when url already contains "?" or "#".')
|
||||
}
|
||||
@ -44,11 +120,54 @@ function buildURL (url, queryParams) {
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number|string|undefined} port
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidPort (port) {
|
||||
const value = parseInt(port, 10)
|
||||
return (
|
||||
value === Number(port) &&
|
||||
value >= 0 &&
|
||||
value <= 65535
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value is a valid http or https prefixed string.
|
||||
*
|
||||
* @param {string} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isHttpOrHttpsPrefixed (value) {
|
||||
return (
|
||||
value != null &&
|
||||
value[0] === 'h' &&
|
||||
value[1] === 't' &&
|
||||
value[2] === 't' &&
|
||||
value[3] === 'p' &&
|
||||
(
|
||||
value[4] === ':' ||
|
||||
(
|
||||
value[4] === 's' &&
|
||||
value[5] === ':'
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|URL|Record<string,string>} url
|
||||
* @returns {URL}
|
||||
*/
|
||||
function parseURL (url) {
|
||||
if (typeof url === 'string') {
|
||||
/**
|
||||
* @type {URL}
|
||||
*/
|
||||
url = new URL(url)
|
||||
|
||||
if (!/^https?:/.test(url.origin || url.protocol)) {
|
||||
if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
|
||||
throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
|
||||
}
|
||||
|
||||
@ -59,12 +178,8 @@ function parseURL (url) {
|
||||
throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.')
|
||||
}
|
||||
|
||||
if (!/^https?:/.test(url.origin || url.protocol)) {
|
||||
throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
|
||||
}
|
||||
|
||||
if (!(url instanceof URL)) {
|
||||
if (url.port != null && url.port !== '' && !Number.isFinite(parseInt(url.port))) {
|
||||
if (url.port != null && url.port !== '' && isValidPort(url.port) === false) {
|
||||
throw new InvalidArgumentError('Invalid URL: port must be a valid integer or a string representation of an integer.')
|
||||
}
|
||||
|
||||
@ -84,33 +199,45 @@ function parseURL (url) {
|
||||
throw new InvalidArgumentError('Invalid URL origin: the origin must be a string or null/undefined.')
|
||||
}
|
||||
|
||||
if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
|
||||
throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
|
||||
}
|
||||
|
||||
const port = url.port != null
|
||||
? url.port
|
||||
: (url.protocol === 'https:' ? 443 : 80)
|
||||
let origin = url.origin != null
|
||||
? url.origin
|
||||
: `${url.protocol}//${url.hostname}:${port}`
|
||||
: `${url.protocol || ''}//${url.hostname || ''}:${port}`
|
||||
let path = url.path != null
|
||||
? url.path
|
||||
: `${url.pathname || ''}${url.search || ''}`
|
||||
|
||||
if (origin.endsWith('/')) {
|
||||
origin = origin.substring(0, origin.length - 1)
|
||||
if (origin[origin.length - 1] === '/') {
|
||||
origin = origin.slice(0, origin.length - 1)
|
||||
}
|
||||
|
||||
if (path && !path.startsWith('/')) {
|
||||
if (path && path[0] !== '/') {
|
||||
path = `/${path}`
|
||||
}
|
||||
// new URL(path, origin) is unsafe when `path` contains an absolute URL
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/API/URL/URL:
|
||||
// If first parameter is a relative URL, second param is required, and will be used as the base URL.
|
||||
// If first parameter is an absolute URL, a given second param will be ignored.
|
||||
url = new URL(origin + path)
|
||||
return new URL(`${origin}${path}`)
|
||||
}
|
||||
|
||||
if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
|
||||
throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|URL|Record<string, string>} url
|
||||
* @returns {URL}
|
||||
*/
|
||||
function parseOrigin (url) {
|
||||
url = parseURL(url)
|
||||
|
||||
@ -121,6 +248,10 @@ function parseOrigin (url) {
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} host
|
||||
* @returns {string}
|
||||
*/
|
||||
function getHostname (host) {
|
||||
if (host[0] === '[') {
|
||||
const idx = host.indexOf(']')
|
||||
@ -135,14 +266,18 @@ function getHostname (host) {
|
||||
return host.substring(0, idx)
|
||||
}
|
||||
|
||||
// IP addresses are not valid server names per RFC6066
|
||||
// > Currently, the only server names supported are DNS hostnames
|
||||
/**
|
||||
* IP addresses are not valid server names per RFC6066
|
||||
* Currently, the only server names supported are DNS hostnames
|
||||
* @param {string|null} host
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function getServerName (host) {
|
||||
if (!host) {
|
||||
return null
|
||||
}
|
||||
|
||||
assert.strictEqual(typeof host, 'string')
|
||||
assert(typeof host === 'string')
|
||||
|
||||
const servername = getHostname(host)
|
||||
if (net.isIP(servername)) {
|
||||
@ -152,18 +287,36 @@ function getServerName (host) {
|
||||
return servername
|
||||
}
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @template T
|
||||
* @param {T} obj
|
||||
* @returns {T}
|
||||
*/
|
||||
function deepClone (obj) {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} obj
|
||||
* @returns {obj is AsyncIterable}
|
||||
*/
|
||||
function isAsyncIterable (obj) {
|
||||
return !!(obj != null && typeof obj[Symbol.asyncIterator] === 'function')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} obj
|
||||
* @returns {obj is Iterable}
|
||||
*/
|
||||
function isIterable (obj) {
|
||||
return !!(obj != null && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function'))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Blob|Buffer|import ('stream').Stream} body
|
||||
* @returns {number|null}
|
||||
*/
|
||||
function bodyLength (body) {
|
||||
if (body == null) {
|
||||
return 0
|
||||
@ -181,15 +334,19 @@ function bodyLength (body) {
|
||||
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
|
||||
/**
|
||||
* @param {import ('stream').Stream} body
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isDestroyed (body) {
|
||||
return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body)))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import ('stream').Stream} stream
|
||||
* @param {Error} [err]
|
||||
* @returns {void}
|
||||
*/
|
||||
function destroy (stream, err) {
|
||||
if (stream == null || !isStream(stream) || isDestroyed(stream)) {
|
||||
return
|
||||
@ -203,9 +360,9 @@ function destroy (stream, err) {
|
||||
|
||||
stream.destroy(err)
|
||||
} else if (err) {
|
||||
process.nextTick((stream, err) => {
|
||||
queueMicrotask(() => {
|
||||
stream.emit('error', err)
|
||||
}, stream, err)
|
||||
})
|
||||
}
|
||||
|
||||
if (stream.destroyed !== true) {
|
||||
@ -214,8 +371,12 @@ function destroy (stream, err) {
|
||||
}
|
||||
|
||||
const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/
|
||||
/**
|
||||
* @param {string} val
|
||||
* @returns {number | null}
|
||||
*/
|
||||
function parseKeepAliveTimeout (val) {
|
||||
const m = val.toString().match(KEEPALIVE_TIMEOUT_EXPR)
|
||||
const m = val.match(KEEPALIVE_TIMEOUT_EXPR)
|
||||
return m ? parseInt(m[1], 10) * 1000 : null
|
||||
}
|
||||
|
||||
@ -225,29 +386,45 @@ function parseKeepAliveTimeout (val) {
|
||||
* @returns {string}
|
||||
*/
|
||||
function headerNameToString (value) {
|
||||
return headerNameLowerCasedRecord[value] || value.toLowerCase()
|
||||
return typeof value === 'string'
|
||||
? headerNameLowerCasedRecord[value] ?? value.toLowerCase()
|
||||
: tree.lookup(value) ?? value.toString('latin1').toLowerCase()
|
||||
}
|
||||
|
||||
function parseHeaders (headers, obj = {}) {
|
||||
// For H2 support
|
||||
if (!Array.isArray(headers)) return headers
|
||||
/**
|
||||
* Receive the buffer as a string and return its lowercase value.
|
||||
* @param {Buffer} value Header name
|
||||
* @returns {string}
|
||||
*/
|
||||
function bufferToLowerCasedHeaderName (value) {
|
||||
return tree.lookup(value) ?? value.toString('latin1').toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(Buffer | string)[]} headers
|
||||
* @param {Record<string, string | string[]>} [obj]
|
||||
* @returns {Record<string, string | string[]>}
|
||||
*/
|
||||
function parseHeaders (headers, obj) {
|
||||
if (obj === undefined) obj = {}
|
||||
|
||||
for (let i = 0; i < headers.length; i += 2) {
|
||||
const key = headers[i].toString().toLowerCase()
|
||||
const key = headerNameToString(headers[i])
|
||||
let val = obj[key]
|
||||
|
||||
if (!val) {
|
||||
if (Array.isArray(headers[i + 1])) {
|
||||
obj[key] = headers[i + 1].map(x => x.toString('utf8'))
|
||||
} else {
|
||||
obj[key] = headers[i + 1].toString('utf8')
|
||||
}
|
||||
} else {
|
||||
if (!Array.isArray(val)) {
|
||||
if (val) {
|
||||
if (typeof val === 'string') {
|
||||
val = [val]
|
||||
obj[key] = val
|
||||
}
|
||||
val.push(headers[i + 1].toString('utf8'))
|
||||
} else {
|
||||
const headersValue = headers[i + 1]
|
||||
if (typeof headersValue === 'string') {
|
||||
obj[key] = headersValue
|
||||
} else {
|
||||
obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('utf8')) : headersValue.toString('utf8')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,23 +436,38 @@ function parseHeaders (headers, obj = {}) {
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Buffer[]} headers
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function parseRawHeaders (headers) {
|
||||
const ret = []
|
||||
const headersLength = headers.length
|
||||
/**
|
||||
* @type {string[]}
|
||||
*/
|
||||
const ret = new Array(headersLength)
|
||||
|
||||
let hasContentLength = false
|
||||
let contentDispositionIdx = -1
|
||||
let key
|
||||
let val
|
||||
let kLen = 0
|
||||
|
||||
for (let n = 0; n < headers.length; n += 2) {
|
||||
const key = headers[n + 0].toString()
|
||||
const val = headers[n + 1].toString('utf8')
|
||||
for (let n = 0; n < headersLength; n += 2) {
|
||||
key = headers[n]
|
||||
val = headers[n + 1]
|
||||
|
||||
if (key.length === 14 && (key === 'content-length' || key.toLowerCase() === 'content-length')) {
|
||||
ret.push(key, val)
|
||||
typeof key !== 'string' && (key = key.toString())
|
||||
typeof val !== 'string' && (val = val.toString('utf8'))
|
||||
|
||||
kLen = key.length
|
||||
if (kLen === 14 && key[7] === '-' && (key === 'content-length' || key.toLowerCase() === 'content-length')) {
|
||||
hasContentLength = true
|
||||
} else if (key.length === 19 && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) {
|
||||
contentDispositionIdx = ret.push(key, val) - 1
|
||||
} else {
|
||||
ret.push(key, val)
|
||||
} else if (kLen === 19 && key[7] === '-' && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) {
|
||||
contentDispositionIdx = n + 1
|
||||
}
|
||||
ret[n] = key
|
||||
ret[n + 1] = val
|
||||
}
|
||||
|
||||
// See https://github.com/nodejs/node/pull/46528
|
||||
@ -286,16 +478,44 @@ function parseRawHeaders (headers) {
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} headers
|
||||
* @param {Buffer[]} headers
|
||||
*/
|
||||
function encodeRawHeaders (headers) {
|
||||
if (!Array.isArray(headers)) {
|
||||
throw new TypeError('expected headers to be an array')
|
||||
}
|
||||
return headers.map(x => Buffer.from(x))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} buffer
|
||||
* @returns {buffer is Buffer}
|
||||
*/
|
||||
function isBuffer (buffer) {
|
||||
// See, https://github.com/mcollina/undici/pull/319
|
||||
return buffer instanceof Uint8Array || Buffer.isBuffer(buffer)
|
||||
}
|
||||
|
||||
function validateHandler (handler, method, upgrade) {
|
||||
/**
|
||||
* Asserts that the handler object is a request handler.
|
||||
*
|
||||
* @param {object} handler
|
||||
* @param {string} method
|
||||
* @param {string} [upgrade]
|
||||
* @returns {asserts handler is import('../api/api-request').RequestHandler}
|
||||
*/
|
||||
function assertRequestHandler (handler, method, upgrade) {
|
||||
if (!handler || typeof handler !== 'object') {
|
||||
throw new InvalidArgumentError('handler must be an object')
|
||||
}
|
||||
|
||||
if (typeof handler.onRequestStart === 'function') {
|
||||
// TODO (fix): More checks...
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof handler.onConnect !== 'function') {
|
||||
throw new InvalidArgumentError('invalid onConnect method')
|
||||
}
|
||||
@ -327,35 +547,33 @@ function validateHandler (handler, method, upgrade) {
|
||||
}
|
||||
}
|
||||
|
||||
// A body is disturbed if it has been read from and it cannot
|
||||
// be re-used without losing state or data.
|
||||
/**
|
||||
* A body is disturbed if it has been read from and it cannot be re-used without
|
||||
* losing state or data.
|
||||
* @param {import('node:stream').Readable} body
|
||||
* @returns {boolean}
|
||||
*/
|
||||
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)
|
||||
))
|
||||
// TODO (fix): Why is body[kBodyUsed] needed?
|
||||
return !!(body && (stream.isDisturbed(body) || body[kBodyUsed]))
|
||||
}
|
||||
|
||||
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)
|
||||
)))
|
||||
}
|
||||
/**
|
||||
* @typedef {object} SocketInfo
|
||||
* @property {string} [localAddress]
|
||||
* @property {number} [localPort]
|
||||
* @property {string} [remoteAddress]
|
||||
* @property {number} [remotePort]
|
||||
* @property {string} [remoteFamily]
|
||||
* @property {number} [timeout]
|
||||
* @property {number} bytesWritten
|
||||
* @property {number} bytesRead
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {import('net').Socket} socket
|
||||
* @returns {SocketInfo}
|
||||
*/
|
||||
function getSocketInfo (socket) {
|
||||
return {
|
||||
localAddress: socket.localAddress,
|
||||
@ -369,21 +587,12 @@ function getSocketInfo (socket) {
|
||||
}
|
||||
}
|
||||
|
||||
async function * convertIterableToBuffer (iterable) {
|
||||
for await (const chunk of iterable) {
|
||||
yield Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
let ReadableStream
|
||||
/**
|
||||
* @param {Iterable} iterable
|
||||
* @returns {ReadableStream}
|
||||
*/
|
||||
function ReadableStreamFrom (iterable) {
|
||||
if (!ReadableStream) {
|
||||
ReadableStream = require('stream/web').ReadableStream
|
||||
}
|
||||
|
||||
if (ReadableStream.from) {
|
||||
return ReadableStream.from(convertIterableToBuffer(iterable))
|
||||
}
|
||||
// We cannot use ReadableStream.from here because it does not return a byte stream.
|
||||
|
||||
let iterator
|
||||
return new ReadableStream(
|
||||
@ -391,28 +600,40 @@ function ReadableStreamFrom (iterable) {
|
||||
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))
|
||||
pull (controller) {
|
||||
async function pull () {
|
||||
const { done, value } = await iterator.next()
|
||||
if (done) {
|
||||
queueMicrotask(() => {
|
||||
controller.close()
|
||||
controller.byobRequest?.respond(0)
|
||||
})
|
||||
} else {
|
||||
const buf = Buffer.isBuffer(value) ? value : Buffer.from(value)
|
||||
if (buf.byteLength) {
|
||||
controller.enqueue(new Uint8Array(buf))
|
||||
} else {
|
||||
return await pull()
|
||||
}
|
||||
}
|
||||
}
|
||||
return controller.desiredSize > 0
|
||||
|
||||
return pull()
|
||||
},
|
||||
async cancel (reason) {
|
||||
async cancel () {
|
||||
await iterator.return()
|
||||
}
|
||||
},
|
||||
0
|
||||
},
|
||||
type: 'bytes'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// The chunk should be a FormData instance and contains
|
||||
// all the required methods.
|
||||
/**
|
||||
* The object should be a FormData instance and contains all the required
|
||||
* methods.
|
||||
* @param {*} object
|
||||
* @returns {object is FormData}
|
||||
*/
|
||||
function isFormDataLike (object) {
|
||||
return (
|
||||
object &&
|
||||
@ -427,50 +648,143 @@ function isFormDataLike (object) {
|
||||
)
|
||||
}
|
||||
|
||||
function throwIfAborted (signal) {
|
||||
if (!signal) { return }
|
||||
if (typeof signal.throwIfAborted === 'function') {
|
||||
signal.throwIfAborted()
|
||||
} else {
|
||||
if (signal.aborted) {
|
||||
// DOMException not available < v17.0.0
|
||||
const err = new Error('The operation was aborted')
|
||||
err.name = 'AbortError'
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addAbortListener (signal, listener) {
|
||||
if ('addEventListener' in signal) {
|
||||
signal.addEventListener('abort', listener, { once: true })
|
||||
return () => signal.removeEventListener('abort', listener)
|
||||
}
|
||||
signal.addListener('abort', listener)
|
||||
signal.once('abort', listener)
|
||||
return () => signal.removeListener('abort', listener)
|
||||
}
|
||||
|
||||
const hasToWellFormed = !!String.prototype.toWellFormed
|
||||
/**
|
||||
* @function
|
||||
* @param {string} value
|
||||
* @returns {string}
|
||||
*/
|
||||
const toUSVString = (() => {
|
||||
if (typeof String.prototype.toWellFormed === 'function') {
|
||||
/**
|
||||
* @param {string} value
|
||||
* @returns {string}
|
||||
*/
|
||||
return (value) => `${value}`.toWellFormed()
|
||||
} else {
|
||||
/**
|
||||
* @param {string} value
|
||||
* @returns {string}
|
||||
*/
|
||||
return nodeUtil.toUSVString
|
||||
}
|
||||
})()
|
||||
|
||||
/**
|
||||
* @param {string} val
|
||||
* @param {*} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function toUSVString (val) {
|
||||
if (hasToWellFormed) {
|
||||
return `${val}`.toWellFormed()
|
||||
} else if (nodeUtil.toUSVString) {
|
||||
return nodeUtil.toUSVString(val)
|
||||
// TODO: move this to webidl
|
||||
const isUSVString = (() => {
|
||||
if (typeof String.prototype.isWellFormed === 'function') {
|
||||
/**
|
||||
* @param {*} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
return (value) => `${value}`.isWellFormed()
|
||||
} else {
|
||||
/**
|
||||
* @param {*} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
return (value) => toUSVString(value) === `${value}`
|
||||
}
|
||||
})()
|
||||
|
||||
return `${val}`
|
||||
/**
|
||||
* @see https://tools.ietf.org/html/rfc7230#section-3.2.6
|
||||
* @param {number} c
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isTokenCharCode (c) {
|
||||
switch (c) {
|
||||
case 0x22:
|
||||
case 0x28:
|
||||
case 0x29:
|
||||
case 0x2c:
|
||||
case 0x2f:
|
||||
case 0x3a:
|
||||
case 0x3b:
|
||||
case 0x3c:
|
||||
case 0x3d:
|
||||
case 0x3e:
|
||||
case 0x3f:
|
||||
case 0x40:
|
||||
case 0x5b:
|
||||
case 0x5c:
|
||||
case 0x5d:
|
||||
case 0x7b:
|
||||
case 0x7d:
|
||||
// DQUOTE and "(),/:;<=>?@[\]{}"
|
||||
return false
|
||||
default:
|
||||
// VCHAR %x21-7E
|
||||
return c >= 0x21 && c <= 0x7e
|
||||
}
|
||||
}
|
||||
|
||||
// Parsed accordingly to RFC 9110
|
||||
// https://www.rfc-editor.org/rfc/rfc9110#field.content-range
|
||||
/**
|
||||
* @param {string} characters
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidHTTPToken (characters) {
|
||||
if (characters.length === 0) {
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < characters.length; ++i) {
|
||||
if (!isTokenCharCode(characters.charCodeAt(i))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// headerCharRegex have been lifted from
|
||||
// https://github.com/nodejs/node/blob/main/lib/_http_common.js
|
||||
|
||||
/**
|
||||
* Matches if val contains an invalid field-vchar
|
||||
* field-value = *( field-content / obs-fold )
|
||||
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
||||
* field-vchar = VCHAR / obs-text
|
||||
*/
|
||||
const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
|
||||
|
||||
/**
|
||||
* @param {string} characters
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidHeaderValue (characters) {
|
||||
return !headerCharRegex.test(characters)
|
||||
}
|
||||
|
||||
const rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+)?$/
|
||||
|
||||
/**
|
||||
* @typedef {object} RangeHeader
|
||||
* @property {number} start
|
||||
* @property {number | null} end
|
||||
* @property {number | null} size
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse accordingly to RFC 9110
|
||||
* @see https://www.rfc-editor.org/rfc/rfc9110#field.content-range
|
||||
* @param {string} [range]
|
||||
* @returns {RangeHeader|null}
|
||||
*/
|
||||
function parseRangeHeader (range) {
|
||||
if (range == null || range === '') return { start: 0, end: null, size: null }
|
||||
|
||||
const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null
|
||||
const m = range ? range.match(rangeHeaderRegex) : null
|
||||
return m
|
||||
? {
|
||||
start: parseInt(m[1]),
|
||||
@ -480,17 +794,82 @@ function parseRangeHeader (range) {
|
||||
: null
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {import("events").EventEmitter} T
|
||||
* @param {T} obj
|
||||
* @param {string} name
|
||||
* @param {(...args: any[]) => void} listener
|
||||
* @returns {T}
|
||||
*/
|
||||
function addListener (obj, name, listener) {
|
||||
const listeners = (obj[kListeners] ??= [])
|
||||
listeners.push([name, listener])
|
||||
obj.on(name, listener)
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {import("events").EventEmitter} T
|
||||
* @param {T} obj
|
||||
* @returns {T}
|
||||
*/
|
||||
function removeAllListeners (obj) {
|
||||
if (obj[kListeners] != null) {
|
||||
for (const [name, listener] of obj[kListeners]) {
|
||||
obj.removeListener(name, listener)
|
||||
}
|
||||
obj[kListeners] = null
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import ('../dispatcher/client')} client
|
||||
* @param {import ('../core/request')} request
|
||||
* @param {Error} err
|
||||
*/
|
||||
function errorRequest (client, request, err) {
|
||||
try {
|
||||
request.onError(err)
|
||||
assert(request.aborted)
|
||||
} catch (err) {
|
||||
client.emit('error', err)
|
||||
}
|
||||
}
|
||||
|
||||
const kEnumerableProperty = Object.create(null)
|
||||
kEnumerableProperty.enumerable = true
|
||||
|
||||
const normalizedMethodRecordsBase = {
|
||||
delete: 'DELETE',
|
||||
DELETE: 'DELETE',
|
||||
get: 'GET',
|
||||
GET: 'GET',
|
||||
head: 'HEAD',
|
||||
HEAD: 'HEAD',
|
||||
options: 'OPTIONS',
|
||||
OPTIONS: 'OPTIONS',
|
||||
post: 'POST',
|
||||
POST: 'POST',
|
||||
put: 'PUT',
|
||||
PUT: 'PUT'
|
||||
}
|
||||
|
||||
const normalizedMethodRecords = {
|
||||
...normalizedMethodRecordsBase,
|
||||
patch: 'patch',
|
||||
PATCH: 'PATCH'
|
||||
}
|
||||
|
||||
// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
|
||||
Object.setPrototypeOf(normalizedMethodRecordsBase, null)
|
||||
Object.setPrototypeOf(normalizedMethodRecords, null)
|
||||
|
||||
module.exports = {
|
||||
kEnumerableProperty,
|
||||
nop,
|
||||
isDisturbed,
|
||||
isErrored,
|
||||
isReadable,
|
||||
toUSVString,
|
||||
isReadableAborted,
|
||||
isUSVString,
|
||||
isBlobLike,
|
||||
parseOrigin,
|
||||
parseURL,
|
||||
@ -500,7 +879,12 @@ module.exports = {
|
||||
isAsyncIterable,
|
||||
isDestroyed,
|
||||
headerNameToString,
|
||||
bufferToLowerCasedHeaderName,
|
||||
addListener,
|
||||
removeAllListeners,
|
||||
errorRequest,
|
||||
parseRawHeaders,
|
||||
encodeRawHeaders,
|
||||
parseHeaders,
|
||||
parseKeepAliveTimeout,
|
||||
destroy,
|
||||
@ -508,15 +892,21 @@ module.exports = {
|
||||
deepClone,
|
||||
ReadableStreamFrom,
|
||||
isBuffer,
|
||||
validateHandler,
|
||||
assertRequestHandler,
|
||||
getSocketInfo,
|
||||
isFormDataLike,
|
||||
buildURL,
|
||||
throwIfAborted,
|
||||
serializePathWithQuery,
|
||||
addAbortListener,
|
||||
isValidHTTPToken,
|
||||
isValidHeaderValue,
|
||||
isTokenCharCode,
|
||||
parseRangeHeader,
|
||||
normalizedMethodRecordsBase,
|
||||
normalizedMethodRecords,
|
||||
isValidPort,
|
||||
isHttpOrHttpsPrefixed,
|
||||
nodeMajor,
|
||||
nodeMinor,
|
||||
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13),
|
||||
safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE']
|
||||
safeHTTPMethods: Object.freeze(['GET', 'HEAD', 'OPTIONS', 'TRACE']),
|
||||
wrapRequestBody
|
||||
}
|
||||
|
Reference in New Issue
Block a user