Skip to content

Commit eb2e3a8

Browse files
committed
[feature] Introduce the generateMask option
The `generateMask` option specifies a function that can be used to generate custom masking keys. Refs: #1986 Refs: #1988 Refs: #1989
1 parent c82b087 commit eb2e3a8

File tree

4 files changed

+80
-4
lines changed

4 files changed

+80
-4
lines changed

doc/ws.md

+4
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ This class represents a WebSocket. It extends the `EventEmitter`.
270270
- `options` {Object}
271271
- `followRedirects` {Boolean} Whether or not to follow redirects. Defaults to
272272
`false`.
273+
- `generateMask` {Function} The function used to generate the masking key. It
274+
takes a `Buffer` that must be filled synchronously and is called before a
275+
message is sent, for each message. By default the buffer is filled with
276+
cryptographically strong random bytes.
273277
- `handshakeTimeout` {Number} Timeout in milliseconds for the handshake
274278
request. This is reset after every redirection.
275279
- `maxPayload` {Number} The maximum allowed message size in bytes.

lib/sender.js

+35-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const { EMPTY_BUFFER } = require('./constants');
1111
const { isValidStatusCode } = require('./validation');
1212
const { mask: applyMask, toBuffer } = require('./buffer-util');
1313

14-
const mask = Buffer.alloc(4);
14+
const maskBuffer = Buffer.alloc(4);
1515

1616
/**
1717
* HyBi Sender implementation.
@@ -22,9 +22,17 @@ class Sender {
2222
*
2323
* @param {(net.Socket|tls.Socket)} socket The connection socket
2424
* @param {Object} [extensions] An object containing the negotiated extensions
25+
* @param {Function} [generateMask] The function used to generate the masking
26+
* key
2527
*/
26-
constructor(socket, extensions) {
28+
constructor(socket, extensions, generateMask) {
2729
this._extensions = extensions || {};
30+
31+
if (generateMask) {
32+
this._generateMask = generateMask;
33+
this._maskBuffer = Buffer.alloc(4);
34+
}
35+
2836
this._socket = socket;
2937

3038
this._firstFragment = true;
@@ -42,8 +50,12 @@ class Sender {
4250
* @param {Object} options Options object
4351
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
4452
* FIN bit
53+
* @param {Function} [options.generateMask] The function used to generate the
54+
* masking key
4555
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
4656
* `data`
57+
* @param {Buffer} [options.maskBuffer] The buffer used to store the masking
58+
* key
4759
* @param {Number} options.opcode The opcode
4860
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
4961
* modified
@@ -81,7 +93,13 @@ class Sender {
8193

8294
if (!options.mask) return [target, data];
8395

84-
randomFillSync(mask, 0, 4);
96+
const mask = options.maskBuffer ? options.maskBuffer : maskBuffer;
97+
98+
if (options.generateMask) {
99+
options.generateMask(mask);
100+
} else {
101+
randomFillSync(mask, 0, 4);
102+
}
85103

86104
target[1] |= 0x80;
87105
target[offset - 4] = mask[0];
@@ -156,6 +174,8 @@ class Sender {
156174
rsv1: false,
157175
opcode: 0x08,
158176
mask,
177+
maskBuffer: this._maskBuffer,
178+
generateMask: this._generateMask,
159179
readOnly: false
160180
}),
161181
cb
@@ -200,6 +220,8 @@ class Sender {
200220
rsv1: false,
201221
opcode: 0x09,
202222
mask,
223+
maskBuffer: this._maskBuffer,
224+
generateMask: this._generateMask,
203225
readOnly
204226
}),
205227
cb
@@ -244,6 +266,8 @@ class Sender {
244266
rsv1: false,
245267
opcode: 0x0a,
246268
mask,
269+
maskBuffer: this._maskBuffer,
270+
generateMask: this._generateMask,
247271
readOnly
248272
}),
249273
cb
@@ -299,6 +323,8 @@ class Sender {
299323
rsv1,
300324
opcode,
301325
mask: options.mask,
326+
maskBuffer: this._maskBuffer,
327+
generateMask: this._generateMask,
302328
readOnly: toBuffer.readOnly
303329
};
304330

@@ -314,6 +340,8 @@ class Sender {
314340
rsv1: false,
315341
opcode,
316342
mask: options.mask,
343+
maskBuffer: this._maskBuffer,
344+
generateMask: this._generateMask,
317345
readOnly: toBuffer.readOnly
318346
}),
319347
cb
@@ -331,8 +359,12 @@ class Sender {
331359
* @param {Number} options.opcode The opcode
332360
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
333361
* FIN bit
362+
* @param {Function} [options.generateMask] The function used to generate the
363+
* masking key
334364
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
335365
* `data`
366+
* @param {Buffer} [options.maskBuffer] The buffer used to store the masking
367+
* key
336368
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
337369
* modified
338370
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the

lib/websocket.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ class WebSocket extends EventEmitter {
192192
* server and client
193193
* @param {Buffer} head The first packet of the upgraded stream
194194
* @param {Object} options Options object
195+
* @param {Function} [options.generateMask] The function used to generate the
196+
* masking key
195197
* @param {Number} [options.maxPayload=0] The maximum allowed message size
196198
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
197199
* not to skip UTF-8 validation for text and close messages
@@ -206,7 +208,7 @@ class WebSocket extends EventEmitter {
206208
skipUTF8Validation: options.skipUTF8Validation
207209
});
208210

209-
this._sender = new Sender(socket, this._extensions);
211+
this._sender = new Sender(socket, this._extensions, options.generateMask);
210212
this._receiver = receiver;
211213
this._socket = socket;
212214

@@ -613,6 +615,8 @@ module.exports = WebSocket;
613615
* @param {Object} [options] Connection options
614616
* @param {Boolean} [options.followRedirects=false] Whether or not to follow
615617
* redirects
618+
* @param {Function} [options.generateMask] The function used to generate the
619+
* masking key
616620
* @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
617621
* handshake request
618622
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
@@ -899,6 +903,7 @@ function initAsClient(websocket, address, protocols, options) {
899903
}
900904

901905
websocket.setSocket(socket, head, {
906+
generateMask: opts.generateMask,
902907
maxPayload: opts.maxPayload,
903908
skipUTF8Validation: opts.skipUTF8Validation
904909
});

test/websocket.test.js

+35
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,41 @@ describe('WebSocket', () => {
126126
/^RangeError: Unsupported protocol version: 1000 \(supported versions: 8, 13\)$/
127127
);
128128
});
129+
130+
it('honors the `generateMask` option', (done) => {
131+
const wss = new WebSocket.Server({ port: 0 }, () => {
132+
const ws = new WebSocket(`ws://localhost:${wss.address().port}`, {
133+
generateMask() {}
134+
});
135+
136+
ws.on('open', () => {
137+
ws.send('foo');
138+
});
139+
140+
ws.on('close', (code, reason) => {
141+
assert.strictEqual(code, 1005);
142+
assert.deepStrictEqual(reason, EMPTY_BUFFER);
143+
144+
wss.close(done);
145+
});
146+
});
147+
148+
wss.on('connection', (ws) => {
149+
const chunks = [];
150+
151+
ws._socket.prependListener('data', (chunk) => {
152+
chunks.push(chunk);
153+
});
154+
155+
ws.on('message', () => {
156+
assert.ok(
157+
Buffer.concat(chunks).slice(2, 6).equals(Buffer.alloc(4))
158+
);
159+
160+
ws.close();
161+
});
162+
});
163+
});
129164
});
130165
});
131166

0 commit comments

Comments
 (0)