aboutsummaryrefslogtreecommitdiffstats
path: root/vanilla/node_modules/undici/lib/core
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-13 21:34:48 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-13 21:34:48 -0800
commit76cb9c2a39d477a64824a985ade40507e3bbade1 (patch)
tree41e997aa9c6f538d3a136af61dae9424db2005a9 /vanilla/node_modules/undici/lib/core
parent819a39a21ac992b1393244a4c283bbb125208c69 (diff)
downloadneko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.gz
neko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.bz2
neko-76cb9c2a39d477a64824a985ade40507e3bbade1.zip
feat(vanilla): add testing infrastructure and tests (NK-wjnczv)
Diffstat (limited to 'vanilla/node_modules/undici/lib/core')
-rw-r--r--vanilla/node_modules/undici/lib/core/connect.js137
-rw-r--r--vanilla/node_modules/undici/lib/core/constants.js143
-rw-r--r--vanilla/node_modules/undici/lib/core/diagnostics.js225
-rw-r--r--vanilla/node_modules/undici/lib/core/errors.js448
-rw-r--r--vanilla/node_modules/undici/lib/core/request.js412
-rw-r--r--vanilla/node_modules/undici/lib/core/symbols.js74
-rw-r--r--vanilla/node_modules/undici/lib/core/tree.js160
-rw-r--r--vanilla/node_modules/undici/lib/core/util.js957
8 files changed, 2556 insertions, 0 deletions
diff --git a/vanilla/node_modules/undici/lib/core/connect.js b/vanilla/node_modules/undici/lib/core/connect.js
new file mode 100644
index 0000000..a49af91
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/core/connect.js
@@ -0,0 +1,137 @@
+'use strict'
+
+const net = require('node:net')
+const assert = require('node:assert')
+const util = require('./util')
+const { InvalidArgumentError } = 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.
+
+const SessionCache = class WeakSessionCache {
+ constructor (maxCachedSessions) {
+ this._maxCachedSessions = maxCachedSessions
+ this._sessionCache = new Map()
+ this._sessionRegistry = new FinalizationRegistry((key) => {
+ if (this._sessionCache.size < this._maxCachedSessions) {
+ return
+ }
+
+ const ref = this._sessionCache.get(key)
+ if (ref !== undefined && ref.deref() === undefined) {
+ this._sessionCache.delete(key)
+ }
+ })
+ }
+
+ get (sessionKey) {
+ const ref = this._sessionCache.get(sessionKey)
+ return ref ? ref.deref() : null
+ }
+
+ set (sessionKey, session) {
+ if (this._maxCachedSessions === 0) {
+ return
+ }
+
+ this._sessionCache.set(sessionKey, new WeakRef(session))
+ this._sessionRegistry.register(session, sessionKey)
+ }
+}
+
+function buildConnector ({ allowH2, useH2c, 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')
+ }
+
+ const options = { path: socketPath, ...opts }
+ const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions)
+ timeout = timeout == null ? 10e3 : timeout
+ allowH2 = allowH2 != null ? allowH2 : false
+ return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) {
+ let socket
+ if (protocol === 'https:') {
+ if (!tls) {
+ tls = require('node:tls')
+ }
+ servername = servername || options.servername || util.getServerName(host) || null
+
+ const sessionKey = servername || hostname
+ 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,
+ servername,
+ session,
+ localAddress,
+ ALPNProtocols: allowH2 ? ['http/1.1', 'h2'] : ['http/1.1'],
+ socket: httpSocket, // upgrade socket connection
+ port,
+ host: hostname
+ })
+
+ socket
+ .on('session', function (session) {
+ // TODO (fix): Can a session become invalid once established? Don't think so?
+ sessionCache.set(sessionKey, session)
+ })
+ } 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,
+ host: hostname
+ })
+ if (useH2c === true) {
+ socket.alpnProtocol = 'h2'
+ }
+ }
+
+ // Set TCP keep alive options on the socket here instead of in connect() for the case of assigning the socket
+ if (options.keepAlive == null || options.keepAlive) {
+ const keepAliveInitialDelay = options.keepAliveInitialDelay === undefined ? 60e3 : options.keepAliveInitialDelay
+ socket.setKeepAlive(true, keepAliveInitialDelay)
+ }
+
+ const clearConnectTimeout = util.setupConnectTimeout(new WeakRef(socket), { timeout, hostname, port })
+
+ socket
+ .setNoDelay(true)
+ .once(protocol === 'https:' ? 'secureConnect' : 'connect', function () {
+ queueMicrotask(clearConnectTimeout)
+
+ if (callback) {
+ const cb = callback
+ callback = null
+ cb(null, this)
+ }
+ })
+ .on('error', function (err) {
+ queueMicrotask(clearConnectTimeout)
+
+ if (callback) {
+ const cb = callback
+ callback = null
+ cb(err)
+ }
+ })
+
+ return socket
+ }
+}
+
+module.exports = buildConnector
diff --git a/vanilla/node_modules/undici/lib/core/constants.js b/vanilla/node_modules/undici/lib/core/constants.js
new file mode 100644
index 0000000..088cf47
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/core/constants.js
@@ -0,0 +1,143 @@
+'use strict'
+
+/**
+ * @see https://developer.mozilla.org/docs/Web/HTTP/Headers
+ */
+const wellknownHeaderNames = /** @type {const} */ ([
+ 'Accept',
+ 'Accept-Encoding',
+ 'Accept-Language',
+ 'Accept-Ranges',
+ 'Access-Control-Allow-Credentials',
+ 'Access-Control-Allow-Headers',
+ 'Access-Control-Allow-Methods',
+ 'Access-Control-Allow-Origin',
+ 'Access-Control-Expose-Headers',
+ 'Access-Control-Max-Age',
+ 'Access-Control-Request-Headers',
+ 'Access-Control-Request-Method',
+ 'Age',
+ 'Allow',
+ 'Alt-Svc',
+ 'Alt-Used',
+ 'Authorization',
+ 'Cache-Control',
+ 'Clear-Site-Data',
+ 'Connection',
+ 'Content-Disposition',
+ 'Content-Encoding',
+ 'Content-Language',
+ 'Content-Length',
+ 'Content-Location',
+ 'Content-Range',
+ 'Content-Security-Policy',
+ 'Content-Security-Policy-Report-Only',
+ 'Content-Type',
+ 'Cookie',
+ 'Cross-Origin-Embedder-Policy',
+ 'Cross-Origin-Opener-Policy',
+ 'Cross-Origin-Resource-Policy',
+ 'Date',
+ 'Device-Memory',
+ 'Downlink',
+ 'ECT',
+ 'ETag',
+ 'Expect',
+ 'Expect-CT',
+ 'Expires',
+ 'Forwarded',
+ 'From',
+ 'Host',
+ 'If-Match',
+ 'If-Modified-Since',
+ 'If-None-Match',
+ 'If-Range',
+ 'If-Unmodified-Since',
+ 'Keep-Alive',
+ 'Last-Modified',
+ 'Link',
+ 'Location',
+ 'Max-Forwards',
+ 'Origin',
+ 'Permissions-Policy',
+ 'Pragma',
+ 'Proxy-Authenticate',
+ 'Proxy-Authorization',
+ 'RTT',
+ 'Range',
+ 'Referer',
+ 'Referrer-Policy',
+ 'Refresh',
+ 'Retry-After',
+ 'Sec-WebSocket-Accept',
+ 'Sec-WebSocket-Extensions',
+ 'Sec-WebSocket-Key',
+ 'Sec-WebSocket-Protocol',
+ 'Sec-WebSocket-Version',
+ 'Server',
+ 'Server-Timing',
+ 'Service-Worker-Allowed',
+ 'Service-Worker-Navigation-Preload',
+ 'Set-Cookie',
+ 'SourceMap',
+ 'Strict-Transport-Security',
+ 'Supports-Loading-Mode',
+ 'TE',
+ 'Timing-Allow-Origin',
+ 'Trailer',
+ 'Transfer-Encoding',
+ 'Upgrade',
+ 'Upgrade-Insecure-Requests',
+ 'User-Agent',
+ 'Vary',
+ 'Via',
+ 'WWW-Authenticate',
+ 'X-Content-Type-Options',
+ 'X-DNS-Prefetch-Control',
+ 'X-Frame-Options',
+ 'X-Permitted-Cross-Domain-Policies',
+ '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]
+ const lowerCasedKey = key.toLowerCase()
+ headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] =
+ lowerCasedKey
+}
+
+module.exports = {
+ wellknownHeaderNames,
+ headerNameLowerCasedRecord,
+ getHeaderNameAsBuffer
+}
diff --git a/vanilla/node_modules/undici/lib/core/diagnostics.js b/vanilla/node_modules/undici/lib/core/diagnostics.js
new file mode 100644
index 0000000..ccd6870
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/core/diagnostics.js
@@ -0,0 +1,225 @@
+'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'),
+ bodyChunkSent: diagnosticsChannel.channel('undici:request:bodyChunkSent'),
+ bodyChunkReceived: diagnosticsChannel.channel('undici:request:bodyChunkReceived'),
+ 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'),
+ // ProxyAgent
+ proxyConnected: diagnosticsChannel.channel('undici:proxy:connected')
+}
+
+let isTrackingClientEvents = false
+
+function trackClientEvents (debugLog = undiciDebugLog) {
+ if (isTrackingClientEvents) {
+ return
+ }
+
+ // Check if any of the channels already have subscribers to prevent duplicate subscriptions
+ // This can happen when both Node.js built-in undici and undici as a dependency are present
+ if (channels.beforeConnect.hasSubscribers || channels.connected.hasSubscribers ||
+ channels.connectError.hasSubscribers || channels.sendHeaders.hasSubscribers) {
+ isTrackingClientEvents = true
+ 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
+ }
+
+ // Check if any of the channels already have subscribers to prevent duplicate subscriptions
+ // This can happen when both Node.js built-in undici and undici as a dependency are present
+ if (channels.headers.hasSubscribers || channels.trailers.hasSubscribers ||
+ channels.error.hasSubscribers) {
+ isTrackingRequestEvents = true
+ 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
+ }
+
+ // Check if any of the channels already have subscribers to prevent duplicate subscriptions
+ // This can happen when both Node.js built-in undici and undici as a dependency are present
+ if (channels.open.hasSubscribers || channels.close.hasSubscribers ||
+ channels.socketError.hasSubscribers || channels.ping.hasSubscribers ||
+ channels.pong.hasSubscribers) {
+ isTrackingWebSocketEvents = true
+ 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
+}
diff --git a/vanilla/node_modules/undici/lib/core/errors.js b/vanilla/node_modules/undici/lib/core/errors.js
new file mode 100644
index 0000000..4b1a8a1
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/core/errors.js
@@ -0,0 +1,448 @@
+'use strict'
+
+const kUndiciError = Symbol.for('undici.error.UND_ERR')
+class UndiciError extends Error {
+ constructor (message, options) {
+ super(message, options)
+ this.name = 'UndiciError'
+ this.code = 'UND_ERR'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kUndiciError] === true
+ }
+
+ get [kUndiciError] () {
+ return true
+ }
+}
+
+const kConnectTimeoutError = Symbol.for('undici.error.UND_ERR_CONNECT_TIMEOUT')
+class ConnectTimeoutError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'ConnectTimeoutError'
+ this.message = message || 'Connect Timeout Error'
+ this.code = 'UND_ERR_CONNECT_TIMEOUT'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kConnectTimeoutError] === true
+ }
+
+ get [kConnectTimeoutError] () {
+ return true
+ }
+}
+
+const kHeadersTimeoutError = Symbol.for('undici.error.UND_ERR_HEADERS_TIMEOUT')
+class HeadersTimeoutError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'HeadersTimeoutError'
+ this.message = message || 'Headers Timeout Error'
+ this.code = 'UND_ERR_HEADERS_TIMEOUT'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kHeadersTimeoutError] === true
+ }
+
+ get [kHeadersTimeoutError] () {
+ return true
+ }
+}
+
+const kHeadersOverflowError = Symbol.for('undici.error.UND_ERR_HEADERS_OVERFLOW')
+class HeadersOverflowError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'HeadersOverflowError'
+ this.message = message || 'Headers Overflow Error'
+ this.code = 'UND_ERR_HEADERS_OVERFLOW'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kHeadersOverflowError] === true
+ }
+
+ get [kHeadersOverflowError] () {
+ return true
+ }
+}
+
+const kBodyTimeoutError = Symbol.for('undici.error.UND_ERR_BODY_TIMEOUT')
+class BodyTimeoutError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'BodyTimeoutError'
+ this.message = message || 'Body Timeout Error'
+ this.code = 'UND_ERR_BODY_TIMEOUT'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kBodyTimeoutError] === true
+ }
+
+ get [kBodyTimeoutError] () {
+ return true
+ }
+}
+
+const kInvalidArgumentError = Symbol.for('undici.error.UND_ERR_INVALID_ARG')
+class InvalidArgumentError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'InvalidArgumentError'
+ this.message = message || 'Invalid Argument Error'
+ this.code = 'UND_ERR_INVALID_ARG'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kInvalidArgumentError] === true
+ }
+
+ get [kInvalidArgumentError] () {
+ return true
+ }
+}
+
+const kInvalidReturnValueError = Symbol.for('undici.error.UND_ERR_INVALID_RETURN_VALUE')
+class InvalidReturnValueError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'InvalidReturnValueError'
+ this.message = message || 'Invalid Return Value Error'
+ this.code = 'UND_ERR_INVALID_RETURN_VALUE'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kInvalidReturnValueError] === true
+ }
+
+ get [kInvalidReturnValueError] () {
+ return true
+ }
+}
+
+const kAbortError = Symbol.for('undici.error.UND_ERR_ABORT')
+class AbortError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'AbortError'
+ this.message = message || 'The operation was aborted'
+ this.code = 'UND_ERR_ABORT'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kAbortError] === true
+ }
+
+ get [kAbortError] () {
+ return true
+ }
+}
+
+const kRequestAbortedError = Symbol.for('undici.error.UND_ERR_ABORTED')
+class RequestAbortedError extends AbortError {
+ constructor (message) {
+ super(message)
+ this.name = 'AbortError'
+ this.message = message || 'Request aborted'
+ this.code = 'UND_ERR_ABORTED'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kRequestAbortedError] === true
+ }
+
+ get [kRequestAbortedError] () {
+ return true
+ }
+}
+
+const kInformationalError = Symbol.for('undici.error.UND_ERR_INFO')
+class InformationalError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'InformationalError'
+ this.message = message || 'Request information'
+ this.code = 'UND_ERR_INFO'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kInformationalError] === true
+ }
+
+ get [kInformationalError] () {
+ return true
+ }
+}
+
+const kRequestContentLengthMismatchError = Symbol.for('undici.error.UND_ERR_REQ_CONTENT_LENGTH_MISMATCH')
+class RequestContentLengthMismatchError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'RequestContentLengthMismatchError'
+ this.message = message || 'Request body length does not match content-length header'
+ this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kRequestContentLengthMismatchError] === true
+ }
+
+ get [kRequestContentLengthMismatchError] () {
+ return true
+ }
+}
+
+const kResponseContentLengthMismatchError = Symbol.for('undici.error.UND_ERR_RES_CONTENT_LENGTH_MISMATCH')
+class ResponseContentLengthMismatchError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'ResponseContentLengthMismatchError'
+ this.message = message || 'Response body length does not match content-length header'
+ this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kResponseContentLengthMismatchError] === true
+ }
+
+ get [kResponseContentLengthMismatchError] () {
+ return true
+ }
+}
+
+const kClientDestroyedError = Symbol.for('undici.error.UND_ERR_DESTROYED')
+class ClientDestroyedError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'ClientDestroyedError'
+ this.message = message || 'The client is destroyed'
+ this.code = 'UND_ERR_DESTROYED'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kClientDestroyedError] === true
+ }
+
+ get [kClientDestroyedError] () {
+ return true
+ }
+}
+
+const kClientClosedError = Symbol.for('undici.error.UND_ERR_CLOSED')
+class ClientClosedError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'ClientClosedError'
+ this.message = message || 'The client is closed'
+ this.code = 'UND_ERR_CLOSED'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kClientClosedError] === true
+ }
+
+ get [kClientClosedError] () {
+ return true
+ }
+}
+
+const kSocketError = Symbol.for('undici.error.UND_ERR_SOCKET')
+class SocketError extends UndiciError {
+ constructor (message, socket) {
+ super(message)
+ this.name = 'SocketError'
+ this.message = message || 'Socket error'
+ this.code = 'UND_ERR_SOCKET'
+ this.socket = socket
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kSocketError] === true
+ }
+
+ get [kSocketError] () {
+ return true
+ }
+}
+
+const kNotSupportedError = Symbol.for('undici.error.UND_ERR_NOT_SUPPORTED')
+class NotSupportedError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'NotSupportedError'
+ this.message = message || 'Not supported error'
+ this.code = 'UND_ERR_NOT_SUPPORTED'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kNotSupportedError] === true
+ }
+
+ get [kNotSupportedError] () {
+ return true
+ }
+}
+
+const kBalancedPoolMissingUpstreamError = Symbol.for('undici.error.UND_ERR_BPL_MISSING_UPSTREAM')
+class BalancedPoolMissingUpstreamError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'MissingUpstreamError'
+ this.message = message || 'No upstream has been added to the BalancedPool'
+ this.code = 'UND_ERR_BPL_MISSING_UPSTREAM'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kBalancedPoolMissingUpstreamError] === true
+ }
+
+ get [kBalancedPoolMissingUpstreamError] () {
+ return true
+ }
+}
+
+const kHTTPParserError = Symbol.for('undici.error.UND_ERR_HTTP_PARSER')
+class HTTPParserError extends Error {
+ constructor (message, code, data) {
+ super(message)
+ this.name = 'HTTPParserError'
+ this.code = code ? `HPE_${code}` : undefined
+ this.data = data ? data.toString() : undefined
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kHTTPParserError] === true
+ }
+
+ get [kHTTPParserError] () {
+ return true
+ }
+}
+
+const kResponseExceededMaxSizeError = Symbol.for('undici.error.UND_ERR_RES_EXCEEDED_MAX_SIZE')
+class ResponseExceededMaxSizeError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'ResponseExceededMaxSizeError'
+ this.message = message || 'Response content exceeded max size'
+ this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kResponseExceededMaxSizeError] === true
+ }
+
+ get [kResponseExceededMaxSizeError] () {
+ return true
+ }
+}
+
+const kRequestRetryError = Symbol.for('undici.error.UND_ERR_REQ_RETRY')
+class RequestRetryError extends UndiciError {
+ constructor (message, code, { headers, data }) {
+ super(message)
+ this.name = 'RequestRetryError'
+ this.message = message || 'Request retry error'
+ this.code = 'UND_ERR_REQ_RETRY'
+ this.statusCode = code
+ this.data = data
+ this.headers = headers
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kRequestRetryError] === true
+ }
+
+ get [kRequestRetryError] () {
+ return true
+ }
+}
+
+const kResponseError = Symbol.for('undici.error.UND_ERR_RESPONSE')
+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
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kResponseError] === true
+ }
+
+ get [kResponseError] () {
+ return true
+ }
+}
+
+const kSecureProxyConnectionError = Symbol.for('undici.error.UND_ERR_PRX_TLS')
+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
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kSecureProxyConnectionError] === true
+ }
+
+ get [kSecureProxyConnectionError] () {
+ return true
+ }
+}
+
+const kMaxOriginsReachedError = Symbol.for('undici.error.UND_ERR_MAX_ORIGINS_REACHED')
+class MaxOriginsReachedError extends UndiciError {
+ constructor (message) {
+ super(message)
+ this.name = 'MaxOriginsReachedError'
+ this.message = message || 'Maximum allowed origins reached'
+ this.code = 'UND_ERR_MAX_ORIGINS_REACHED'
+ }
+
+ static [Symbol.hasInstance] (instance) {
+ return instance && instance[kMaxOriginsReachedError] === true
+ }
+
+ get [kMaxOriginsReachedError] () {
+ return true
+ }
+}
+
+module.exports = {
+ AbortError,
+ HTTPParserError,
+ UndiciError,
+ HeadersTimeoutError,
+ HeadersOverflowError,
+ BodyTimeoutError,
+ RequestContentLengthMismatchError,
+ ConnectTimeoutError,
+ InvalidArgumentError,
+ InvalidReturnValueError,
+ RequestAbortedError,
+ ClientDestroyedError,
+ ClientClosedError,
+ InformationalError,
+ SocketError,
+ NotSupportedError,
+ ResponseContentLengthMismatchError,
+ BalancedPoolMissingUpstreamError,
+ ResponseExceededMaxSizeError,
+ RequestRetryError,
+ ResponseError,
+ SecureProxyConnectionError,
+ MaxOriginsReachedError
+}
diff --git a/vanilla/node_modules/undici/lib/core/request.js b/vanilla/node_modules/undici/lib/core/request.js
new file mode 100644
index 0000000..7dbf781
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/core/request.js
@@ -0,0 +1,412 @@
+'use strict'
+
+const {
+ InvalidArgumentError,
+ NotSupportedError
+} = require('./errors')
+const assert = require('node:assert')
+const {
+ isValidHTTPToken,
+ isValidHeaderValue,
+ isStream,
+ destroy,
+ isBuffer,
+ isFormDataLike,
+ isIterable,
+ isBlobLike,
+ serializePathWithQuery,
+ assertRequestHandler,
+ getServerName,
+ normalizedMethodRecords,
+ getProtocolFromUrlString
+} = 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')
+
+class Request {
+ constructor (origin, {
+ path,
+ method,
+ body,
+ headers,
+ query,
+ idempotent,
+ blocking,
+ upgrade,
+ headersTimeout,
+ bodyTimeout,
+ reset,
+ expectContinue,
+ servername,
+ throwOnError,
+ maxRedirections
+ }, 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')
+ } 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 (normalizedMethodRecords[method] === undefined && !isValidHTTPToken(method)) {
+ throw new InvalidArgumentError('invalid request method')
+ }
+
+ 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')
+ }
+
+ if (reset != null && typeof reset !== 'boolean') {
+ throw new InvalidArgumentError('invalid reset')
+ }
+
+ if (expectContinue != null && typeof expectContinue !== 'boolean') {
+ throw new InvalidArgumentError('invalid expectContinue')
+ }
+
+ if (throwOnError != null) {
+ throw new InvalidArgumentError('invalid throwOnError')
+ }
+
+ if (maxRedirections != null && maxRedirections !== 0) {
+ throw new InvalidArgumentError('maxRedirections is not supported, use the redirect interceptor')
+ }
+
+ this.headersTimeout = headersTimeout
+
+ this.bodyTimeout = bodyTimeout
+
+ this.method = method
+
+ this.abort = null
+
+ if (body == null) {
+ this.body = null
+ } else if (isStream(body)) {
+ this.body = body
+
+ const rState = this.body._readableState
+ if (!rState || !rState.autoDestroy) {
+ this.endHandler = function autoDestroy () {
+ destroy(this)
+ }
+ this.body.on('end', this.endHandler)
+ }
+
+ this.errorHandler = err => {
+ if (this.abort) {
+ this.abort(err)
+ } else {
+ this.error = err
+ }
+ }
+ this.body.on('error', this.errorHandler)
+ } 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
+ } 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 (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 ? serializePathWithQuery(path, query) : path
+
+ // TODO: shall we maybe standardize it to an URL object?
+ this.origin = origin
+
+ this.protocol = getProtocolFromUrlString(origin)
+
+ this.idempotent = idempotent == null
+ ? method === 'HEAD' || method === 'GET'
+ : idempotent
+
+ this.blocking = blocking ?? this.method !== 'HEAD'
+
+ this.reset = reset == null ? null : reset
+
+ this.host = null
+
+ this.contentLength = null
+
+ this.contentType = null
+
+ this.headers = []
+
+ // Only for H2
+ this.expectContinue = expectContinue != null ? expectContinue : false
+
+ 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') {
+ 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')
+ }
+
+ assertRequestHandler(handler, method, upgrade)
+
+ this.servername = servername || getServerName(this.host) || null
+
+ this[kHandler] = handler
+
+ if (channels.create.hasSubscribers) {
+ channels.create.publish({ request: this })
+ }
+ }
+
+ onBodySent (chunk) {
+ if (channels.bodyChunkSent.hasSubscribers) {
+ channels.bodyChunkSent.publish({ request: this, chunk })
+ }
+ if (this[kHandler].onBodySent) {
+ try {
+ return this[kHandler].onBodySent(chunk)
+ } catch (err) {
+ this.abort(err)
+ }
+ }
+ }
+
+ onRequestSent () {
+ if (channels.bodySent.hasSubscribers) {
+ channels.bodySent.publish({ request: this })
+ }
+
+ if (this[kHandler].onRequestSent) {
+ try {
+ return this[kHandler].onRequestSent()
+ } catch (err) {
+ this.abort(err)
+ }
+ }
+ }
+
+ onConnect (abort) {
+ assert(!this.aborted)
+ assert(!this.completed)
+
+ if (this.error) {
+ abort(this.error)
+ } else {
+ this.abort = abort
+ return this[kHandler].onConnect(abort)
+ }
+ }
+
+ onResponseStarted () {
+ return this[kHandler].onResponseStarted?.()
+ }
+
+ onHeaders (statusCode, headers, resume, statusText) {
+ assert(!this.aborted)
+ assert(!this.completed)
+
+ if (channels.headers.hasSubscribers) {
+ channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
+ }
+
+ try {
+ return this[kHandler].onHeaders(statusCode, headers, resume, statusText)
+ } catch (err) {
+ this.abort(err)
+ }
+ }
+
+ onData (chunk) {
+ assert(!this.aborted)
+ assert(!this.completed)
+
+ if (channels.bodyChunkReceived.hasSubscribers) {
+ channels.bodyChunkReceived.publish({ request: this, chunk })
+ }
+ try {
+ return this[kHandler].onData(chunk)
+ } catch (err) {
+ this.abort(err)
+ return false
+ }
+ }
+
+ onUpgrade (statusCode, headers, socket) {
+ assert(!this.aborted)
+ assert(!this.completed)
+
+ return this[kHandler].onUpgrade(statusCode, headers, socket)
+ }
+
+ onComplete (trailers) {
+ this.onFinally()
+
+ assert(!this.aborted)
+ assert(!this.completed)
+
+ this.completed = true
+ if (channels.trailers.hasSubscribers) {
+ channels.trailers.publish({ request: this, trailers })
+ }
+
+ try {
+ return this[kHandler].onComplete(trailers)
+ } catch (err) {
+ // TODO (fix): This might be a bad idea?
+ this.onError(err)
+ }
+ }
+
+ onError (error) {
+ this.onFinally()
+
+ if (channels.error.hasSubscribers) {
+ channels.error.publish({ request: this, error })
+ }
+
+ if (this.aborted) {
+ return
+ }
+ this.aborted = true
+
+ return this[kHandler].onError(error)
+ }
+
+ onFinally () {
+ if (this.errorHandler) {
+ this.body.off('error', this.errorHandler)
+ this.errorHandler = null
+ }
+
+ if (this.endHandler) {
+ this.body.off('end', this.endHandler)
+ this.endHandler = null
+ }
+ }
+
+ addHeader (key, value) {
+ processHeader(this, key, value)
+ return this
+ }
+}
+
+function processHeader (request, key, val) {
+ if (val && (typeof val === 'object' && !Array.isArray(val))) {
+ throw new InvalidArgumentError(`invalid ${key} header`)
+ } else if (val === undefined) {
+ return
+ }
+
+ 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 && 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 && headerName === 'content-type') {
+ request.contentType = val
+ 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')
+ }
+
+ if (value === 'close') {
+ request.reset = true
+ }
+ } else if (headerName === 'expect') {
+ throw new NotSupportedError('expect header not supported')
+ } else {
+ request.headers.push(key, val)
+ }
+}
+
+module.exports = Request
diff --git a/vanilla/node_modules/undici/lib/core/symbols.js b/vanilla/node_modules/undici/lib/core/symbols.js
new file mode 100644
index 0000000..fd7af0c
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/core/symbols.js
@@ -0,0 +1,74 @@
+'use strict'
+
+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'),
+ 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'),
+ kLocalAddress: Symbol('local address'),
+ kHost: Symbol('host'),
+ kNoRef: Symbol('no ref'),
+ kBodyUsed: Symbol('used'),
+ kBody: Symbol('abstracted request body'),
+ 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.for('nodejs.stream.destroyed'),
+ kResume: Symbol('resume'),
+ kOnError: Symbol('on error'),
+ 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('pipelining'),
+ 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'),
+ kMaxResponseSize: Symbol('max response size'),
+ kHTTP2Session: Symbol('http2Session'),
+ kHTTP2SessionState: Symbol('http2Session state'),
+ kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
+ kConstruct: Symbol('constructable'),
+ kListeners: Symbol('listeners'),
+ kHTTPContext: Symbol('http context'),
+ kMaxConcurrentStreams: Symbol('max concurrent streams'),
+ kHTTP2InitialWindowSize: Symbol('http2 initial window size'),
+ kHTTP2ConnectionWindowSize: Symbol('http2 connection window size'),
+ kEnableConnectProtocol: Symbol('http2session connect protocol'),
+ kRemoteSettings: Symbol('http2session remote settings'),
+ kHTTP2Stream: Symbol('http2session client stream'),
+ kPingInterval: Symbol('ping interval'),
+ kNoProxyAgent: Symbol('no proxy agent'),
+ kHttpProxyAgent: Symbol('http proxy agent'),
+ kHttpsProxyAgent: Symbol('https proxy agent')
+}
diff --git a/vanilla/node_modules/undici/lib/core/tree.js b/vanilla/node_modules/undici/lib/core/tree.js
new file mode 100644
index 0000000..6eed58a
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/core/tree.js
@@ -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
+ * @returns {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
+}
diff --git a/vanilla/node_modules/undici/lib/core/util.js b/vanilla/node_modules/undici/lib/core/util.js
new file mode 100644
index 0000000..be2c1a7
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/core/util.js
@@ -0,0 +1,957 @@
+'use strict'
+
+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 { stringify } = require('node:querystring')
+const { EventEmitter: EE } = require('node:events')
+const timers = require('../util/timers')
+const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
+const { headerNameLowerCasedRecord } = require('./constants')
+const { tree } = require('./tree')
+
+const [nodeMajor, nodeMinor] = process.versions.node.split('.', 2).map(v => Number(v))
+
+class BodyAsyncIterable {
+ constructor (body) {
+ this[kBody] = body
+ this[kBodyUsed] = false
+ }
+
+ async * [Symbol.asyncIterator] () {
+ assert(!this[kBodyUsed], 'disturbed')
+ this[kBodyUsed] = true
+ yield * this[kBody]
+ }
+}
+
+function noop () {}
+
+/**
+ * @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 && isFormDataLike(body)) {
+ return 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'
+}
+
+/**
+ * @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) {
+ 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')
+ )
+ }
+}
+
+/**
+ * @param {string} url The path to check for query strings or fragments.
+ * @returns {boolean} Returns true if the path contains a query string or fragment.
+ */
+function pathHasQueryOrFragment (url) {
+ return (
+ url.includes('?') ||
+ url.includes('#')
+ )
+}
+
+/**
+ * @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 (pathHasQueryOrFragment(url)) {
+ throw new Error('Query params cannot be passed when url already contains "?" or "#".')
+ }
+
+ const stringified = stringify(queryParams)
+
+ if (stringified) {
+ url += '?' + stringified
+ }
+
+ 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 (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
+ throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
+ }
+
+ return url
+ }
+
+ if (!url || typeof url !== 'object') {
+ throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.')
+ }
+
+ if (!(url instanceof URL)) {
+ 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.')
+ }
+
+ if (url.path != null && typeof url.path !== 'string') {
+ throw new InvalidArgumentError('Invalid URL path: the path must be a string or null/undefined.')
+ }
+
+ if (url.pathname != null && typeof url.pathname !== 'string') {
+ throw new InvalidArgumentError('Invalid URL pathname: the pathname must be a string or null/undefined.')
+ }
+
+ if (url.hostname != null && typeof url.hostname !== 'string') {
+ throw new InvalidArgumentError('Invalid URL hostname: the hostname must be a string or null/undefined.')
+ }
+
+ if (url.origin != null && typeof url.origin !== 'string') {
+ 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}`
+ let path = url.path != null
+ ? url.path
+ : `${url.pathname || ''}${url.search || ''}`
+
+ if (origin[origin.length - 1] === '/') {
+ origin = origin.slice(0, origin.length - 1)
+ }
+
+ 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.
+ 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)
+
+ if (url.pathname !== '/' || url.search || url.hash) {
+ throw new InvalidArgumentError('invalid url')
+ }
+
+ return url
+}
+
+/**
+ * @param {string} host
+ * @returns {string}
+ */
+function getHostname (host) {
+ if (host[0] === '[') {
+ const idx = host.indexOf(']')
+
+ assert(idx !== -1)
+ return host.substring(1, idx)
+ }
+
+ const idx = host.indexOf(':')
+ if (idx === -1) return host
+
+ return host.substring(0, idx)
+}
+
+/**
+ * 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(typeof host === 'string')
+
+ const servername = getHostname(host)
+ if (net.isIP(servername)) {
+ return ''
+ }
+
+ 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
+ } else if (isStream(body)) {
+ const state = body._readableState
+ return state && state.objectMode === false && 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
+}
+
+/**
+ * @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
+ }
+
+ 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) {
+ queueMicrotask(() => {
+ stream.emit('error', err)
+ })
+ }
+
+ if (stream.destroyed !== true) {
+ stream[kDestroyed] = true
+ }
+}
+
+const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/
+/**
+ * @param {string} val
+ * @returns {number | null}
+ */
+function parseKeepAliveTimeout (val) {
+ const m = val.match(KEEPALIVE_TIMEOUT_EXPR)
+ return m ? parseInt(m[1], 10) * 1000 : null
+}
+
+/**
+ * Retrieves a header name and returns its lowercase value.
+ * @param {string | Buffer} value Header name
+ * @returns {string}
+ */
+function headerNameToString (value) {
+ return typeof value === 'string'
+ ? headerNameLowerCasedRecord[value] ?? value.toLowerCase()
+ : tree.lookup(value) ?? value.toString('latin1').toLowerCase()
+}
+
+/**
+ * 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 = headerNameToString(headers[i])
+ let val = obj[key]
+
+ if (val) {
+ if (typeof val === 'string') {
+ val = [val]
+ obj[key] = val
+ }
+ val.push(headers[i + 1].toString('latin1'))
+ } else {
+ const headersValue = headers[i + 1]
+ if (typeof headersValue === 'string') {
+ obj[key] = headersValue
+ } else {
+ obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('latin1')) : headersValue.toString('latin1')
+ }
+ }
+ }
+
+ return obj
+}
+
+/**
+ * @param {Buffer[]} headers
+ * @returns {string[]}
+ */
+function parseRawHeaders (headers) {
+ const headersLength = headers.length
+ /**
+ * @type {string[]}
+ */
+ const ret = new Array(headersLength)
+
+ let key
+ let val
+
+ for (let n = 0; n < headersLength; n += 2) {
+ key = headers[n]
+ val = headers[n + 1]
+
+ typeof key !== 'string' && (key = key.toString())
+ typeof val !== 'string' && (val = val.toString('latin1'))
+
+ ret[n] = key
+ ret[n + 1] = val
+ }
+
+ 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)
+}
+
+/**
+ * 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')
+ }
+
+ 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.
+ * @param {import('node:stream').Readable} body
+ * @returns {boolean}
+ */
+function isDisturbed (body) {
+ // TODO (fix): Why is body[kBodyUsed] needed?
+ return !!(body && (stream.isDisturbed(body) || body[kBodyUsed]))
+}
+
+/**
+ * @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,
+ localPort: socket.localPort,
+ remoteAddress: socket.remoteAddress,
+ remotePort: socket.remotePort,
+ remoteFamily: socket.remoteFamily,
+ timeout: socket.timeout,
+ bytesWritten: socket.bytesWritten,
+ bytesRead: socket.bytesRead
+ }
+}
+
+/**
+ * @param {Iterable} iterable
+ * @returns {ReadableStream}
+ */
+function ReadableStreamFrom (iterable) {
+ // We cannot use ReadableStream.from here because it does not return a byte stream.
+
+ let iterator
+ return new ReadableStream(
+ {
+ start () {
+ iterator = iterable[Symbol.asyncIterator]()
+ },
+ pull (controller) {
+ return iterator.next().then(({ done, value }) => {
+ if (done) {
+ return queueMicrotask(() => {
+ controller.close()
+ controller.byobRequest?.respond(0)
+ })
+ } else {
+ const buf = Buffer.isBuffer(value) ? value : Buffer.from(value)
+ if (buf.byteLength) {
+ return controller.enqueue(new Uint8Array(buf))
+ } else {
+ return this.pull(controller)
+ }
+ }
+ })
+ },
+ cancel () {
+ return iterator.return()
+ },
+ type: 'bytes'
+ }
+ )
+}
+
+/**
+ * The object should be a FormData instance and contains all the required
+ * methods.
+ * @param {*} object
+ * @returns {object is FormData}
+ */
+function isFormDataLike (object) {
+ return (
+ object &&
+ typeof object === 'object' &&
+ typeof object.append === 'function' &&
+ typeof object.delete === 'function' &&
+ typeof object.get === 'function' &&
+ typeof object.getAll === 'function' &&
+ typeof object.has === 'function' &&
+ typeof object.set === 'function' &&
+ object[Symbol.toStringTag] === 'FormData'
+ )
+}
+
+function addAbortListener (signal, listener) {
+ if ('addEventListener' in signal) {
+ signal.addEventListener('abort', listener, { once: true })
+ return () => signal.removeEventListener('abort', listener)
+ }
+ signal.once('abort', listener)
+ return () => signal.removeListener('abort', listener)
+}
+
+const validTokenChars = new Uint8Array([
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31
+ 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32-47 (!"#$%&'()*+,-./)
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48-63 (0-9:;<=>?)
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64-79 (@A-O)
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80-95 (P-Z[\]^_)
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96-111 (`a-o)
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112-127 (p-z{|}~)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128-143
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 144-159
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160-175
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 176-191
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 192-207
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 208-223
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224-239
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240-255
+])
+
+/**
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.6
+ * @param {number} c
+ * @returns {boolean}
+ */
+function isTokenCharCode (c) {
+ return (validTokenChars[c] === 1)
+}
+
+const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/
+
+/**
+ * @param {string} characters
+ * @returns {boolean}
+ */
+function isValidHTTPToken (characters) {
+ if (characters.length >= 12) return tokenRegExp.test(characters)
+ if (characters.length === 0) return false
+
+ for (let i = 0; i < characters.length; i++) {
+ if (validTokenChars[characters.charCodeAt(i)] !== 1) {
+ 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(rangeHeaderRegex) : null
+ return m
+ ? {
+ start: parseInt(m[1]),
+ end: m[2] ? parseInt(m[2]) : null,
+ size: m[3] ? parseInt(m[3]) : null
+ }
+ : 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)
+ }
+}
+
+/**
+ * @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
+ }
+
+ 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)`
+
+ destroy(socket, new ConnectTimeoutError(message))
+}
+
+/**
+ * @param {string} urlString
+ * @returns {string}
+ */
+function getProtocolFromUrlString (urlString) {
+ if (
+ urlString[0] === 'h' &&
+ urlString[1] === 't' &&
+ urlString[2] === 't' &&
+ urlString[3] === 'p'
+ ) {
+ switch (urlString[4]) {
+ case ':':
+ return 'http:'
+ case 's':
+ if (urlString[5] === ':') {
+ return 'https:'
+ }
+ }
+ }
+ // fallback if none of the usual suspects
+ return urlString.slice(0, urlString.indexOf(':') + 1)
+}
+
+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,
+ isDisturbed,
+ isBlobLike,
+ parseOrigin,
+ parseURL,
+ getServerName,
+ isStream,
+ isIterable,
+ isAsyncIterable,
+ isDestroyed,
+ headerNameToString,
+ bufferToLowerCasedHeaderName,
+ addListener,
+ removeAllListeners,
+ errorRequest,
+ parseRawHeaders,
+ encodeRawHeaders,
+ parseHeaders,
+ parseKeepAliveTimeout,
+ destroy,
+ bodyLength,
+ deepClone,
+ ReadableStreamFrom,
+ isBuffer,
+ assertRequestHandler,
+ getSocketInfo,
+ isFormDataLike,
+ pathHasQueryOrFragment,
+ serializePathWithQuery,
+ addAbortListener,
+ isValidHTTPToken,
+ isValidHeaderValue,
+ isTokenCharCode,
+ parseRangeHeader,
+ normalizedMethodRecordsBase,
+ normalizedMethodRecords,
+ isValidPort,
+ isHttpOrHttpsPrefixed,
+ nodeMajor,
+ nodeMinor,
+ safeHTTPMethods: Object.freeze(['GET', 'HEAD', 'OPTIONS', 'TRACE']),
+ wrapRequestBody,
+ setupConnectTimeout,
+ getProtocolFromUrlString
+}