47096b973597bf8e00149b5105c736f60cc8ee63
[platform/framework/web/wrtjs.git] /
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 };