diff options
| author | Egor Tensin <egor@tensin.name> | 2026-05-12 16:20:31 +0200 |
|---|---|---|
| committer | Egor Tensin <egor@tensin.name> | 2026-05-12 16:20:31 +0200 |
| commit | 3974efd722fa10f283df76342125776a352072dd (patch) | |
| tree | 21b5240dc856fe452da37f21313a7e1c7801c17f /node_modules/undici/lib/web/websocket/receiver.js | |
| parent | workflows/test: reschedule for my convenience (diff) | |
| download | cleanup-path-3974efd722fa10f283df76342125776a352072dd.tar.gz cleanup-path-3974efd722fa10f283df76342125776a352072dd.zip | |
bump dependenciesv5.0.2
Diffstat (limited to 'node_modules/undici/lib/web/websocket/receiver.js')
| -rw-r--r-- | node_modules/undici/lib/web/websocket/receiver.js | 109 |
1 files changed, 86 insertions, 23 deletions
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) |
