Skip to content

Commit 0aecf0c

Browse files
committedJul 14, 2021
[major] Validate subprotocol names
Make the `WebSocket` constructor throw a `SyntaxError` if any of the subprotocol names are invalid or duplicated.
1 parent 4c1849a commit 0aecf0c

File tree

2 files changed

+53
-18
lines changed

2 files changed

+53
-18
lines changed
 

‎lib/websocket.js

+35-14
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const { format, parse } = require('./extension');
2424
const { toBuffer } = require('./buffer-util');
2525

2626
const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
27+
const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
2728
const protocolVersions = [8, 13];
2829
const closeTimeout = 30 * 1000;
2930

@@ -61,11 +62,15 @@ class WebSocket extends EventEmitter {
6162
this._isServer = false;
6263
this._redirects = 0;
6364

64-
if (Array.isArray(protocols)) {
65-
protocols = protocols.join(', ');
66-
} else if (typeof protocols === 'object' && protocols !== null) {
67-
options = protocols;
68-
protocols = undefined;
65+
if (protocols === undefined) {
66+
protocols = [];
67+
} else if (!Array.isArray(protocols)) {
68+
if (typeof protocols === 'object' && protocols !== null) {
69+
options = protocols;
70+
protocols = [];
71+
} else {
72+
protocols = [protocols];
73+
}
6974
}
7075

7176
initAsClient(this, address, protocols, options);
@@ -558,7 +563,7 @@ module.exports = WebSocket;
558563
*
559564
* @param {WebSocket} websocket The client to initialize
560565
* @param {(String|URL)} address The URL to which to connect
561-
* @param {String} [protocols] The subprotocols
566+
* @param {Array} protocols The subprotocols
562567
* @param {Object} [options] Connection options
563568
* @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable
564569
* permessage-deflate
@@ -623,6 +628,7 @@ function initAsClient(websocket, address, protocols, options) {
623628
const defaultPort = isSecure ? 443 : 80;
624629
const key = randomBytes(16).toString('base64');
625630
const get = isSecure ? https.get : http.get;
631+
const protocolSet = new Set();
626632
let perMessageDeflate;
627633

628634
opts.createConnection = isSecure ? tlsConnect : netConnect;
@@ -651,8 +657,22 @@ function initAsClient(websocket, address, protocols, options) {
651657
[PerMessageDeflate.extensionName]: perMessageDeflate.offer()
652658
});
653659
}
654-
if (protocols) {
655-
opts.headers['Sec-WebSocket-Protocol'] = protocols;
660+
if (protocols.length) {
661+
for (const protocol of protocols) {
662+
if (
663+
typeof protocol !== 'string' ||
664+
!subprotocolRegex.test(protocol) ||
665+
protocolSet.has(protocol)
666+
) {
667+
throw new SyntaxError(
668+
'An invalid or duplicated subprotocol was specified'
669+
);
670+
}
671+
672+
protocolSet.add(protocol);
673+
}
674+
675+
opts.headers['Sec-WebSocket-Protocol'] = protocols.join(',');
656676
}
657677
if (opts.origin) {
658678
if (opts.protocolVersion < 13) {
@@ -739,15 +759,16 @@ function initAsClient(websocket, address, protocols, options) {
739759
}
740760

741761
const serverProt = res.headers['sec-websocket-protocol'];
742-
const protList = (protocols || '').split(/, */);
743762
let protError;
744763

745-
if (!protocols && serverProt) {
746-
protError = 'Server sent a subprotocol but none was requested';
747-
} else if (protocols && !serverProt) {
764+
if (serverProt) {
765+
if (!protocolSet.size) {
766+
protError = 'Server sent a subprotocol but none was requested';
767+
} else if (!protocolSet.has(serverProt)) {
768+
protError = 'Server sent an invalid subprotocol';
769+
}
770+
} else if (protocolSet.size) {
748771
protError = 'Server sent no subprotocol';
749-
} else if (serverProt && !protList.includes(serverProt)) {
750-
protError = 'Server sent an invalid subprotocol';
751772
}
752773

753774
if (protError) {

‎test/websocket.test.js

+18-4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ describe('WebSocket', () => {
2626
);
2727
});
2828

29+
it('throws an error if a subprotocol is invalid or duplicated', () => {
30+
for (const subprotocol of [null, '', 'a,b', ['a', 'a']]) {
31+
assert.throws(
32+
() => new WebSocket('ws://localhost', subprotocol),
33+
/^SyntaxError: An invalid or duplicated subprotocol was specified$/
34+
);
35+
}
36+
});
37+
2938
it('accepts `url.URL` objects as url', (done) => {
3039
const agent = new CustomAgent();
3140

@@ -44,13 +53,18 @@ describe('WebSocket', () => {
4453
let count = 0;
4554
let ws;
4655

47-
agent.addRequest = () => count++;
56+
agent.addRequest = (req) => {
57+
assert.strictEqual(
58+
req.getHeader('sec-websocket-protocol'),
59+
undefined
60+
);
61+
count++;
62+
};
4863

4964
ws = new WebSocket('ws://localhost', undefined, { agent });
50-
ws = new WebSocket('ws://localhost', null, { agent });
5165
ws = new WebSocket('ws://localhost', [], { agent });
5266

53-
assert.strictEqual(count, 3);
67+
assert.strictEqual(count, 2);
5468
});
5569

5670
it('accepts the `maxPayload` option', (done) => {
@@ -651,7 +665,7 @@ describe('WebSocket', () => {
651665
server.once('upgrade', (req, socket) => socket.on('end', socket.end));
652666

653667
const port = server.address().port;
654-
const ws = new WebSocket(`ws://localhost:${port}`, null, {
668+
const ws = new WebSocket(`ws://localhost:${port}`, {
655669
handshakeTimeout: 100
656670
});
657671

0 commit comments

Comments
 (0)
Please sign in to comment.