Skip to content

Commit

Permalink
Attach error codes to all receiver errors
Browse files Browse the repository at this point in the history
  • Loading branch information
pimterry committed Jun 14, 2021
1 parent 074e6a8 commit ff56ba6
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 19 deletions.
1 change: 1 addition & 0 deletions lib/permessage-deflate.js
Expand Up @@ -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();
Expand Down
138 changes: 119 additions & 19 deletions lib/receiver.js
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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'
);
}

Expand All @@ -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'
);
}
}

Expand Down Expand Up @@ -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'
)
);
}

Expand Down Expand Up @@ -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());
Expand All @@ -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());
Expand All @@ -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;
}
1 change: 1 addition & 0 deletions test/create-websocket-stream.test.js
Expand Up @@ -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'
Expand Down

0 comments on commit ff56ba6

Please sign in to comment.