From bb85cd891366decfc83a372d1ead0135e1e0a7fd Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sun, 11 Dec 2022 00:27:37 +0000 Subject: [PATCH] deps: update undici to 5.14.0 --- deps/undici/src/docs/api/Connector.md | 4 +- deps/undici/src/lib/core/connect.js | 81 +++++++-- deps/undici/src/lib/fetch/body.js | 45 ++++- deps/undici/src/lib/fetch/file.js | 15 +- deps/undici/src/lib/fetch/formdata.js | 7 +- deps/undici/src/lib/fetch/headers.js | 32 ++-- deps/undici/src/lib/fetch/index.js | 44 +++-- deps/undici/src/lib/fetch/request.js | 2 +- deps/undici/src/lib/fetch/response.js | 2 +- deps/undici/src/lib/fetch/symbols.js | 3 +- deps/undici/src/lib/fetch/util.js | 9 +- deps/undici/src/lib/fileapi/filereader.js | 30 ++++ deps/undici/src/lib/fileapi/util.js | 12 +- deps/undici/src/package.json | 6 +- deps/undici/src/types/connector.d.ts | 4 +- deps/undici/undici.js | 200 ++++++++++++++++------ 16 files changed, 359 insertions(+), 137 deletions(-) diff --git a/deps/undici/src/docs/api/Connector.md b/deps/undici/src/docs/api/Connector.md index fe446b46e62735..7c966507e5fceb 100644 --- a/deps/undici/src/docs/api/Connector.md +++ b/deps/undici/src/docs/api/Connector.md @@ -24,8 +24,10 @@ Once you call `buildConnector`, it will return a connector function, which takes * **hostname** `string` (required) * **host** `string` (optional) * **protocol** `string` (required) -* **port** `number` (required) +* **port** `string` (required) * **servername** `string` (optional) +* **localAddress** `string | null` (optional) Local address the socket should connect from. +* **httpSocket** `Socket` (optional) Establish secure connection on a given socket rather than creating a new socket. It can only be sent on TLS update. ### Basic example diff --git a/deps/undici/src/lib/core/connect.js b/deps/undici/src/lib/core/connect.js index a73f3c32b9acc7..89561f16fbe0e4 100644 --- a/deps/undici/src/lib/core/connect.js +++ b/deps/undici/src/lib/core/connect.js @@ -4,6 +4,7 @@ const net = require('net') const assert = require('assert') const util = require('./util') const { InvalidArgumentError, ConnectTimeoutError } = require('./errors') + let tls // include tls conditionally since it is not always available // TODO: session re-use does not wait for the first @@ -11,15 +12,73 @@ let tls // include tls conditionally since it is not always available // resolve the same servername multiple times even when // re-use is enabled. +let SessionCache +if (global.FinalizationRegistry) { + SessionCache = class WeakSessionCache { + constructor (maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions + this._sessionCache = new Map() + this._sessionRegistry = new global.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) + } + } +} else { + SessionCache = class SimpleSessionCache { + constructor (maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions + this._sessionCache = new Map() + } + + get (sessionKey) { + return this._sessionCache.get(sessionKey) + } + + set (sessionKey, session) { + if (this._maxCachedSessions === 0) { + return + } + + if (this._sessionCache.size >= this._maxCachedSessions) { + // remove the oldest session + const { value: oldestKey } = this._sessionCache.keys().next() + this._sessionCache.delete(oldestKey) + } + + this._sessionCache.set(sessionKey, session) + } + } +} + function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) { if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) { throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero') } const options = { path: socketPath, ...opts } - const sessionCache = new Map() + const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions) timeout = timeout == null ? 10e3 : timeout - maxCachedSessions = maxCachedSessions == null ? 100 : maxCachedSessions return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) { let socket @@ -47,25 +106,9 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) { socket .on('session', function (session) { - // cache is disabled - if (maxCachedSessions === 0) { - return - } - - if (sessionCache.size >= maxCachedSessions) { - // remove the oldest session - const { value: oldestKey } = sessionCache.keys().next() - sessionCache.delete(oldestKey) - } - + // TODO (fix): Can a session become invalid once established? Don't think so? sessionCache.set(sessionKey, session) }) - .on('error', function (err) { - if (sessionKey && err.code !== 'UND_ERR_INFO') { - // TODO (fix): Only delete for session related errors. - sessionCache.delete(sessionKey) - } - }) } else { assert(!httpSocket, 'httpSocket can only be sent on TLS update') socket = net.connect({ diff --git a/deps/undici/src/lib/fetch/body.js b/deps/undici/src/lib/fetch/body.js index 130a0bc669bb40..920d873f2cb66a 100644 --- a/deps/undici/src/lib/fetch/body.js +++ b/deps/undici/src/lib/fetch/body.js @@ -7,17 +7,19 @@ const { FormData } = require('./formdata') const { kState } = require('./symbols') const { webidl } = require('./webidl') const { DOMException, structuredClone } = require('./constants') -const { Blob } = require('buffer') +const { Blob, File: NativeFile } = require('buffer') const { kBodyUsed } = require('../core/symbols') const assert = require('assert') const { isErrored } = require('../core/util') const { isUint8Array, isArrayBuffer } = require('util/types') -const { File } = require('./file') +const { File: UndiciFile } = require('./file') const { StringDecoder } = require('string_decoder') const { parseMIMEType, serializeAMimeType } = require('./dataURL') -/** @type {globalThis['ReadableStream']} */ -let ReadableStream +let ReadableStream = globalThis.ReadableStream + +/** @type {globalThis['File']} */ +const File = NativeFile ?? UndiciFile // https://fetch.spec.whatwg.org/#concept-bodyinit-extract function extractBody (object, keepalive = false) { @@ -142,7 +144,33 @@ function extractBody (object, keepalive = false) { source = object // Set length to unclear, see html/6424 for improving this. - // TODO + length = (() => { + const prefixLength = prefix.length + const boundaryLength = boundary.length + let bodyLength = 0 + + for (const [name, value] of object) { + if (typeof value === 'string') { + bodyLength += + prefixLength + + Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"\r\n\r\n${normalizeLinefeeds(value)}\r\n`) + } else { + bodyLength += + prefixLength + + Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"` + (value.name ? `; filename="${escape(value.name)}"` : '')) + + 2 + // \r\n + `Content-Type: ${ + value.type || 'application/octet-stream' + }\r\n\r\n`.length + + // value is a Blob or File, and \r\n + bodyLength += value.size + 2 + } + } + + bodyLength += boundaryLength + 4 // --boundary-- + return bodyLength + })() // Set type to `multipart/form-data; boundary=`, // followed by the multipart/form-data boundary string generated @@ -348,7 +376,10 @@ function bodyMixinMethods (instance) { let busboy try { - busboy = Busboy({ headers }) + busboy = Busboy({ + headers, + defParamCharset: 'utf8' + }) } catch (err) { // Error due to headers: throw Object.assign(new TypeError(), { cause: err }) @@ -361,7 +392,7 @@ function bodyMixinMethods (instance) { const { filename, encoding, mimeType } = info const chunks = [] - if (encoding.toLowerCase() === 'base64') { + if (encoding === 'base64' || encoding.toLowerCase() === 'base64') { let base64chunk = '' value.on('data', (chunk) => { diff --git a/deps/undici/src/lib/fetch/file.js b/deps/undici/src/lib/fetch/file.js index f27fc7fb7be61a..81bb7b2441aaa2 100644 --- a/deps/undici/src/lib/fetch/file.js +++ b/deps/undici/src/lib/fetch/file.js @@ -1,6 +1,6 @@ 'use strict' -const { Blob } = require('buffer') +const { Blob, File: NativeFile } = require('buffer') const { types } = require('util') const { kState } = require('./symbols') const { isBlobLike } = require('./util') @@ -329,11 +329,14 @@ function convertLineEndingsNative (s) { // rollup) will warn about circular dependencies. See: // https://github.com/nodejs/undici/issues/1629 function isFileLike (object) { - return object instanceof File || ( - object && - (typeof object.stream === 'function' || - typeof object.arrayBuffer === 'function') && - object[Symbol.toStringTag] === 'File' + return ( + (NativeFile && object instanceof NativeFile) || + object instanceof File || ( + object && + (typeof object.stream === 'function' || + typeof object.arrayBuffer === 'function') && + object[Symbol.toStringTag] === 'File' + ) ) } diff --git a/deps/undici/src/lib/fetch/formdata.js b/deps/undici/src/lib/fetch/formdata.js index 5d0649ba92efce..a0c9e2f9373944 100644 --- a/deps/undici/src/lib/fetch/formdata.js +++ b/deps/undici/src/lib/fetch/formdata.js @@ -2,9 +2,12 @@ const { isBlobLike, toUSVString, makeIterator } = require('./util') const { kState } = require('./symbols') -const { File, FileLike, isFileLike } = require('./file') +const { File: UndiciFile, FileLike, isFileLike } = require('./file') const { webidl } = require('./webidl') -const { Blob } = require('buffer') +const { Blob, File: NativeFile } = require('buffer') + +/** @type {globalThis['File']} */ +const File = NativeFile ?? UndiciFile // https://xhr.spec.whatwg.org/#formdata class FormData { diff --git a/deps/undici/src/lib/fetch/headers.js b/deps/undici/src/lib/fetch/headers.js index 76d5cde578b341..60d6de72a54c44 100644 --- a/deps/undici/src/lib/fetch/headers.js +++ b/deps/undici/src/lib/fetch/headers.js @@ -3,7 +3,7 @@ 'use strict' const { kHeadersList } = require('../core/symbols') -const { kGuard } = require('./symbols') +const { kGuard, kHeadersCaseInsensitive } = require('./symbols') const { kEnumerableProperty } = require('../core/util') const { makeIterator, @@ -96,27 +96,27 @@ class HeadersList { // 1. If list contains name, then set name to the first such // header’s name. - name = name.toLowerCase() - const exists = this[kHeadersMap].get(name) + const lowercaseName = name.toLowerCase() + const exists = this[kHeadersMap].get(lowercaseName) // 2. Append (name, value) to list. if (exists) { - this[kHeadersMap].set(name, `${exists}, ${value}`) + this[kHeadersMap].set(lowercaseName, { name: exists.name, value: `${exists.value}, ${value}` }) } else { - this[kHeadersMap].set(name, `${value}`) + this[kHeadersMap].set(lowercaseName, { name, value }) } } // https://fetch.spec.whatwg.org/#concept-header-list-set set (name, value) { this[kHeadersSortedMap] = null - name = name.toLowerCase() + const lowercaseName = name.toLowerCase() // 1. If list contains name, then set the value of // the first such header to value and remove the // others. // 2. Otherwise, append header (name, value) to list. - return this[kHeadersMap].set(name, value) + return this[kHeadersMap].set(lowercaseName, { name, value }) } // https://fetch.spec.whatwg.org/#concept-header-list-delete @@ -137,14 +137,26 @@ class HeadersList { // 2. Return the values of all headers in list whose name // is a byte-case-insensitive match for name, // separated from each other by 0x2C 0x20, in order. - return this[kHeadersMap].get(name.toLowerCase()) ?? null + return this[kHeadersMap].get(name.toLowerCase())?.value ?? null } * [Symbol.iterator] () { - for (const pair of this[kHeadersMap]) { - yield pair + // use the lowercased name + for (const [name, { value }] of this[kHeadersMap]) { + yield [name, value] } } + + get [kHeadersCaseInsensitive] () { + /** @type {string[]} */ + const flatList = [] + + for (const { name, value } of this[kHeadersMap].values()) { + flatList.push(name, value) + } + + return flatList + } } // https://fetch.spec.whatwg.org/#headers-class diff --git a/deps/undici/src/lib/fetch/index.js b/deps/undici/src/lib/fetch/index.js index 94f2d1e0aca5ab..8d940c27ca94a1 100644 --- a/deps/undici/src/lib/fetch/index.js +++ b/deps/undici/src/lib/fetch/index.js @@ -39,7 +39,7 @@ const { readableStreamClose, isomorphicEncode } = require('./util') -const { kState, kHeaders, kGuard, kRealm } = require('./symbols') +const { kState, kHeaders, kGuard, kRealm, kHeadersCaseInsensitive } = require('./symbols') const assert = require('assert') const { safelyExtractBody } = require('./body') const { @@ -61,8 +61,7 @@ const { webidl } = require('./webidl') /** @type {import('buffer').resolveObjectURL} */ let resolveObjectURL -/** @type {globalThis['ReadableStream']} */ -let ReadableStream +let ReadableStream = globalThis.ReadableStream const nodeVersion = process.versions.node.split('.') const nodeMajor = Number(nodeVersion[0]) @@ -781,8 +780,11 @@ async function mainFetch (fetchParams, recursive = false) { // https://fetch.spec.whatwg.org/#concept-scheme-fetch // given a fetch params fetchParams async function schemeFetch (fetchParams) { + // Note: since the connection is destroyed on redirect, which sets fetchParams to a + // cancelled state, we do not want this condition to trigger *unless* there have been + // no redirects. See https://github.com/nodejs/undici/issues/1776 // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams. - if (isCancelled(fetchParams)) { + if (isCancelled(fetchParams) && fetchParams.request.redirectCount === 0) { return makeAppropriateNetworkError(fetchParams) } @@ -840,8 +842,8 @@ async function schemeFetch (fetchParams) { const response = makeResponse({ statusText: 'OK', headersList: [ - ['content-length', length], - ['content-type', type] + ['content-length', { name: 'Content-Length', value: length }], + ['content-type', { name: 'Content-Type', value: type }] ] }) @@ -870,7 +872,7 @@ async function schemeFetch (fetchParams) { return makeResponse({ statusText: 'OK', headersList: [ - ['content-type', mimeType] + ['content-type', { name: 'Content-Type', value: mimeType }] ], body: safelyExtractBody(dataURLStruct.body)[0] }) @@ -1135,12 +1137,12 @@ async function httpRedirectFetch (fetchParams, response) { return makeNetworkError('URL scheme must be a HTTP(S) scheme') } - // 7. If request’s redirect count is twenty, return a network error. + // 7. If request’s redirect count is 20, then return a network error. if (request.redirectCount === 20) { return makeNetworkError('redirect count exceeded') } - // 8. Increase request’s redirect count by one. + // 8. Increase request’s redirect count by 1. request.redirectCount += 1 // 9. If request’s mode is "cors", locationURL includes credentials, and @@ -1195,36 +1197,44 @@ async function httpRedirectFetch (fetchParams, response) { } } - // 13. If request’s body is non-null, then set request’s body to the first return + // 13. If request’s current URL’s origin is not same origin with locationURL’s + // origin, then for each headerName of CORS non-wildcard request-header name, + // delete headerName from request’s header list. + if (!sameOrigin(requestCurrentURL(request), locationURL)) { + // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name + request.headersList.delete('authorization') + } + + // 14. If request’s body is non-null, then set request’s body to the first return // value of safely extracting request’s body’s source. if (request.body != null) { assert(request.body.source) request.body = safelyExtractBody(request.body.source)[0] } - // 14. Let timingInfo be fetchParams’s timing info. + // 15. Let timingInfo be fetchParams’s timing info. const timingInfo = fetchParams.timingInfo - // 15. Set timingInfo’s redirect end time and post-redirect start time to the + // 16. Set timingInfo’s redirect end time and post-redirect start time to the // coarsened shared current time given fetchParams’s cross-origin isolated // capability. timingInfo.redirectEndTime = timingInfo.postRedirectStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability) - // 16. If timingInfo’s redirect start time is 0, then set timingInfo’s + // 17. If timingInfo’s redirect start time is 0, then set timingInfo’s // redirect start time to timingInfo’s start time. if (timingInfo.redirectStartTime === 0) { timingInfo.redirectStartTime = timingInfo.startTime } - // 17. Append locationURL to request’s URL list. + // 18. Append locationURL to request’s URL list. request.urlList.push(locationURL) - // 18. Invoke set request’s referrer policy on redirect on request and + // 19. Invoke set request’s referrer policy on redirect on request and // actualResponse. setRequestReferrerPolicyOnRedirect(request, actualResponse) - // 19. Return the result of running main fetch given fetchParams and true. + // 20. Return the result of running main fetch given fetchParams and true. return mainFetch(fetchParams, true) } @@ -1930,7 +1940,7 @@ async function httpNetworkFetch ( origin: url.origin, method: request.method, body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body, - headers: [...request.headersList].flat(), + headers: request.headersList[kHeadersCaseInsensitive], maxRedirections: 0, bodyTimeout: 300_000, headersTimeout: 300_000 diff --git a/deps/undici/src/lib/fetch/request.js b/deps/undici/src/lib/fetch/request.js index 533ee91bc1023c..ce1584ca1900f1 100644 --- a/deps/undici/src/lib/fetch/request.js +++ b/deps/undici/src/lib/fetch/request.js @@ -29,7 +29,7 @@ const { URLSerializer } = require('./dataURL') const { kHeadersList } = require('../core/symbols') const assert = require('assert') -let TransformStream +let TransformStream = globalThis.TransformStream const kInit = Symbol('init') diff --git a/deps/undici/src/lib/fetch/response.js b/deps/undici/src/lib/fetch/response.js index fc6746bfa8d840..09732114e7ae00 100644 --- a/deps/undici/src/lib/fetch/response.js +++ b/deps/undici/src/lib/fetch/response.js @@ -436,7 +436,7 @@ function makeAppropriateNetworkError (fetchParams) { // otherwise return a network error. return isAborted(fetchParams) ? makeNetworkError(new DOMException('The operation was aborted.', 'AbortError')) - : makeNetworkError(fetchParams.controller.terminated.reason) + : makeNetworkError('Request was cancelled.') } // https://whatpr.org/fetch/1392.html#initialize-a-response diff --git a/deps/undici/src/lib/fetch/symbols.js b/deps/undici/src/lib/fetch/symbols.js index 0b947d55bad8e4..e841ac730a7f68 100644 --- a/deps/undici/src/lib/fetch/symbols.js +++ b/deps/undici/src/lib/fetch/symbols.js @@ -6,5 +6,6 @@ module.exports = { kSignal: Symbol('signal'), kState: Symbol('state'), kGuard: Symbol('guard'), - kRealm: Symbol('realm') + kRealm: Symbol('realm'), + kHeadersCaseInsensitive: Symbol('headers case insensitive') } diff --git a/deps/undici/src/lib/fetch/util.js b/deps/undici/src/lib/fetch/util.js index 4d21e2e17b981c..f2fd1088629207 100644 --- a/deps/undici/src/lib/fetch/util.js +++ b/deps/undici/src/lib/fetch/util.js @@ -863,6 +863,8 @@ function isReadableStreamLike (stream) { ) } +const MAXIMUM_ARGUMENT_LENGTH = 65535 + /** * @see https://infra.spec.whatwg.org/#isomorphic-decode * @param {number[]|Uint8Array} input @@ -871,13 +873,12 @@ function isomorphicDecode (input) { // 1. To isomorphic decode a byte sequence input, return a string whose code point // length is equal to input’s length and whose code points have the same values // as the values of input’s bytes, in the same order. - let output = '' - for (let i = 0; i < input.length; i++) { - output += String.fromCharCode(input[i]) + if (input.length < MAXIMUM_ARGUMENT_LENGTH) { + return String.fromCharCode(...input) } - return output + return input.reduce((previous, current) => previous + String.fromCharCode(current), '') } /** diff --git a/deps/undici/src/lib/fileapi/filereader.js b/deps/undici/src/lib/fileapi/filereader.js index 9a8bdd90335b87..cd36a22ff6f14a 100644 --- a/deps/undici/src/lib/fileapi/filereader.js +++ b/deps/undici/src/lib/fileapi/filereader.js @@ -182,8 +182,13 @@ class FileReader extends EventTarget { set onloadend (fn) { webidl.brandCheck(this, FileReader) + if (this[kEvents].loadend) { + this.removeEventListener('loadend', this[kEvents].loadend) + } + if (typeof fn === 'function') { this[kEvents].loadend = fn + this.addEventListener('loadend', fn) } else { this[kEvents].loadend = null } @@ -198,8 +203,13 @@ class FileReader extends EventTarget { set onerror (fn) { webidl.brandCheck(this, FileReader) + if (this[kEvents].error) { + this.removeEventListener('error', this[kEvents].error) + } + if (typeof fn === 'function') { this[kEvents].error = fn + this.addEventListener('error', fn) } else { this[kEvents].error = null } @@ -214,8 +224,13 @@ class FileReader extends EventTarget { set onloadstart (fn) { webidl.brandCheck(this, FileReader) + if (this[kEvents].loadstart) { + this.removeEventListener('loadstart', this[kEvents].loadstart) + } + if (typeof fn === 'function') { this[kEvents].loadstart = fn + this.addEventListener('loadstart', fn) } else { this[kEvents].loadstart = null } @@ -230,8 +245,13 @@ class FileReader extends EventTarget { set onprogress (fn) { webidl.brandCheck(this, FileReader) + if (this[kEvents].progress) { + this.removeEventListener('progress', this[kEvents].progress) + } + if (typeof fn === 'function') { this[kEvents].progress = fn + this.addEventListener('progress', fn) } else { this[kEvents].progress = null } @@ -246,8 +266,13 @@ class FileReader extends EventTarget { set onload (fn) { webidl.brandCheck(this, FileReader) + if (this[kEvents].load) { + this.removeEventListener('load', this[kEvents].load) + } + if (typeof fn === 'function') { this[kEvents].load = fn + this.addEventListener('load', fn) } else { this[kEvents].load = null } @@ -262,8 +287,13 @@ class FileReader extends EventTarget { set onabort (fn) { webidl.brandCheck(this, FileReader) + if (this[kEvents].abort) { + this.removeEventListener('abort', this[kEvents].abort) + } + if (typeof fn === 'function') { this[kEvents].abort = fn + this.addEventListener('abort', fn) } else { this[kEvents].abort = null } diff --git a/deps/undici/src/lib/fileapi/util.js b/deps/undici/src/lib/fileapi/util.js index b37e0dd2b512b5..1d10899cee8291 100644 --- a/deps/undici/src/lib/fileapi/util.js +++ b/deps/undici/src/lib/fileapi/util.js @@ -191,25 +191,19 @@ function readOperation (fr, blob, type, encodingName) { /** * @see https://w3c.github.io/FileAPI/#fire-a-progress-event + * @see https://dom.spec.whatwg.org/#concept-event-fire * @param {string} e The name of the event * @param {import('./filereader').FileReader} reader */ function fireAProgressEvent (e, reader) { + // The progress event e does not bubble. e.bubbles must be false + // The progress event e is NOT cancelable. e.cancelable must be false const event = new ProgressEvent(e, { bubbles: false, cancelable: false }) reader.dispatchEvent(event) - try { - // eslint-disable-next-line no-useless-call - reader[`on${e}`]?.call(reader, event) - } catch (err) { - // Prevent the error from being swallowed - queueMicrotask(() => { - throw err - }) - } } /** diff --git a/deps/undici/src/package.json b/deps/undici/src/package.json index a192b2d674ce61..0c592721f25037 100644 --- a/deps/undici/src/package.json +++ b/deps/undici/src/package.json @@ -1,6 +1,6 @@ { "name": "undici", - "version": "5.13.0", + "version": "5.14.0", "description": "An HTTP/1.1 client, written from scratch for Node.js", "homepage": "https://undici.nodejs.org", "bugs": { @@ -89,12 +89,12 @@ "proxy": "^1.0.2", "proxyquire": "^2.1.3", "semver": "^7.3.5", - "sinon": "^14.0.0", + "sinon": "^15.0.0", "snazzy": "^9.0.0", "standard": "^17.0.0", "table": "^6.8.0", "tap": "^16.1.0", - "tsd": "^0.24.1", + "tsd": "^0.25.0", "typescript": "^4.8.4", "wait-on": "^6.0.0" }, diff --git a/deps/undici/src/types/connector.d.ts b/deps/undici/src/types/connector.d.ts index 8a425d2972c0c8..2b28771af2a6cc 100644 --- a/deps/undici/src/types/connector.d.ts +++ b/deps/undici/src/types/connector.d.ts @@ -16,8 +16,10 @@ declare namespace buildConnector { hostname: string host?: string protocol: string - port: number + port: string servername?: string + localAddress?: string | null + httpSocket?: Socket } export type Callback = (...args: CallbackArgs) => void diff --git a/deps/undici/undici.js b/deps/undici/undici.js index 8fd0c48f62b597..e492ccc6e65abd 100644 --- a/deps/undici/undici.js +++ b/deps/undici/undici.js @@ -75,7 +75,8 @@ var require_symbols2 = __commonJS({ kSignal: Symbol("signal"), kState: Symbol("state"), kGuard: Symbol("guard"), - kRealm: Symbol("realm") + kRealm: Symbol("realm"), + kHeadersCaseInsensitive: Symbol("headers case insensitive") }; } }); @@ -1157,12 +1158,12 @@ var require_util2 = __commonJS({ } return stream instanceof ReadableStream || stream[Symbol.toStringTag] === "ReadableStream" && typeof stream.tee === "function"; } + var MAXIMUM_ARGUMENT_LENGTH = 65535; function isomorphicDecode(input) { - let output = ""; - for (let i = 0; i < input.length; i++) { - output += String.fromCharCode(input[i]); + if (input.length < MAXIMUM_ARGUMENT_LENGTH) { + return String.fromCharCode(...input); } - return output; + return input.reduce((previous, current) => previous + String.fromCharCode(current), ""); } function readableStreamClose(controller) { try { @@ -1578,7 +1579,7 @@ var require_headers = __commonJS({ "lib/fetch/headers.js"(exports2, module2) { "use strict"; var { kHeadersList } = require_symbols(); - var { kGuard } = require_symbols2(); + var { kGuard, kHeadersCaseInsensitive } = require_symbols2(); var { kEnumerableProperty } = require_util(); var { makeIterator, @@ -1634,18 +1635,18 @@ var require_headers = __commonJS({ } append(name, value) { this[kHeadersSortedMap] = null; - name = name.toLowerCase(); - const exists = this[kHeadersMap].get(name); + const lowercaseName = name.toLowerCase(); + const exists = this[kHeadersMap].get(lowercaseName); if (exists) { - this[kHeadersMap].set(name, `${exists}, ${value}`); + this[kHeadersMap].set(lowercaseName, { name: exists.name, value: `${exists.value}, ${value}` }); } else { - this[kHeadersMap].set(name, `${value}`); + this[kHeadersMap].set(lowercaseName, { name, value }); } } set(name, value) { this[kHeadersSortedMap] = null; - name = name.toLowerCase(); - return this[kHeadersMap].set(name, value); + const lowercaseName = name.toLowerCase(); + return this[kHeadersMap].set(lowercaseName, { name, value }); } delete(name) { this[kHeadersSortedMap] = null; @@ -1656,12 +1657,19 @@ var require_headers = __commonJS({ if (!this.contains(name)) { return null; } - return this[kHeadersMap].get(name.toLowerCase()) ?? null; + return this[kHeadersMap].get(name.toLowerCase())?.value ?? null; } *[Symbol.iterator]() { - for (const pair of this[kHeadersMap]) { - yield pair; + for (const [name, { value }] of this[kHeadersMap]) { + yield [name, value]; + } + } + get [kHeadersCaseInsensitive]() { + const flatList = []; + for (const { name, value } of this[kHeadersMap].values()) { + flatList.push(name, value); } + return flatList; } }; var Headers = class { @@ -5741,7 +5749,7 @@ var require_dataURL = __commonJS({ var require_file = __commonJS({ "lib/fetch/file.js"(exports2, module2) { "use strict"; - var { Blob } = require("buffer"); + var { Blob, File: NativeFile } = require("buffer"); var { types } = require("util"); var { kState } = require_symbols2(); var { isBlobLike } = require_util2(); @@ -5912,7 +5920,7 @@ var require_file = __commonJS({ return s.replace(/\r?\n/g, nativeLineEnding); } function isFileLike(object) { - return object instanceof File || object && (typeof object.stream === "function" || typeof object.arrayBuffer === "function") && object[Symbol.toStringTag] === "File"; + return NativeFile && object instanceof NativeFile || object instanceof File || object && (typeof object.stream === "function" || typeof object.arrayBuffer === "function") && object[Symbol.toStringTag] === "File"; } module2.exports = { File, FileLike, isFileLike }; } @@ -5924,9 +5932,10 @@ var require_formdata = __commonJS({ "use strict"; var { isBlobLike, toUSVString, makeIterator } = require_util2(); var { kState } = require_symbols2(); - var { File, FileLike, isFileLike } = require_file(); + var { File: UndiciFile, FileLike, isFileLike } = require_file(); var { webidl } = require_webidl(); - var { Blob } = require("buffer"); + var { Blob, File: NativeFile } = require("buffer"); + var File = NativeFile ?? UndiciFile; var FormData = class { constructor(form) { if (form !== void 0) { @@ -6068,15 +6077,16 @@ var require_body = __commonJS({ var { kState } = require_symbols2(); var { webidl } = require_webidl(); var { DOMException, structuredClone } = require_constants(); - var { Blob } = require("buffer"); + var { Blob, File: NativeFile } = require("buffer"); var { kBodyUsed } = require_symbols(); var assert = require("assert"); var { isErrored } = require_util(); var { isUint8Array, isArrayBuffer } = require("util/types"); - var { File } = require_file(); + var { File: UndiciFile } = require_file(); var { StringDecoder } = require("string_decoder"); var { parseMIMEType, serializeAMimeType } = require_dataURL(); - var ReadableStream; + var ReadableStream = globalThis.ReadableStream; + var File = NativeFile ?? UndiciFile; function extractBody(object, keepalive = false) { if (!ReadableStream) { ReadableStream = require("stream/web").ReadableStream; @@ -6138,6 +6148,26 @@ Content-Type: ${value.type || "application/octet-stream"}\r yield enc.encode(`--${boundary}--`); }; source = object; + length = (() => { + const prefixLength = prefix.length; + const boundaryLength = boundary.length; + let bodyLength = 0; + for (const [name, value] of object) { + if (typeof value === "string") { + bodyLength += prefixLength + Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"\r +\r +${normalizeLinefeeds(value)}\r +`); + } else { + bodyLength += prefixLength + Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"` + (value.name ? `; filename="${escape(value.name)}"` : "")) + 2 + `Content-Type: ${value.type || "application/octet-stream"}\r +\r +`.length; + bodyLength += value.size + 2; + } + } + bodyLength += boundaryLength + 4; + return bodyLength; + })(); type = "multipart/form-data; boundary=" + boundary; } else if (isBlobLike(object)) { source = object; @@ -6255,7 +6285,10 @@ Content-Type: ${value.type || "application/octet-stream"}\r const responseFormData = new FormData(); let busboy; try { - busboy = Busboy({ headers }); + busboy = Busboy({ + headers, + defParamCharset: "utf8" + }); } catch (err) { throw Object.assign(new TypeError(), { cause: err }); } @@ -6265,7 +6298,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r busboy.on("file", (name, value, info) => { const { filename, encoding, mimeType } = info; const chunks = []; - if (encoding.toLowerCase() === "base64") { + if (encoding === "base64" || encoding.toLowerCase() === "base64") { let base64chunk = ""; value.on("data", (chunk) => { base64chunk += chunk.toString().replace(/[\r\n]/gm, ""); @@ -6733,7 +6766,7 @@ var require_response = __commonJS({ } function makeAppropriateNetworkError(fetchParams) { assert(isCancelled(fetchParams)); - return isAborted(fetchParams) ? makeNetworkError(new DOMException("The operation was aborted.", "AbortError")) : makeNetworkError(fetchParams.controller.terminated.reason); + return isAborted(fetchParams) ? makeNetworkError(new DOMException("The operation was aborted.", "AbortError")) : makeNetworkError("Request was cancelled."); } function initializeResponse(response, init, body) { if (init.status !== null && (init.status < 200 || init.status > 599)) { @@ -6886,7 +6919,7 @@ var require_request = __commonJS({ var { URLSerializer } = require_dataURL(); var { kHeadersList } = require_symbols(); var assert = require("assert"); - var TransformStream; + var TransformStream = globalThis.TransformStream; var kInit = Symbol("init"); var requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { signal.removeEventListener("abort", abort); @@ -7800,14 +7833,62 @@ var require_connect = __commonJS({ var util = require_util(); var { InvalidArgumentError, ConnectTimeoutError } = require_errors(); var tls; + var SessionCache; + if (global.FinalizationRegistry) { + SessionCache = class WeakSessionCache { + constructor(maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions; + this._sessionCache = /* @__PURE__ */ new Map(); + this._sessionRegistry = new global.FinalizationRegistry((key) => { + if (this._sessionCache.size < this._maxCachedSessions) { + return; + } + const ref = this._sessionCache.get(key); + if (ref !== void 0 && ref.deref() === void 0) { + 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); + } + }; + } else { + SessionCache = class SimpleSessionCache { + constructor(maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions; + this._sessionCache = /* @__PURE__ */ new Map(); + } + get(sessionKey) { + return this._sessionCache.get(sessionKey); + } + set(sessionKey, session) { + if (this._maxCachedSessions === 0) { + return; + } + if (this._sessionCache.size >= this._maxCachedSessions) { + const { value: oldestKey } = this._sessionCache.keys().next(); + this._sessionCache.delete(oldestKey); + } + this._sessionCache.set(sessionKey, session); + } + }; + } function buildConnector({ maxCachedSessions, socketPath, timeout, ...opts }) { if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) { throw new InvalidArgumentError("maxCachedSessions must be a positive integer or zero"); } const options = { path: socketPath, ...opts }; - const sessionCache = /* @__PURE__ */ new Map(); + const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions); timeout = timeout == null ? 1e4 : timeout; - maxCachedSessions = maxCachedSessions == null ? 100 : maxCachedSessions; return function connect({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) { let socket; if (protocol === "https:") { @@ -7829,18 +7910,7 @@ var require_connect = __commonJS({ host: hostname }); socket.on("session", function(session2) { - if (maxCachedSessions === 0) { - return; - } - if (sessionCache.size >= maxCachedSessions) { - const { value: oldestKey } = sessionCache.keys().next(); - sessionCache.delete(oldestKey); - } sessionCache.set(sessionKey, session2); - }).on("error", function(err) { - if (sessionKey && err.code !== "UND_ERR_INFO") { - sessionCache.delete(sessionKey); - } }); } else { assert(!httpSocket, "httpSocket can only be sent on TLS update"); @@ -10202,7 +10272,7 @@ var require_agent = __commonJS({ var Client = require_client(); var util = require_util(); var createRedirectInterceptor = require_redirectInterceptor(); - var { WeakRef, FinalizationRegistry } = require_dispatcher_weakref()(); + var { WeakRef: WeakRef2, FinalizationRegistry } = require_dispatcher_weakref()(); var kOnConnect = Symbol("onConnect"); var kOnDisconnect = Symbol("onDisconnect"); var kOnConnectionError = Symbol("onConnectionError"); @@ -10276,7 +10346,7 @@ var require_agent = __commonJS({ let dispatcher = ref ? ref.deref() : null; if (!dispatcher) { dispatcher = this[kFactory](opts.origin, this[kOptions]).on("drain", this[kOnDrain]).on("connect", this[kOnConnect]).on("disconnect", this[kOnDisconnect]).on("connectionError", this[kOnConnectionError]); - this[kClients].set(key, new WeakRef(dispatcher)); + this[kClients].set(key, new WeakRef2(dispatcher)); this[kFinalizer].register(dispatcher, key); } return dispatcher.dispatch(opts, handler); @@ -12709,13 +12779,6 @@ var require_util3 = __commonJS({ cancelable: false }); reader.dispatchEvent(event); - try { - reader[`on${e}`]?.call(reader, event); - } catch (err) { - queueMicrotask(() => { - throw err; - }); - } } function packageData(bytes, type, mimeType, encodingName) { switch (type) { @@ -12905,8 +12968,12 @@ var require_filereader = __commonJS({ } set onloadend(fn) { webidl.brandCheck(this, FileReader); + if (this[kEvents].loadend) { + this.removeEventListener("loadend", this[kEvents].loadend); + } if (typeof fn === "function") { this[kEvents].loadend = fn; + this.addEventListener("loadend", fn); } else { this[kEvents].loadend = null; } @@ -12917,8 +12984,12 @@ var require_filereader = __commonJS({ } set onerror(fn) { webidl.brandCheck(this, FileReader); + if (this[kEvents].error) { + this.removeEventListener("error", this[kEvents].error); + } if (typeof fn === "function") { this[kEvents].error = fn; + this.addEventListener("error", fn); } else { this[kEvents].error = null; } @@ -12929,8 +13000,12 @@ var require_filereader = __commonJS({ } set onloadstart(fn) { webidl.brandCheck(this, FileReader); + if (this[kEvents].loadstart) { + this.removeEventListener("loadstart", this[kEvents].loadstart); + } if (typeof fn === "function") { this[kEvents].loadstart = fn; + this.addEventListener("loadstart", fn); } else { this[kEvents].loadstart = null; } @@ -12941,8 +13016,12 @@ var require_filereader = __commonJS({ } set onprogress(fn) { webidl.brandCheck(this, FileReader); + if (this[kEvents].progress) { + this.removeEventListener("progress", this[kEvents].progress); + } if (typeof fn === "function") { this[kEvents].progress = fn; + this.addEventListener("progress", fn); } else { this[kEvents].progress = null; } @@ -12953,8 +13032,12 @@ var require_filereader = __commonJS({ } set onload(fn) { webidl.brandCheck(this, FileReader); + if (this[kEvents].load) { + this.removeEventListener("load", this[kEvents].load); + } if (typeof fn === "function") { this[kEvents].load = fn; + this.addEventListener("load", fn); } else { this[kEvents].load = null; } @@ -12965,8 +13048,12 @@ var require_filereader = __commonJS({ } set onabort(fn) { webidl.brandCheck(this, FileReader); + if (this[kEvents].abort) { + this.removeEventListener("abort", this[kEvents].abort); + } if (typeof fn === "function") { this[kEvents].abort = fn; + this.addEventListener("abort", fn); } else { this[kEvents].abort = null; } @@ -13166,7 +13253,7 @@ var require_fetch = __commonJS({ readableStreamClose, isomorphicEncode } = require_util2(); - var { kState, kHeaders, kGuard, kRealm } = require_symbols2(); + var { kState, kHeaders, kGuard, kRealm, kHeadersCaseInsensitive } = require_symbols2(); var assert = require("assert"); var { safelyExtractBody } = require_body(); var { @@ -13186,7 +13273,7 @@ var require_fetch = __commonJS({ var { getGlobalDispatcher } = require_undici(); var { webidl } = require_webidl(); var resolveObjectURL; - var ReadableStream; + var ReadableStream = globalThis.ReadableStream; var nodeVersion = process.versions.node.split("."); var nodeMajor = Number(nodeVersion[0]); var nodeMinor = Number(nodeVersion[1]); @@ -13488,7 +13575,7 @@ var require_fetch = __commonJS({ } } async function schemeFetch(fetchParams) { - if (isCancelled(fetchParams)) { + if (isCancelled(fetchParams) && fetchParams.request.redirectCount === 0) { return makeAppropriateNetworkError(fetchParams); } const { request } = fetchParams; @@ -13516,8 +13603,8 @@ var require_fetch = __commonJS({ const response = makeResponse({ statusText: "OK", headersList: [ - ["content-length", length], - ["content-type", type] + ["content-length", { name: "Content-Length", value: length }], + ["content-type", { name: "Content-Type", value: type }] ] }); response.body = body; @@ -13533,7 +13620,7 @@ var require_fetch = __commonJS({ return makeResponse({ statusText: "OK", headersList: [ - ["content-type", mimeType] + ["content-type", { name: "Content-Type", value: mimeType }] ], body: safelyExtractBody(dataURLStruct.body)[0] }); @@ -13678,6 +13765,9 @@ var require_fetch = __commonJS({ request.headersList.delete(headerName); } } + if (!sameOrigin(requestCurrentURL(request), locationURL)) { + request.headersList.delete("authorization"); + } if (request.body != null) { assert(request.body.source); request.body = safelyExtractBody(request.body.source)[0]; @@ -13958,7 +14048,7 @@ var require_fetch = __commonJS({ origin: url.origin, method: request.method, body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body, - headers: [...request.headersList].flat(), + headers: request.headersList[kHeadersCaseInsensitive], maxRedirections: 0, bodyTimeout: 3e5, headersTimeout: 3e5