Skip to content

Commit d21c810

Browse files
committed
[feature] Add ability to skip UTF-8 validation (#1928)
Add the `skipUTF8Validation` option to skip UTF-8 validation for text and close messages. Refs: #1878 Closes #1924
1 parent 9bd3bd1 commit d21c810

File tree

6 files changed

+58
-8
lines changed

6 files changed

+58
-8
lines changed

bench/parser.benchmark.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ const suite = new benchmark.Suite();
3939
const receiver = new Receiver({
4040
binaryType: 'nodebuffer',
4141
extensions: {},
42-
isServer: true
42+
isServer: true,
43+
skipUTF8Validation: false
4344
});
4445

4546
suite.add('ping frame (5 bytes payload)', {

doc/ws.md

+6
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ This class represents a WebSocket server. It extends the `EventEmitter`.
7878
- `clientTracking` {Boolean} Specifies whether or not to track clients.
7979
- `perMessageDeflate` {Boolean|Object} Enable/disable permessage-deflate.
8080
- `maxPayload` {Number} The maximum allowed message size in bytes.
81+
- `skipUTF8Validation` {Boolean} Specifies whether or not to skip UTF-8
82+
validation for text and close messages. Defaults to `false`. Set to `true`
83+
only if clients are trusted.
8184
- `callback` {Function}
8285

8386
Create a new server instance. One and only one of `port`, `server` or `noServer`
@@ -273,6 +276,9 @@ This class represents a WebSocket. It extends the `EventEmitter`.
273276
- `origin` {String} Value of the `Origin` or `Sec-WebSocket-Origin` header
274277
depending on the `protocolVersion`.
275278
- `maxPayload` {Number} The maximum allowed message size in bytes.
279+
- `skipUTF8Validation` {Boolean} Specifies whether or not to skip UTF-8
280+
validation for text and close messages. Defaults to `false`. Set to `true`
281+
only if the server is trusted.
276282
- Any other option allowed in [http.request()][] or [https.request()][].
277283
Options given do not have any effect if parsed from the URL given with the
278284
`address` parameter.

lib/receiver.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class Receiver extends Writable {
3535
* @param {Boolean} [options.isServer=false] Specifies whether to operate in
3636
* client or server mode
3737
* @param {Number} [options.maxPayload=0] The maximum allowed message length
38+
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
39+
* not to skip UTF-8 validation for text and close messages
3840
*/
3941
constructor(options = {}) {
4042
super();
@@ -43,6 +45,7 @@ class Receiver extends Writable {
4345
this._extensions = options.extensions || {};
4446
this._isServer = !!options.isServer;
4547
this._maxPayload = options.maxPayload | 0;
48+
this._skipUTF8Validation = !!options.skipUTF8Validation;
4649
this[kWebSocket] = undefined;
4750

4851
this._bufferedBytes = 0;
@@ -505,7 +508,7 @@ class Receiver extends Writable {
505508
} else {
506509
const buf = concat(fragments, messageLength);
507510

508-
if (!isValidUTF8(buf)) {
511+
if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
509512
this._loop = false;
510513
return error(
511514
Error,
@@ -560,7 +563,7 @@ class Receiver extends Writable {
560563

561564
const buf = data.slice(2);
562565

563-
if (!isValidUTF8(buf)) {
566+
if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
564567
return error(
565568
Error,
566569
'invalid UTF-8 sequence',

lib/websocket-server.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class WebSocketServer extends EventEmitter {
4646
* @param {Number} [options.port] The port where to bind the server
4747
* @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S
4848
* server to use
49+
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
50+
* not to skip UTF-8 validation for text and close messages
4951
* @param {Function} [options.verifyClient] A hook to reject connections
5052
* @param {Function} [callback] A listener for the `listening` event
5153
*/
@@ -54,6 +56,7 @@ class WebSocketServer extends EventEmitter {
5456

5557
options = {
5658
maxPayload: 100 * 1024 * 1024,
59+
skipUTF8Validation: false,
5760
perMessageDeflate: false,
5861
handleProtocols: null,
5962
clientTracking: true,
@@ -386,7 +389,10 @@ class WebSocketServer extends EventEmitter {
386389
socket.write(headers.concat('\r\n').join('\r\n'));
387390
socket.removeListener('error', socketOnError);
388391

389-
ws.setSocket(socket, head, this.options.maxPayload);
392+
ws.setSocket(socket, head, {
393+
maxPayload: this.options.maxPayload,
394+
skipUTF8Validation: this.options.skipUTF8Validation
395+
});
390396

391397
if (this.clients) {
392398
this.clients.add(ws);

lib/websocket.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -180,15 +180,19 @@ class WebSocket extends EventEmitter {
180180
* @param {(net.Socket|tls.Socket)} socket The network socket between the
181181
* server and client
182182
* @param {Buffer} head The first packet of the upgraded stream
183-
* @param {Number} [maxPayload=0] The maximum allowed message size
183+
* @param {Object} options Options object
184+
* @param {Number} [options.maxPayload=0] The maximum allowed message size
185+
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
186+
* not to skip UTF-8 validation for text and close messages
184187
* @private
185188
*/
186-
setSocket(socket, head, maxPayload) {
189+
setSocket(socket, head, options) {
187190
const receiver = new Receiver({
188191
binaryType: this.binaryType,
189192
extensions: this._extensions,
190193
isServer: this._isServer,
191-
maxPayload
194+
maxPayload: options.maxPayload,
195+
skipUTF8Validation: options.skipUTF8Validation
192196
});
193197

194198
this._sender = new Sender(socket, this._extensions);
@@ -575,12 +579,15 @@ module.exports = WebSocket;
575579
* redirects
576580
* @param {Number} [options.maxRedirects=10] The maximum number of redirects
577581
* allowed
582+
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
583+
* not to skip UTF-8 validation for text and close messages
578584
* @private
579585
*/
580586
function initAsClient(websocket, address, protocols, options) {
581587
const opts = {
582588
protocolVersion: protocolVersions[1],
583589
maxPayload: 100 * 1024 * 1024,
590+
skipUTF8Validation: false,
584591
perMessageDeflate: true,
585592
followRedirects: false,
586593
maxRedirects: 10,
@@ -832,7 +839,10 @@ function initAsClient(websocket, address, protocols, options) {
832839
perMessageDeflate;
833840
}
834841

835-
websocket.setSocket(socket, head, opts.maxPayload);
842+
websocket.setSocket(socket, head, {
843+
maxPayload: opts.maxPayload,
844+
skipUTF8Validation: opts.skipUTF8Validation
845+
});
836846
});
837847
}
838848

test/receiver.test.js

+24
Original file line numberDiff line numberDiff line change
@@ -1059,4 +1059,28 @@ describe('Receiver', () => {
10591059
}).forEach((buf) => receiver.write(buf));
10601060
});
10611061
});
1062+
1063+
it('honors the `skipUTF8Validation` option (1/2)', (done) => {
1064+
const receiver = new Receiver({ skipUTF8Validation: true });
1065+
1066+
receiver.on('message', (data, isBinary) => {
1067+
assert.deepStrictEqual(data, Buffer.from([0xf8]));
1068+
assert.ok(!isBinary);
1069+
done();
1070+
});
1071+
1072+
receiver.write(Buffer.from([0x81, 0x01, 0xf8]));
1073+
});
1074+
1075+
it('honors the `skipUTF8Validation` option (2/2)', (done) => {
1076+
const receiver = new Receiver({ skipUTF8Validation: true });
1077+
1078+
receiver.on('conclude', (code, data) => {
1079+
assert.strictEqual(code, 1000);
1080+
assert.deepStrictEqual(data, Buffer.from([0xf8]));
1081+
done();
1082+
});
1083+
1084+
receiver.write(Buffer.from([0x88, 0x03, 0x03, 0xe8, 0xf8]));
1085+
});
10621086
});

0 commit comments

Comments
 (0)