-
-
Notifications
You must be signed in to change notification settings - Fork 744
feat(websocket): expand diagnostics channel payloads #4888
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
b8b8fec
639d09f
aa50095
b8d7ae5
0f5ae89
99d34aa
30d5a53
0b44030
98c9429
ed80a36
0419a1b
805449c
381a54f
69af2db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ const { failWebsocketConnection } = require('./connection') | |
| const { WebsocketFrameSend } = require('./frame') | ||
| const { PerMessageDeflate } = require('./permessage-deflate') | ||
| const { MessageSizeExceededError } = require('../../core/errors') | ||
| const { channels } = require('../../core/diagnostics') | ||
|
|
||
| // This code was influenced by ws released under the MIT license. | ||
| // Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com> | ||
|
|
@@ -97,11 +98,13 @@ class ByteParser extends Writable { | |
| const rsv3 = buffer[0] & 0x10 | ||
|
|
||
| if (!isValidOpcode(opcode)) { | ||
| this.publishFrameError(new Error('Invalid opcode received')) | ||
|
||
| failWebsocketConnection(this.#handler, 1002, 'Invalid opcode received') | ||
| return callback() | ||
| } | ||
|
|
||
| if (masked) { | ||
| this.publishFrameError(new Error('Frame cannot be masked')) | ||
| failWebsocketConnection(this.#handler, 1002, 'Frame cannot be masked') | ||
| return callback() | ||
| } | ||
|
|
@@ -116,42 +119,49 @@ class ByteParser extends Writable { | |
| // WebSocket connection where a PMCE is in use, this bit indicates | ||
| // whether a message is compressed or not. | ||
| if (rsv1 !== 0 && !this.#extensions.has('permessage-deflate')) { | ||
| this.publishFrameError(new Error('Expected RSV1 to be clear.')) | ||
| failWebsocketConnection(this.#handler, 1002, 'Expected RSV1 to be clear.') | ||
| return | ||
| } | ||
|
|
||
| if (rsv2 !== 0 || rsv3 !== 0) { | ||
| this.publishFrameError(new Error('RSV1, RSV2, RSV3 must be clear')) | ||
| failWebsocketConnection(this.#handler, 1002, 'RSV1, RSV2, RSV3 must be clear') | ||
| return | ||
| } | ||
|
|
||
| if (fragmented && !isTextBinaryFrame(opcode)) { | ||
| // Only text and binary frames can be fragmented | ||
| this.publishFrameError(new Error('Invalid frame type was fragmented.')) | ||
| failWebsocketConnection(this.#handler, 1002, 'Invalid frame type was fragmented.') | ||
| return | ||
| } | ||
|
|
||
| // If we are already parsing a text/binary frame and do not receive either | ||
| // a continuation frame or close frame, fail the connection. | ||
| if (isTextBinaryFrame(opcode) && this.#fragments.length > 0) { | ||
| this.publishFrameError(new Error('Expected continuation frame')) | ||
| failWebsocketConnection(this.#handler, 1002, 'Expected continuation frame') | ||
| return | ||
| } | ||
|
|
||
| if (this.#info.fragmented && fragmented) { | ||
| // A fragmented frame can't be fragmented itself | ||
| this.publishFrameError(new Error('Fragmented frame exceeded 125 bytes.')) | ||
| failWebsocketConnection(this.#handler, 1002, 'Fragmented frame exceeded 125 bytes.') | ||
| 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)) { | ||
| this.publishFrameError(new Error('Control frame either too large or fragmented')) | ||
| failWebsocketConnection(this.#handler, 1002, 'Control frame either too large or fragmented') | ||
| return | ||
| } | ||
|
|
||
| if (isContinuationFrame(opcode) && this.#fragments.length === 0 && !this.#info.compressed) { | ||
| this.publishFrameError(new Error('Unexpected continuation frame')) | ||
| failWebsocketConnection(this.#handler, 1002, 'Unexpected continuation frame') | ||
| return | ||
| } | ||
|
|
@@ -199,6 +209,7 @@ class ByteParser extends Writable { | |
| // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275 | ||
| // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e | ||
| if (upper !== 0 || lower > 2 ** 31 - 1) { | ||
| this.publishFrameError(new Error('Received payload length > 2^31 bytes.')) | ||
| failWebsocketConnection(this.#handler, 1009, 'Received payload length > 2^31 bytes.') | ||
| return | ||
| } | ||
|
|
@@ -212,6 +223,15 @@ class ByteParser extends Writable { | |
|
|
||
| const body = this.consume(this.#info.payloadLength) | ||
|
|
||
| if (channels.frameReceived.hasSubscribers) { | ||
| channels.frameReceived.publish({ | ||
| websocket: this.#handler.websocket, | ||
| opcode: this.#info.opcode, | ||
| mask: this.#info.masked, | ||
tsctx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| payloadData: Buffer.from(body) | ||
| }) | ||
| } | ||
|
|
||
| if (isControlFrame(this.#info.opcode)) { | ||
| this.#loop = this.parseControlFrame(body) | ||
| this.#state = parserStates.INFO | ||
|
|
@@ -231,7 +251,7 @@ class ByteParser extends Writable { | |
| } else { | ||
| this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => { | ||
| if (error) { | ||
| // Use 1009 (Message Too Big) for decompression size limit errors | ||
| this.publishFrameError(error) | ||
| const code = error instanceof MessageSizeExceededError ? 1009 : 1007 | ||
| failWebsocketConnection(this.#handler, code, error.message) | ||
| return | ||
|
|
@@ -384,6 +404,7 @@ class ByteParser extends Writable { | |
|
|
||
| if (opcode === opcodes.CLOSE) { | ||
| if (payloadLength === 1) { | ||
| this.publishFrameError(new Error('Received close frame with a 1-byte body.')) | ||
| failWebsocketConnection(this.#handler, 1002, 'Received close frame with a 1-byte body.') | ||
| return false | ||
| } | ||
|
|
@@ -393,6 +414,7 @@ class ByteParser extends Writable { | |
| if (this.#info.closeInfo.error) { | ||
| const { code, reason } = this.#info.closeInfo | ||
|
|
||
| this.publishFrameError(new Error(reason)) | ||
| failWebsocketConnection(this.#handler, code, reason) | ||
| return false | ||
| } | ||
|
|
@@ -448,6 +470,17 @@ class ByteParser extends Writable { | |
| get closingInfo () { | ||
| return this.#info.closeInfo | ||
| } | ||
|
|
||
| publishFrameError (error) { | ||
| if (!channels.frameError.hasSubscribers) { | ||
| return | ||
| } | ||
|
|
||
| channels.frameError.publish({ | ||
| websocket: this.#handler.websocket, | ||
| error | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| module.exports = { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.