| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 | 'use strict';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 };
 |