Skip to content

Commit e814110

Browse files
committedJul 14, 2021
[major] Make the Sec-WebSocket-Extensions header parser stricter
Make the parser throw an error if the header field value is empty or if it begins or ends with a white space.
1 parent 1877dde commit e814110

File tree

4 files changed

+42
-37
lines changed

4 files changed

+42
-37
lines changed
 

‎lib/extension.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,27 @@ function push(dest, name, elem) {
2626
*/
2727
function parse(header) {
2828
const offers = Object.create(null);
29-
30-
if (header === undefined || header === '') return offers;
31-
3229
let params = Object.create(null);
3330
let mustUnescape = false;
3431
let isEscaping = false;
3532
let inQuotes = false;
3633
let extensionName;
3734
let paramName;
3835
let start = -1;
36+
let code = -1;
3937
let end = -1;
4038
let i = 0;
4139

4240
for (; i < header.length; i++) {
43-
const code = header.charCodeAt(i);
41+
code = header.charCodeAt(i);
4442

4543
if (extensionName === undefined) {
4644
if (end === -1 && tokenChars[code] === 1) {
4745
if (start === -1) start = i;
48-
} else if (code === 0x20 /* ' ' */ || code === 0x09 /* '\t' */) {
46+
} else if (
47+
i !== 0 &&
48+
(code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */
49+
) {
4950
if (end === -1 && start !== -1) end = i;
5051
} else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) {
5152
if (start === -1) {
@@ -146,7 +147,7 @@ function parse(header) {
146147
}
147148
}
148149

149-
if (start === -1 || inQuotes) {
150+
if (start === -1 || inQuotes || code === 0x20 || code === 0x09) {
150151
throw new SyntaxError('Unexpected end of input');
151152
}
152153

‎lib/websocket-server.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ class WebSocketServer extends EventEmitter {
212212
? req.headers['sec-websocket-key']
213213
: false;
214214
const version = +req.headers['sec-websocket-version'];
215-
const extensions = {};
216215

217216
if (
218217
req.method !== 'GET' ||
@@ -236,15 +235,21 @@ class WebSocketServer extends EventEmitter {
236235
}
237236
}
238237

239-
if (this.options.perMessageDeflate) {
238+
const secWebSocketExtensions = req.headers['sec-websocket-extensions'];
239+
const extensions = {};
240+
241+
if (
242+
this.options.perMessageDeflate &&
243+
secWebSocketExtensions !== undefined
244+
) {
240245
const perMessageDeflate = new PerMessageDeflate(
241246
this.options.perMessageDeflate,
242247
true,
243248
this.options.maxPayload
244249
);
245250

246251
try {
247-
const offers = extension.parse(req.headers['sec-websocket-extensions']);
252+
const offers = extension.parse(secWebSocketExtensions);
248253

249254
if (offers[PerMessageDeflate.extensionName]) {
250255
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);

‎lib/websocket.js

+17-20
Original file line numberDiff line numberDiff line change
@@ -801,28 +801,25 @@ function initAsClient(websocket, address, protocols, options) {
801801

802802
const extensionNames = Object.keys(extensions);
803803

804-
if (extensionNames.length) {
805-
if (
806-
extensionNames.length !== 1 ||
807-
extensionNames[0] !== PerMessageDeflate.extensionName
808-
) {
809-
const message =
810-
'Server indicated an extension that was not requested';
811-
abortHandshake(websocket, socket, message);
812-
return;
813-
}
814-
815-
try {
816-
perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
817-
} catch (err) {
818-
const message = 'Invalid Sec-WebSocket-Extensions header';
819-
abortHandshake(websocket, socket, message);
820-
return;
821-
}
804+
if (
805+
extensionNames.length !== 1 ||
806+
extensionNames[0] !== PerMessageDeflate.extensionName
807+
) {
808+
const message = 'Server indicated an extension that was not requested';
809+
abortHandshake(websocket, socket, message);
810+
return;
811+
}
822812

823-
websocket._extensions[PerMessageDeflate.extensionName] =
824-
perMessageDeflate;
813+
try {
814+
perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
815+
} catch (err) {
816+
const message = 'Invalid Sec-WebSocket-Extensions header';
817+
abortHandshake(websocket, socket, message);
818+
return;
825819
}
820+
821+
websocket._extensions[PerMessageDeflate.extensionName] =
822+
perMessageDeflate;
826823
}
827824

828825
websocket.setSocket(socket, head, opts.maxPayload);

‎test/extension.test.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@ const { format, parse } = require('../lib/extension');
66

77
describe('extension', () => {
88
describe('parse', () => {
9-
it('returns an empty object if the argument is `undefined`', () => {
10-
assert.deepStrictEqual(parse(), { __proto__: null });
11-
assert.deepStrictEqual(parse(''), { __proto__: null });
12-
});
13-
149
it('parses a single extension', () => {
1510
assert.deepStrictEqual(parse('foo'), {
1611
foo: [{ __proto__: null }],
@@ -73,7 +68,7 @@ describe('extension', () => {
7368
});
7469

7570
it('ignores the optional white spaces', () => {
76-
const header = 'foo; bar\t; \tbaz=1\t ; bar="1"\t\t, \tqux\t ;norf ';
71+
const header = 'foo; bar\t; \tbaz=1\t ; bar="1"\t\t, \tqux\t ;norf';
7772

7873
assert.deepStrictEqual(parse(header), {
7974
foo: [{ bar: [true, '1'], baz: ['1'], __proto__: null }],
@@ -105,10 +100,12 @@ describe('extension', () => {
105100

106101
it('throws an error if a white space is misplaced', () => {
107102
[
103+
[' foo', 0],
108104
['f oo', 2],
109105
['foo;ba r', 7],
110106
['foo;bar =', 8],
111-
['foo;bar= ', 8]
107+
['foo;bar= ', 8],
108+
['foo;bar=ba z', 11]
112109
].forEach((element) => {
113110
assert.throws(
114111
() => parse(element[0]),
@@ -147,13 +144,18 @@ describe('extension', () => {
147144

148145
it('throws an error if the header value ends prematurely', () => {
149146
[
147+
'',
148+
'foo ',
149+
'foo\t',
150150
'foo, ',
151151
'foo;',
152+
'foo;bar ',
152153
'foo;bar,',
153154
'foo;bar; ',
154155
'foo;bar=',
155156
'foo;bar="baz',
156-
'foo;bar="1\\'
157+
'foo;bar="1\\',
158+
'foo;bar="baz" '
157159
].forEach((header) => {
158160
assert.throws(
159161
() => parse(header),

0 commit comments

Comments
 (0)
Please sign in to comment.