From e8c131163a43104a9d46e2eb862261b8ab42069d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:30:01 +0000 Subject: [PATCH 1/2] Bump undici from 6.23.0 to 6.28.0 Bumps [undici](https://github.com/nodejs/undici) from 6.23.0 to 6.28.0. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v6.23.0...v6.28.0) --- updated-dependencies: - dependency-name: undici dependency-version: 6.28.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5a0d1c..18e2bd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12473,9 +12473,9 @@ } }, "node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", + "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", "license": "MIT", "engines": { "node": ">=18.17" From eb5cdbda30187f29cc680c348bdbc33021815771 Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Tue, 4 Aug 2026 16:31:08 +0200 Subject: [PATCH 2/2] Package code --- dist/index.js | 615 ++++++++++++++++++++++++++++++++++++++-------- dist/index.js.map | 2 +- 2 files changed, 512 insertions(+), 105 deletions(-) diff --git a/dist/index.js b/dist/index.js index 51c2e01..e5783ec 100644 --- a/dist/index.js +++ b/dist/index.js @@ -934,6 +934,24 @@ function requireErrors () { [kSecureProxyConnectionError] = true } + const kMessageSizeExceededError = Symbol.for('undici.error.UND_ERR_WS_MESSAGE_SIZE_EXCEEDED'); + class MessageSizeExceededError extends UndiciError { + constructor (message) { + super(message); + this.name = 'MessageSizeExceededError'; + this.message = message || 'Max decompressed message size exceeded'; + this.code = 'UND_ERR_WS_MESSAGE_SIZE_EXCEEDED'; + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kMessageSizeExceededError] === true + } + + get [kMessageSizeExceededError] () { + return true + } + } + errors = { AbortError, HTTPParserError, @@ -957,7 +975,8 @@ function requireErrors () { ResponseExceededMaxSizeError, RequestRetryError, ResponseError, - SecureProxyConnectionError + SecureProxyConnectionError, + MessageSizeExceededError }; return errors; } @@ -2258,6 +2277,10 @@ function requireRequest$1 () { throw new InvalidArgumentError('upgrade must be a string') } + if (upgrade && !isValidHeaderValue(upgrade)) { + throw new InvalidArgumentError('invalid upgrade header') + } + if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) { throw new InvalidArgumentError('invalid headersTimeout') } @@ -2538,7 +2561,13 @@ function requireRequest$1 () { } else if (typeof val[i] === 'object') { throw new InvalidArgumentError(`invalid ${key} header`) } else { - arr.push(`${val[i]}`); + // Coerce primitives (and reject unsafe coercions such as functions + // with a crafted toString/Symbol.toPrimitive). + const str = `${val[i]}`; + if (!isValidHeaderValue(str)) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + arr.push(str); } } val = arr; @@ -2549,16 +2578,27 @@ function requireRequest$1 () { } else if (val === null) { val = ''; } else { + // Coerce primitives (and reject unsafe coercions such as functions + // with a crafted toString/Symbol.toPrimitive). val = `${val}`; + if (!isValidHeaderValue(val)) { + throw new InvalidArgumentError(`invalid ${key} header`) + } } - if (request.host === null && headerName === 'host') { + if (headerName === 'host') { + if (request.host !== null) { + throw new InvalidArgumentError('duplicate host header') + } if (typeof val !== 'string') { throw new InvalidArgumentError('invalid host header') } // Consumed by Client request.host = val; - } else if (request.contentLength === null && headerName === 'content-length') { + } else if (headerName === 'content-length') { + if (request.contentLength !== null) { + throw new InvalidArgumentError('duplicate content-length header') + } request.contentLength = parseInt(val, 10); if (!Number.isFinite(request.contentLength)) { throw new InvalidArgumentError('invalid content-length header') @@ -2679,15 +2719,24 @@ function requireDispatcherBase () { const kOnDestroyed = Symbol('onDestroyed'); const kOnClosed = Symbol('onClosed'); const kInterceptedDispatch = Symbol('Intercepted Dispatch'); + const kWebSocketOptions = Symbol('webSocketOptions'); class DispatcherBase extends Dispatcher { - constructor () { + constructor (opts) { super(); this[kDestroyed] = false; this[kOnDestroyed] = null; this[kClosed] = false; this[kOnClosed] = []; + this[kWebSocketOptions] = opts?.webSocket ?? {}; + } + + get webSocketOptions () { + return { + maxFragments: this[kWebSocketOptions].maxFragments ?? 131072, + maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024 + } } get destroyed () { @@ -8590,6 +8639,7 @@ function requireClientH1 () { RequestContentLengthMismatchError, ResponseContentLengthMismatchError, RequestAbortedError, + InvalidArgumentError, HeadersTimeoutError, HeadersOverflowError, SocketError, @@ -8637,6 +8687,9 @@ function requireClientH1 () { const FastBuffer = Buffer[Symbol.species]; const addListener = util.addListener; const removeAllListeners = util.removeAllListeners; + const kIdleSocketValidation = Symbol('kIdleSocketValidation'); + const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout'); + const kSocketUsed = Symbol('kSocketUsed'); let extractBody; @@ -8859,29 +8912,71 @@ function requireClientH1 () { const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr; - if (ret === constants.ERROR.PAUSED_UPGRADE) { - this.onUpgrade(data.slice(offset)); - } else if (ret === constants.ERROR.PAUSED) { - this.paused = true; - socket.unshift(data.slice(offset)); - } else if (ret !== constants.ERROR.OK) { - const ptr = llhttp.llhttp_get_error_reason(this.ptr); - let message = ''; - /* istanbul ignore else: difficult to make a test case for */ - if (ptr) { - const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0); - message = - 'Response does not match the HTTP/1.1 protocol (' + - Buffer.from(llhttp.memory.buffer, ptr, len).toString() + - ')'; + if (ret !== constants.ERROR.OK) { + const body = data.subarray(offset); + + if (ret === constants.ERROR.PAUSED_UPGRADE) { + this.onUpgrade(body); + } else if (ret === constants.ERROR.PAUSED) { + this.paused = true; + socket.unshift(body); + } else { + throw this.createError(ret, body) } - throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset)) } } catch (err) { util.destroy(socket, err); } } + finish () { + assert(currentParser === null); + assert(this.ptr != null); + assert(!this.paused); + + const { llhttp } = this; + + let ret; + + try { + currentParser = this; + ret = llhttp.llhttp_finish(this.ptr); + } finally { + currentParser = null; + } + + if (ret === constants.ERROR.OK) { + return null + } + + if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) { + this.paused = true; + return null + } + + return this.createError(ret, EMPTY_BUF) + } + + createError (ret, data) { + const { llhttp, contentLength, bytesRead } = this; + + if (contentLength && bytesRead !== parseInt(contentLength, 10)) { + return new ResponseContentLengthMismatchError() + } + + const ptr = llhttp.llhttp_get_error_reason(this.ptr); + let message = ''; + if (ptr) { + const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0); + message = + 'Response does not match the HTTP/1.1 protocol (' + + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + + ')'; + } + + return new HTTPParserError(message, constants.ERROR[ret], data) + } + destroy () { assert(this.ptr != null); assert(currentParser == null); @@ -8909,6 +9004,11 @@ function requireClientH1 () { return -1 } + if (client[kRunning] === 0) { + util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket))); + return -1 + } + const request = client[kQueue][client[kRunningIdx]]; if (!request) { return -1 @@ -9012,6 +9112,11 @@ function requireClientH1 () { return -1 } + if (client[kRunning] === 0) { + util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket))); + return -1 + } + const request = client[kQueue][client[kRunningIdx]]; /* istanbul ignore next: difficult to make a test case for */ @@ -9185,6 +9290,7 @@ function requireClientH1 () { request.onComplete(headers); client[kQueue][client[kRunningIdx]++] = null; + socket[kSocketUsed] = true; if (socket[kWriting]) { assert(client[kRunning] === 0); @@ -9243,6 +9349,9 @@ function requireClientH1 () { socket[kWriting] = false; socket[kReset] = false; socket[kBlocking] = false; + socket[kIdleSocketValidation] = 0; + socket[kIdleSocketValidationTimeout] = null; + socket[kSocketUsed] = false; socket[kParser] = new Parser(client, socket, llhttpInstance); addListener(socket, 'error', function (err) { @@ -9253,8 +9362,11 @@ function requireClientH1 () { // On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded // to the user. if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) { - // We treat all incoming data so for as a valid response. - parser.onMessageComplete(); + const parserErr = parser.finish(); + if (parserErr) { + this[kError] = parserErr; + this[kClient][kOnError](parserErr); + } return } @@ -9273,8 +9385,10 @@ function requireClientH1 () { const parser = this[kParser]; if (parser.statusCode && !parser.shouldKeepAlive) { - // We treat all incoming data so far as a valid response. - parser.onMessageComplete(); + const parserErr = parser.finish(); + if (parserErr) { + util.destroy(this, parserErr); + } return } @@ -9284,10 +9398,11 @@ function requireClientH1 () { const client = this[kClient]; const parser = this[kParser]; + clearIdleSocketValidation(this); + if (parser) { if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) { - // We treat all incoming data so far as a valid response. - parser.onMessageComplete(); + this[kError] = parser.finish() || this[kError]; } this[kParser].destroy(); @@ -9350,7 +9465,7 @@ function requireClientH1 () { return socket.destroyed }, busy (request) { - if (socket[kWriting] || socket[kReset] || socket[kBlocking]) { + if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) { return true } @@ -9388,6 +9503,31 @@ function requireClientH1 () { } } + function clearIdleSocketValidation (socket) { + if (socket[kIdleSocketValidationTimeout]) { + clearTimeout(socket[kIdleSocketValidationTimeout]); + socket[kIdleSocketValidationTimeout] = null; + } + + socket[kIdleSocketValidation] = 0; + } + + function scheduleIdleSocketValidation (client, socket) { + socket[kIdleSocketValidation] = 1; + socket[kIdleSocketValidationTimeout] = setTimeout(() => { + socket[kIdleSocketValidationTimeout] = null; + socket[kIdleSocketValidation] = 2; + + if (client[kSocket] === socket && !socket.destroyed) { + client[kResume](); + } + }, 0); + socket[kIdleSocketValidationTimeout].unref?.(); + } + + /** + * @param {import('./client.js')} client + */ function resumeH1 (client) { const socket = client[kSocket]; @@ -9402,6 +9542,32 @@ function requireClientH1 () { socket[kNoRef] = false; } + if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) { + if (socket[kIdleSocketValidation] === 0) { + scheduleIdleSocketValidation(client, socket); + socket[kParser].readMore(); + if (socket.destroyed) { + return + } + return + } + + if (socket[kIdleSocketValidation] === 1) { + socket[kParser].readMore(); + if (socket.destroyed) { + return + } + return + } + } + + if (client[kRunning] === 0) { + socket[kParser].readMore(); + if (socket.destroyed) { + return + } + } + if (client[kSize] === 0) { if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) { socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE); @@ -9457,8 +9623,16 @@ function requireClientH1 () { } body = bodyStream.stream; contentLength = bodyStream.length; - } else if (util.isBlobLike(body) && request.contentType == null && body.type) { - headers.push('content-type', body.type); + } else if (util.isBlobLike(body) && request.contentType == null) { + const contentType = body.type; + if (contentType) { + const contentTypeValue = `${contentType}`; + if (!util.isValidHeaderValue(contentTypeValue)) { + util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header')); + return false + } + headers.push('content-type', contentTypeValue); + } } if (body && typeof body.read === 'function') { @@ -9495,6 +9669,7 @@ function requireClientH1 () { } const socket = client[kSocket]; + clearIdleSocketValidation(socket); const abort = (err) => { if (request.aborted || request.completed) { @@ -11065,9 +11240,10 @@ function requireClient () { autoSelectFamilyAttemptTimeout, // h2 maxConcurrentStreams, - allowH2 + allowH2, + webSocket } = {}) { - super(); + super({ webSocket }); if (keepAlive !== undefined) { throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead') @@ -11774,8 +11950,8 @@ function requirePoolBase () { const kStats = Symbol('stats'); class PoolBase extends DispatcherBase { - constructor () { - super(); + constructor (opts) { + super(opts); this[kQueue] = new FixedQueue(); this[kClients] = []; @@ -11994,8 +12170,6 @@ function requirePool () { allowH2, ...options } = {}) { - super(); - if (connections != null && (!Number.isFinite(connections) || connections < 0)) { throw new InvalidArgumentError('invalid connections') } @@ -12020,6 +12194,8 @@ function requirePool () { }); } + super(options); + this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool) ? options.interceptors.Pool : []; @@ -12313,8 +12489,6 @@ function requireAgent () { class Agent extends DispatcherBase { constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { - super(); - if (typeof factory !== 'function') { throw new InvalidArgumentError('factory must be a function.') } @@ -12327,6 +12501,8 @@ function requireAgent () { throw new InvalidArgumentError('maxRedirections must be a positive number') } + super(options); + if (connect && typeof connect !== 'function') { connect = { ...connect }; } @@ -12891,6 +13067,28 @@ function requireRetryHandler () { return new Date(retryAfter).getTime() - current } + function validatePartialResponseContentLength (headers, range, statusCode, retryCount) { + const contentLength = headers['content-length']; + if (contentLength == null) { + return null + } + + if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) { + return null + } + + const length = Number(contentLength); + const expectedLength = range.end - range.start + 1; + if (!Number.isFinite(length) || length !== expectedLength) { + return new RequestRetryError('Content-Length mismatch', statusCode, { + headers, + data: { count: retryCount } + }) + } + + return null + } + class RetryHandler { constructor (opts, handlers) { const { retryOptions, ...dispatchOpts } = opts; @@ -13105,6 +13303,12 @@ function requireRetryHandler () { return false } + const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount); + if (contentLengthError != null) { + this.abort(contentLengthError); + return false + } + const { start, size, end = size - 1 } = contentRange; assert(this.start === start, 'content-range mismatch'); @@ -13128,6 +13332,12 @@ function requireRetryHandler () { ) } + const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount); + if (contentLengthError != null) { + this.abort(contentLengthError); + return false + } + const { start, size, end = size - 1 } = range; assert( start != null && Number.isFinite(start), @@ -23516,7 +23726,7 @@ function requireUtil$2 () { if ( code < 0x20 || // exclude CTLs (0-31) - code === 0x7F || // DEL + code > 0x7E || // exclude DEL and non-ascii code === 0x3B // ; ) { throw new Error('Invalid cookie path') @@ -23525,16 +23735,80 @@ function requireUtil$2 () { } /** - * I have no idea why these values aren't allowed to be honest, - * but Deno tests these. - Khafra + * ::= | + * + * ::= any one of the 52 alphabetic characters A through Z in + * upper case and a through z in lower case + * + * ::= any one of the ten digits 0 through 9r + * + * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5 + * @param {number} code + */ + function isLetterOrDigit (code) { + return ( + (code >= 0x30 && code <= 0x39) || // 0-9 + (code >= 0x41 && code <= 0x5A) || // A-Z + (code >= 0x61 && code <= 0x7A) // a-z + ) + } + + /** + * Validates a cookie domain against the "preferred name syntax". + * + * ::= | " " + * ::=