diff --git a/deps/undici/src/docs/api/MockPool.md b/deps/undici/src/docs/api/MockPool.md index c86f9a30adb8e0..923c157aa64657 100644 --- a/deps/undici/src/docs/api/MockPool.md +++ b/deps/undici/src/docs/api/MockPool.md @@ -63,7 +63,8 @@ Returns: `MockInterceptor` corresponding to the input options. We can define the behaviour of an intercepted request with the following options. -* **reply** `(statusCode: number, replyData: string | Buffer | object | MockInterceptor.MockResponseDataHandler, responseOptions?: MockResponseOptions) => MockScope` - define a reply for a matching request. You can define this as a callback to read incoming request data. Default for `responseOptions` is `{}`. +* **reply** `(statusCode: number, replyData: string | Buffer | object | MockInterceptor.MockResponseDataHandler, responseOptions?: MockResponseOptions) => MockScope` - define a reply for a matching request. You can define the replyData as a callback to read incoming request data. Default for `responseOptions` is `{}`. +* **reply** `(callback: MockInterceptor.MockReplyOptionsCallback) => MockScope` - define a reply for a matching request, allowing dynamic mocking of all reply options rather than just the data. * **replyWithError** `(error: Error) => MockScope` - define an error for a matching request to throw. * **defaultReplyHeaders** `(headers: Record) => MockInterceptor` - define default headers to be included in subsequent replies. These are in addition to headers on a specific reply. * **defaultReplyTrailers** `(trailers: Record) => MockInterceptor` - define default trailers to be included in subsequent replies. These are in addition to trailers on a specific reply. diff --git a/deps/undici/src/lib/cookies/index.js b/deps/undici/src/lib/cookies/index.js index def04f5bc4ac72..c9c1f28ee1ff51 100644 --- a/deps/undici/src/lib/cookies/index.js +++ b/deps/undici/src/lib/cookies/index.js @@ -83,7 +83,8 @@ function getSetCookies (headers) { return [] } - return cookies.map((pair) => parseSetCookie(pair[1])) + // In older versions of undici, cookies is a list of name:value. + return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair)) } /** diff --git a/deps/undici/src/lib/core/request.js b/deps/undici/src/lib/core/request.js index 7271bc64f24901..adb5bfcb3a86d4 100644 --- a/deps/undici/src/lib/core/request.js +++ b/deps/undici/src/lib/core/request.js @@ -304,6 +304,9 @@ function processHeader (request, key, val) { key.length === 4 && key.toLowerCase() === 'host' ) { + if (headerCharRegex.exec(val) !== null) { + throw new InvalidArgumentError(`invalid ${key} header`) + } // Consumed by Client request.host = val } else if ( diff --git a/deps/undici/src/lib/core/util.js b/deps/undici/src/lib/core/util.js index 6f38ffc8a4de0b..3b9b56cb64d1ea 100644 --- a/deps/undici/src/lib/core/util.js +++ b/deps/undici/src/lib/core/util.js @@ -213,25 +213,42 @@ function parseHeaders (headers, obj = {}) { for (let i = 0; i < headers.length; i += 2) { const key = headers[i].toString().toLowerCase() let val = obj[key] + + const encoding = key.length === 19 && key === 'content-disposition' + ? 'latin1' + : 'utf8' + if (!val) { if (Array.isArray(headers[i + 1])) { obj[key] = headers[i + 1] } else { - obj[key] = headers[i + 1].toString() + obj[key] = headers[i + 1].toString(encoding) } } else { if (!Array.isArray(val)) { val = [val] obj[key] = val } - val.push(headers[i + 1].toString()) + val.push(headers[i + 1].toString(encoding)) } } return obj } function parseRawHeaders (headers) { - return headers.map(header => header.toString()) + const ret = [] + for (let n = 0; n < headers.length; n += 2) { + const key = headers[n + 0].toString() + + const encoding = key.length === 19 && key.toLowerCase() === 'content-disposition' + ? 'latin1' + : 'utf8' + + const val = headers[n + 1].toString(encoding) + + ret.push(key, val) + } + return ret } function isBuffer (buffer) { diff --git a/deps/undici/src/lib/fetch/headers.js b/deps/undici/src/lib/fetch/headers.js index 0febd114db5fa0..ea3b9a14a43c74 100644 --- a/deps/undici/src/lib/fetch/headers.js +++ b/deps/undici/src/lib/fetch/headers.js @@ -11,6 +11,7 @@ const { isValidHeaderValue } = require('./util') const { webidl } = require('./webidl') +const assert = require('assert') const kHeadersMap = Symbol('headers map') const kHeadersSortedMap = Symbol('headers map sorted') @@ -23,10 +24,12 @@ function headerValueNormalize (potentialValue) { // To normalize a byte sequence potentialValue, remove // any leading and trailing HTTP whitespace bytes from // potentialValue. - return potentialValue.replace( - /^[\r\n\t ]+|[\r\n\t ]+$/g, - '' - ) + + // Trimming the end with `.replace()` and a RegExp is typically subject to + // ReDoS. This is safer and faster. + let i = potentialValue.length + while (/[\r\n\t ]/.test(potentialValue.charAt(--i))); + return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, '') } function fill (headers, object) { @@ -115,7 +118,7 @@ class HeadersList { if (lowercaseName === 'set-cookie') { this.cookies ??= [] - this.cookies.push([name, value]) + this.cookies.push(value) } } @@ -125,7 +128,7 @@ class HeadersList { const lowercaseName = name.toLowerCase() if (lowercaseName === 'set-cookie') { - this.cookies = [[name, value]] + this.cookies = [value] } // 1. If list contains name, then set the value of @@ -383,18 +386,74 @@ class Headers { return this[kHeadersList].set(name, value) } + // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie + getSetCookie () { + webidl.brandCheck(this, Headers) + + // 1. If this’s header list does not contain `Set-Cookie`, then return « ». + // 2. Return the values of all headers in this’s header list whose name is + // a byte-case-insensitive match for `Set-Cookie`, in order. + + const list = this[kHeadersList].cookies + + if (list) { + return [...list] + } + + return [] + } + + // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine get [kHeadersSortedMap] () { - if (!this[kHeadersList][kHeadersSortedMap]) { - this[kHeadersList][kHeadersSortedMap] = new Map([...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1)) + if (this[kHeadersList][kHeadersSortedMap]) { + return this[kHeadersList][kHeadersSortedMap] } - return this[kHeadersList][kHeadersSortedMap] + + // 1. Let headers be an empty list of headers with the key being the name + // and value the value. + const headers = [] + + // 2. Let names be the result of convert header names to a sorted-lowercase + // set with all the names of the headers in list. + const names = [...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1) + const cookies = this[kHeadersList].cookies + + // 3. For each name of names: + for (const [name, value] of names) { + // 1. If name is `set-cookie`, then: + if (name === 'set-cookie') { + // 1. Let values be a list of all values of headers in list whose name + // is a byte-case-insensitive match for name, in order. + + // 2. For each value of values: + // 1. Append (name, value) to headers. + for (const value of cookies) { + headers.push([name, value]) + } + } else { + // 2. Otherwise: + + // 1. Let value be the result of getting name from list. + + // 2. Assert: value is non-null. + assert(value !== null) + + // 3. Append (name, value) to headers. + headers.push([name, value]) + } + } + + this[kHeadersList][kHeadersSortedMap] = headers + + // 4. Return headers. + return headers } keys () { webidl.brandCheck(this, Headers) return makeIterator( - () => [...this[kHeadersSortedMap].entries()], + () => [...this[kHeadersSortedMap].values()], 'Headers', 'key' ) @@ -404,7 +463,7 @@ class Headers { webidl.brandCheck(this, Headers) return makeIterator( - () => [...this[kHeadersSortedMap].entries()], + () => [...this[kHeadersSortedMap].values()], 'Headers', 'value' ) @@ -414,7 +473,7 @@ class Headers { webidl.brandCheck(this, Headers) return makeIterator( - () => [...this[kHeadersSortedMap].entries()], + () => [...this[kHeadersSortedMap].values()], 'Headers', 'key+value' ) diff --git a/deps/undici/src/lib/fetch/request.js b/deps/undici/src/lib/fetch/request.js index 7264d152f87631..eca7b060e0ea1f 100644 --- a/deps/undici/src/lib/fetch/request.js +++ b/deps/undici/src/lib/fetch/request.js @@ -28,6 +28,7 @@ const { getGlobalOrigin } = require('./global') const { URLSerializer } = require('./dataURL') const { kHeadersList } = require('../core/symbols') const assert = require('assert') +const { setMaxListeners, getEventListeners, defaultMaxListeners } = require('events') let TransformStream = globalThis.TransformStream @@ -352,6 +353,11 @@ class Request { const abort = function () { acRef.deref()?.abort(this.reason) } + + if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) { + setMaxListeners(100, signal) + } + signal.addEventListener('abort', abort, { once: true }) requestFinalizer.register(this, { signal, abort }) } diff --git a/deps/undici/src/lib/mock/mock-utils.js b/deps/undici/src/lib/mock/mock-utils.js index 9dc414d0a3578f..42ea185cc0ebd0 100644 --- a/deps/undici/src/lib/mock/mock-utils.js +++ b/deps/undici/src/lib/mock/mock-utils.js @@ -188,7 +188,11 @@ function buildKey (opts) { } function generateKeyValues (data) { - return Object.entries(data).reduce((keyValuePairs, [key, value]) => [...keyValuePairs, key, value], []) + return Object.entries(data).reduce((keyValuePairs, [key, value]) => [ + ...keyValuePairs, + Buffer.from(`${key}`), + Array.isArray(value) ? value.map(x => Buffer.from(`${x}`)) : Buffer.from(`${value}`) + ], []) } /** diff --git a/deps/undici/src/package.json b/deps/undici/src/package.json index af56b2ab98fc5e..c664a9af9b3d32 100644 --- a/deps/undici/src/package.json +++ b/deps/undici/src/package.json @@ -1,6 +1,6 @@ { "name": "undici", - "version": "5.18.0", + "version": "5.19.1", "description": "An HTTP/1.1 client, written from scratch for Node.js", "homepage": "https://undici.nodejs.org", "bugs": { diff --git a/deps/undici/src/types/connector.d.ts b/deps/undici/src/types/connector.d.ts index 2b28771af2a6cc..d53d7952b5c9e0 100644 --- a/deps/undici/src/types/connector.d.ts +++ b/deps/undici/src/types/connector.d.ts @@ -10,6 +10,8 @@ declare namespace buildConnector { socketPath?: string | null; timeout?: number | null; port?: number; + keepAlive?: boolean | null; + keepAliveInitialDelay?: number | null; } export interface Options { diff --git a/deps/undici/src/types/fetch.d.ts b/deps/undici/src/types/fetch.d.ts index 58bd4d2a76919a..fa4619c9182739 100644 --- a/deps/undici/src/types/fetch.d.ts +++ b/deps/undici/src/types/fetch.d.ts @@ -59,6 +59,7 @@ export declare class Headers implements SpecIterable<[string, string]> { readonly get: (name: string) => string | null readonly has: (name: string) => boolean readonly set: (name: string, value: string) => void + readonly getSetCookie: () => string[] readonly forEach: ( callbackfn: (value: string, key: string, iterable: Headers) => void, thisArg?: unknown diff --git a/deps/undici/undici.js b/deps/undici/undici.js index 9fd6547bd9529d..85070025e90576 100644 --- a/deps/undici/undici.js +++ b/deps/undici/undici.js @@ -438,24 +438,32 @@ var require_util = __commonJS({ for (let i = 0; i < headers.length; i += 2) { const key = headers[i].toString().toLowerCase(); let val = obj[key]; + const encoding = key.length === 19 && key === "content-disposition" ? "latin1" : "utf8"; if (!val) { if (Array.isArray(headers[i + 1])) { obj[key] = headers[i + 1]; } else { - obj[key] = headers[i + 1].toString(); + obj[key] = headers[i + 1].toString(encoding); } } else { if (!Array.isArray(val)) { val = [val]; obj[key] = val; } - val.push(headers[i + 1].toString()); + val.push(headers[i + 1].toString(encoding)); } } return obj; } function parseRawHeaders(headers) { - return headers.map((header) => header.toString()); + const ret = []; + for (let n = 0; n < headers.length; n += 2) { + const key = headers[n + 0].toString(); + const encoding = key.length === 19 && key.toLowerCase() === "content-disposition" ? "latin1" : "utf8"; + const val = headers[n + 1].toString(encoding); + ret.push(key, val); + } + return ret; } function isBuffer(buffer) { return buffer instanceof Uint8Array || Buffer.isBuffer(buffer); @@ -1607,10 +1615,14 @@ var require_headers = __commonJS({ isValidHeaderValue } = require_util2(); var { webidl } = require_webidl(); + var assert = require("assert"); var kHeadersMap = Symbol("headers map"); var kHeadersSortedMap = Symbol("headers map sorted"); function headerValueNormalize(potentialValue) { - return potentialValue.replace(/^[\r\n\t ]+|[\r\n\t ]+$/g, ""); + let i = potentialValue.length; + while (/[\r\n\t ]/.test(potentialValue.charAt(--i))) + ; + return potentialValue.slice(0, i + 1).replace(/^[\r\n\t ]+/, ""); } function fill(headers, object) { if (Array.isArray(object)) { @@ -1669,14 +1681,14 @@ var require_headers = __commonJS({ } if (lowercaseName === "set-cookie") { this.cookies ??= []; - this.cookies.push([name, value]); + this.cookies.push(value); } } set(name, value) { this[kHeadersSortedMap] = null; const lowercaseName = name.toLowerCase(); if (lowercaseName === "set-cookie") { - this.cookies = [[name, value]]; + this.cookies = [value]; } return this[kHeadersMap].set(lowercaseName, { name, value }); } @@ -1812,23 +1824,45 @@ var require_headers = __commonJS({ } return this[kHeadersList].set(name, value); } + getSetCookie() { + webidl.brandCheck(this, Headers); + const list = this[kHeadersList].cookies; + if (list) { + return [...list]; + } + return []; + } get [kHeadersSortedMap]() { - if (!this[kHeadersList][kHeadersSortedMap]) { - this[kHeadersList][kHeadersSortedMap] = new Map([...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1)); + if (this[kHeadersList][kHeadersSortedMap]) { + return this[kHeadersList][kHeadersSortedMap]; + } + const headers = []; + const names = [...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1); + const cookies = this[kHeadersList].cookies; + for (const [name, value] of names) { + if (name === "set-cookie") { + for (const value2 of cookies) { + headers.push([name, value2]); + } + } else { + assert(value !== null); + headers.push([name, value]); + } } - return this[kHeadersList][kHeadersSortedMap]; + this[kHeadersList][kHeadersSortedMap] = headers; + return headers; } keys() { webidl.brandCheck(this, Headers); - return makeIterator(() => [...this[kHeadersSortedMap].entries()], "Headers", "key"); + return makeIterator(() => [...this[kHeadersSortedMap].values()], "Headers", "key"); } values() { webidl.brandCheck(this, Headers); - return makeIterator(() => [...this[kHeadersSortedMap].entries()], "Headers", "value"); + return makeIterator(() => [...this[kHeadersSortedMap].values()], "Headers", "value"); } entries() { webidl.brandCheck(this, Headers); - return makeIterator(() => [...this[kHeadersSortedMap].entries()], "Headers", "key+value"); + return makeIterator(() => [...this[kHeadersSortedMap].values()], "Headers", "key+value"); } forEach(callbackFn, thisArg = globalThis) { webidl.brandCheck(this, Headers); @@ -6933,6 +6967,7 @@ var require_request = __commonJS({ var { URLSerializer } = require_dataURL(); var { kHeadersList } = require_symbols(); var assert = require("assert"); + var { setMaxListeners, getEventListeners, defaultMaxListeners } = require("events"); var TransformStream = globalThis.TransformStream; var kInit = Symbol("init"); var requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { @@ -7094,6 +7129,9 @@ var require_request = __commonJS({ const abort = function() { acRef.deref()?.abort(this.reason); }; + if (getEventListeners(signal, "abort").length >= defaultMaxListeners) { + setMaxListeners(100, signal); + } signal.addEventListener("abort", abort, { once: true }); requestFinalizer.register(this, { signal, abort }); } @@ -8149,6 +8187,9 @@ var require_request2 = __commonJS({ return; } if (request.host === null && key.length === 4 && key.toLowerCase() === "host") { + if (headerCharRegex.exec(val) !== null) { + throw new InvalidArgumentError(`invalid ${key} header`); + } request.host = val; } else if (request.contentLength === null && key.length === 14 && key.toLowerCase() === "content-length") { request.contentLength = parseInt(val, 10); diff --git a/src/undici_version.h b/src/undici_version.h index 89b01122b24b5f..620cd358695e17 100644 --- a/src/undici_version.h +++ b/src/undici_version.h @@ -2,5 +2,5 @@ // Refer to tools/update-undici.sh #ifndef SRC_UNDICI_VERSION_H_ #define SRC_UNDICI_VERSION_H_ -#define UNDICI_VERSION "5.18.0" +#define UNDICI_VERSION "5.19.1" #endif // SRC_UNDICI_VERSION_H_