Skip to content

Commit

Permalink
[feature] Add ability to skip UTF-8 validation (#1928)
Browse files Browse the repository at this point in the history
Add the `skipUTF8Validation` option to skip UTF-8 validation for text
and close messages.

Refs: #1878
Closes #1924
  • Loading branch information
lpinca committed Aug 11, 2021
1 parent 9bd3bd1 commit ea29b6c
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 7 deletions.
6 changes: 6 additions & 0 deletions doc/ws.md
Expand Up @@ -78,6 +78,9 @@ This class represents a WebSocket server. It extends the `EventEmitter`.
- `clientTracking` {Boolean} Specifies whether or not to track clients.
- `perMessageDeflate` {Boolean|Object} Enable/disable permessage-deflate.
- `maxPayload` {Number} The maximum allowed message size in bytes.
- `skipUTF8Validation` {Boolean} Specifies whether or not to skip UTF-8
validation for text and close messages. Defaults to `false`. Set to `true`
only if clients are trusted.
- `callback` {Function}

Create a new server instance. One and only one of `port`, `server` or `noServer`
Expand Down Expand Up @@ -273,6 +276,9 @@ This class represents a WebSocket. It extends the `EventEmitter`.
- `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header
depending on the `protocolVersion`.
- `maxPayload` {Number} The maximum allowed message size in bytes.
- `skipUTF8Validation` {Boolean} Specifies whether or not to skip UTF-8
validation for text and close messages. Defaults to `false`. Set to `true`
only if the server is trusted.
- Any other option allowed in [http.request()][] or [https.request()][].
Options given do not have any effect if parsed from the URL given with the
`address` parameter.
Expand Down
5 changes: 3 additions & 2 deletions lib/receiver.js
Expand Up @@ -43,6 +43,7 @@ class Receiver extends Writable {
this._extensions = options.extensions || {};
this._isServer = !!options.isServer;
this._maxPayload = options.maxPayload | 0;
this._skipUTF8Validation = !!options.skipUTF8Validation;
this[kWebSocket] = undefined;

this._bufferedBytes = 0;
Expand Down Expand Up @@ -505,7 +506,7 @@ class Receiver extends Writable {
} else {
const buf = concat(fragments, messageLength);

if (!isValidUTF8(buf)) {
if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
this._loop = false;
return error(
Error,
Expand Down Expand Up @@ -560,7 +561,7 @@ class Receiver extends Writable {

const buf = data.slice(2);

if (!isValidUTF8(buf)) {
if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
return error(
Error,
'invalid UTF-8 sequence',
Expand Down
8 changes: 7 additions & 1 deletion lib/websocket-server.js
Expand Up @@ -46,6 +46,8 @@ class WebSocketServer extends EventEmitter {
* @param {Number} [options.port] The port where to bind the server
* @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S
* server to use
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
* not to skip UTF-8 validation for text and close messages
* @param {Function} [options.verifyClient] A hook to reject connections
* @param {Function} [callback] A listener for the `listening` event
*/
Expand All @@ -54,6 +56,7 @@ class WebSocketServer extends EventEmitter {

options = {
maxPayload: 100 * 1024 * 1024,
skipUTF8Validation: false,
perMessageDeflate: false,
handleProtocols: null,
clientTracking: true,
Expand Down Expand Up @@ -386,7 +389,10 @@ class WebSocketServer extends EventEmitter {
socket.write(headers.concat('\r\n').join('\r\n'));
socket.removeListener('error', socketOnError);

ws.setSocket(socket, head, this.options.maxPayload);
ws.setSocket(socket, head, {
maxPayload: this.options.maxPayload,
skipUTF8Validation: this.options.skipUTF8Validation
});

if (this.clients) {
this.clients.add(ws);
Expand Down
18 changes: 14 additions & 4 deletions lib/websocket.js
Expand Up @@ -180,15 +180,19 @@ class WebSocket extends EventEmitter {
* @param {(net.Socket|tls.Socket)} socket The network socket between the
* server and client
* @param {Buffer} head The first packet of the upgraded stream
* @param {Number} [maxPayload=0] The maximum allowed message size
* @param {Object} [options] Options object
* @param {Number} [options.maxPayload=0] The maximum allowed message size
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
* not to skip UTF-8 validation for text and close messages
* @private
*/
setSocket(socket, head, maxPayload) {
setSocket(socket, head, options = {}) {
const receiver = new Receiver({
binaryType: this.binaryType,
extensions: this._extensions,
isServer: this._isServer,
maxPayload
maxPayload: options.maxPayload,
skipUTF8Validation: options.skipUTF8Validation
});

this._sender = new Sender(socket, this._extensions);
Expand Down Expand Up @@ -575,12 +579,15 @@ module.exports = WebSocket;
* redirects
* @param {Number} [options.maxRedirects=10] The maximum number of redirects
* allowed
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
* not to skip UTF-8 validation for text and close messages
* @private
*/
function initAsClient(websocket, address, protocols, options) {
const opts = {
protocolVersion: protocolVersions[1],
maxPayload: 100 * 1024 * 1024,
skipUTF8Validation: false,
perMessageDeflate: true,
followRedirects: false,
maxRedirects: 10,
Expand Down Expand Up @@ -832,7 +839,10 @@ function initAsClient(websocket, address, protocols, options) {
perMessageDeflate;
}

websocket.setSocket(socket, head, opts.maxPayload);
websocket.setSocket(socket, head, {
maxPayload: opts.maxPayload,
skipUTF8Validation: opts.skipUTF8Validation
});
});
}

Expand Down
24 changes: 24 additions & 0 deletions test/receiver.test.js
Expand Up @@ -1059,4 +1059,28 @@ describe('Receiver', () => {
}).forEach((buf) => receiver.write(buf));
});
});

it('honors the `skipUTF8Validation` option (1/2)', (done) => {
const receiver = new Receiver({ skipUTF8Validation: true });

receiver.on('message', (data, isBinary) => {
assert.deepStrictEqual(data, Buffer.from([0xf8]));
assert.ok(!isBinary);
done();
});

receiver.write(Buffer.from([0x81, 0x01, 0xf8]));
});

it('honors the `skipUTF8Validation` option (2/2)', (done) => {
const receiver = new Receiver({ skipUTF8Validation: true });

receiver.on('conclude', (code, data) => {
assert.strictEqual(code, 1000);
assert.deepStrictEqual(data, Buffer.from([0xf8]));
done();
});

receiver.write(Buffer.from([0x88, 0x03, 0x03, 0xe8, 0xf8]));
});
});

0 comments on commit ea29b6c

Please sign in to comment.