-
Notifications
You must be signed in to change notification settings - Fork 578
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix 9.3.2-9.3.8; 9.4.2-9.4.8; 10.1.1
- Loading branch information
Showing
1 changed file
with
35 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 <[email protected]> | ||
|
@@ -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,40 +276,24 @@ 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) | ||
|
||
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 | ||
|