Spaces:
Runtime error
Runtime error
; | |
const { tokenChars } = require('./validation'); | |
/** | |
* Adds an offer to the map of extension offers or a parameter to the map of | |
* parameters. | |
* | |
* @param {Object} dest The map of extension offers or parameters | |
* @param {String} name The extension or parameter name | |
* @param {(Object|Boolean|String)} elem The extension parameters or the | |
* parameter value | |
* @private | |
*/ | |
function push(dest, name, elem) { | |
if (dest[name] === undefined) dest[name] = [elem]; | |
else dest[name].push(elem); | |
} | |
/** | |
* Parses the `Sec-WebSocket-Extensions` header into an object. | |
* | |
* @param {String} header The field value of the header | |
* @return {Object} The parsed object | |
* @public | |
*/ | |
function parse(header) { | |
const offers = Object.create(null); | |
let params = Object.create(null); | |
let mustUnescape = false; | |
let isEscaping = false; | |
let inQuotes = false; | |
let extensionName; | |
let paramName; | |
let start = -1; | |
let code = -1; | |
let end = -1; | |
let i = 0; | |
for (; i < header.length; i++) { | |
code = header.charCodeAt(i); | |
if (extensionName === undefined) { | |
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 === 0x3b /* ';' */ || code === 0x2c /* ',' */) { | |
if (start === -1) { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
if (end === -1) end = i; | |
const name = header.slice(start, end); | |
if (code === 0x2c) { | |
push(offers, name, params); | |
params = Object.create(null); | |
} else { | |
extensionName = name; | |
} | |
start = end = -1; | |
} else { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
} else if (paramName === undefined) { | |
if (end === -1 && tokenChars[code] === 1) { | |
if (start === -1) start = i; | |
} else if (code === 0x20 || code === 0x09) { | |
if (end === -1 && start !== -1) end = i; | |
} else if (code === 0x3b || code === 0x2c) { | |
if (start === -1) { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
if (end === -1) end = i; | |
push(params, header.slice(start, end), true); | |
if (code === 0x2c) { | |
push(offers, extensionName, params); | |
params = Object.create(null); | |
extensionName = undefined; | |
} | |
start = end = -1; | |
} else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) { | |
paramName = header.slice(start, i); | |
start = end = -1; | |
} else { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
} else { | |
// | |
// The value of a quoted-string after unescaping must conform to the | |
// token ABNF, so only token characters are valid. | |
// Ref: https://tools.ietf.org/html/rfc6455#section-9.1 | |
// | |
if (isEscaping) { | |
if (tokenChars[code] !== 1) { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
if (start === -1) start = i; | |
else if (!mustUnescape) mustUnescape = true; | |
isEscaping = false; | |
} else if (inQuotes) { | |
if (tokenChars[code] === 1) { | |
if (start === -1) start = i; | |
} else if (code === 0x22 /* '"' */ && start !== -1) { | |
inQuotes = false; | |
end = i; | |
} else if (code === 0x5c /* '\' */) { | |
isEscaping = true; | |
} else { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
} else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) { | |
inQuotes = true; | |
} else if (end === -1 && tokenChars[code] === 1) { | |
if (start === -1) start = i; | |
} else if (start !== -1 && (code === 0x20 || code === 0x09)) { | |
if (end === -1) end = i; | |
} else if (code === 0x3b || code === 0x2c) { | |
if (start === -1) { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
if (end === -1) end = i; | |
let value = header.slice(start, end); | |
if (mustUnescape) { | |
value = value.replace(/\\/g, ''); | |
mustUnescape = false; | |
} | |
push(params, paramName, value); | |
if (code === 0x2c) { | |
push(offers, extensionName, params); | |
params = Object.create(null); | |
extensionName = undefined; | |
} | |
paramName = undefined; | |
start = end = -1; | |
} else { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
} | |
} | |
if (start === -1 || inQuotes || code === 0x20 || code === 0x09) { | |
throw new SyntaxError('Unexpected end of input'); | |
} | |
if (end === -1) end = i; | |
const token = header.slice(start, end); | |
if (extensionName === undefined) { | |
push(offers, token, params); | |
} else { | |
if (paramName === undefined) { | |
push(params, token, true); | |
} else if (mustUnescape) { | |
push(params, paramName, token.replace(/\\/g, '')); | |
} else { | |
push(params, paramName, token); | |
} | |
push(offers, extensionName, params); | |
} | |
return offers; | |
} | |
/** | |
* Builds the `Sec-WebSocket-Extensions` header field value. | |
* | |
* @param {Object} extensions The map of extensions and parameters to format | |
* @return {String} A string representing the given object | |
* @public | |
*/ | |
function format(extensions) { | |
return Object.keys(extensions) | |
.map((extension) => { | |
let configurations = extensions[extension]; | |
if (!Array.isArray(configurations)) configurations = [configurations]; | |
return configurations | |
.map((params) => { | |
return [extension] | |
.concat( | |
Object.keys(params).map((k) => { | |
let values = params[k]; | |
if (!Array.isArray(values)) values = [values]; | |
return values | |
.map((v) => (v === true ? k : `${k}=${v}`)) | |
.join('; '); | |
}) | |
) | |
.join('; '); | |
}) | |
.join(', '); | |
}) | |
.join(', '); | |
} | |
module.exports = { format, parse }; | |