From eff7bf81350e95158637c13f2fa5ca2e31b82fcf Mon Sep 17 00:00:00 2001 From: Khafra Date: Mon, 13 May 2024 17:27:01 -0400 Subject: [PATCH] fix 9.3.2-9.3.8; 9.4.2-9.4.8; 10.1.1 --- lib/web/websocket/receiver.js | 149 ++++++++-------------------------- 1 file changed, 35 insertions(+), 114 deletions(-) diff --git a/lib/web/websocket/receiver.js b/lib/web/websocket/receiver.js index dc5aaaf8907..fd3872b4605 100644 --- a/lib/web/websocket/receiver.js +++ b/lib/web/websocket/receiver.js @@ -16,7 +16,7 @@ const { isTextBinaryFrame } = require('./util') const { WebsocketFrameSend } = require('./frame') -const { CloseEvent } = require('./events') +const { closeWebSocketConnection } = require('./connection') // This code was influenced by ws released under the MIT license. // Copyright (c) 2011 Einar Otto Stangvik @@ -115,32 +115,11 @@ class ByteParser extends Writable { return } - if (isControlFrame(opcode)) { - const loop = this.parseControlFrame(callback, { - header: buffer, - opcode, - fragmented, - payloadLength - }) - - if (loop) { - continue - } else { - return - } - } else if (isContinuationFrame(opcode)) { - const loop = this.parseContinuationFrame(callback, { - header: buffer, - fin, - fragmented, - payloadLength - }) - - if (loop) { - continue - } else { - return - } + // "All control frames MUST have a payload length of 125 bytes or less + // and MUST NOT be fragmented." + if ((payloadLength > 125 || fragmented) && isControlFrame(opcode)) { + failWebsocketConnection(this.ws, 'Control frame either too large or fragmented') + return } if (payloadLength <= 125) { @@ -194,17 +173,22 @@ class ByteParser extends Writable { } const body = this.consume(this.#info.payloadLength) - this.#fragments.push(body) - - // 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) { - const fullMessage = Buffer.concat(this.#fragments) - websocketMessageReceived(this.ws, this.#info.opcode, fullMessage) - this.#info = {} - this.#fragments.length = 0 + + if (isControlFrame(this.#info.opcode)) { + this.#loop = this.parseControlFrame(body) + } else { + this.#fragments.push(body) + + // 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.opcode, fullMessage) + this.#info = {} + this.#fragments.length = 0 + } } this.#state = parserStates.INFO @@ -215,11 +199,11 @@ class ByteParser extends Writable { /** * Take n bytes from the buffered Buffers * @param {number} n - * @returns {Buffer|null} + * @returns {Buffer} */ consume (n) { if (n > this.#byteOffset) { - return null + throw new Error('Called consume() before buffers satiated.') } else if (n === 0) { return emptyBuffer } @@ -292,31 +276,15 @@ class ByteParser extends Writable { /** * Parses control frames. - * @param {Buffer} data - * @param {(err?: Error) => void} callback - * @param {{ opcode: number, fragmented: boolean, payloadLength: number, header: Buffer }} info + * @param {Buffer} body */ - parseControlFrame (callback, info) { - assert(!info.fragmented) + parseControlFrame (body) { + const { opcode, payloadLength } = this.#info - if (info.payloadLength > 125) { - // Control frames can have a payload length of 125 bytes MAX - failWebsocketConnection(this.ws, 'Payload length for control frame exceeded 125 bytes.') - return false - } else if (this.#byteOffset < info.payloadLength) { - this.#buffers.unshift(info.header) - this.#byteOffset += 2 - - callback() - return false - } - - const body = this.consume(info.payloadLength) - - if (info.opcode === opcodes.CLOSE) { - if (info.payloadLength === 1) { + if (opcode === opcodes.CLOSE) { + if (payloadLength === 1) { failWebsocketConnection(this.ws, 'Received close frame with a 1-byte body.') - return + return false } this.#info.closeInfo = this.parseCloseBody(body) @@ -324,8 +292,8 @@ class ByteParser extends Writable { if (this.#info.closeInfo.error) { const { code, reason } = this.#info.closeInfo - callback(new CloseEvent('close', { wasClean: false, reason, code })) - return + closeWebSocketConnection(this.ws, code, reason, reason.length) + return false } if (this.ws[kSentClose] !== sentCloseFrameState.SENT) { @@ -357,9 +325,8 @@ class ByteParser extends Writable { this.ws[kReceivedClose] = true this.end() - - return - } else if (info.opcode === opcodes.PING) { + return false + } else if (opcode === opcodes.PING) { // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in // response, unless it already received a Close frame. // A Pong frame sent in response to a Ping frame must have identical @@ -376,12 +343,7 @@ class ByteParser extends Writable { }) } } - - if (this.#byteOffset <= 0) { - callback() - return false - } - } else if (info.opcode === opcodes.PONG) { + } else if (opcode === opcodes.PONG) { // A Pong frame MAY be sent unsolicited. This serves as a // unidirectional heartbeat. A response to an unsolicited Pong frame is // not expected. @@ -391,47 +353,6 @@ class ByteParser extends Writable { payload: body }) } - - if (this.#byteOffset <= 0) { - callback() - return false - } - } - - return true - } - - /** - * Parses continuation frames. - * @param {Buffer} data - * @param {(err?: Error) => void} callback - * @param {{ fin: boolean, fragmented: boolean, payloadLength: number, header: Buffer }} info - */ - parseContinuationFrame (callback, info) { - // If we received a continuation frame before we started parsing another frame. - if (this.#info.opcode === undefined) { - failWebsocketConnection(this.ws, 'Received unexpected continuation frame.') - return false - } else if (this.#byteOffset < info.payloadLength) { - this.#buffers.unshift(info.header) - this.#byteOffset += 2 - - callback() - return false - } - - const body = this.consume(info.payloadLength) - this.#fragments.push(body) - - // A fragmented message consists of a single frame with the FIN bit - // clear and an opcode other than 0, followed by zero or more frames - // with the FIN bit clear and the opcode set to 0, and terminated by - // a single frame with the FIN bit set and an opcode of 0. - if (info.fin) { - const message = Buffer.concat(this.#fragments) - websocketMessageReceived(this.ws, this.#info.opcode, message) - this.#fragments.length = 0 - this.#info = {} } return true