From 349d704a2a3c9f1854b6d2f54bbba700048150ef Mon Sep 17 00:00:00 2001 From: Jitender Pal Singh Date: Thu, 9 Jul 2026 11:12:00 +0530 Subject: [PATCH 1/3] Add Node version validation and manifest fetch retry --- __tests__/official-installer.test.ts | 111 +- dist/cache-save/index.js | 466 +- dist/setup/index.js | 24572 +++++++++++++++- .../official_builds/official_builds.ts | 68 +- 4 files changed, 24406 insertions(+), 811 deletions(-) diff --git a/__tests__/official-installer.test.ts b/__tests__/official-installer.test.ts index fa46c35ef..dcb4528e7 100644 --- a/__tests__/official-installer.test.ts +++ b/__tests__/official-installer.test.ts @@ -129,7 +129,11 @@ describe('setup-node', () => { // @actions/exec getExecOutputSpy = jest.spyOn(exec, 'getExecOutput'); - getExecOutputSpy.mockImplementation(() => 'v16.15.0'); + getExecOutputSpy.mockImplementation(async () => ({ + stdout: 'v16.15.0', + stderr: '', + exitCode: 0 + })); }); afterEach(() => { @@ -250,6 +254,11 @@ describe('setup-node', () => { whichSpy.mockImplementation(cmd => { return `some/${cmd}/path`; }); + getExecOutputSpy.mockImplementation(async (cmd: string) => ({ + stdout: cmd === 'node' ? `v${resolvedVersion}` : '1.0.0', + stderr: '', + exitCode: 0 + })); await main.run(); @@ -627,7 +636,7 @@ describe('setup-node', () => { `Attempting to download ${versionSpec}...` ); expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`); - }); + }, 10000); }); describe('LTS version', () => { @@ -800,9 +809,9 @@ describe('setup-node', () => { 'Getting manifest from actions/node-versions@main' ); expect(cnSpy).toHaveBeenCalledWith( - `::error::Unable to download manifest${osm.EOL}` + `::error::Failed to fetch a valid manifest after 3 attempts. Last error: Unable to download manifest${osm.EOL}` ); - }); + }, 10000); }); describe('latest alias syntax', () => { @@ -824,10 +833,13 @@ describe('setup-node', () => { await main.run(); // assert - expect(logSpy).toHaveBeenCalledWith('Unable to download manifest'); + expect(logSpy).toHaveBeenCalledWith( + 'Failed to fetch a valid manifest after 3 attempts. Last error: Unable to download manifest' + ); expect(logSpy).toHaveBeenCalledWith('getting latest node version...'); - } + }, + 10000 ); }); @@ -898,4 +910,91 @@ describe('setup-node', () => { ); } }, 100000); + + describe('manifest retry and validation', () => { + beforeEach(() => { + os.platform = 'linux'; + os.arch = 'x64'; + inputs['node-version'] = 'lts/erbium'; + findSpy.mockImplementation(() => ''); + }); + + it('retries fetching the manifest and succeeds on a later attempt', async () => { + let calls = 0; + getManifestSpy.mockImplementation(() => { + calls++; + if (calls < 2) { + throw new Error('transient network failure'); + } + return nodeTestManifest; + }); + + dlSpy.mockImplementation(async () => '/some/temp/path'); + const toolPath = path.normalize('/cache/node/12.16.2/x64'); + exSpy.mockImplementation(async () => '/some/other/temp/path'); + cacheSpy.mockImplementation(async () => toolPath); + getExecOutputSpy.mockImplementation(async () => ({ + stdout: `v${path.basename(path.dirname(toolPath))}\n`, + stderr: '', + exitCode: 0 + })); + + await main.run(); + + expect(calls).toBe(2); + expect(logSpy).toHaveBeenCalledWith('Retrying to fetch the manifest...'); + expect(dbgSpy).toHaveBeenCalledWith( + `Found LTS release '12.16.2' for Node version 'lts/erbium'` + ); + }, 10000); + + it('rejects an empty manifest as invalid and retries', async () => { + getManifestSpy.mockImplementation(() => []); + + await main.run(); + + expect(getManifestSpy).toHaveBeenCalledTimes(3); + expect(cnSpy).toHaveBeenCalledWith( + `::error::Failed to fetch a valid manifest after 3 attempts. Last error: The manifest fetched is empty, truncated, or does not contain any valid tool release entries.${osm.EOL}` + ); + }, 10000); + }); + + describe('node version verification', () => { + beforeEach(() => { + os.platform = 'linux'; + os.arch = 'x64'; + inputs['node-version'] = '12'; + }); + + it('fails when the installed node version does not match the expected version', async () => { + const toolPath = path.normalize('/cache/node/12.16.1/x64'); + findSpy.mockReturnValue(toolPath); + getExecOutputSpy.mockImplementation(async () => ({ + stdout: 'v10.0.0\n', + stderr: '', + exitCode: 0 + })); + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith( + `::error::Node v12.16.1 installation failed, likely due to an incomplete or corrupted download.${osm.EOL}` + ); + }); + + it('fails when the node executable cannot be invoked', async () => { + const toolPath = path.normalize('/cache/node/12.16.1/x64'); + findSpy.mockReturnValue(toolPath); + getExecOutputSpy.mockImplementation(async () => { + throw new Error('node not found'); + }); + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith( + `::error::Node installation failed. Node may not be installed or not on PATH: node not found${osm.EOL}` + ); + }); + }); }); diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index d83e4de6f..7a305cd2c 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -49,7 +49,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.FinalizeCacheError = exports.CacheWriteDeniedError = exports.CACHE_WRITE_DENIED_PREFIX = exports.ReserveCacheError = exports.ValidationError = void 0; +exports.FinalizeCacheError = exports.ReserveCacheError = exports.ValidationError = void 0; exports.isFeatureAvailable = isFeatureAvailable; exports.restoreCache = restoreCache; exports.saveCache = saveCache; @@ -77,26 +77,6 @@ class ReserveCacheError extends Error { } } exports.ReserveCacheError = ReserveCacheError; -/** - * Stable prefix used by the cache receiver to signal that the token has - * no writable scopes (read-only cache policy). Consumers can match on - * this prefix to distinguish policy denials from ordinary contention. - */ -exports.CACHE_WRITE_DENIED_PREFIX = 'cache write denied:'; -/** - * Extends ReserveCacheError for source-compatibility: existing - * `instanceof ReserveCacheError` checks and `typedError.name === - * ReserveCacheError.name` paths keep working, while consumers that want to - * distinguish a policy denial can check for CacheWriteDeniedError.name. - */ -class CacheWriteDeniedError extends ReserveCacheError { - constructor(message) { - super(message); - this.name = 'CacheWriteDeniedError'; - Object.setPrototypeOf(this, CacheWriteDeniedError.prototype); - } -} -exports.CacheWriteDeniedError = CacheWriteDeniedError; class FinalizeCacheError extends Error { constructor(message) { super(message); @@ -407,11 +387,7 @@ function saveCacheV1(paths_1, key_1, options_1) { throw new Error((_d = (_c = reserveCacheResponse === null || reserveCacheResponse === void 0 ? void 0 : reserveCacheResponse.error) === null || _c === void 0 ? void 0 : _c.message) !== null && _d !== void 0 ? _d : `Cache size of ~${Math.round(archiveFileSize / (1024 * 1024))} MB (${archiveFileSize} B) is over the data cap limit, not saving cache.`); } else { - const detailMessage = (_e = reserveCacheResponse === null || reserveCacheResponse === void 0 ? void 0 : reserveCacheResponse.error) === null || _e === void 0 ? void 0 : _e.message; - if (detailMessage === null || detailMessage === void 0 ? void 0 : detailMessage.startsWith(exports.CACHE_WRITE_DENIED_PREFIX)) { - throw new CacheWriteDeniedError(`Unable to reserve cache with key ${key}. More details: ${detailMessage}`); - } - throw new ReserveCacheError(`Unable to reserve cache with key ${key}, another job may be creating this cache. More details: ${detailMessage}`); + throw new ReserveCacheError(`Unable to reserve cache with key ${key}, another job may be creating this cache. More details: ${(_e = reserveCacheResponse === null || reserveCacheResponse === void 0 ? void 0 : reserveCacheResponse.error) === null || _e === void 0 ? void 0 : _e.message}`); } core.debug(`Saving Cache (ID: ${cacheId})`); yield cacheHttpClient.saveCache(cacheId, archivePath, '', options); @@ -421,9 +397,6 @@ function saveCacheV1(paths_1, key_1, options_1) { if (typedError.name === ValidationError.name) { throw error; } - else if (typedError.name === CacheWriteDeniedError.name) { - core.warning(`Failed to save: ${typedError.message}`); - } else if (typedError.name === ReserveCacheError.name) { core.info(`Failed to save: ${typedError.message}`); } @@ -462,7 +435,6 @@ function saveCacheV1(paths_1, key_1, options_1) { */ function saveCacheV2(paths_1, key_1, options_1) { return __awaiter(this, arguments, void 0, function* (paths, key, options, enableCrossOsArchive = false) { - var _a; // Override UploadOptions to force the use of Azure // ...options goes first because we want to override the default values // set in UploadOptions with these specific figures @@ -498,11 +470,7 @@ function saveCacheV2(paths_1, key_1, options_1) { try { const response = yield twirpClient.CreateCacheEntry(request); if (!response.ok) { - // Skip the redundant inner warning when the receiver signalled a - // policy denial: the outer catch arm below will log a single - // customer-facing warning. - if (response.message && - !response.message.startsWith(exports.CACHE_WRITE_DENIED_PREFIX)) { + if (response.message) { core.warning(`Cache reservation failed: ${response.message}`); } throw new Error(response.message || 'Response was not ok'); @@ -511,10 +479,6 @@ function saveCacheV2(paths_1, key_1, options_1) { } catch (error) { core.debug(`Failed to reserve cache: ${error}`); - const errorMessage = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : ''; - if (errorMessage.startsWith(exports.CACHE_WRITE_DENIED_PREFIX)) { - throw new CacheWriteDeniedError(`Unable to reserve cache with key ${key}. More details: ${errorMessage}`); - } throw new ReserveCacheError(`Unable to reserve cache with key ${key}, another job may be creating this cache.`); } core.debug(`Attempting to upload cache located at: ${archivePath}`); @@ -539,9 +503,6 @@ function saveCacheV2(paths_1, key_1, options_1) { if (typedError.name === ValidationError.name) { throw error; } - else if (typedError.name === CacheWriteDeniedError.name) { - core.warning(`Failed to save: ${typedError.message}`); - } else if (typedError.name === ReserveCacheError.name) { core.info(`Failed to save: ${typedError.message}`); } @@ -22949,6 +22910,8 @@ function defaultFactory (origin, opts) { class Agent extends DispatcherBase { constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { + super() + if (typeof factory !== 'function') { throw new InvalidArgumentError('factory must be a function.') } @@ -22961,8 +22924,6 @@ class Agent extends DispatcherBase { throw new InvalidArgumentError('maxRedirections must be a positive number') } - super(options) - if (connect && typeof connect !== 'function') { connect = { ...connect } } @@ -23336,9 +23297,6 @@ const EMPTY_BUF = Buffer.alloc(0) 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 @@ -23561,71 +23519,29 @@ class Parser { const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr - 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) - } + 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() + + ')' + } + 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) @@ -23653,11 +23569,6 @@ class Parser { 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 @@ -23761,11 +23672,6 @@ class Parser { 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 */ @@ -23939,7 +23845,6 @@ class Parser { request.onComplete(headers) client[kQueue][client[kRunningIdx]++] = null - socket[kSocketUsed] = true if (socket[kWriting]) { assert(client[kRunning] === 0) @@ -23998,9 +23903,6 @@ async function connectH1 (client, socket) { 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) { @@ -24011,11 +23913,8 @@ async function connectH1 (client, socket) { // 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) { - const parserErr = parser.finish() - if (parserErr) { - this[kError] = parserErr - this[kClient][kOnError](parserErr) - } + // We treat all incoming data so for as a valid response. + parser.onMessageComplete() return } @@ -24034,10 +23933,8 @@ async function connectH1 (client, socket) { const parser = this[kParser] if (parser.statusCode && !parser.shouldKeepAlive) { - const parserErr = parser.finish() - if (parserErr) { - util.destroy(this, parserErr) - } + // We treat all incoming data so far as a valid response. + parser.onMessageComplete() return } @@ -24047,11 +23944,10 @@ async function connectH1 (client, socket) { const client = this[kClient] const parser = this[kParser] - clearIdleSocketValidation(this) - if (parser) { if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) { - this[kError] = parser.finish() || this[kError] + // We treat all incoming data so far as a valid response. + parser.onMessageComplete() } this[kParser].destroy() @@ -24114,7 +24010,7 @@ async function connectH1 (client, socket) { return socket.destroyed }, busy (request) { - if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) { + if (socket[kWriting] || socket[kReset] || socket[kBlocking]) { return true } @@ -24152,31 +24048,6 @@ async function connectH1 (client, socket) { } } -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] @@ -24191,32 +24062,6 @@ function resumeH1 (client) { 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) @@ -24310,7 +24155,6 @@ function writeH1 (client, request) { } const socket = client[kSocket] - clearIdleSocketValidation(socket) const abort = (err) => { if (request.aborted || request.completed) { @@ -25632,10 +25476,9 @@ class Client extends DispatcherBase { autoSelectFamilyAttemptTimeout, // h2 maxConcurrentStreams, - allowH2, - webSocket + allowH2 } = {}) { - super({ webSocket }) + super() if (keepAlive !== undefined) { throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead') @@ -26168,24 +26011,15 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = __nc const kOnDestroyed = Symbol('onDestroyed') const kOnClosed = Symbol('onClosed') const kInterceptedDispatch = Symbol('Intercepted Dispatch') -const kWebSocketOptions = Symbol('webSocketOptions') class DispatcherBase extends Dispatcher { - constructor (opts) { + constructor () { 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 () { @@ -26749,8 +26583,8 @@ const kRemoveClient = Symbol('remove client') const kStats = Symbol('stats') class PoolBase extends DispatcherBase { - constructor (opts) { - super(opts) + constructor () { + super() this[kQueue] = new FixedQueue() this[kClients] = [] @@ -27010,6 +26844,8 @@ class Pool extends PoolBase { allowH2, ...options } = {}) { + super() + if (connections != null && (!Number.isFinite(connections) || connections < 0)) { throw new InvalidArgumentError('invalid connections') } @@ -27034,8 +26870,6 @@ class Pool extends PoolBase { }) } - super(options) - this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool) ? options.interceptors.Pool : [] @@ -32120,25 +31954,32 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {}) // If the attribute-name case-insensitively matches the string // "SameSite", the user agent MUST process the cookie-av as follows: + // 1. Let enforcement be "Default". + let enforcement = 'Default' + const attributeValueLowercase = attributeValue.toLowerCase() + // 2. If cookie-av's attribute-value is a case-insensitive match for + // "None", set enforcement to "None". + if (attributeValueLowercase.includes('none')) { + enforcement = 'None' + } - // 1. If cookie-av's attribute-value is a case-insensitive match for - // "None", append an attribute to the cookie-attribute-list with an - // attribute-name of "SameSite" and an attribute-value of "None". - if (attributeValueLowercase === 'none') { - cookieAttributeList.sameSite = 'None' - } else if (attributeValueLowercase === 'strict') { - // 2. If cookie-av's attribute-value is a case-insensitive match for - // "Strict", append an attribute to the cookie-attribute-list with - // an attribute-name of "SameSite" and an attribute-value of - // "Strict". - cookieAttributeList.sameSite = 'Strict' - } else if (attributeValueLowercase === 'lax') { - // 3. If cookie-av's attribute-value is a case-insensitive match for - // "Lax", append an attribute to the cookie-attribute-list with an - // attribute-name of "SameSite" and an attribute-value of "Lax". - cookieAttributeList.sameSite = 'Lax' + // 3. If cookie-av's attribute-value is a case-insensitive match for + // "Strict", set enforcement to "Strict". + if (attributeValueLowercase.includes('strict')) { + enforcement = 'Strict' } + + // 4. If cookie-av's attribute-value is a case-insensitive match for + // "Lax", set enforcement to "Lax". + if (attributeValueLowercase.includes('lax')) { + enforcement = 'Lax' + } + + // 5. Append an attribute to the cookie-attribute-list with an + // attribute-name of "SameSite" and an attribute-value of + // enforcement. + cookieAttributeList.sameSite = enforcement } else { cookieAttributeList.unparsed ??= [] @@ -44844,35 +44685,40 @@ const tail = Buffer.from([0x00, 0x00, 0xff, 0xff]) const kBuffer = Symbol('kBuffer') const kLength = Symbol('kLength') +// Default maximum decompressed message size: 4 MB +const kDefaultMaxDecompressedSize = 4 * 1024 * 1024 + class PerMessageDeflate { /** @type {import('node:zlib').InflateRaw} */ #inflate #options = {} - #maxPayloadSize = 0 + /** @type {boolean} */ + #aborted = false + + /** @type {Function|null} */ + #currentCallback = null /** * @param {Map} extensions */ - constructor (extensions, options) { + constructor (extensions) { this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover') this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits') - - this.#maxPayloadSize = options.maxPayloadSize } - /** - * Decompress a compressed payload. - * @param {Buffer} chunk Compressed data - * @param {boolean} fin Final fragment flag - * @param {Function} callback Callback function - */ decompress (chunk, fin, callback) { // An endpoint uses the following algorithm to decompress a message. // 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the // payload of the message. // 2. Decompress the resulting data using DEFLATE. + + if (this.#aborted) { + callback(new MessageSizeExceededError()) + return + } + if (!this.#inflate) { let windowBits = Z_DEFAULT_WINDOWBITS @@ -44895,12 +44741,23 @@ class PerMessageDeflate { this.#inflate[kLength] = 0 this.#inflate.on('data', (data) => { + if (this.#aborted) { + return + } + this.#inflate[kLength] += data.length - if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) { - callback(new MessageSizeExceededError()) + if (this.#inflate[kLength] > kDefaultMaxDecompressedSize) { + this.#aborted = true this.#inflate.removeAllListeners() + this.#inflate.destroy() this.#inflate = null + + if (this.#currentCallback) { + const cb = this.#currentCallback + this.#currentCallback = null + cb(new MessageSizeExceededError()) + } return } @@ -44913,13 +44770,14 @@ class PerMessageDeflate { }) } + this.#currentCallback = callback this.#inflate.write(chunk) if (fin) { this.#inflate.write(tail) } this.#inflate.flush(() => { - if (!this.#inflate) { + if (this.#aborted || !this.#inflate) { return } @@ -44927,6 +44785,7 @@ class PerMessageDeflate { this.#inflate[kBuffer].length = 0 this.#inflate[kLength] = 0 + this.#currentCallback = null callback(null, full) }) @@ -44962,12 +44821,6 @@ const { const { WebsocketFrameSend } = __nccwpck_require__(3264) const { closeWebSocketConnection } = __nccwpck_require__(86897) const { PerMessageDeflate } = __nccwpck_require__(19469) -const { MessageSizeExceededError } = __nccwpck_require__(68707) - -function failWebsocketConnectionWithCode (ws, code, reason) { - closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason)) - failWebsocketConnection(ws, reason) -} // This code was influenced by ws released under the MIT license. // Copyright (c) 2011 Einar Otto Stangvik @@ -44976,7 +44829,6 @@ function failWebsocketConnectionWithCode (ws, code, reason) { class ByteParser extends Writable { #buffers = [] - #fragmentsBytes = 0 #byteOffset = 0 #loop = false @@ -44988,27 +44840,18 @@ class ByteParser extends Writable { /** @type {Map} */ #extensions - /** @type {number} */ - #maxFragments - - /** @type {number} */ - #maxPayloadSize - /** * @param {import('./websocket').WebSocket} ws * @param {Map|null} extensions - * @param {{ maxFragments?: number, maxPayloadSize?: number }} [options] */ - constructor (ws, extensions, options = {}) { + constructor (ws, extensions) { super() this.ws = ws this.#extensions = extensions == null ? new Map() : extensions - this.#maxFragments = options.maxFragments ?? 0 - this.#maxPayloadSize = options.maxPayloadSize ?? 0 if (this.#extensions.has('permessage-deflate')) { - this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options)) + this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions)) } } @@ -45024,19 +44867,6 @@ class ByteParser extends Writable { this.run(callback) } - #validatePayloadLength () { - if ( - this.#maxPayloadSize > 0 && - !isControlFrame(this.#info.opcode) && - this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize - ) { - failWebsocketConnectionWithCode(this.ws, 1009, 'Payload size exceeds maximum allowed size') - return false - } - - return true - } - /** * Runs whenever a new chunk is received. * Callback is called whenever there are no more chunks buffering, @@ -45125,10 +44955,6 @@ class ByteParser extends Writable { if (payloadLength <= 125) { this.#info.payloadLength = payloadLength this.#state = parserStates.READ_DATA - - if (!this.#validatePayloadLength()) { - return - } } else if (payloadLength === 126) { this.#state = parserStates.PAYLOADLENGTH_16 } else if (payloadLength === 127) { @@ -45153,10 +44979,6 @@ class ByteParser extends Writable { this.#info.payloadLength = buffer.readUInt16BE(0) this.#state = parserStates.READ_DATA - - if (!this.#validatePayloadLength()) { - return - } } else if (this.#state === parserStates.PAYLOADLENGTH_64) { if (this.#byteOffset < 8) { return callback() @@ -45179,10 +45001,6 @@ class ByteParser extends Writable { this.#info.payloadLength = lower this.#state = parserStates.READ_DATA - - if (!this.#validatePayloadLength()) { - return - } } else if (this.#state === parserStates.READ_DATA) { if (this.#byteOffset < this.#info.payloadLength) { return callback() @@ -45195,58 +45013,42 @@ class ByteParser extends Writable { this.#state = parserStates.INFO } else { if (!this.#info.compressed) { - if (!this.writeFragments(body)) { - return - } - - if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) { - failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message) - return - } + this.#fragments.push(body) // If the frame is not fragmented, a message has been received. // If the frame is fragmented, it will terminate with a fin bit set // and an opcode of 0 (continuation), therefore we handle that when // parsing continuation frames, not here. if (!this.#info.fragmented && this.#info.fin) { - websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments()) + const fullMessage = Buffer.concat(this.#fragments) + websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage) + this.#fragments.length = 0 } this.#state = parserStates.INFO } else { - this.#extensions.get('permessage-deflate').decompress( - body, - this.#info.fin, - (error, data) => { - if (error) { - const code = error instanceof MessageSizeExceededError ? 1009 : 1007 - failWebsocketConnectionWithCode(this.ws, code, error.message) - return - } - - if (!this.writeFragments(data)) { - return - } - - if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) { - failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message) - return - } - - if (!this.#info.fin) { - this.#state = parserStates.INFO - this.#loop = true - this.run(callback) - return - } + this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => { + if (error) { + failWebsocketConnection(this.ws, error.message) + return + } - websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments()) + this.#fragments.push(data) - this.#loop = true + if (!this.#info.fin) { this.#state = parserStates.INFO + this.#loop = true this.run(callback) + return } - ) + + websocketMessageReceived(this.ws, this.#info.binaryType, Buffer.concat(this.#fragments)) + + this.#loop = true + this.#state = parserStates.INFO + this.#fragments.length = 0 + this.run(callback) + }) this.#loop = false break @@ -45298,35 +45100,6 @@ class ByteParser extends Writable { return buffer } - writeFragments (fragment) { - if ( - this.#maxFragments > 0 && - this.#fragments.length === this.#maxFragments - ) { - failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments') - return false - } - - this.#fragmentsBytes += fragment.length - this.#fragments.push(fragment) - return true - } - - consumeFragments () { - const fragments = this.#fragments - - if (fragments.length === 1) { - this.#fragmentsBytes = 0 - return fragments.shift() - } - - const output = Buffer.concat(fragments, this.#fragmentsBytes) - this.#fragments = [] - this.#fragmentsBytes = 0 - - return output - } - parseCloseBody (data) { assert(data.length !== 1) @@ -46362,14 +46135,7 @@ class WebSocket extends EventTarget { // once this happens, the connection is open this[kResponse] = response - const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions - const maxFragments = webSocketOptions?.maxFragments - const maxPayloadSize = webSocketOptions?.maxPayloadSize - - const parser = new ByteParser(this, parsedExtensions, { - maxFragments, - maxPayloadSize - }) + const parser = new ByteParser(this, parsedExtensions) parser.on('drain', onParserDrain) parser.on('error', onParserError.bind(this)) @@ -88485,7 +88251,7 @@ function randomUUID() { /***/ 50591: /***/ ((module) => { -(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{XMLBuilder:()=>xe,XMLParser:()=>Jt,XMLValidator:()=>be});const i=":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD",n=new RegExp("^["+i+"]["+i+"\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$");function r(t,e){const i=[];let n=e.exec(t);for(;n;){const r=[];r.startIndex=e.lastIndex-n[0].length;const s=n.length;for(let t=0;t"!==t[s]&&" "!==t[s]&&"\t"!==t[s]&&"\n"!==t[s]&&"\r"!==t[s];s++)l+=t[s];if(l=l.trim(),"/"===l[l.length-1]&&(l=l.substring(0,l.length-1),s--),!E(l)){let e;return e=0===l.trim().length?"Invalid space after '<'.":"Tag '"+l+"' is an invalid name.",y("InvalidTag",e,w(t,s))}const p=g(t,s);if(!1===p)return y("InvalidAttr","Attributes for '"+l+"' have open quote.",w(t,s));let u=p.value;if(s=p.index,"/"===u[u.length-1]){const i=s-u.length;u=u.substring(0,u.length-1);const r=x(u,e);if(!0!==r)return y(r.err.code,r.err.msg,w(t,i+r.err.line));n=!0}else if(a){if(!p.tagClosed)return y("InvalidTag","Closing tag '"+l+"' doesn't have proper closing.",w(t,s));if(u.trim().length>0)return y("InvalidTag","Closing tag '"+l+"' can't have attributes or invalid starting.",w(t,o));if(0===i.length)return y("InvalidTag","Closing tag '"+l+"' has not been opened.",w(t,o));{const e=i.pop();if(l!==e.tagName){let i=w(t,e.tagStartPos);return y("InvalidTag","Expected closing tag '"+e.tagName+"' (opened in line "+i.line+", col "+i.col+") instead of closing tag '"+l+"'.",w(t,o))}0==i.length&&(r=!0)}}else{const a=x(u,e);if(!0!==a)return y(a.err.code,a.err.msg,w(t,s-u.length+a.err.line));if(!0===r)return y("InvalidXml","Multiple possible root nodes found.",w(t,s));-1!==e.unpairedTags.indexOf(l)||i.push({tagName:l,tagStartPos:o}),n=!0}for(s++;s0)||y("InvalidXml","Invalid '"+JSON.stringify(i.map(t=>t.tagName),null,4).replace(/\r?\n/g,"")+"' found.",{line:1,col:1}):y("InvalidXml","Start tag expected.",1)}function c(t){return" "===t||"\t"===t||"\n"===t||"\r"===t}function h(t,e){const i=e;for(;e5&&"xml"===n)return y("InvalidXml","XML declaration allowed only at the start of the document.",w(t,e));if("?"==t[e]&&">"==t[e+1]){e++;break}continue}return e}function d(t,e){if(t.length>e+5&&"-"===t[e+1]&&"-"===t[e+2]){for(e+=3;e"===t[e+2]){e+=2;break}}else if(t.length>e+8&&"D"===t[e+1]&&"O"===t[e+2]&&"C"===t[e+3]&&"T"===t[e+4]&&"Y"===t[e+5]&&"P"===t[e+6]&&"E"===t[e+7]){let i=1;for(e+=8;e"===t[e]&&(i--,0===i))break}else if(t.length>e+9&&"["===t[e+1]&&"C"===t[e+2]&&"D"===t[e+3]&&"A"===t[e+4]&&"T"===t[e+5]&&"A"===t[e+6]&&"["===t[e+7])for(e+=8;e"===t[e+2]){e+=2;break}return e}const u='"',f="'";function g(t,e){let i="",n="",r=!1;for(;e"===t[e]&&""===n){r=!0;break}i+=t[e]}return""===n&&{value:i,index:e,tagClosed:r}}const m=new RegExp("(\\s*)([^\\s=]+)(\\s*=)?(\\s*(['\"])(([\\s\\S])*?)\\5)?","g");function x(t,e){const i=r(t,m),n={};for(let t=0;to.includes(t)?"__"+t:t,A={preserveOrder:!1,attributeNamePrefix:"@_",attributesGroupName:!1,textNodeName:"#text",ignoreAttributes:!0,removeNSPrefix:!1,allowBooleanAttributes:!1,parseTagValue:!0,parseAttributeValue:!1,trimValues:!0,cdataPropName:!1,numberParseOptions:{hex:!0,leadingZeros:!0,eNotation:!0,unicode:!1},tagValueProcessor:function(t,e){return e},attributeValueProcessor:function(t,e){return e},stopNodes:[],alwaysCreateTextNode:!1,isArray:()=>!1,commentPropName:!1,unpairedTags:[],processEntities:!0,htmlEntities:!1,entityDecoder:null,ignoreDeclaration:!1,ignorePiTags:!1,transformTagName:!1,transformAttributeName:!1,updateTag:function(t,e,i){return t},captureMetaData:!1,maxNestedTags:100,strictReservedNames:!0,jPath:!0,onDangerousProperty:S};function T(t,e){if("string"!=typeof t)return;const i=t.toLowerCase();if(o.some(t=>i===t.toLowerCase()))throw new Error(`[SECURITY] Invalid ${e}: "${t}" is a reserved JavaScript keyword that could cause prototype pollution`);if(a.some(t=>i===t.toLowerCase()))throw new Error(`[SECURITY] Invalid ${e}: "${t}" is a reserved JavaScript keyword that could cause prototype pollution`)}function _(t,e){return"boolean"==typeof t?{enabled:t,maxEntitySize:1e4,maxExpansionDepth:1e4,maxTotalExpansions:1/0,maxExpandedLength:1e5,maxEntityCount:1e3,allowedTags:null,tagFilter:null,appliesTo:"all"}:"object"==typeof t&&null!==t?{enabled:!1!==t.enabled,maxEntitySize:Math.max(1,t.maxEntitySize??1e4),maxExpansionDepth:Math.max(1,t.maxExpansionDepth??1e4),maxTotalExpansions:Math.max(1,t.maxTotalExpansions??1/0),maxExpandedLength:Math.max(1,t.maxExpandedLength??1e5),maxEntityCount:Math.max(1,t.maxEntityCount??1e3),allowedTags:t.allowedTags??null,tagFilter:t.tagFilter??null,appliesTo:t.appliesTo??"all"}:_(!0)}const C=function(t){const e=Object.assign({},A,t),i=[{value:e.attributeNamePrefix,name:"attributeNamePrefix"},{value:e.attributesGroupName,name:"attributesGroupName"},{value:e.textNodeName,name:"textNodeName"},{value:e.cdataPropName,name:"cdataPropName"},{value:e.commentPropName,name:"commentPropName"}];for(const{value:t,name:e}of i)t&&T(t,e);return null===e.onDangerousProperty&&(e.onDangerousProperty=S),e.processEntities=_(e.processEntities,e.htmlEntities),e.unpairedTagsSet=new Set(e.unpairedTags),e.stopNodes&&Array.isArray(e.stopNodes)&&(e.stopNodes=e.stopNodes.map(t=>"string"==typeof t&&t.startsWith("*.")?".."+t.substring(2):t)),e};let $;$="function"!=typeof Symbol?"@@xmlMetadata":Symbol("XML Node Metadata");class O{constructor(t){this.tagname=t,this.child=[],this[":@"]=Object.create(null)}add(t,e){"__proto__"===t&&(t="#__proto__"),this.child.push({[t]:e})}addChild(t,e){"__proto__"===t.tagname&&(t.tagname="#__proto__"),t[":@"]&&Object.keys(t[":@"]).length>0?this.child.push({[t.tagname]:t.child,":@":t[":@"]}):this.child.push({[t.tagname]:t.child}),void 0!==e&&(this.child[this.child.length-1][$]={startIndex:e})}static getMetaDataSymbol(){return $}}const P=":A-Za-z_À-ÖØ-öø-˿Ͱ-ͽͿ-҆҈-῿‌-‍⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�",j=":A-Za-z_À-˿Ͱ-ͽͿ-҆҈-῿‌-‍⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�𐀀-󯿿",I=j+"\\-\\.\\d·̀-ͯ҇‿-⁀",k=(t,e,i="")=>{const n=`[${t.replace(":","")}][${e.replace(":","")}]*`;return{name:new RegExp(`^[${t}][${e}]*$`,i),ncName:new RegExp(`^${n}$`,i),qName:new RegExp(`^${n}(?::${n})?$`,i),nmToken:new RegExp(`^[${e}]+$`,i),nmTokens:new RegExp(`^[${e}]+(?:\\s+[${e}]+)*$`,i)}},L=k(P,P+"\\-\\.\\d·̀-ͯ‿-⁀"),D=k(j,I,"u"),R=(t,{xmlVersion:e="1.0"}={})=>((t="1.0")=>"1.1"===t?D:L)(e).qName.test(t);class M{constructor(t,e){this.suppressValidationErr=!t,this.options=t,this.xmlVersion=e||1}setXmlVersion(t=1){this.xmlVersion=t}readDocType(t,e){const i=Object.create(null);let n=0;if("O"!==t[e+3]||"C"!==t[e+4]||"T"!==t[e+5]||"Y"!==t[e+6]||"P"!==t[e+7]||"E"!==t[e+8])throw new Error("Invalid Tag instead of DOCTYPE");{e+=9;let r=1,s=!1,o=!1,a="";for(;e"===t[e]){if(o?"-"===t[e-1]&&"-"===t[e-2]&&(o=!1,r--):r--,0===r)break}else"["===t[e]?s=!0:a+=t[e];else{if(s&&q(t,"!ENTITY",e)){let r,s;if(e+=7,[r,s,e]=this.readEntityExp(t,e+1,this.suppressValidationErr),-1===s.indexOf("&")){if(!1!==this.options.enabled&&null!=this.options.maxEntityCount&&n>=this.options.maxEntityCount)throw new Error(`Entity count (${n+1}) exceeds maximum allowed (${this.options.maxEntityCount})`);i[r]=s,n++}}else if(s&&q(t,"!ELEMENT",e)){e+=8;const{index:i}=this.readElementExp(t,e+1);e=i}else if(s&&q(t,"!ATTLIST",e))e+=8;else if(s&&q(t,"!NOTATION",e)){e+=9;const{index:i}=this.readNotationExp(t,e+1,this.suppressValidationErr);e=i}else{if(!q(t,"!--",e))throw new Error("Invalid DOCTYPE");o=!0}r++,a=""}if(0!==r)throw new Error("Unclosed DOCTYPE")}return{entities:i,i:e}}readEntityExp(t,e){const i=e=V(t,e);for(;ethis.options.maxEntitySize)throw new Error(`Entity "${n}" size (${r.length}) exceeds maximum allowed size (${this.options.maxEntitySize})`);return[n,r,--e]}readNotationExp(t,e){const i=e=V(t,e);for(;e{for(;e=48&&r<=57||45===r))if(r=55296&&r<=56319){if(n+1=56320&&e<=57343){const t=65536+(r-55296<<10)+(e-56320);if(G.has(t)){i=n;break}}}}else if(255!==W[r-B]||X.has(r)){i=n;break}}if(-1===i)return t;const n=[];i>0&&n.push(t.slice(0,i));for(let r=i;r=48&&i<=57||45===i){n.push(t[r]);continue}if(i=55296&&i<=56319){if(r+1=56320&&e<=57343){const t=65536+(i-55296<<10)+(e-56320),s=G.get(t);if(void 0!==s){n.push(String.fromCharCode(s+48)),r++;continue}}}n.push(t[r]);continue}if(X.has(i)){n.push("-");continue}const s=W[i-B];n.push(255!==s?String.fromCharCode(s+48):t[r])}return n.join("")}(i),"0"===i))return 0;if(e.hex&&Y.test(i))return tt(i,16);if(e.binary&&z.test(i))return tt(i,2);if(e.octal&&H.test(i))return tt(i,8);if(isFinite(i)){if(i.includes("e")||i.includes("E"))return function(t,e,i){if(!i.eNotation)return t;const n=e.match(K);if(n){let r=n[1]||"";const s=-1===n[3].indexOf("e")?"E":"e",o=n[2],a=r?t[o.length+1]===s:t[o.length]===s;return o.length>1&&a?t:(1!==o.length||!n[3].startsWith(`.${s}`)&&n[3][0]!==s)&&o.length>0?i.leadingZeros&&!a?(e=(n[1]||"")+n[3],Number(e)):t:Number(e)}return t}(t,i,e);{const r=Q.exec(i);if(r){const s=r[1]||"",o=r[2];let a=(n=r[3])&&-1!==n.indexOf(".")?("."===(n=n.replace(/0+$/,""))?n="0":"."===n[0]?n="0"+n:"."===n[n.length-1]&&(n=n.substring(0,n.length-1)),n):n;const l=s?"."===t[o.length+1]:"."===t[o.length];if(!e.leadingZeros&&(o.length>1||1===o.length&&!l))return t;{const n=Number(i),r=String(n);if(0===n)return n;if(-1!==r.search(/[eE]/))return e.eNotation?n:t;if(-1!==i.indexOf("."))return"0"===r||r===a||r===`${s}${a}`?n:t;let l=o?a:i;return o?l===r||s+l===r?n:t:l===r||l===s+r?n:t}}return t}}var n;return function(t,e,i){const n=e===1/0;switch(i.infinity.toLowerCase()){case"null":return null;case"infinity":return e;case"string":return n?"Infinity":"-Infinity";default:return t}}(t,Number(i),e)}const K=/^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/;function tt(t,e){const i=t.trim();if(2!==e&&8!==e||(t=i.substring(2)),parseInt)return parseInt(t,e);if(Number.parseInt)return Number.parseInt(t,e);if(window&&window.parseInt)return window.parseInt(t,e);throw new Error("parseInt, Number.parseInt, window.parseInt are not supported")}class et{constructor(t){this._matcher=t}get separator(){return this._matcher.separator}getCurrentTag(){const t=this._matcher.path;return t.length>0?t[t.length-1].tag:void 0}getCurrentNamespace(){const t=this._matcher.path;return t.length>0?t[t.length-1].namespace:void 0}getAttrValue(t){const e=this._matcher.path;if(0!==e.length)return e[e.length-1].values?.[t]}hasAttr(t){const e=this._matcher.path;if(0===e.length)return!1;const i=e[e.length-1];return void 0!==i.values&&t in i.values}getPosition(){const t=this._matcher.path;return 0===t.length?-1:t[t.length-1].position??0}getCounter(){const t=this._matcher.path;return 0===t.length?-1:t[t.length-1].counter??0}getIndex(){return this.getPosition()}getDepth(){return this._matcher.path.length}toString(t,e=!0){return this._matcher.toString(t,e)}toArray(){return this._matcher.path.map(t=>t.tag)}matches(t){return this._matcher.matches(t)}matchesAny(t){return t.matchesAny(this._matcher)}}class it{constructor(t={}){this.separator=t.separator||".",this.path=[],this.siblingStacks=[],this._pathStringCache=null,this._view=new et(this)}push(t,e=null,i=null){this._pathStringCache=null,this.path.length>0&&(this.path[this.path.length-1].values=void 0);const n=this.path.length;this.siblingStacks[n]||(this.siblingStacks[n]=new Map);const r=this.siblingStacks[n],s=i?`${i}:${t}`:t,o=r.get(s)||0;let a=0;for(const t of r.values())a+=t;r.set(s,o+1);const l={tag:t,position:a,counter:o};null!=i&&(l.namespace=i),null!=e&&(l.values=e),this.path.push(l)}pop(){if(0===this.path.length)return;this._pathStringCache=null;const t=this.path.pop();return this.siblingStacks.length>this.path.length+1&&(this.siblingStacks.length=this.path.length+1),t}updateCurrent(t){if(this.path.length>0){const e=this.path[this.path.length-1];null!=t&&(e.values=t)}}getCurrentTag(){return this.path.length>0?this.path[this.path.length-1].tag:void 0}getCurrentNamespace(){return this.path.length>0?this.path[this.path.length-1].namespace:void 0}getAttrValue(t){if(0!==this.path.length)return this.path[this.path.length-1].values?.[t]}hasAttr(t){if(0===this.path.length)return!1;const e=this.path[this.path.length-1];return void 0!==e.values&&t in e.values}getPosition(){return 0===this.path.length?-1:this.path[this.path.length-1].position??0}getCounter(){return 0===this.path.length?-1:this.path[this.path.length-1].counter??0}getIndex(){return this.getPosition()}getDepth(){return this.path.length}toString(t,e=!0){const i=t||this.separator;if(i===this.separator&&!0===e){if(null!==this._pathStringCache)return this._pathStringCache;const t=this.path.map(t=>t.namespace?`${t.namespace}:${t.tag}`:t.tag).join(i);return this._pathStringCache=t,t}return this.path.map(t=>e&&t.namespace?`${t.namespace}:${t.tag}`:t.tag).join(i)}toArray(){return this.path.map(t=>t.tag)}reset(){this._pathStringCache=null,this.path=[],this.siblingStacks=[]}matches(t){const e=t.segments;return 0!==e.length&&(t.hasDeepWildcard()?this._matchWithDeepWildcard(e):this._matchSimple(e))}_matchSimple(t){if(this.path.length!==t.length)return!1;for(let e=0;e=0&&e>=0;){const n=t[i];if("deep-wildcard"===n.type){if(i--,i<0)return!0;const n=t[i];let r=!1;for(let t=e;t>=0;t--)if(this._matchSegment(n,this.path[t],t===this.path.length-1)){e=t-1,i--,r=!0;break}if(!r)return!1}else{if(!this._matchSegment(n,this.path[e],e===this.path.length-1))return!1;e--,i--}}return i<0}_matchSegment(t,e,i){if("*"!==t.tag&&t.tag!==e.tag)return!1;if(void 0!==t.namespace&&"*"!==t.namespace&&t.namespace!==e.namespace)return!1;if(void 0!==t.attrName){if(!i)return!1;if(!e.values||!(t.attrName in e.values))return!1;if(void 0!==t.attrValue&&String(e.values[t.attrName])!==String(t.attrValue))return!1}if(void 0!==t.position){if(!i)return!1;const n=e.counter??0;if("first"===t.position&&0!==n)return!1;if("odd"===t.position&&n%2!=1)return!1;if("even"===t.position&&n%2!=0)return!1;if("nth"===t.position&&n!==t.positionValue)return!1}return!0}matchesAny(t){return t.matchesAny(this)}snapshot(){return{path:this.path.map(t=>({...t})),siblingStacks:this.siblingStacks.map(t=>new Map(t))}}restore(t){this._pathStringCache=null,this.path=t.path.map(t=>({...t})),this.siblingStacks=t.siblingStacks.map(t=>new Map(t))}readOnly(){return this._view}}class nt{constructor(t,e={},i){this.pattern=t,this.separator=e.separator||".",this.segments=this._parse(t),this.data=i,this._hasDeepWildcard=this.segments.some(t=>"deep-wildcard"===t.type),this._hasAttributeCondition=this.segments.some(t=>void 0!==t.attrName),this._hasPositionSelector=this.segments.some(t=>void 0!==t.position)}_parse(t){const e=[];let i=0,n="";for(;i",lt:"<",quot:'"'},at={nbsp:" ",copy:"©",reg:"®",trade:"™",mdash:"—",ndash:"–",hellip:"…",laquo:"«",raquo:"»",lsquo:"‘",rsquo:"’",ldquo:"“",rdquo:"”",bull:"•",para:"¶",sect:"§",deg:"°",frac12:"½",frac14:"¼",frac34:"¾"},lt=Object.freeze({ALLOW:"allow",BLOCK:"block",THROW:"throw"}),pt=new Set("!?\\\\/[]$%{}^&*()<>|+");function ct(t){if("#"===t[0])throw new Error(`[EntityReplacer] Invalid character '#' in entity name: "${t}"`);for(const e of t)if(pt.has(e))throw new Error(`[EntityReplacer] Invalid character '${e}' in entity name: "${t}"`);return t}function ht(...t){const e=Object.create(null);for(const i of t)if(i)for(const t of Object.keys(i)){const n=i[t];if("string"==typeof n)e[t]=n;else if(n&&"object"==typeof n&&void 0!==n.val){const i=n.val;"string"==typeof i&&(e[t]=i)}}return e}const dt="external",ut="base",ft="all",gt=Object.freeze({allow:0,leave:1,remove:2,throw:3}),mt=new Set([9,10,13]);class xt{constructor(t={}){var e;this._limit=t.limit||{},this._maxTotalExpansions=this._limit.maxTotalExpansions||0,this._maxExpandedLength=this._limit.maxExpandedLength||0,this._postCheck="function"==typeof t.postCheck?t.postCheck:t=>t,this._limitTiers=(e=this._limit.applyLimitsTo??dt)&&e!==dt?e===ft?new Set([ft]):e===ut?new Set([ut]):Array.isArray(e)?new Set(e):new Set([dt]):new Set([dt]),this._numericAllowed=t.numericAllowed??!0,this._baseMap=ht(ot,t.namedEntities||null),this._externalMap=Object.create(null),this._inputMap=Object.create(null),this._totalExpansions=0,this._expandedLength=0,this._removeSet=new Set(t.remove&&Array.isArray(t.remove)?t.remove:[]),this._leaveSet=new Set(t.leave&&Array.isArray(t.leave)?t.leave:[]);const i=function(t){if(!t)return{xmlVersion:1,onLevel:gt.allow,nullLevel:gt.remove};const e=1.1===t.xmlVersion?1.1:1,i=gt[t.onNCR]??gt.allow,n=gt[t.nullNCR]??gt.remove;return{xmlVersion:e,onLevel:i,nullLevel:Math.max(n,gt.remove)}}(t.ncr);this._ncrXmlVersion=i.xmlVersion,this._ncrOnLevel=i.onLevel,this._ncrNullLevel=i.nullLevel,this._onExternalEntity="function"==typeof t.onExternalEntity?t.onExternalEntity:null,this._onInputEntity="function"==typeof t.onInputEntity?t.onInputEntity:null}_applyRegistrationHook(t,e,i,n){if(!t)return!0;const r=t(e,i);if(r===lt.BLOCK)return!1;if(r===lt.THROW)throw new Error(`[EntityDecoder] Registration of ${n} entity "&${e};" was rejected by hook`);return!0}setExternalEntities(t){if(t)for(const e of Object.keys(t))ct(e);if(!this._onExternalEntity)return void(this._externalMap=ht(t));const e=ht(t),i=Object.create(null);for(const[t,n]of Object.entries(e))this._applyRegistrationHook(this._onExternalEntity,t,n,"external")&&(i[t]=n);this._externalMap=i}addExternalEntity(t,e){ct(t),"string"==typeof e&&-1===e.indexOf("&")&&this._applyRegistrationHook(this._onExternalEntity,t,e,"external")&&(this._externalMap[t]=e)}addInputEntities(t){if(this._totalExpansions=0,this._expandedLength=0,!this._onInputEntity)return void(this._inputMap=ht(t));const e=ht(t),i=Object.create(null);for(const[t,n]of Object.entries(e))this._applyRegistrationHook(this._onInputEntity,t,n,"input")&&(i[t]=n);this._inputMap=i}reset(){return this._inputMap=Object.create(null),this._totalExpansions=0,this._expandedLength=0,this}setXmlVersion(t){this._ncrXmlVersion=1.1===t?1.1:1}decode(t){if("string"!=typeof t||0===t.length)return t;if(-1===t.indexOf("&"))return t;const e=t,i=[],n=t.length;let r=0,s=0;const o=this._maxTotalExpansions>0,a=this._maxExpandedLength>0,l=o||a;for(;s=n||59!==t.charCodeAt(e)){s++;continue}const p=t.slice(s+1,e);if(0===p.length){s++;continue}let c,h;if(this._removeSet.has(p))c="",void 0===h&&(h=dt);else{if(this._leaveSet.has(p)){s++;continue}if(35===p.charCodeAt(0)){const t=this._resolveNCR(p);if(void 0===t){s++;continue}c=t,h=ut}else{const t=this._resolveName(p);c=t?.value,h=t?.tier}}if(void 0!==c){if(s>r&&i.push(t.slice(r,s)),i.push(c),r=e+1,s=r,l&&this._tierCounts(h)){if(o&&(this._totalExpansions++,this._totalExpansions>this._maxTotalExpansions))throw new Error(`[EntityReplacer] Entity expansion count limit exceeded: ${this._totalExpansions} > ${this._maxTotalExpansions}`);if(a){const t=c.length-(p.length+2);if(t>0&&(this._expandedLength+=t,this._expandedLength>this._maxExpandedLength))throw new Error(`[EntityReplacer] Expanded content length limit exceeded: ${this._expandedLength} > ${this._maxExpandedLength}`)}}}else s++}r=55296&&t<=57343||1===this._ncrXmlVersion&&t>=1&&t<=31&&!mt.has(t)?gt.remove:-1}_applyNCRAction(t,e,i){switch(t){case gt.allow:return String.fromCodePoint(i);case gt.remove:return"";case gt.leave:return;case gt.throw:throw new Error(`[EntityDecoder] Prohibited numeric character reference &${e}; (U+${i.toString(16).toUpperCase().padStart(4,"0")})`);default:return String.fromCodePoint(i)}}_resolveNCR(t){const e=t.charCodeAt(1);let i;if(i=120===e||88===e?parseInt(t.slice(2),16):parseInt(t.slice(1),10),Number.isNaN(i)||i<0||i>1114111)return;const n=this._classifyNCR(i);if(!this._numericAllowed&&n/]/i},{id:"html-script-close",description:"<\/script closing tag",pattern:/<\/script[\s>]/i},{id:"html-javascript-protocol",description:"javascript: URI scheme (with optional whitespace/encoding)",pattern:/j[\t\n\r ]*a[\t\n\r ]*v[\t\n\r ]*a[\t\n\r ]*s[\t\n\r ]*c[\t\n\r ]*r[\t\n\r ]*i[\t\n\r ]*p[\t\n\r ]*t[\t\n\r ]*:/i},{id:"html-vbscript-protocol",description:"vbscript: URI scheme",pattern:/vbscript[\t\n\r ]*:/i},{id:"html-data-html",description:"data:text/html URI — can execute scripts in browsers",pattern:/data[\t\n\r ]*:[\t\n\r ]*text\/html/i},{id:"html-data-xhtml",description:"data:application/xhtml+xml URI",pattern:/data[\t\n\r ]*:[\t\n\r ]*application\/xhtml/i},{id:"html-data-svg",description:"data:image/svg+xml URI — can execute scripts",pattern:/data[\t\n\r ]*:[\t\n\r ]*image\/svg\+xml/i},{id:"html-inline-event-handler",description:"Inline event handler attributes: onclick=, onerror=, onload=, etc.",pattern:/\bon\w{1,30}\s*=/i},{id:"html-entity-obfuscated-script",description:"HTML-entity-encoded