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

2 files +306 -142
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