aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/node_modules/undici/lib/web/websocket
diff options
context:
space:
mode:
authorEgor Tensin <egor@tensin.name>2026-05-12 16:20:31 +0200
committerEgor Tensin <egor@tensin.name>2026-05-12 16:20:31 +0200
commit3974efd722fa10f283df76342125776a352072dd (patch)
tree21b5240dc856fe452da37f21313a7e1c7801c17f /node_modules/undici/lib/web/websocket
parentworkflows/test: reschedule for my convenience (diff)
downloadcleanup-path-5.0.2.tar.gz
cleanup-path-5.0.2.zip
bump dependenciesv5.0.2
Diffstat (limited to '')
-rw-r--r--node_modules/undici/lib/web/websocket/permessage-deflate.js44
-rw-r--r--node_modules/undici/lib/web/websocket/receiver.js109
-rw-r--r--node_modules/undici/lib/web/websocket/websocket.js6
3 files changed, 104 insertions, 55 deletions
diff --git a/node_modules/undici/lib/web/websocket/permessage-deflate.js b/node_modules/undici/lib/web/websocket/permessage-deflate.js
index 1f1a130..6a6e438 100644
--- a/node_modules/undici/lib/web/websocket/permessage-deflate.js
+++ b/node_modules/undici/lib/web/websocket/permessage-deflate.js
@@ -8,40 +8,35 @@ 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 = {}
- /** @type {boolean} */
- #aborted = false
-
- /** @type {Function|null} */
- #currentCallback = null
+ #maxPayloadSize = 0
/**
* @param {Map<string, string>} extensions
*/
- constructor (extensions) {
+ constructor (extensions, options) {
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
@@ -64,23 +59,12 @@ class PerMessageDeflate {
this.#inflate[kLength] = 0
this.#inflate.on('data', (data) => {
- if (this.#aborted) {
- return
- }
-
this.#inflate[kLength] += data.length
- if (this.#inflate[kLength] > kDefaultMaxDecompressedSize) {
- this.#aborted = true
+ if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
+ callback(new MessageSizeExceededError())
this.#inflate.removeAllListeners()
- this.#inflate.destroy()
this.#inflate = null
-
- if (this.#currentCallback) {
- const cb = this.#currentCallback
- this.#currentCallback = null
- cb(new MessageSizeExceededError())
- }
return
}
@@ -93,14 +77,13 @@ class PerMessageDeflate {
})
}
- this.#currentCallback = callback
this.#inflate.write(chunk)
if (fin) {
this.#inflate.write(tail)
}
this.#inflate.flush(() => {
- if (this.#aborted || !this.#inflate) {
+ if (!this.#inflate) {
return
}
@@ -108,7 +91,6 @@ class PerMessageDeflate {
this.#inflate[kBuffer].length = 0
this.#inflate[kLength] = 0
- this.#currentCallback = null
callback(null, full)
})
diff --git a/node_modules/undici/lib/web/websocket/receiver.js b/node_modules/undici/lib/web/websocket/receiver.js
index e7f7512..53e427e 100644
--- a/node_modules/undici/lib/web/websocket/receiver.js
+++ b/node_modules/undici/lib/web/websocket/receiver.js
@@ -18,6 +18,7 @@ const {
const { WebsocketFrameSend } = require('./frame')
const { closeWebSocketConnection } = require('./connection')
const { PerMessageDeflate } = require('./permessage-deflate')
+const { MessageSizeExceededError } = require('../../core/errors')
// This code was influenced by ws released under the MIT license.
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
@@ -26,6 +27,7 @@ const { PerMessageDeflate } = require('./permessage-deflate')
class ByteParser extends Writable {
#buffers = []
+ #fragmentsBytes = 0
#byteOffset = 0
#loop = false
@@ -37,18 +39,23 @@ class ByteParser extends Writable {
/** @type {Map<string, PerMessageDeflate>} */
#extensions
+ /** @type {number} */
+ #maxPayloadSize
+
/**
* @param {import('./websocket').WebSocket} ws
* @param {Map<string, string>|null} extensions
+ * @param {{ maxPayloadSize?: number }} [options]
*/
- constructor (ws, extensions) {
+ constructor (ws, extensions, options = {}) {
super()
this.ws = ws
this.#extensions = extensions == null ? new Map() : extensions
+ this.#maxPayloadSize = options.maxPayloadSize ?? 0
if (this.#extensions.has('permessage-deflate')) {
- this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions))
+ this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options))
}
}
@@ -64,6 +71,19 @@ class ByteParser extends Writable {
this.run(callback)
}
+ #validatePayloadLength () {
+ if (
+ this.#maxPayloadSize > 0 &&
+ !isControlFrame(this.#info.opcode) &&
+ this.#info.payloadLength > this.#maxPayloadSize
+ ) {
+ failWebsocketConnection(this.ws, '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,
@@ -152,6 +172,10 @@ 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) {
@@ -176,6 +200,10 @@ 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()
@@ -198,6 +226,10 @@ 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()
@@ -210,42 +242,53 @@ class ByteParser extends Writable {
this.#state = parserStates.INFO
} else {
if (!this.#info.compressed) {
- this.#fragments.push(body)
+ this.writeFragments(body)
+
+ if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
+ failWebsocketConnection(this.ws, new MessageSizeExceededError().message)
+ return
+ }
// 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) {
- const fullMessage = Buffer.concat(this.#fragments)
- websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
- this.#fragments.length = 0
+ websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
}
this.#state = parserStates.INFO
} else {
- this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
- if (error) {
- failWebsocketConnection(this.ws, error.message)
- return
- }
+ this.#extensions.get('permessage-deflate').decompress(
+ body,
+ this.#info.fin,
+ (error, data) => {
+ if (error) {
+ failWebsocketConnection(this.ws, error.message)
+ return
+ }
- this.#fragments.push(data)
+ this.writeFragments(data)
+
+ if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
+ failWebsocketConnection(this.ws, new MessageSizeExceededError().message)
+ return
+ }
+
+ if (!this.#info.fin) {
+ this.#state = parserStates.INFO
+ this.#loop = true
+ this.run(callback)
+ return
+ }
+
+ websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
- if (!this.#info.fin) {
- this.#state = parserStates.INFO
this.#loop = true
+ this.#state = parserStates.INFO
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
@@ -297,6 +340,26 @@ class ByteParser extends Writable {
return buffer
}
+ writeFragments (fragment) {
+ this.#fragmentsBytes += fragment.length
+ this.#fragments.push(fragment)
+ }
+
+ 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)
diff --git a/node_modules/undici/lib/web/websocket/websocket.js b/node_modules/undici/lib/web/websocket/websocket.js
index aa2a20a..ccedb79 100644
--- a/node_modules/undici/lib/web/websocket/websocket.js
+++ b/node_modules/undici/lib/web/websocket/websocket.js
@@ -435,7 +435,11 @@ class WebSocket extends EventTarget {
// once this happens, the connection is open
this[kResponse] = response
- const parser = new ByteParser(this, parsedExtensions)
+ const maxPayloadSize = this[kController]?.dispatcher?.webSocketOptions?.maxPayloadSize
+
+ const parser = new ByteParser(this, parsedExtensions, {
+ maxPayloadSize
+ })
parser.on('drain', onParserDrain)
parser.on('error', onParserError.bind(this))