1 | 'use strict';
|
2 |
|
3 | const { tokenChars } = require('./validation');
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | function push(dest, name, elem) {
|
16 | if (dest[name] === undefined) dest[name] = [elem];
|
17 | else dest[name].push(elem);
|
18 | }
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | function parse(header) {
|
28 | const offers = Object.create(null);
|
29 | let params = Object.create(null);
|
30 | let mustUnescape = false;
|
31 | let isEscaping = false;
|
32 | let inQuotes = false;
|
33 | let extensionName;
|
34 | let paramName;
|
35 | let start = -1;
|
36 | let code = -1;
|
37 | let end = -1;
|
38 | let i = 0;
|
39 |
|
40 | for (; i < header.length; i++) {
|
41 | code = header.charCodeAt(i);
|
42 |
|
43 | if (extensionName === undefined) {
|
44 | if (end === -1 && tokenChars[code] === 1) {
|
45 | if (start === -1) start = i;
|
46 | } else if (
|
47 | i !== 0 &&
|
48 | (code === 0x20 || code === 0x09)
|
49 | ) {
|
50 | if (end === -1 && start !== -1) end = i;
|
51 | } else if (code === 0x3b || code === 0x2c ) {
|
52 | if (start === -1) {
|
53 | throw new SyntaxError(`Unexpected character at index ${i}`);
|
54 | }
|
55 |
|
56 | if (end === -1) end = i;
|
57 | const name = header.slice(start, end);
|
58 | if (code === 0x2c) {
|
59 | push(offers, name, params);
|
60 | params = Object.create(null);
|
61 | } else {
|
62 | extensionName = name;
|
63 | }
|
64 |
|
65 | start = end = -1;
|
66 | } else {
|
67 | throw new SyntaxError(`Unexpected character at index ${i}`);
|
68 | }
|
69 | } else if (paramName === undefined) {
|
70 | if (end === -1 && tokenChars[code] === 1) {
|
71 | if (start === -1) start = i;
|
72 | } else if (code === 0x20 || code === 0x09) {
|
73 | if (end === -1 && start !== -1) end = i;
|
74 | } else if (code === 0x3b || code === 0x2c) {
|
75 | if (start === -1) {
|
76 | throw new SyntaxError(`Unexpected character at index ${i}`);
|
77 | }
|
78 |
|
79 | if (end === -1) end = i;
|
80 | push(params, header.slice(start, end), true);
|
81 | if (code === 0x2c) {
|
82 | push(offers, extensionName, params);
|
83 | params = Object.create(null);
|
84 | extensionName = undefined;
|
85 | }
|
86 |
|
87 | start = end = -1;
|
88 | } else if (code === 0x3d && start !== -1 && end === -1) {
|
89 | paramName = header.slice(start, i);
|
90 | start = end = -1;
|
91 | } else {
|
92 | throw new SyntaxError(`Unexpected character at index ${i}`);
|
93 | }
|
94 | } else {
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 | if (isEscaping) {
|
101 | if (tokenChars[code] !== 1) {
|
102 | throw new SyntaxError(`Unexpected character at index ${i}`);
|
103 | }
|
104 | if (start === -1) start = i;
|
105 | else if (!mustUnescape) mustUnescape = true;
|
106 | isEscaping = false;
|
107 | } else if (inQuotes) {
|
108 | if (tokenChars[code] === 1) {
|
109 | if (start === -1) start = i;
|
110 | } else if (code === 0x22 && start !== -1) {
|
111 | inQuotes = false;
|
112 | end = i;
|
113 | } else if (code === 0x5c ) {
|
114 | isEscaping = true;
|
115 | } else {
|
116 | throw new SyntaxError(`Unexpected character at index ${i}`);
|
117 | }
|
118 | } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) {
|
119 | inQuotes = true;
|
120 | } else if (end === -1 && tokenChars[code] === 1) {
|
121 | if (start === -1) start = i;
|
122 | } else if (start !== -1 && (code === 0x20 || code === 0x09)) {
|
123 | if (end === -1) end = i;
|
124 | } else if (code === 0x3b || code === 0x2c) {
|
125 | if (start === -1) {
|
126 | throw new SyntaxError(`Unexpected character at index ${i}`);
|
127 | }
|
128 |
|
129 | if (end === -1) end = i;
|
130 | let value = header.slice(start, end);
|
131 | if (mustUnescape) {
|
132 | value = value.replace(/\\/g, '');
|
133 | mustUnescape = false;
|
134 | }
|
135 | push(params, paramName, value);
|
136 | if (code === 0x2c) {
|
137 | push(offers, extensionName, params);
|
138 | params = Object.create(null);
|
139 | extensionName = undefined;
|
140 | }
|
141 |
|
142 | paramName = undefined;
|
143 | start = end = -1;
|
144 | } else {
|
145 | throw new SyntaxError(`Unexpected character at index ${i}`);
|
146 | }
|
147 | }
|
148 | }
|
149 |
|
150 | if (start === -1 || inQuotes || code === 0x20 || code === 0x09) {
|
151 | throw new SyntaxError('Unexpected end of input');
|
152 | }
|
153 |
|
154 | if (end === -1) end = i;
|
155 | const token = header.slice(start, end);
|
156 | if (extensionName === undefined) {
|
157 | push(offers, token, params);
|
158 | } else {
|
159 | if (paramName === undefined) {
|
160 | push(params, token, true);
|
161 | } else if (mustUnescape) {
|
162 | push(params, paramName, token.replace(/\\/g, ''));
|
163 | } else {
|
164 | push(params, paramName, token);
|
165 | }
|
166 | push(offers, extensionName, params);
|
167 | }
|
168 |
|
169 | return offers;
|
170 | }
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | function format(extensions) {
|
180 | return Object.keys(extensions)
|
181 | .map((extension) => {
|
182 | let configurations = extensions[extension];
|
183 | if (!Array.isArray(configurations)) configurations = [configurations];
|
184 | return configurations
|
185 | .map((params) => {
|
186 | return [extension]
|
187 | .concat(
|
188 | Object.keys(params).map((k) => {
|
189 | let values = params[k];
|
190 | if (!Array.isArray(values)) values = [values];
|
191 | return values
|
192 | .map((v) => (v === true ? k : `${k}=${v}`))
|
193 | .join('; ');
|
194 | })
|
195 | )
|
196 | .join('; ');
|
197 | })
|
198 | .join(', ');
|
199 | })
|
200 | .join(', ');
|
201 | }
|
202 |
|
203 | module.exports = { format, parse };
|