Projekt

Obecné

Profil

Stáhnout (6.65 KB) Statistiky
| Větev: | Revize:
1
'use strict';
2

    
3
//
4
// Allowed token characters:
5
//
6
// '!', '#', '$', '%', '&', ''', '*', '+', '-',
7
// '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
8
//
9
// tokenChars[32] === 0 // ' '
10
// tokenChars[33] === 1 // '!'
11
// tokenChars[34] === 0 // '"'
12
// ...
13
//
14
// prettier-ignore
15
const tokenChars = [
16
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
17
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
18
  0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47
19
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
20
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
21
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95
22
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
23
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127
24
];
25

    
26
/**
27
 * Adds an offer to the map of extension offers or a parameter to the map of
28
 * parameters.
29
 *
30
 * @param {Object} dest The map of extension offers or parameters
31
 * @param {String} name The extension or parameter name
32
 * @param {(Object|Boolean|String)} elem The extension parameters or the
33
 *     parameter value
34
 * @private
35
 */
36
function push(dest, name, elem) {
37
  if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem);
38
  else dest[name] = [elem];
39
}
40

    
41
/**
42
 * Parses the `Sec-WebSocket-Extensions` header into an object.
43
 *
44
 * @param {String} header The field value of the header
45
 * @return {Object} The parsed object
46
 * @public
47
 */
48
function parse(header) {
49
  const offers = {};
50

    
51
  if (header === undefined || header === '') return offers;
52

    
53
  var params = {};
54
  var mustUnescape = false;
55
  var isEscaping = false;
56
  var inQuotes = false;
57
  var extensionName;
58
  var paramName;
59
  var start = -1;
60
  var end = -1;
61

    
62
  for (var i = 0; i < header.length; i++) {
63
    const code = header.charCodeAt(i);
64

    
65
    if (extensionName === undefined) {
66
      if (end === -1 && tokenChars[code] === 1) {
67
        if (start === -1) start = i;
68
      } else if (code === 0x20 /* ' ' */ || code === 0x09 /* '\t' */) {
69
        if (end === -1 && start !== -1) end = i;
70
      } else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) {
71
        if (start === -1) {
72
          throw new SyntaxError(`Unexpected character at index ${i}`);
73
        }
74

    
75
        if (end === -1) end = i;
76
        const name = header.slice(start, end);
77
        if (code === 0x2c) {
78
          push(offers, name, params);
79
          params = {};
80
        } else {
81
          extensionName = name;
82
        }
83

    
84
        start = end = -1;
85
      } else {
86
        throw new SyntaxError(`Unexpected character at index ${i}`);
87
      }
88
    } else if (paramName === undefined) {
89
      if (end === -1 && tokenChars[code] === 1) {
90
        if (start === -1) start = i;
91
      } else if (code === 0x20 || code === 0x09) {
92
        if (end === -1 && start !== -1) end = i;
93
      } else if (code === 0x3b || code === 0x2c) {
94
        if (start === -1) {
95
          throw new SyntaxError(`Unexpected character at index ${i}`);
96
        }
97

    
98
        if (end === -1) end = i;
99
        push(params, header.slice(start, end), true);
100
        if (code === 0x2c) {
101
          push(offers, extensionName, params);
102
          params = {};
103
          extensionName = undefined;
104
        }
105

    
106
        start = end = -1;
107
      } else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) {
108
        paramName = header.slice(start, i);
109
        start = end = -1;
110
      } else {
111
        throw new SyntaxError(`Unexpected character at index ${i}`);
112
      }
113
    } else {
114
      //
115
      // The value of a quoted-string after unescaping must conform to the
116
      // token ABNF, so only token characters are valid.
117
      // Ref: https://tools.ietf.org/html/rfc6455#section-9.1
118
      //
119
      if (isEscaping) {
120
        if (tokenChars[code] !== 1) {
121
          throw new SyntaxError(`Unexpected character at index ${i}`);
122
        }
123
        if (start === -1) start = i;
124
        else if (!mustUnescape) mustUnescape = true;
125
        isEscaping = false;
126
      } else if (inQuotes) {
127
        if (tokenChars[code] === 1) {
128
          if (start === -1) start = i;
129
        } else if (code === 0x22 /* '"' */ && start !== -1) {
130
          inQuotes = false;
131
          end = i;
132
        } else if (code === 0x5c /* '\' */) {
133
          isEscaping = true;
134
        } else {
135
          throw new SyntaxError(`Unexpected character at index ${i}`);
136
        }
137
      } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) {
138
        inQuotes = true;
139
      } else if (end === -1 && tokenChars[code] === 1) {
140
        if (start === -1) start = i;
141
      } else if (start !== -1 && (code === 0x20 || code === 0x09)) {
142
        if (end === -1) end = i;
143
      } else if (code === 0x3b || code === 0x2c) {
144
        if (start === -1) {
145
          throw new SyntaxError(`Unexpected character at index ${i}`);
146
        }
147

    
148
        if (end === -1) end = i;
149
        var value = header.slice(start, end);
150
        if (mustUnescape) {
151
          value = value.replace(/\\/g, '');
152
          mustUnescape = false;
153
        }
154
        push(params, paramName, value);
155
        if (code === 0x2c) {
156
          push(offers, extensionName, params);
157
          params = {};
158
          extensionName = undefined;
159
        }
160

    
161
        paramName = undefined;
162
        start = end = -1;
163
      } else {
164
        throw new SyntaxError(`Unexpected character at index ${i}`);
165
      }
166
    }
167
  }
168

    
169
  if (start === -1 || inQuotes) {
170
    throw new SyntaxError('Unexpected end of input');
171
  }
172

    
173
  if (end === -1) end = i;
174
  const token = header.slice(start, end);
175
  if (extensionName === undefined) {
176
    push(offers, token, {});
177
  } else {
178
    if (paramName === undefined) {
179
      push(params, token, true);
180
    } else if (mustUnescape) {
181
      push(params, paramName, token.replace(/\\/g, ''));
182
    } else {
183
      push(params, paramName, token);
184
    }
185
    push(offers, extensionName, params);
186
  }
187

    
188
  return offers;
189
}
190

    
191
/**
192
 * Builds the `Sec-WebSocket-Extensions` header field value.
193
 *
194
 * @param {Object} extensions The map of extensions and parameters to format
195
 * @return {String} A string representing the given object
196
 * @public
197
 */
198
function format(extensions) {
199
  return Object.keys(extensions)
200
    .map((extension) => {
201
      var configurations = extensions[extension];
202
      if (!Array.isArray(configurations)) configurations = [configurations];
203
      return configurations
204
        .map((params) => {
205
          return [extension]
206
            .concat(
207
              Object.keys(params).map((k) => {
208
                var values = params[k];
209
                if (!Array.isArray(values)) values = [values];
210
                return values
211
                  .map((v) => (v === true ? k : `${k}=${v}`))
212
                  .join('; ');
213
              })
214
            )
215
            .join('; ');
216
        })
217
        .join(', ');
218
    })
219
    .join(', ');
220
}
221

    
222
module.exports = { format, parse };
(4-4/10)