-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[major] Validate the Sec-WebSocket-Protocol header
Abort the handshake if the `Sec-WebSocket-Protocol` header is invalid.
- Loading branch information
Showing
7 changed files
with
240 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
'use strict'; | ||
|
||
const { tokenChars } = require('./validation'); | ||
|
||
/** | ||
* Parses the `Sec-WebSocket-Protocol` header into a set of subprotocol names. | ||
* | ||
* @param {String} header The field value of the header | ||
* @return {Set} The subprotocol names | ||
* @public | ||
*/ | ||
function parse(header) { | ||
const protocols = new Set(); | ||
let start = -1; | ||
let end = -1; | ||
let i = 0; | ||
|
||
for (i; i < header.length; i++) { | ||
const code = header.charCodeAt(i); | ||
|
||
if (end === -1 && tokenChars[code] === 1) { | ||
if (start === -1) start = i; | ||
} else if ( | ||
i !== 0 && | ||
(code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */ | ||
) { | ||
if (end === -1 && start !== -1) end = i; | ||
} else if (code === 0x2c /* ',' */) { | ||
if (start === -1) { | ||
throw new SyntaxError(`Unexpected character at index ${i}`); | ||
} | ||
|
||
if (end === -1) end = i; | ||
|
||
const protocol = header.slice(start, end); | ||
|
||
if (protocols.has(protocol)) { | ||
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`); | ||
} | ||
|
||
protocols.add(protocol); | ||
start = end = -1; | ||
} else { | ||
throw new SyntaxError(`Unexpected character at index ${i}`); | ||
} | ||
} | ||
|
||
if (start === -1 || end !== -1) { | ||
throw new SyntaxError('Unexpected end of input'); | ||
} | ||
|
||
const protocol = header.slice(start, i); | ||
|
||
if (protocols.has(protocol)) { | ||
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`); | ||
} | ||
|
||
protocols.add(protocol); | ||
return protocols; | ||
} | ||
|
||
module.exports = { parse }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
'use strict'; | ||
|
||
const assert = require('assert'); | ||
|
||
const { parse } = require('../lib/subprotocol'); | ||
|
||
describe('subprotocol', () => { | ||
describe('parse', () => { | ||
it('parses a single subprotocol', () => { | ||
assert.deepStrictEqual(parse('foo'), new Set(['foo'])); | ||
}); | ||
|
||
it('parses multiple subprotocols', () => { | ||
assert.deepStrictEqual( | ||
parse('foo,bar,baz'), | ||
new Set(['foo', 'bar', 'baz']) | ||
); | ||
}); | ||
|
||
it('ignores the optional white spaces', () => { | ||
const header = 'foo , bar\t, \tbaz\t , qux\t\t,norf'; | ||
|
||
assert.deepStrictEqual( | ||
parse(header), | ||
new Set(['foo', 'bar', 'baz', 'qux', 'norf']) | ||
); | ||
}); | ||
|
||
it('throws an error if a subprotocol is empty', () => { | ||
[ | ||
[',', 0], | ||
['foo,,', 4], | ||
['foo, ,', 6] | ||
].forEach((element) => { | ||
assert.throws( | ||
() => parse(element[0]), | ||
new RegExp( | ||
`^SyntaxError: Unexpected character at index ${element[1]}$` | ||
) | ||
); | ||
}); | ||
}); | ||
|
||
it('throws an error if a subprotocol is duplicated', () => { | ||
['foo,foo,bar', 'foo,bar,foo'].forEach((header) => { | ||
assert.throws( | ||
() => parse(header), | ||
/^SyntaxError: The "foo" subprotocol is duplicated$/ | ||
); | ||
}); | ||
}); | ||
|
||
it('throws an error if a white space is misplaced', () => { | ||
[ | ||
['f oo', 2], | ||
[' foo', 0] | ||
].forEach((element) => { | ||
assert.throws( | ||
() => parse(element[0]), | ||
new RegExp( | ||
`^SyntaxError: Unexpected character at index ${element[1]}$` | ||
) | ||
); | ||
}); | ||
}); | ||
|
||
it('throws an error if a subprotocol contains invalid characters', () => { | ||
[ | ||
['f@o', 1], | ||
['f\\oo', 1], | ||
['foo,b@r', 5] | ||
].forEach((element) => { | ||
assert.throws( | ||
() => parse(element[0]), | ||
new RegExp( | ||
`^SyntaxError: Unexpected character at index ${element[1]}$` | ||
) | ||
); | ||
}); | ||
}); | ||
|
||
it('throws an error if the header value ends prematurely', () => { | ||
['foo ', 'foo, ', 'foo,bar ', 'foo,bar,'].forEach((header) => { | ||
assert.throws( | ||
() => parse(header), | ||
/^SyntaxError: Unexpected end of input$/ | ||
); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.