@@ -14461,6 +14461,24 @@ class SecureProxyConnectionError extends UndiciError {
1446114461 [kSecureProxyConnectionError] = true
1446214462}
1446314463
14464+ const kMessageSizeExceededError = Symbol.for('undici.error.UND_ERR_WS_MESSAGE_SIZE_EXCEEDED')
14465+ class MessageSizeExceededError extends UndiciError {
14466+ constructor (message) {
14467+ super(message)
14468+ this.name = 'MessageSizeExceededError'
14469+ this.message = message || 'Max decompressed message size exceeded'
14470+ this.code = 'UND_ERR_WS_MESSAGE_SIZE_EXCEEDED'
14471+ }
14472+
14473+ static [Symbol.hasInstance] (instance) {
14474+ return instance && instance[kMessageSizeExceededError] === true
14475+ }
14476+
14477+ get [kMessageSizeExceededError] () {
14478+ return true
14479+ }
14480+ }
14481+
1446414482module.exports = {
1446514483 AbortError,
1446614484 HTTPParserError,
@@ -14484,7 +14502,8 @@ module.exports = {
1448414502 ResponseExceededMaxSizeError,
1448514503 RequestRetryError,
1448614504 ResponseError,
14487- SecureProxyConnectionError
14505+ SecureProxyConnectionError,
14506+ MessageSizeExceededError
1448814507}
1448914508
1449014509
@@ -14562,6 +14581,10 @@ class Request {
1456214581 throw new InvalidArgumentError('upgrade must be a string')
1456314582 }
1456414583
14584+ if (upgrade && !isValidHeaderValue(upgrade)) {
14585+ throw new InvalidArgumentError('invalid upgrade header')
14586+ }
14587+
1456514588 if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) {
1456614589 throw new InvalidArgumentError('invalid headersTimeout')
1456714590 }
@@ -14856,13 +14879,19 @@ function processHeader (request, key, val) {
1485614879 val = `${val}`
1485714880 }
1485814881
14859- if (request.host === null && headerName === 'host') {
14882+ if (headerName === 'host') {
14883+ if (request.host !== null) {
14884+ throw new InvalidArgumentError('duplicate host header')
14885+ }
1486014886 if (typeof val !== 'string') {
1486114887 throw new InvalidArgumentError('invalid host header')
1486214888 }
1486314889 // Consumed by Client
1486414890 request.host = val
14865- } else if (request.contentLength === null && headerName === 'content-length') {
14891+ } else if (headerName === 'content-length') {
14892+ if (request.contentLength !== null) {
14893+ throw new InvalidArgumentError('duplicate content-length header')
14894+ }
1486614895 request.contentLength = parseInt(val, 10)
1486714896 if (!Number.isFinite(request.contentLength)) {
1486814897 throw new InvalidArgumentError('invalid content-length header')
@@ -37653,17 +37682,30 @@ module.exports = {
3765337682
3765437683const { createInflateRaw, Z_DEFAULT_WINDOWBITS } = __nccwpck_require__(5628)
3765537684const { isValidClientWindowBits } = __nccwpck_require__(9902)
37685+ const { MessageSizeExceededError } = __nccwpck_require__(8045)
3765637686
3765737687const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
3765837688const kBuffer = Symbol('kBuffer')
3765937689const kLength = Symbol('kLength')
3766037690
37691+ // Default maximum decompressed message size: 4 MB
37692+ const kDefaultMaxDecompressedSize = 4 * 1024 * 1024
37693+
3766137694class PerMessageDeflate {
3766237695 /** @type {import('node:zlib').InflateRaw} */
3766337696 #inflate
3766437697
3766537698 #options = {}
3766637699
37700+ /** @type {boolean} */
37701+ #aborted = false
37702+
37703+ /** @type {Function|null} */
37704+ #currentCallback = null
37705+
37706+ /**
37707+ * @param {Map<string, string>} extensions
37708+ */
3766737709 constructor (extensions) {
3766837710 this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
3766937711 this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
@@ -37675,6 +37717,11 @@ class PerMessageDeflate {
3767537717 // payload of the message.
3767637718 // 2. Decompress the resulting data using DEFLATE.
3767737719
37720+ if (this.#aborted) {
37721+ callback(new MessageSizeExceededError())
37722+ return
37723+ }
37724+
3767837725 if (!this.#inflate) {
3767937726 let windowBits = Z_DEFAULT_WINDOWBITS
3768037727
@@ -37687,13 +37734,37 @@ class PerMessageDeflate {
3768737734 windowBits = Number.parseInt(this.#options.serverMaxWindowBits)
3768837735 }
3768937736
37690- this.#inflate = createInflateRaw({ windowBits })
37737+ try {
37738+ this.#inflate = createInflateRaw({ windowBits })
37739+ } catch (err) {
37740+ callback(err)
37741+ return
37742+ }
3769137743 this.#inflate[kBuffer] = []
3769237744 this.#inflate[kLength] = 0
3769337745
3769437746 this.#inflate.on('data', (data) => {
37695- this.#inflate[kBuffer].push(data)
37747+ if (this.#aborted) {
37748+ return
37749+ }
37750+
3769637751 this.#inflate[kLength] += data.length
37752+
37753+ if (this.#inflate[kLength] > kDefaultMaxDecompressedSize) {
37754+ this.#aborted = true
37755+ this.#inflate.removeAllListeners()
37756+ this.#inflate.destroy()
37757+ this.#inflate = null
37758+
37759+ if (this.#currentCallback) {
37760+ const cb = this.#currentCallback
37761+ this.#currentCallback = null
37762+ cb(new MessageSizeExceededError())
37763+ }
37764+ return
37765+ }
37766+
37767+ this.#inflate[kBuffer].push(data)
3769737768 })
3769837769
3769937770 this.#inflate.on('error', (err) => {
@@ -37702,16 +37773,22 @@ class PerMessageDeflate {
3770237773 })
3770337774 }
3770437775
37776+ this.#currentCallback = callback
3770537777 this.#inflate.write(chunk)
3770637778 if (fin) {
3770737779 this.#inflate.write(tail)
3770837780 }
3770937781
3771037782 this.#inflate.flush(() => {
37783+ if (this.#aborted || !this.#inflate) {
37784+ return
37785+ }
37786+
3771137787 const full = Buffer.concat(this.#inflate[kBuffer], this.#inflate[kLength])
3771237788
3771337789 this.#inflate[kBuffer].length = 0
3771437790 this.#inflate[kLength] = 0
37791+ this.#currentCallback = null
3771537792
3771637793 callback(null, full)
3771737794 })
@@ -37766,6 +37843,10 @@ class ByteParser extends Writable {
3776637843 /** @type {Map<string, PerMessageDeflate>} */
3776737844 #extensions
3776837845
37846+ /**
37847+ * @param {import('./websocket').WebSocket} ws
37848+ * @param {Map<string, string>|null} extensions
37849+ */
3776937850 constructor (ws, extensions) {
3777037851 super()
3777137852
@@ -37908,21 +37989,20 @@ class ByteParser extends Writable {
3790837989
3790937990 const buffer = this.consume(8)
3791037991 const upper = buffer.readUInt32BE(0)
37992+ const lower = buffer.readUInt32BE(4)
3791137993
3791237994 // 2^31 is the maximum bytes an arraybuffer can contain
3791337995 // on 32-bit systems. Although, on 64-bit systems, this is
3791437996 // 2^53-1 bytes.
3791537997 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
3791637998 // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275
3791737999 // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e
37918- if (upper > 2 ** 31 - 1) {
38000+ if (upper !== 0 || lower > 2 ** 31 - 1) {
3791938001 failWebsocketConnection(this.ws, 'Received payload length > 2^31 bytes.')
3792038002 return
3792138003 }
3792238004
37923- const lower = buffer.readUInt32BE(4)
37924-
37925- this.#info.payloadLength = (upper << 8) + lower
38005+ this.#info.payloadLength = lower
3792638006 this.#state = parserStates.READ_DATA
3792738007 } else if (this.#state === parserStates.READ_DATA) {
3792838008 if (this.#byteOffset < this.#info.payloadLength) {
@@ -37952,7 +38032,7 @@ class ByteParser extends Writable {
3795238032 } else {
3795338033 this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
3795438034 if (error) {
37955- closeWebSocketConnection (this.ws, 1007, error.message, error.message.length )
38035+ failWebsocketConnection (this.ws, error.message)
3795638036 return
3795738037 }
3795838038
@@ -38559,6 +38639,12 @@ function parseExtensions (extensions) {
3855938639 * @param {string} value
3856038640 */
3856138641function isValidClientWindowBits (value) {
38642+ // Must have at least one character
38643+ if (value.length === 0) {
38644+ return false
38645+ }
38646+
38647+ // Check all characters are ASCII digits
3856238648 for (let i = 0; i < value.length; i++) {
3856338649 const byte = value.charCodeAt(i)
3856438650
@@ -38567,7 +38653,9 @@ function isValidClientWindowBits (value) {
3856738653 }
3856838654 }
3856938655
38570- return true
38656+ // Check numeric range: zlib requires windowBits in range 8-15
38657+ const num = Number.parseInt(value, 10)
38658+ return num >= 8 && num <= 15
3857138659}
3857238660
3857338661// https://nodejs.org/api/intl.html#detecting-internationalization-support
@@ -39046,7 +39134,7 @@ class WebSocket extends EventTarget {
3904639134 * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
3904739135 */
3904839136 #onConnectionEstablished (response, parsedExtensions) {
39049- // processResponse is called when the "response’ s header list has been received and initialized."
39137+ // processResponse is called when the "response' s header list has been received and initialized."
3905039138 // once this happens, the connection is open
3905139139 this[kResponse] = response
3905239140
0 commit comments