From ff56ba6a3aa7d7acbc305fe8ca678a8374645274 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Mon, 14 Jun 2021 19:35:28 +0200 Subject: [PATCH] Attach error codes to all receiver errors --- lib/permessage-deflate.js | 1 + lib/receiver.js | 138 +++++++++++++++++++++++---- test/create-websocket-stream.test.js | 1 + test/receiver.test.js | 22 +++++ test/websocket.test.js | 1 + 5 files changed, 144 insertions(+), 19 deletions(-) diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js index a8974b988..8229280f1 100644 --- a/lib/permessage-deflate.js +++ b/lib/permessage-deflate.js @@ -495,6 +495,7 @@ function inflateOnData(chunk) { } this[kError] = new RangeError('Max payload size exceeded'); + this[kError].code = 'WS_ERR_INVALID_DATA_PAYLOAD_LENGTH'; this[kError][kStatusCode] = 1009; this.removeListener('data', inflateOnData); this.reset(); diff --git a/lib/receiver.js b/lib/receiver.js index 65a5ab45f..848a52561 100644 --- a/lib/receiver.js +++ b/lib/receiver.js @@ -168,14 +168,26 @@ class Receiver extends Writable { if ((buf[0] & 0x30) !== 0x00) { this._loop = false; - return error(RangeError, 'RSV2 and RSV3 must be clear', true, 1002); + return error( + RangeError, + 'RSV2 and RSV3 must be clear', + true, + 1002, + 'WS_ERR_UNEXPECTED_RSV_2_3' + ); } const compressed = (buf[0] & 0x40) === 0x40; if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { this._loop = false; - return error(RangeError, 'RSV1 must be clear', true, 1002); + return error( + RangeError, + 'RSV1 must be clear', + true, + 1002, + 'WS_ERR_UNEXPECTED_RSV_1' + ); } this._fin = (buf[0] & 0x80) === 0x80; @@ -185,31 +197,61 @@ class Receiver extends Writable { if (this._opcode === 0x00) { if (compressed) { this._loop = false; - return error(RangeError, 'RSV1 must be clear', true, 1002); + return error( + RangeError, + 'RSV1 must be clear', + true, + 1002, + 'WS_ERR_UNEXPECTED_RSV_1' + ); } if (!this._fragmented) { this._loop = false; - return error(RangeError, 'invalid opcode 0', true, 1002); + return error( + RangeError, + 'invalid opcode 0', + true, + 1002, + 'WS_ERR_INVALID_OPCODE' + ); } this._opcode = this._fragmented; } else if (this._opcode === 0x01 || this._opcode === 0x02) { if (this._fragmented) { this._loop = false; - return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002); + return error( + RangeError, + `invalid opcode ${this._opcode}`, + true, + 1002, + 'WS_ERR_INVALID_OPCODE' + ); } this._compressed = compressed; } else if (this._opcode > 0x07 && this._opcode < 0x0b) { if (!this._fin) { this._loop = false; - return error(RangeError, 'FIN must be set', true, 1002); + return error( + RangeError, + 'FIN must be set', + true, + 1002, + 'WS_ERR_EXPECTED_FIN' + ); } if (compressed) { this._loop = false; - return error(RangeError, 'RSV1 must be clear', true, 1002); + return error( + RangeError, + 'RSV1 must be clear', + true, + 1002, + 'WS_ERR_UNEXPECTED_RSV_1' + ); } if (this._payloadLength > 0x7d) { @@ -218,12 +260,19 @@ class Receiver extends Writable { RangeError, `invalid payload length ${this._payloadLength}`, true, - 1002 + 1002, + 'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH' ); } } else { this._loop = false; - return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002); + return error( + RangeError, + `invalid opcode ${this._opcode}`, + true, + 1002, + 'WS_ERR_INVALID_OPCODE' + ); } if (!this._fin && !this._fragmented) this._fragmented = this._opcode; @@ -232,11 +281,23 @@ class Receiver extends Writable { if (this._isServer) { if (!this._masked) { this._loop = false; - return error(RangeError, 'MASK must be set', true, 1002); + return error( + RangeError, + 'MASK must be set', + true, + 1002, + 'WS_ERR_EXPECTED_MASK' + ); } } else if (this._masked) { this._loop = false; - return error(RangeError, 'MASK must be clear', true, 1002); + return error( + RangeError, + 'MASK must be clear', + true, + 1002, + 'WS_ERR_UNEXPECTED_MASK' + ); } if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16; @@ -285,7 +346,8 @@ class Receiver extends Writable { RangeError, 'Unsupported WebSocket frame: payload length > 2^53 - 1', false, - 1009 + 1009, + 'WS_ERR_INVALID_DATA_PAYLOAD_LENGTH' ); } @@ -304,7 +366,13 @@ class Receiver extends Writable { this._totalPayloadLength += this._payloadLength; if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) { this._loop = false; - return error(RangeError, 'Max payload size exceeded', false, 1009); + return error( + RangeError, + 'Max payload size exceeded', + false, + 1009, + 'WS_ERR_INVALID_DATA_PAYLOAD_LENGTH' + ); } } @@ -384,7 +452,13 @@ class Receiver extends Writable { this._messageLength += buf.length; if (this._messageLength > this._maxPayload && this._maxPayload > 0) { return cb( - error(RangeError, 'Max payload size exceeded', false, 1009) + error( + RangeError, + 'Max payload size exceeded', + false, + 1009, + 'WS_ERR_INVALID_DATA_PAYLOAD_LENGTH' + ) ); } @@ -431,7 +505,13 @@ class Receiver extends Writable { if (!isValidUTF8(buf)) { this._loop = false; - return error(Error, 'invalid UTF-8 sequence', true, 1007); + return error( + Error, + 'invalid UTF-8 sequence', + true, + 1007, + 'WS_ERR_INVALID_UTF8' + ); } this.emit('message', buf.toString()); @@ -456,18 +536,36 @@ class Receiver extends Writable { this.emit('conclude', 1005, ''); this.end(); } else if (data.length === 1) { - return error(RangeError, 'invalid payload length 1', true, 1002); + return error( + RangeError, + 'invalid payload length 1', + true, + 1002, + 'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH' + ); } else { const code = data.readUInt16BE(0); if (!isValidStatusCode(code)) { - return error(RangeError, `invalid status code ${code}`, true, 1002); + return error( + RangeError, + `invalid status code ${code}`, + true, + 1002, + 'WS_ERR_INVALID_CLOSE_CODE' + ); } const buf = data.slice(2); if (!isValidUTF8(buf)) { - return error(Error, 'invalid UTF-8 sequence', true, 1007); + return error( + Error, + 'invalid UTF-8 sequence', + true, + 1007, + 'WS_ERR_INVALID_UTF8' + ); } this.emit('conclude', code, buf.toString()); @@ -493,15 +591,17 @@ module.exports = Receiver; * @param {Boolean} prefix Specifies whether or not to add a default prefix to * `message` * @param {Number} statusCode The status code + * @param {String} errorCode The exposed error code * @return {(Error|RangeError)} The error * @private */ -function error(ErrorCtor, message, prefix, statusCode) { +function error(ErrorCtor, message, prefix, statusCode, errorCode) { const err = new ErrorCtor( prefix ? `Invalid WebSocket frame: ${message}` : message ); Error.captureStackTrace(err, error); + err.code = errorCode; err[kStatusCode] = statusCode; return err; } diff --git a/test/create-websocket-stream.test.js b/test/create-websocket-stream.test.js index ddccb56b2..bcd240974 100644 --- a/test/create-websocket-stream.test.js +++ b/test/create-websocket-stream.test.js @@ -210,6 +210,7 @@ describe('createWebSocketStream', () => { duplex.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_OPCODE'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 5' diff --git a/test/receiver.test.js b/test/receiver.test.js index a70cc8dbe..54f32b04b 100644 --- a/test/receiver.test.js +++ b/test/receiver.test.js @@ -513,6 +513,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_UNEXPECTED_RSV_1'); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV1 must be clear' @@ -534,6 +535,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_UNEXPECTED_RSV_1'); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV1 must be clear' @@ -550,6 +552,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_UNEXPECTED_RSV_2_3'); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV2 and RSV3 must be clear' @@ -566,6 +569,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_UNEXPECTED_RSV_2_3'); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV2 and RSV3 must be clear' @@ -582,6 +586,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_OPCODE'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 0' @@ -598,6 +603,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_OPCODE'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 1' @@ -615,6 +621,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_OPCODE'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 2' @@ -632,6 +639,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_EXPECTED_FIN'); assert.strictEqual( err.message, 'Invalid WebSocket frame: FIN must be set' @@ -653,6 +661,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_UNEXPECTED_RSV_1'); assert.strictEqual( err.message, 'Invalid WebSocket frame: RSV1 must be clear' @@ -669,6 +678,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_EXPECTED_FIN'); assert.strictEqual( err.message, 'Invalid WebSocket frame: FIN must be set' @@ -685,6 +695,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_EXPECTED_MASK'); assert.strictEqual( err.message, 'Invalid WebSocket frame: MASK must be set' @@ -701,6 +712,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_UNEXPECTED_MASK'); assert.strictEqual( err.message, 'Invalid WebSocket frame: MASK must be clear' @@ -719,6 +731,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid payload length 126' @@ -735,6 +748,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_DATA_PAYLOAD_LENGTH'); assert.strictEqual( err.message, 'Unsupported WebSocket frame: payload length > 2^53 - 1' @@ -756,6 +770,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'WS_ERR_INVALID_UTF8'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid UTF-8 sequence' @@ -778,6 +793,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'WS_ERR_INVALID_UTF8'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid UTF-8 sequence' @@ -799,6 +815,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid payload length 1' @@ -815,6 +832,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_CLOSE_CODE'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid status code 0' @@ -831,6 +849,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'WS_ERR_INVALID_UTF8'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid UTF-8 sequence' @@ -860,6 +879,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_DATA_PAYLOAD_LENGTH'); assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(err[kStatusCode], 1009); done(); @@ -884,6 +904,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_DATA_PAYLOAD_LENGTH'); assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(err[kStatusCode], 1009); done(); @@ -913,6 +934,7 @@ describe('Receiver', () => { receiver.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_DATA_PAYLOAD_LENGTH'); assert.strictEqual(err.message, 'Max payload size exceeded'); assert.strictEqual(err[kStatusCode], 1009); done(); diff --git a/test/websocket.test.js b/test/websocket.test.js index a0557980e..03f984fcb 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -435,6 +435,7 @@ describe('WebSocket', () => { ws.on('error', (err) => { assert.ok(err instanceof RangeError); + assert.strictEqual(err.code, 'WS_ERR_INVALID_OPCODE'); assert.strictEqual( err.message, 'Invalid WebSocket frame: invalid opcode 5'