Skip to content

Commit f48ab82

Browse files
committed
[minor] Add ability to specify the masking key
Refs: #1986 Refs: #1988
1 parent c82b087 commit f48ab82

File tree

5 files changed

+119
-27
lines changed

5 files changed

+119
-27
lines changed

doc/ws.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,9 @@ is a noop if the ready state is `CONNECTING` or `CLOSED`.
462462

463463
- `data` {Array|Number|Object|String|ArrayBuffer|Buffer|DataView|TypedArray} The
464464
data to send in the ping frame.
465-
- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to
466-
`true` when `websocket` is not a server client.
465+
- `mask` {Boolean|Buffer} Specifies whether `data` should be masked or not. If a
466+
`Buffer` is provided, then its contents is used as the masking key. Defaults
467+
to `true` when `websocket` is not a server client.
467468
- `callback` {Function} An optional callback which is invoked when the ping
468469
frame is written out. If an error occurs, the callback is called with the
469470
error as its first argument.
@@ -474,8 +475,9 @@ Send a ping. This method throws an error if the ready state is `CONNECTING`.
474475

475476
- `data` {Array|Number|Object|String|ArrayBuffer|Buffer|DataView|TypedArray} The
476477
data to send in the pong frame.
477-
- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults to
478-
`true` when `websocket` is not a server client.
478+
- `mask` {Boolean|Buffer} Specifies whether `data` should be masked or not. If a
479+
`Buffer` is provided, then its contents is used as the masking key. Defaults
480+
to `true` when `websocket` is not a server client.
479481
- `callback` {Function} An optional callback which is invoked when the pong
480482
frame is written out. If an error occurs, the callback is called with the
481483
error as its first argument.
@@ -519,8 +521,9 @@ only removes listeners added with
519521
Defaults to `true` when permessage-deflate is enabled.
520522
- `fin` {Boolean} Specifies whether `data` is the last fragment of a message
521523
or not. Defaults to `true`.
522-
- `mask` {Boolean} Specifies whether `data` should be masked or not. Defaults
523-
to `true` when `websocket` is not a server client.
524+
- `mask` {Boolean|Buffer} Specifies whether `data` should be masked or not. If
525+
a `Buffer` is provided, then its contents is used as the masking key.
526+
Defaults to `true` when `websocket` is not a server client.
524527
- `callback` {Function} An optional callback which is invoked when `data` is
525528
written out. If an error occurs, the callback is called with the error as its
526529
first argument.

lib/receiver.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,12 @@ class Receiver extends Writable {
417417
}
418418

419419
data = this.consume(this._payloadLength);
420-
if (this._masked) unmask(data, this._mask);
420+
if (
421+
this._masked &&
422+
(this._mask[0] | this._mask[1] | this._mask[2] | this._mask[3]) !== 0
423+
) {
424+
unmask(data, this._mask);
425+
}
421426
}
422427

423428
if (this._opcode > 0x07) return this.controlMessage(data);

lib/sender.js

Lines changed: 47 additions & 14 deletions
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 _mask = Buffer.alloc(4);
1515

