Skip to content
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

Add ability to skip UTF-8 validation #1928

Merged
merged 1 commit into from Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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]));
});
});