Bump undici from 6.24.0 to 6.27.0 in the npm_and_yarn group across 1 directory (#477)
c35be0e · dependabot[bot] · 2026-06-29 22:24
Message
{commit_body(@commit)}
Files changed
modified
dist/index.js
+303
−139
@@ -7047,8 +7047,6 @@ function defaultFactory (origin, opts) {
| 7047 | 7047 | |
| 7048 | 7048 | class Agent extends DispatcherBase { |
| 7049 | 7049 | constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { |
| 7050 | − super() | |
| 7051 | − | |
| 7052 | 7050 | if (typeof factory !== 'function') { |
| 7053 | 7051 | throw new InvalidArgumentError('factory must be a function.') |
| 7054 | 7052 | } |
@@ -7061,6 +7059,8 @@ class Agent extends DispatcherBase {
| 7061 | 7059 | throw new InvalidArgumentError('maxRedirections must be a positive number') |
| 7062 | 7060 | } |
| 7063 | 7061 | |
| 7062 | + super(options) | |
| 7063 | + | |
| 7064 | 7064 | if (connect && typeof connect !== 'function') { |
| 7065 | 7065 | connect = { ...connect } |
| 7066 | 7066 | } |
@@ -7432,6 +7432,9 @@ const EMPTY_BUF = Buffer.alloc(0)
| 7432 | 7432 | const FastBuffer = Buffer[Symbol.species] |
| 7433 | 7433 | const addListener = util.addListener |
| 7434 | 7434 | const removeAllListeners = util.removeAllListeners |
| 7435 | +const kIdleSocketValidation = Symbol('kIdleSocketValidation') | |
| 7436 | +const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout') | |
| 7437 | +const kSocketUsed = Symbol('kSocketUsed') | |
| 7435 | 7438 | |
| 7436 | 7439 | let extractBody |
| 7437 | 7440 |
@@ -7654,29 +7657,71 @@ class Parser {
| 7654 | 7657 | |
| 7655 | 7658 | const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr |
| 7656 | 7659 | |
| 7657 | − if (ret === constants.ERROR.PAUSED_UPGRADE) { | |
| 7658 | − this.onUpgrade(data.slice(offset)) | |
| 7659 | − } else if (ret === constants.ERROR.PAUSED) { | |
| 7660 | − this.paused = true | |
| 7661 | − socket.unshift(data.slice(offset)) | |
| 7662 | − } else if (ret !== constants.ERROR.OK) { | |
| 7663 | − const ptr = llhttp.llhttp_get_error_reason(this.ptr) | |
| 7664 | − let message = '' | |
| 7665 | − /* istanbul ignore else: difficult to make a test case for */ | |
| 7666 | − if (ptr) { | |
| 7667 | − const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0) | |
| 7668 | − message = | |
| 7669 | − 'Response does not match the HTTP/1.1 protocol (' + | |
| 7670 | − Buffer.from(llhttp.memory.buffer, ptr, len).toString() + | |
| 7671 | − ')' | |
| 7672 | − } | |
| 7673 | − throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset)) | |
| 7660 | + if (ret !== constants.ERROR.OK) { | |
| 7661 | + const body = data.subarray(offset) | |
| 7662 | + | |
| 7663 | + if (ret === constants.ERROR.PAUSED_UPGRADE) { | |
| 7664 | + this.onUpgrade(body) | |
| 7665 | + } else if (ret === constants.ERROR.PAUSED) { | |
| 7666 | + this.paused = true | |
| 7667 | + socket.unshift(body) | |
| 7668 | + } else { | |
| 7669 | + throw this.createError(ret, body) | |
| 7670 | + } | |
| 7674 | 7671 | } |
| 7675 | 7672 | } catch (err) { |
| 7676 | 7673 | util.destroy(socket, err) |
| 7677 | 7674 | } |
| 7678 | 7675 | } |
| 7679 | 7676 | |
| 7677 | + finish () { | |
| 7678 | + assert(currentParser === null) | |
| 7679 | + assert(this.ptr != null) | |
| 7680 | + assert(!this.paused) | |
| 7681 | + | |
| 7682 | + const { llhttp } = this | |
| 7683 | + | |
| 7684 | + let ret | |
| 7685 | + | |
| 7686 | + try { | |
| 7687 | + currentParser = this | |
| 7688 | + ret = llhttp.llhttp_finish(this.ptr) | |
| 7689 | + } finally { | |
| 7690 | + currentParser = null | |
| 7691 | + } | |
| 7692 | + | |
| 7693 | + if (ret === constants.ERROR.OK) { | |
| 7694 | + return null | |
| 7695 | + } | |
| 7696 | + | |
| 7697 | + if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) { | |
| 7698 | + this.paused = true | |
| 7699 | + return null | |
| 7700 | + } | |
| 7701 | + | |
| 7702 | + return this.createError(ret, EMPTY_BUF) | |
| 7703 | + } | |
| 7704 | + | |
| 7705 | + createError (ret, data) { | |
| 7706 | + const { llhttp, contentLength, bytesRead } = this | |
| 7707 | + | |
| 7708 | + if (contentLength && bytesRead !== parseInt(contentLength, 10)) { | |
| 7709 | + return new ResponseContentLengthMismatchError() | |
| 7710 | + } | |
| 7711 | + | |
| 7712 | + const ptr = llhttp.llhttp_get_error_reason(this.ptr) | |
| 7713 | + let message = '' | |
| 7714 | + if (ptr) { | |
| 7715 | + const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0) | |
| 7716 | + message = | |
| 7717 | + 'Response does not match the HTTP/1.1 protocol (' + | |
| 7718 | + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + | |
| 7719 | + ')' | |
| 7720 | + } | |
| 7721 | + | |
| 7722 | + return new HTTPParserError(message, constants.ERROR[ret], data) | |
| 7723 | + } | |
| 7724 | + | |
| 7680 | 7725 | destroy () { |
| 7681 | 7726 | assert(this.ptr != null) |
| 7682 | 7727 | assert(currentParser == null) |
@@ -7704,6 +7749,11 @@ class Parser {
| 7704 | 7749 | return -1 |
| 7705 | 7750 | } |
| 7706 | 7751 | |
| 7752 | + if (client[kRunning] === 0) { | |
| 7753 | + util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket))) | |
| 7754 | + return -1 | |
| 7755 | + } | |
| 7756 | + | |
| 7707 | 7757 | const request = client[kQueue][client[kRunningIdx]] |
| 7708 | 7758 | if (!request) { |
| 7709 | 7759 | return -1 |
@@ -7807,6 +7857,11 @@ class Parser {
| 7807 | 7857 | return -1 |
| 7808 | 7858 | } |
| 7809 | 7859 | |
| 7860 | + if (client[kRunning] === 0) { | |
| 7861 | + util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket))) | |
| 7862 | + return -1 | |
| 7863 | + } | |
| 7864 | + | |
| 7810 | 7865 | const request = client[kQueue][client[kRunningIdx]] |
| 7811 | 7866 | |
| 7812 | 7867 | /* istanbul ignore next: difficult to make a test case for */ |
@@ -7980,6 +8035,7 @@ class Parser {
| 7980 | 8035 | request.onComplete(headers) |
| 7981 | 8036 | |
| 7982 | 8037 | client[kQueue][client[kRunningIdx]++] = null |
| 8038 | + socket[kSocketUsed] = true | |
| 7983 | 8039 | |
| 7984 | 8040 | if (socket[kWriting]) { |
| 7985 | 8041 | assert(client[kRunning] === 0) |
@@ -8038,6 +8094,9 @@ async function connectH1 (client, socket) {
| 8038 | 8094 | socket[kWriting] = false |
| 8039 | 8095 | socket[kReset] = false |
| 8040 | 8096 | socket[kBlocking] = false |
| 8097 | + socket[kIdleSocketValidation] = 0 | |
| 8098 | + socket[kIdleSocketValidationTimeout] = null | |
| 8099 | + socket[kSocketUsed] = false | |
| 8041 | 8100 | socket[kParser] = new Parser(client, socket, llhttpInstance) |
| 8042 | 8101 | |
| 8043 | 8102 | addListener(socket, 'error', function (err) { |
@@ -8048,8 +8107,11 @@ async function connectH1 (client, socket) {
| 8048 | 8107 | // On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded |
| 8049 | 8108 | // to the user. |
| 8050 | 8109 | if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) { |
| 8051 | − // We treat all incoming data so for as a valid response. | |
| 8052 | − parser.onMessageComplete() | |
| 8110 | + const parserErr = parser.finish() | |
| 8111 | + if (parserErr) { | |
| 8112 | + this[kError] = parserErr | |
| 8113 | + this[kClient][kOnError](parserErr) | |
| 8114 | + } | |
| 8053 | 8115 | return |
| 8054 | 8116 | } |
| 8055 | 8117 |
@@ -8068,8 +8130,10 @@ async function connectH1 (client, socket) {
| 8068 | 8130 | const parser = this[kParser] |
| 8069 | 8131 | |
| 8070 | 8132 | if (parser.statusCode && !parser.shouldKeepAlive) { |
| 8071 | − // We treat all incoming data so far as a valid response. | |
| 8072 | − parser.onMessageComplete() | |
| 8133 | + const parserErr = parser.finish() | |
| 8134 | + if (parserErr) { | |
| 8135 | + util.destroy(this, parserErr) | |
| 8136 | + } | |
| 8073 | 8137 | return |
| 8074 | 8138 | } |
| 8075 | 8139 |
@@ -8079,10 +8143,11 @@ async function connectH1 (client, socket) {
| 8079 | 8143 | const client = this[kClient] |
| 8080 | 8144 | const parser = this[kParser] |
| 8081 | 8145 | |
| 8146 | + clearIdleSocketValidation(this) | |
| 8147 | + | |
| 8082 | 8148 | if (parser) { |
| 8083 | 8149 | if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) { |
| 8084 | − // We treat all incoming data so far as a valid response. | |
| 8085 | − parser.onMessageComplete() | |
| 8150 | + this[kError] = parser.finish() || this[kError] | |
| 8086 | 8151 | } |
| 8087 | 8152 | |
| 8088 | 8153 | this[kParser].destroy() |
@@ -8145,7 +8210,7 @@ async function connectH1 (client, socket) {
| 8145 | 8210 | return socket.destroyed |
| 8146 | 8211 | }, |
| 8147 | 8212 | busy (request) { |
| 8148 | − if (socket[kWriting] || socket[kReset] || socket[kBlocking]) { | |
| 8213 | + if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) { | |
| 8149 | 8214 | return true |
| 8150 | 8215 | } |
| 8151 | 8216 |
@@ -8183,6 +8248,31 @@ async function connectH1 (client, socket) {
| 8183 | 8248 | } |
| 8184 | 8249 | } |
| 8185 | 8250 | |
| 8251 | +function clearIdleSocketValidation (socket) { | |
| 8252 | + if (socket[kIdleSocketValidationTimeout]) { | |
| 8253 | + clearTimeout(socket[kIdleSocketValidationTimeout]) | |
| 8254 | + socket[kIdleSocketValidationTimeout] = null | |
| 8255 | + } | |
| 8256 | + | |
| 8257 | + socket[kIdleSocketValidation] = 0 | |
| 8258 | +} | |
| 8259 | + | |
| 8260 | +function scheduleIdleSocketValidation (client, socket) { | |
| 8261 | + socket[kIdleSocketValidation] = 1 | |
| 8262 | + socket[kIdleSocketValidationTimeout] = setTimeout(() => { | |
| 8263 | + socket[kIdleSocketValidationTimeout] = null | |
| 8264 | + socket[kIdleSocketValidation] = 2 | |
| 8265 | + | |
| 8266 | + if (client[kSocket] === socket && !socket.destroyed) { | |
| 8267 | + client[kResume]() | |
| 8268 | + } | |
| 8269 | + }, 0) | |
| 8270 | + socket[kIdleSocketValidationTimeout].unref?.() | |
| 8271 | +} | |
| 8272 | + | |
| 8273 | +/** | |
| 8274 | + * @param {import('./client.js')} client | |
| 8275 | + */ | |
| 8186 | 8276 | function resumeH1 (client) { |
| 8187 | 8277 | const socket = client[kSocket] |
| 8188 | 8278 |
@@ -8197,6 +8287,32 @@ function resumeH1 (client) {
| 8197 | 8287 | socket[kNoRef] = false |
| 8198 | 8288 | } |
| 8199 | 8289 | |
| 8290 | + if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) { | |
| 8291 | + if (socket[kIdleSocketValidation] === 0) { | |
| 8292 | + scheduleIdleSocketValidation(client, socket) | |
| 8293 | + socket[kParser].readMore() | |
| 8294 | + if (socket.destroyed) { | |
| 8295 | + return | |
| 8296 | + } | |
| 8297 | + return | |
| 8298 | + } | |
| 8299 | + | |
| 8300 | + if (socket[kIdleSocketValidation] === 1) { | |
| 8301 | + socket[kParser].readMore() | |
| 8302 | + if (socket.destroyed) { | |
| 8303 | + return | |
| 8304 | + } | |
| 8305 | + return | |
| 8306 | + } | |
| 8307 | + } | |
| 8308 | + | |
| 8309 | + if (client[kRunning] === 0) { | |
| 8310 | + socket[kParser].readMore() | |
| 8311 | + if (socket.destroyed) { | |
| 8312 | + return | |
| 8313 | + } | |
| 8314 | + } | |
| 8315 | + | |
| 8200 | 8316 | if (client[kSize] === 0) { |
| 8201 | 8317 | if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) { |
| 8202 | 8318 | socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE) |
@@ -8290,6 +8406,7 @@ function writeH1 (client, request) {
| 8290 | 8406 | } |
| 8291 | 8407 | |
| 8292 | 8408 | const socket = client[kSocket] |
| 8409 | + clearIdleSocketValidation(socket) | |
| 8293 | 8410 | |
| 8294 | 8411 | const abort = (err) => { |
| 8295 | 8412 | if (request.aborted || request.completed) { |
@@ -9609,9 +9726,10 @@ class Client extends DispatcherBase {
| 9609 | 9726 | autoSelectFamilyAttemptTimeout, |
| 9610 | 9727 | // h2 |
| 9611 | 9728 | maxConcurrentStreams, |
| 9612 | − allowH2 | |
| 9729 | + allowH2, | |
| 9730 | + webSocket | |
| 9613 | 9731 | } = {}) { |
| 9614 | − super() | |
| 9732 | + super({ webSocket }) | |
| 9615 | 9733 | |
| 9616 | 9734 | if (keepAlive !== undefined) { |
| 9617 | 9735 | throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead') |
@@ -10143,15 +10261,24 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = __nc
| 10143 | 10261 | const kOnDestroyed = Symbol('onDestroyed') |
| 10144 | 10262 | const kOnClosed = Symbol('onClosed') |
| 10145 | 10263 | const kInterceptedDispatch = Symbol('Intercepted Dispatch') |
| 10264 | +const kWebSocketOptions = Symbol('webSocketOptions') | |
| 10146 | 10265 | |
| 10147 | 10266 | class DispatcherBase extends Dispatcher { |
| 10148 | − constructor () { | |
| 10267 | + constructor (opts) { | |
| 10149 | 10268 | super() |
| 10150 | 10269 | |
| 10151 | 10270 | this[kDestroyed] = false |
| 10152 | 10271 | this[kOnDestroyed] = null |
| 10153 | 10272 | this[kClosed] = false |
| 10154 | 10273 | this[kOnClosed] = [] |
| 10274 | + this[kWebSocketOptions] = opts?.webSocket ?? {} | |
| 10275 | + } | |
| 10276 | + | |
| 10277 | + get webSocketOptions () { | |
| 10278 | + return { | |
| 10279 | + maxFragments: this[kWebSocketOptions].maxFragments ?? 131072, | |
| 10280 | + maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024 | |
| 10281 | + } | |
| 10155 | 10282 | } |
| 10156 | 10283 | |
| 10157 | 10284 | get destroyed () { |
@@ -10711,8 +10838,8 @@ const kRemoveClient = Symbol('remove client')
| 10711 | 10838 | const kStats = Symbol('stats') |
| 10712 | 10839 | |
| 10713 | 10840 | class PoolBase extends DispatcherBase { |
| 10714 | − constructor () { | |
| 10715 | − super() | |
| 10841 | + constructor (opts) { | |
| 10842 | + super(opts) | |
| 10716 | 10843 | |
| 10717 | 10844 | this[kQueue] = new FixedQueue() |
| 10718 | 10845 | this[kClients] = [] |
@@ -10971,8 +11098,6 @@ class Pool extends PoolBase {
| 10971 | 11098 | allowH2, |
| 10972 | 11099 | ...options |
| 10973 | 11100 | } = {}) { |
| 10974 | − super() | |
| 10975 | − | |
| 10976 | 11101 | if (connections != null && (!Number.isFinite(connections) || connections < 0)) { |
| 10977 | 11102 | throw new InvalidArgumentError('invalid connections') |
| 10978 | 11103 | } |
@@ -10997,6 +11122,8 @@ class Pool extends PoolBase {
| 10997 | 11122 | }) |
| 10998 | 11123 | } |
| 10999 | 11124 | |
| 11125 | + super(options) | |
| 11126 | + | |
| 11000 | 11127 | this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool) |
| 11001 | 11128 | ? options.interceptors.Pool |
| 11002 | 11129 | : [] |
@@ -16049,32 +16176,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
| 16049 | 16176 | // If the attribute-name case-insensitively matches the string |
| 16050 | 16177 | // "SameSite", the user agent MUST process the cookie-av as follows: |
| 16051 | 16178 | |
| 16052 | − // 1. Let enforcement be "Default". | |
| 16053 | − let enforcement = 'Default' | |
| 16054 | − | |
| 16055 | 16179 | const attributeValueLowercase = attributeValue.toLowerCase() |
| 16056 | − // 2. If cookie-av's attribute-value is a case-insensitive match for | |
| 16057 | − // "None", set enforcement to "None". | |
| 16058 | − if (attributeValueLowercase.includes('none')) { | |
| 16059 | − enforcement = 'None' | |
| 16060 | − } | |
| 16061 | − | |
| 16062 | − // 3. If cookie-av's attribute-value is a case-insensitive match for | |
| 16063 | − // "Strict", set enforcement to "Strict". | |
| 16064 | − if (attributeValueLowercase.includes('strict')) { | |
| 16065 | − enforcement = 'Strict' | |
| 16066 | − } | |
| 16067 | 16180 | |
| 16068 | − // 4. If cookie-av's attribute-value is a case-insensitive match for | |
| 16069 | − // "Lax", set enforcement to "Lax". | |
| 16070 | − if (attributeValueLowercase.includes('lax')) { | |
| 16071 | − enforcement = 'Lax' | |
| 16181 | + // 1. If cookie-av's attribute-value is a case-insensitive match for | |
| 16182 | + // "None", append an attribute to the cookie-attribute-list with an | |
| 16183 | + // attribute-name of "SameSite" and an attribute-value of "None". | |
| 16184 | + if (attributeValueLowercase === 'none') { | |
| 16185 | + cookieAttributeList.sameSite = 'None' | |
| 16186 | + } else if (attributeValueLowercase === 'strict') { | |
| 16187 | + // 2. If cookie-av's attribute-value is a case-insensitive match for | |
| 16188 | + // "Strict", append an attribute to the cookie-attribute-list with | |
| 16189 | + // an attribute-name of "SameSite" and an attribute-value of | |
| 16190 | + // "Strict". | |
| 16191 | + cookieAttributeList.sameSite = 'Strict' | |
| 16192 | + } else if (attributeValueLowercase === 'lax') { | |
| 16193 | + // 3. If cookie-av's attribute-value is a case-insensitive match for | |
| 16194 | + // "Lax", append an attribute to the cookie-attribute-list with an | |
| 16195 | + // attribute-name of "SameSite" and an attribute-value of "Lax". | |
| 16196 | + cookieAttributeList.sameSite = 'Lax' | |
| 16072 | 16197 | } |
| 16073 | − | |
| 16074 | − // 5. Append an attribute to the cookie-attribute-list with an | |
| 16075 | − // attribute-name of "SameSite" and an attribute-value of | |
| 16076 | − // enforcement. | |
| 16077 | − cookieAttributeList.sameSite = enforcement | |
| 16078 | 16198 | } else { |
| 16079 | 16199 | cookieAttributeList.unparsed ??= [] |
| 16080 | 16200 |
@@ -28751,45 +28871,35 @@ const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
| 28751 | 28871 | const kBuffer = Symbol('kBuffer') |
| 28752 | 28872 | const kLength = Symbol('kLength') |
| 28753 | 28873 | |
| 28754 | −// Default maximum decompressed message size: 4 MB | |
| 28755 | −const kDefaultMaxDecompressedSize = 4 * 1024 * 1024 | |
| 28756 | − | |
| 28757 | 28874 | class PerMessageDeflate { |
| 28758 | 28875 | /** @type {import('node:zlib').InflateRaw} */ |
| 28759 | 28876 | #inflate |
| 28760 | 28877 | |
| 28761 | 28878 | #options = {} |
| 28762 | 28879 | |
| 28763 | − /** @type {number} */ | |
| 28764 | − #maxDecompressedSize | |
| 28765 | − | |
| 28766 | − /** @type {boolean} */ | |
| 28767 | − #aborted = false | |
| 28768 | − | |
| 28769 | − /** @type {Function|null} */ | |
| 28770 | − #currentCallback = null | |
| 28880 | + #maxPayloadSize = 0 | |
| 28771 | 28881 | |
| 28772 | 28882 | /** |
| 28773 | 28883 | * @param {Map<string, string>} extensions |
| 28774 | − * @param {{ maxDecompressedMessageSize?: number }} [options] | |
| 28775 | 28884 | */ |
| 28776 | − constructor (extensions, options = {}) { | |
| 28885 | + constructor (extensions, options) { | |
| 28777 | 28886 | this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover') |
| 28778 | 28887 | this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits') |
| 28779 | − this.#maxDecompressedSize = options.maxDecompressedMessageSize ?? kDefaultMaxDecompressedSize | |
| 28888 | + | |
| 28889 | + this.#maxPayloadSize = options.maxPayloadSize | |
| 28780 | 28890 | } |
| 28781 | 28891 | |
| 28892 | + /** | |
| 28893 | + * Decompress a compressed payload. | |
| 28894 | + * @param {Buffer} chunk Compressed data | |
| 28895 | + * @param {boolean} fin Final fragment flag | |
| 28896 | + * @param {Function} callback Callback function | |
| 28897 | + */ | |
| 28782 | 28898 | decompress (chunk, fin, callback) { |
| 28783 | 28899 | // An endpoint uses the following algorithm to decompress a message. |
| 28784 | 28900 | // 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the |
| 28785 | 28901 | // payload of the message. |
| 28786 | 28902 | // 2. Decompress the resulting data using DEFLATE. |
| 28787 | − | |
| 28788 | − if (this.#aborted) { | |
| 28789 | − callback(new MessageSizeExceededError()) | |
| 28790 | − return | |
| 28791 | − } | |
| 28792 | − | |
| 28793 | 28903 | if (!this.#inflate) { |
| 28794 | 28904 | let windowBits = Z_DEFAULT_WINDOWBITS |
| 28795 | 28905 |
@@ -28812,23 +28922,12 @@ class PerMessageDeflate {
| 28812 | 28922 | this.#inflate[kLength] = 0 |
| 28813 | 28923 | |
| 28814 | 28924 | this.#inflate.on('data', (data) => { |
| 28815 | − if (this.#aborted) { | |
| 28816 | − return | |
| 28817 | − } | |
| 28818 | − | |
| 28819 | 28925 | this.#inflate[kLength] += data.length |
| 28820 | 28926 | |
| 28821 | − if (this.#inflate[kLength] > this.#maxDecompressedSize) { | |
| 28822 | − this.#aborted = true | |
| 28927 | + if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) { | |
| 28928 | + callback(new MessageSizeExceededError()) | |
| 28823 | 28929 | this.#inflate.removeAllListeners() |
| 28824 | − this.#inflate.destroy() | |
| 28825 | 28930 | this.#inflate = null |
| 28826 | − | |
| 28827 | − if (this.#currentCallback) { | |
| 28828 | − const cb = this.#currentCallback | |
| 28829 | − this.#currentCallback = null | |
| 28830 | − cb(new MessageSizeExceededError()) | |
| 28831 | − } | |
| 28832 | 28931 | return |
| 28833 | 28932 | } |
| 28834 | 28933 |
@@ -28841,14 +28940,13 @@ class PerMessageDeflate {
| 28841 | 28940 | }) |
| 28842 | 28941 | } |
| 28843 | 28942 | |
| 28844 | − this.#currentCallback = callback | |
| 28845 | 28943 | this.#inflate.write(chunk) |
| 28846 | 28944 | if (fin) { |
| 28847 | 28945 | this.#inflate.write(tail) |
| 28848 | 28946 | } |
| 28849 | 28947 | |
| 28850 | 28948 | this.#inflate.flush(() => { |
| 28851 | − if (this.#aborted || !this.#inflate) { | |
| 28949 | + if (!this.#inflate) { | |
| 28852 | 28950 | return |
| 28853 | 28951 | } |
| 28854 | 28952 |
@@ -28856,7 +28954,6 @@ class PerMessageDeflate {
| 28856 | 28954 | |
| 28857 | 28955 | this.#inflate[kBuffer].length = 0 |
| 28858 | 28956 | this.#inflate[kLength] = 0 |
| 28859 | − this.#currentCallback = null | |
| 28860 | 28957 | |
| 28861 | 28958 | callback(null, full) |
| 28862 | 28959 | }) |
@@ -28891,6 +28988,12 @@ const {
| 28891 | 28988 | const { WebsocketFrameSend } = __nccwpck_require__(3264) |
| 28892 | 28989 | const { closeWebSocketConnection } = __nccwpck_require__(6897) |
| 28893 | 28990 | const { PerMessageDeflate } = __nccwpck_require__(9469) |
| 28991 | +const { MessageSizeExceededError } = __nccwpck_require__(8707) | |
| 28992 | + | |
| 28993 | +function failWebsocketConnectionWithCode (ws, code, reason) { | |
| 28994 | + closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason)) | |
| 28995 | + failWebsocketConnection(ws, reason) | |
| 28996 | +} | |
| 28894 | 28997 | |
| 28895 | 28998 | // This code was influenced by ws released under the MIT license. |
| 28896 | 28999 | // Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com> |
@@ -28899,6 +29002,7 @@ const { PerMessageDeflate } = __nccwpck_require__(9469)
| 28899 | 29002 | |
| 28900 | 29003 | class ByteParser extends Writable { |
| 28901 | 29004 | #buffers = [] |
| 29005 | + #fragmentsBytes = 0 | |
| 28902 | 29006 | #byteOffset = 0 |
| 28903 | 29007 | #loop = false |
| 28904 | 29008 |
@@ -28910,20 +29014,24 @@ class ByteParser extends Writable {
| 28910 | 29014 | /** @type {Map<string, PerMessageDeflate>} */ |
| 28911 | 29015 | #extensions |
| 28912 | 29016 | |
| 28913 | − /** @type {{ maxDecompressedMessageSize?: number }} */ | |
| 28914 | − #options | |
| 29017 | + /** @type {number} */ | |
| 29018 | + #maxFragments | |
| 29019 | + | |
| 29020 | + /** @type {number} */ | |
| 29021 | + #maxPayloadSize | |
| 28915 | 29022 | |
| 28916 | 29023 | /** |
| 28917 | 29024 | * @param {import('./websocket').WebSocket} ws |
| 28918 | 29025 | * @param {Map<string, string>|null} extensions |
| 28919 | − * @param {{ maxDecompressedMessageSize?: number }} [options] | |
| 29026 | + * @param {{ maxFragments?: number, maxPayloadSize?: number }} [options] | |
| 28920 | 29027 | */ |
| 28921 | 29028 | constructor (ws, extensions, options = {}) { |
| 28922 | 29029 | super() |
| 28923 | 29030 | |
| 28924 | 29031 | this.ws = ws |
| 28925 | 29032 | this.#extensions = extensions == null ? new Map() : extensions |
| 28926 | − this.#options = options | |
| 29033 | + this.#maxFragments = options.maxFragments ?? 0 | |
| 29034 | + this.#maxPayloadSize = options.maxPayloadSize ?? 0 | |
| 28927 | 29035 | |
| 28928 | 29036 | if (this.#extensions.has('permessage-deflate')) { |
| 28929 | 29037 | this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options)) |
@@ -28942,6 +29050,19 @@ class ByteParser extends Writable {
| 28942 | 29050 | this.run(callback) |
| 28943 | 29051 | } |
| 28944 | 29052 | |
| 29053 | + #validatePayloadLength () { | |
| 29054 | + if ( | |
| 29055 | + this.#maxPayloadSize > 0 && | |
| 29056 | + !isControlFrame(this.#info.opcode) && | |
| 29057 | + this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize | |
| 29058 | + ) { | |
| 29059 | + failWebsocketConnectionWithCode(this.ws, 1009, 'Payload size exceeds maximum allowed size') | |
| 29060 | + return false | |
| 29061 | + } | |
| 29062 | + | |
| 29063 | + return true | |
| 29064 | + } | |
| 29065 | + | |
| 28945 | 29066 | /** |
| 28946 | 29067 | * Runs whenever a new chunk is received. |
| 28947 | 29068 | * Callback is called whenever there are no more chunks buffering, |
@@ -29030,6 +29151,10 @@ class ByteParser extends Writable {
| 29030 | 29151 | if (payloadLength <= 125) { |
| 29031 | 29152 | this.#info.payloadLength = payloadLength |
| 29032 | 29153 | this.#state = parserStates.READ_DATA |
| 29154 | + | |
| 29155 | + if (!this.#validatePayloadLength()) { | |
| 29156 | + return | |
| 29157 | + } | |
| 29033 | 29158 | } else if (payloadLength === 126) { |
| 29034 | 29159 | this.#state = parserStates.PAYLOADLENGTH_16 |
| 29035 | 29160 | } else if (payloadLength === 127) { |
@@ -29054,6 +29179,10 @@ class ByteParser extends Writable {
| 29054 | 29179 | |
| 29055 | 29180 | this.#info.payloadLength = buffer.readUInt16BE(0) |
| 29056 | 29181 | this.#state = parserStates.READ_DATA |
| 29182 | + | |
| 29183 | + if (!this.#validatePayloadLength()) { | |
| 29184 | + return | |
| 29185 | + } | |
| 29057 | 29186 | } else if (this.#state === parserStates.PAYLOADLENGTH_64) { |
| 29058 | 29187 | if (this.#byteOffset < 8) { |
| 29059 | 29188 | return callback() |
@@ -29076,6 +29205,10 @@ class ByteParser extends Writable {
| 29076 | 29205 | |
| 29077 | 29206 | this.#info.payloadLength = lower |
| 29078 | 29207 | this.#state = parserStates.READ_DATA |
| 29208 | + | |
| 29209 | + if (!this.#validatePayloadLength()) { | |
| 29210 | + return | |
| 29211 | + } | |
| 29079 | 29212 | } else if (this.#state === parserStates.READ_DATA) { |
| 29080 | 29213 | if (this.#byteOffset < this.#info.payloadLength) { |
| 29081 | 29214 | return callback() |
@@ -29088,42 +29221,58 @@ class ByteParser extends Writable {
| 29088 | 29221 | this.#state = parserStates.INFO |
| 29089 | 29222 | } else { |
| 29090 | 29223 | if (!this.#info.compressed) { |
| 29091 | − this.#fragments.push(body) | |
| 29224 | + if (!this.writeFragments(body)) { | |
| 29225 | + return | |
| 29226 | + } | |
| 29227 | + | |
| 29228 | + if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) { | |
| 29229 | + failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message) | |
| 29230 | + return | |
| 29231 | + } | |
| 29092 | 29232 | |
| 29093 | 29233 | // If the frame is not fragmented, a message has been received. |
| 29094 | 29234 | // If the frame is fragmented, it will terminate with a fin bit set |
| 29095 | 29235 | // and an opcode of 0 (continuation), therefore we handle that when |
| 29096 | 29236 | // parsing continuation frames, not here. |
| 29097 | 29237 | if (!this.#info.fragmented && this.#info.fin) { |
| 29098 | − const fullMessage = Buffer.concat(this.#fragments) | |
| 29099 | − websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage) | |
| 29100 | − this.#fragments.length = 0 | |
| 29238 | + websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments()) | |
| 29101 | 29239 | } |
| 29102 | 29240 | |
| 29103 | 29241 | this.#state = parserStates.INFO |
| 29104 | 29242 | } else { |
| 29105 | − this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => { | |
| 29106 | − if (error) { | |
| 29107 | − failWebsocketConnection(this.ws, error.message) | |
| 29108 | − return | |
| 29109 | − } | |
| 29243 | + this.#extensions.get('permessage-deflate').decompress( | |
| 29244 | + body, | |
| 29245 | + this.#info.fin, | |
| 29246 | + (error, data) => { | |
| 29247 | + if (error) { | |
| 29248 | + const code = error instanceof MessageSizeExceededError ? 1009 : 1007 | |
| 29249 | + failWebsocketConnectionWithCode(this.ws, code, error.message) | |
| 29250 | + return | |
| 29251 | + } | |
| 29110 | 29252 | |
| 29111 | − this.#fragments.push(data) | |
| 29253 | + if (!this.writeFragments(data)) { | |
| 29254 | + return | |
| 29255 | + } | |
| 29256 | + | |
| 29257 | + if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) { | |
| 29258 | + failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message) | |
| 29259 | + return | |
| 29260 | + } | |
| 29261 | + | |
| 29262 | + if (!this.#info.fin) { | |
| 29263 | + this.#state = parserStates.INFO | |
| 29264 | + this.#loop = true | |
| 29265 | + this.run(callback) | |
| 29266 | + return | |
| 29267 | + } | |
| 29268 | + | |
| 29269 | + websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments()) | |
| 29112 | 29270 | |
| 29113 | − if (!this.#info.fin) { | |
| 29114 | − this.#state = parserStates.INFO | |
| 29115 | 29271 | this.#loop = true |
| 29272 | + this.#state = parserStates.INFO | |
| 29116 | 29273 | this.run(callback) |
| 29117 | − return | |
| 29118 | 29274 | } |
| 29119 | − | |
| 29120 | − websocketMessageReceived(this.ws, this.#info.binaryType, Buffer.concat(this.#fragments)) | |
| 29121 | − | |
| 29122 | − this.#loop = true | |
| 29123 | − this.#state = parserStates.INFO | |
| 29124 | − this.#fragments.length = 0 | |
| 29125 | − this.run(callback) | |
| 29126 | − }) | |
| 29275 | + ) | |
| 29127 | 29276 | |
| 29128 | 29277 | this.#loop = false |
| 29129 | 29278 | break |
@@ -29175,6 +29324,35 @@ class ByteParser extends Writable {
| 29175 | 29324 | return buffer |
| 29176 | 29325 | } |
| 29177 | 29326 | |
| 29327 | + writeFragments (fragment) { | |
| 29328 | + if ( | |
| 29329 | + this.#maxFragments > 0 && | |
| 29330 | + this.#fragments.length === this.#maxFragments | |
| 29331 | + ) { | |
| 29332 | + failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments') | |
| 29333 | + return false | |
| 29334 | + } | |
| 29335 | + | |
| 29336 | + this.#fragmentsBytes += fragment.length | |
| 29337 | + this.#fragments.push(fragment) | |
| 29338 | + return true | |
| 29339 | + } | |
| 29340 | + | |
| 29341 | + consumeFragments () { | |
| 29342 | + const fragments = this.#fragments | |
| 29343 | + | |
| 29344 | + if (fragments.length === 1) { | |
| 29345 | + this.#fragmentsBytes = 0 | |
| 29346 | + return fragments.shift() | |
| 29347 | + } | |
| 29348 | + | |
| 29349 | + const output = Buffer.concat(fragments, this.#fragmentsBytes) | |
| 29350 | + this.#fragments = [] | |
| 29351 | + this.#fragmentsBytes = 0 | |
| 29352 | + | |
| 29353 | + return output | |
| 29354 | + } | |
| 29355 | + | |
| 29178 | 29356 | parseCloseBody (data) { |
| 29179 | 29357 | assert(data.length !== 1) |
| 29180 | 29358 |
@@ -29815,9 +29993,6 @@ class WebSocket extends EventTarget {
| 29815 | 29993 | /** @type {SendQueue} */ |
| 29816 | 29994 | #sendQueue |
| 29817 | 29995 | |
| 29818 | − /** @type {{ maxDecompressedMessageSize?: number }} */ | |
| 29819 | − #options | |
| 29820 | − | |
| 29821 | 29996 | /** |
| 29822 | 29997 | * @param {string} url |
| 29823 | 29998 | * @param {string|string[]} protocols |
@@ -29891,11 +30066,6 @@ class WebSocket extends EventTarget {
| 29891 | 30066 | // 10. Set this's url to urlRecord. |
| 29892 | 30067 | this[kWebSocketURL] = new URL(urlRecord.href) |
| 29893 | 30068 | |
| 29894 | − // Store options for later use (e.g., maxDecompressedMessageSize) | |
| 29895 | − this.#options = { | |
| 29896 | − maxDecompressedMessageSize: options.maxDecompressedMessageSize | |
| 29897 | − } | |
| 29898 | − | |
| 29899 | 30069 | // 11. Let client be this's relevant settings object. |
| 29900 | 30070 | const client = environmentSettingsObject.settingsObject |
| 29901 | 30071 |
@@ -30214,7 +30384,14 @@ class WebSocket extends EventTarget {
| 30214 | 30384 | // once this happens, the connection is open |
| 30215 | 30385 | this[kResponse] = response |
| 30216 | 30386 | |
| 30217 | − const parser = new ByteParser(this, parsedExtensions, this.#options) | |
| 30387 | + const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions | |
| 30388 | + const maxFragments = webSocketOptions?.maxFragments | |
| 30389 | + const maxPayloadSize = webSocketOptions?.maxPayloadSize | |
| 30390 | + | |
| 30391 | + const parser = new ByteParser(this, parsedExtensions, { | |
| 30392 | + maxFragments, | |
| 30393 | + maxPayloadSize | |
| 30394 | + }) | |
| 30218 | 30395 | parser.on('drain', onParserDrain) |
| 30219 | 30396 | parser.on('error', onParserError.bind(this)) |
| 30220 | 30397 |
@@ -30317,19 +30494,6 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
| 30317 | 30494 | { |
| 30318 | 30495 | key: 'headers', |
| 30319 | 30496 | converter: webidl.nullableConverter(webidl.converters.HeadersInit) |
| 30320 | − }, | |
| 30321 | − { | |
| 30322 | − key: 'maxDecompressedMessageSize', | |
| 30323 | − converter: webidl.nullableConverter((V) => { | |
| 30324 | − V = webidl.converters['unsigned long long'](V) | |
| 30325 | − if (V <= 0) { | |
| 30326 | − throw webidl.errors.exception({ | |
| 30327 | − header: 'WebSocket constructor', | |
| 30328 | − message: 'maxDecompressedMessageSize must be greater than 0' | |
| 30329 | − }) | |
| 30330 | − } | |
| 30331 | − return V | |
| 30332 | − }) | |
| 30333 | 30497 | } |
| 30334 | 30498 | ]) |
| 30335 | 30499 |
modified
package-lock.json
+3
−3
@@ -3657,9 +3657,9 @@
| 3657 | 3657 | } |
| 3658 | 3658 | }, |
| 3659 | 3659 | "node_modules/undici": { |
| 3660 | − "version": "6.24.0", | |
| 3661 | − "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz", | |
| 3662 | − "integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==", | |
| 3660 | + "version": "6.27.0", | |
| 3661 | + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", | |
| 3662 | + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", | |
| 3663 | 3663 | "license": "MIT", |
| 3664 | 3664 | "engines": { |
| 3665 | 3665 | "node": ">=18.17" |
Parents: e6d7811