Skip to content

Commit

Permalink
fix 9.3.2-9.3.8; 9.4.2-9.4.8; 10.1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
KhafraDev committed May 13, 2024
1 parent ed79a2f commit eff7bf8
Showing 1 changed file with 35 additions and 114 deletions.
149 changes: 35 additions & 114 deletions lib/web/websocket/receiver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]>
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down

0 comments on commit eff7bf8

Please sign in to comment.