1616
/**
1717
* HyBi Sender implementation.
@@ -42,8 +42,9 @@ class Sender {
4242
* @param {Object} options Options object
4343
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
4444
* FIN bit
45-
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
46-
* `data`
45+
* @param {(Boolean|Buffer)} [options.mask=false] Specifies whether or not to
46+
* mask `data`. If a `Buffer` is provided, then its contents is used as
47+
* the masking key
4748
* @param {Number} options.opcode The opcode
4849
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
4950
* modified
@@ -81,14 +82,24 @@ class Sender {
8182

8283
if (!options.mask) return [target, data];
8384

84-
randomFillSync(mask, 0, 4);
85+
let mask = _mask;
86+
87+
if (Buffer.isBuffer(options.mask)) {
88+
mask = options.mask;
89+
} else {
90+
randomFillSync(_mask, 0, 4);
91+
}
8592

8693
target[1] |= 0x80;
8794
target[offset - 4] = mask[0];
8895
target[offset - 3] = mask[1];
8996
target[offset - 2] = mask[2];
9097
target[offset - 1] = mask[3];
9198

99+
if ((mask[0] | mask[1] | mask[2] | mask[3]) === 0) {
100+
return [target, data];
101+
}
102+
92103
if (merge) {
93104
applyMask(data, mask, target, offset, data.length);
94105
return [target];
@@ -103,7 +114,9 @@ class Sender {
103114
*
104115
* @param {Number} [code] The status code component of the body
105116
* @param {(String|Buffer)} [data] The message component of the body
106-
* @param {Boolean} [mask=false] Specifies whether or not to mask the message
117+
* @param {(Boolean|Buffer)} [mask=false] Specifies whether or not to mask the
118+
* message. If a `Buffer` is provided, then its contents is used as the
119+
* masking key
107120
* @param {Function} [cb] Callback
108121
* @public
109122
*/
@@ -145,7 +158,9 @@ class Sender {
145158
* Frames and sends a close message.
146159
*
147160
* @param {Buffer} data The message to send
148-
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
161+
* @param {(Boolean|Buffer)} [mask=false] Specifies whether or not to mask
162+
* `data`. If a `Buffer` is provided, then its contents is used as the
163+
* masking key
149164
* @param {Function} [cb] Callback
150165
* @private
151166
*/
@@ -166,7 +181,9 @@ class Sender {
166181
* Sends a ping message to the other peer.
167182
*
168183
* @param {*} data The message to send
169-
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
184+
* @param {(Boolean|Buffer)} [mask=false] Specifies whether or not to mask
185+
* `data`. If a `Buffer` is provided, then its contents is used as the
186+
* masking key
170187
* @param {Function} [cb] Callback
171188
* @public
172189
*/
@@ -188,7 +205,9 @@ class Sender {
188205
* Frames and sends a ping message.
189206
*
190207
* @param {Buffer} data The message to send
191-
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
208+
* @param {(Boolean|Buffer)} [mask=false] Specifies whether or not to mask
209+
* `data`. If a `Buffer` is provided, then its contents is used as the
210+
* masking key
192211
* @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
193212
* @param {Function} [cb] Callback
194213
* @private
@@ -210,7 +229,9 @@ class Sender {
210229
* Sends a pong message to the other peer.
211230
*
212231
* @param {*} data The message to send
213-
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
232+
* @param {(Boolean|Buffer)} [mask=false] Specifies whether or not to mask
233+
* `data`. If a `Buffer` is provided, then its contents is used as the
234+
* masking key
214235
* @param {Function} [cb] Callback
215236
* @public
216237
*/
@@ -232,7 +253,9 @@ class Sender {
232253
* Frames and sends a pong message.
233254
*
234255
* @param {Buffer} data The message to send
235-
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
256+
* @param {(Boolean|Buffer)} [mask=false] Specifies whether or not to mask
257+
* `data`. If a `Buffer` is provided, then its contents is used as the
258+
* masking key
236259
* @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
237260
* @param {Function} [cb] Callback
238261
* @private
@@ -261,8 +284,9 @@ class Sender {
261284
* compress `data`
262285
* @param {Boolean} [options.fin=false] Specifies whether the fragment is the
263286
* last one
264-
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
265-
* `data`
287+
* @param {(Boolean|Buffer)} [options.mask=false] Specifies whether or not to
288+
* mask `data`. If a `Buffer` is provided, then its contents is used as
289+
* the masking key
266290
* @param {Function} [cb] Callback
267291
* @public
268292
*/
@@ -331,8 +355,9 @@ class Sender {
331355
* @param {Number} options.opcode The opcode
332356
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
333357
* FIN bit
334-
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
335-
* `data`
358+
* @param {(Boolean|Buffer)} [options.mask=false] Specifies whether or not to
359+
* mask `data`. If a `Buffer` is provided, then its contents is used as
360+
* the masking key
336361
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
337362
* modified
338363
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
@@ -348,6 +373,7 @@ class Sender {
348373

349374
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
350375

376+
if (options.mask && options.mask.length) this._bufferedBytes += 4;
351377
this._bufferedBytes += data.length;
352378
this._deflating = true;
353379
perMessageDeflate.compress(data, options.fin, (_, buf) => {
@@ -367,6 +393,7 @@ class Sender {
367393
return;
368394
}
369395

396+
if (options.mask && options.mask.length) this._bufferedBytes -= 4;
370397
this._bufferedBytes -= data.length;
371398
this._deflating = false;
372399
options.readOnly = false;
@@ -383,7 +410,9 @@ class Sender {
383410
dequeue() {
384411
while (!this._deflating && this._queue.length) {
385412
const params = this._queue.shift();
413+
const mask = params[0] === this.dispatch ? params[3].mask : params[2];
386414

415+
if (mask && mask.length) this._bufferedBytes -= 4;
387416
this._bufferedBytes -= params[1].length;
388417
Reflect.apply(params[0], this, params.slice(1));
389418
}
@@ -396,7 +425,11 @@ class Sender {
396425
* @private
397426
*/
398427
enqueue(params) {
428+
const mask = params[0] === this.dispatch ? params[3].mask : params[2];
429+
430+
if (mask && mask.length) this._bufferedBytes += 4;
399431
this._bufferedBytes += params[1].length;
432+
400433
this._queue.push(params);
401434
}
402435

lib/websocket.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,8 @@ class WebSocket extends EventEmitter {
341341
* Send a ping.
342342
*
343343
* @param {*} [data] The data to send
344-
* @param {Boolean} [mask] Indicates whether or not to mask `data`
344+
* @param {(Boolean|Buffer)} [mask] Indicates whether or not to mask `data`.
345+
* If a `Buffer` is provided, then its contents is used as the masking key
345346
* @param {Function} [cb] Callback which is executed when the ping is sent
346347
* @public
347348
*/
@@ -373,7 +374,8 @@ class WebSocket extends EventEmitter {
373374
* Send a pong.
374375
*
375376
* @param {*} [data] The data to send
376-
* @param {Boolean} [mask] Indicates whether or not to mask `data`
377+
* @param {(Boolean|Buffer)} [mask] Indicates whether or not to mask `data`.
378+
* If a `Buffer` is provided, then its contents is used as the masking key
377379
* @param {Function} [cb] Callback which is executed when the pong is sent
378380
* @public
379381
*/
@@ -429,7 +431,9 @@ class WebSocket extends EventEmitter {
429431
* `data`
430432
* @param {Boolean} [options.fin=true] Specifies whether the fragment is the
431433
* last one
432-
* @param {Boolean} [options.mask] Specifies whether or not to mask `data`
434+
* @param {(Boolean|Buffer)} [options.mask] Specifies whether or not to mask
435+
* `data`. If a `Buffer` is provided, then its contents is used as the
436+
* masking key
433437
* @param {Function} [cb] Callback which is executed when data is written out
434438
* @public
435439
*/

test/websocket.test.js

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,10 +1299,11 @@ describe('WebSocket', () => {
12991299

13001300
it('can send a ping with data', (done) => {
13011301
const wss = new WebSocket.Server({ port: 0 }, () => {
1302+
const mask = Buffer.alloc(4);
13021303
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
13031304

13041305
ws.on('open', () => {
1305-
ws.ping('hi', () => {
1306+
ws.ping('hi', mask, () => {
13061307
ws.ping('hi', true);
13071308
ws.close();
13081309
});
@@ -1468,10 +1469,11 @@ describe('WebSocket', () => {
14681469

14691470
it('can send a pong with data', (done) => {
14701471
const wss = new WebSocket.Server({ port: 0 }, () => {
1472+
const mask = Buffer.alloc(4);
14711473
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
14721474

14731475
ws.on('open', () => {
1474-
ws.pong('hi', () => {
1476+
ws.pong('hi', mask, () => {
14751477
ws.pong('hi', true);
14761478
ws.close();
14771479
});
@@ -1877,7 +1879,7 @@ describe('WebSocket', () => {
18771879
});
18781880
});
18791881

1880-
it('honors the `mask` option', (done) => {
1882+
it('honors the `mask` option (1/2)', (done) => {
18811883
let clientCloseEventEmitted = false;
18821884
let serverClientCloseEventEmitted = false;
18831885

@@ -1921,6 +1923,51 @@ describe('WebSocket', () => {
19211923
});
19221924
});
19231925
});
1926+
1927+
it('honors the `mask` option (2/2)', (done) => {
1928+
const mask1 = Buffer.from('00000000', 'hex');
1929+
const mask2 = Buffer.from('00000001', 'hex');
1930+
const wss = new WebSocket.Server(
1931+
{ perMessageDeflate: true, port: 0 },
1932+
() => {
1933+
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
1934+
1935+
ws.on('open', () => {
1936+
ws.send('foo', { mask: mask1 });
1937+
ws.send('bar', { mask: mask2 });
1938+
1939+
assert.strictEqual(ws.bufferedAmount, 14);
1940+
1941+
ws.on('close', (code, reason) => {
1942+
assert.strictEqual(code, 1005);
1943+
assert.deepStrictEqual(reason, EMPTY_BUFFER);
1944+
1945+
wss.close(done);
1946+
});
1947+
});
1948+
}
1949+
);
1950+
1951+
wss.on('connection', (ws) => {
1952+
const chunks = [];
1953+
1954+
ws._socket.prependListener('data', (chunk) => {
1955+
chunks.push(chunk);
1956+
});
1957+
1958+
ws.on('message', (message) => {
1959+
if (message.toString() === 'foo') return;
1960+
1961+
const data = Buffer.concat(chunks);
1962+
const length = data[1] & 0x7f;
1963+
1964+
assert.ok(data.slice(2, 6).equals(mask1));
1965+
assert.ok(data.slice(length + 8, length + 12).equals(mask2));
1966+
1967+
ws.close();
1968+
});
1969+
});
1970+
});
19241971
});
19251972

19261973
describe('#close', () => {

0 commit comments

Comments
 (0)