UNPKG

16.1 kBJavaScriptView Raw
1'use strict';
2
3function parseContentType(str) {
4 if (str.length === 0)
5 return;
6
7 const params = Object.create(null);
8 let i = 0;
9
10 // Parse type
11 for (; i < str.length; ++i) {
12 const code = str.charCodeAt(i);
13 if (TOKEN[code] !== 1) {
14 if (code !== 47/* '/' */ || i === 0)
15 return;
16 break;
17 }
18 }
19 // Check for type without subtype
20 if (i === str.length)
21 return;
22
23 const type = str.slice(0, i).toLowerCase();
24
25 // Parse subtype
26 const subtypeStart = ++i;
27 for (; i < str.length; ++i) {
28 const code = str.charCodeAt(i);
29 if (TOKEN[code] !== 1) {
30 // Make sure we have a subtype
31 if (i === subtypeStart)
32 return;
33
34 if (parseContentTypeParams(str, i, params) === undefined)
35 return;
36 break;
37 }
38 }
39 // Make sure we have a subtype
40 if (i === subtypeStart)
41 return;
42
43 const subtype = str.slice(subtypeStart, i).toLowerCase();
44
45 return { type, subtype, params };
46}
47
48function parseContentTypeParams(str, i, params) {
49 while (i < str.length) {
50 // Consume whitespace
51 for (; i < str.length; ++i) {
52 const code = str.charCodeAt(i);
53 if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
54 break;
55 }
56
57 // Ended on whitespace
58 if (i === str.length)
59 break;
60
61 // Check for malformed parameter
62 if (str.charCodeAt(i++) !== 59/* ';' */)
63 return;
64
65 // Consume whitespace
66 for (; i < str.length; ++i) {
67 const code = str.charCodeAt(i);
68 if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
69 break;
70 }
71
72 // Ended on whitespace (malformed)
73 if (i === str.length)
74 return;
75
76 let name;
77 const nameStart = i;
78 // Parse parameter name
79 for (; i < str.length; ++i) {
80 const code = str.charCodeAt(i);
81 if (TOKEN[code] !== 1) {
82 if (code !== 61/* '=' */)
83 return;
84 break;
85 }
86 }
87
88 // No value (malformed)
89 if (i === str.length)
90 return;
91
92 name = str.slice(nameStart, i);
93 ++i; // Skip over '='
94
95 // No value (malformed)
96 if (i === str.length)
97 return;
98
99 let value = '';
100 let valueStart;
101 if (str.charCodeAt(i) === 34/* '"' */) {
102 valueStart = ++i;
103 let escaping = false;
104 // Parse quoted value
105 for (; i < str.length; ++i) {
106 const code = str.charCodeAt(i);
107 if (code === 92/* '\\' */) {
108 if (escaping) {
109 valueStart = i;
110 escaping = false;
111 } else {
112 value += str.slice(valueStart, i);
113 escaping = true;
114 }
115 continue;
116 }
117 if (code === 34/* '"' */) {
118 if (escaping) {
119 valueStart = i;
120 escaping = false;
121 continue;
122 }
123 value += str.slice(valueStart, i);
124 break;
125 }
126 if (escaping) {
127 valueStart = i - 1;
128 escaping = false;
129 }
130 // Invalid unescaped quoted character (malformed)
131 if (QDTEXT[code] !== 1)
132 return;
133 }
134
135 // No end quote (malformed)
136 if (i === str.length)
137 return;
138
139 ++i; // Skip over double quote
140 } else {
141 valueStart = i;
142 // Parse unquoted value
143 for (; i < str.length; ++i) {
144 const code = str.charCodeAt(i);
145 if (TOKEN[code] !== 1) {
146 // No value (malformed)
147 if (i === valueStart)
148 return;
149 break;
150 }
151 }
152 value = str.slice(valueStart, i);
153 }
154
155 name = name.toLowerCase();
156 if (params[name] === undefined)
157 params[name] = value;
158 }
159
160 return params;
161}
162
163function parseDisposition(str) {
164 if (str.length === 0)
165 return;
166
167 const params = Object.create(null);
168 let i = 0;
169
170 for (; i < str.length; ++i) {
171 const code = str.charCodeAt(i);
172 if (TOKEN[code] !== 1) {
173 if (parseDispositionParams(str, i, params) === undefined)
174 return;
175 break;
176 }
177 }
178
179 const type = str.slice(0, i).toLowerCase();
180
181 return { type, params };
182}
183
184function parseDispositionParams(str, i, params) {
185 while (i < str.length) {
186 // Consume whitespace
187 for (; i < str.length; ++i) {
188 const code = str.charCodeAt(i);
189 if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
190 break;
191 }
192
193 // Ended on whitespace
194 if (i === str.length)
195 break;
196
197 // Check for malformed parameter
198 if (str.charCodeAt(i++) !== 59/* ';' */)
199 return;
200
201 // Consume whitespace
202 for (; i < str.length; ++i) {
203 const code = str.charCodeAt(i);
204 if (code !== 32/* ' ' */ && code !== 9/* '\t' */)
205 break;
206 }
207
208 // Ended on whitespace (malformed)
209 if (i === str.length)
210 return;
211
212 let name;
213 const nameStart = i;
214 // Parse parameter name
215 for (; i < str.length; ++i) {
216 const code = str.charCodeAt(i);
217 if (TOKEN[code] !== 1) {
218 if (code === 61/* '=' */)
219 break;
220 return;
221 }
222 }
223
224 // No value (malformed)
225 if (i === str.length)
226 return;
227
228 let value = '';
229 let valueStart;
230 let charset;
231 //~ let lang;
232 name = str.slice(nameStart, i);
233 if (name.charCodeAt(name.length - 1) === 42/* '*' */) {
234 // Extended value
235
236 const charsetStart = ++i;
237 // Parse charset name
238 for (; i < str.length; ++i) {
239 const code = str.charCodeAt(i);
240 if (CHARSET[code] !== 1) {
241 if (code !== 39/* '\'' */)
242 return;
243 break;
244 }
245 }
246
247 // Incomplete charset (malformed)
248 if (i === str.length)
249 return;
250
251 charset = str.slice(charsetStart, i);
252 ++i; // Skip over the '\''
253
254 //~ const langStart = ++i;
255 // Parse language name
256 for (; i < str.length; ++i) {
257 const code = str.charCodeAt(i);
258 if (code === 39/* '\'' */)
259 break;
260 }
261
262 // Incomplete language (malformed)
263 if (i === str.length)
264 return;
265
266 //~ lang = str.slice(langStart, i);
267 ++i; // Skip over the '\''
268
269 // No value (malformed)
270 if (i === str.length)
271 return;
272
273 valueStart = i;
274
275 let encode = 0;
276 // Parse value
277 for (; i < str.length; ++i) {
278 const code = str.charCodeAt(i);
279 if (EXTENDED_VALUE[code] !== 1) {
280 if (code === 37/* '%' */) {
281 let hexUpper;
282 let hexLower;
283 if (i + 2 < str.length
284 && (hexUpper = HEX_VALUES[str.charCodeAt(i + 1)]) !== -1
285 && (hexLower = HEX_VALUES[str.charCodeAt(i + 2)]) !== -1) {
286 const byteVal = (hexUpper << 4) + hexLower;
287 value += str.slice(valueStart, i);
288 value += String.fromCharCode(byteVal);
289 i += 2;
290 valueStart = i + 1;
291 if (byteVal >= 128)
292 encode = 2;
293 else if (encode === 0)
294 encode = 1;
295 continue;
296 }
297 // '%' disallowed in non-percent encoded contexts (malformed)
298 return;
299 }
300 break;
301 }
302 }
303
304 value += str.slice(valueStart, i);
305 value = convertToUTF8(value, charset, encode);
306 if (value === undefined)
307 return;
308 } else {
309 // Non-extended value
310
311 ++i; // Skip over '='
312
313 // No value (malformed)
314 if (i === str.length)
315 return;
316
317 if (str.charCodeAt(i) === 34/* '"' */) {
318 valueStart = ++i;
319 let escaping = false;
320 // Parse quoted value
321 for (; i < str.length; ++i) {
322 const code = str.charCodeAt(i);
323 if (code === 92/* '\\' */) {
324 if (escaping) {
325 valueStart = i;
326 escaping = false;
327 } else {
328 value += str.slice(valueStart, i);
329 escaping = true;
330 }
331 continue;
332 }
333 if (code === 34/* '"' */) {
334 if (escaping) {
335 valueStart = i;
336 escaping = false;
337 continue;
338 }
339 value += str.slice(valueStart, i);
340 break;
341 }
342 if (escaping) {
343 valueStart = i - 1;
344 escaping = false;
345 }
346 // Invalid unescaped quoted character (malformed)
347 if (QDTEXT[code] !== 1)
348 return;
349 }
350
351 // No end quote (malformed)
352 if (i === str.length)
353 return;
354
355 ++i; // Skip over double quote
356 } else {
357 valueStart = i;
358 // Parse unquoted value
359 for (; i < str.length; ++i) {
360 const code = str.charCodeAt(i);
361 if (TOKEN[code] !== 1) {
362 // No value (malformed)
363 if (i === valueStart)
364 return;
365 break;
366 }
367 }
368 value = str.slice(valueStart, i);
369 }
370 }
371
372 name = name.toLowerCase();
373 if (params[name] === undefined)
374 params[name] = value;
375 }
376
377 return params;
378}
379
380function getDecoder(charset) {
381 let lc;
382 while (true) {
383 switch (charset) {
384 case 'utf-8':
385 case 'utf8':
386 return decoders.utf8;
387 case 'latin1':
388 case 'ascii': // TODO: Make these a separate, strict decoder?
389 case 'us-ascii':
390 case 'iso-8859-1':
391 case 'iso8859-1':
392 case 'iso88591':
393 case 'iso_8859-1':
394 case 'windows-1252':
395 case 'iso_8859-1:1987':
396 case 'cp1252':
397 case 'x-cp1252':
398 return decoders.latin1;
399 case 'utf16le':
400 case 'utf-16le':
401 case 'ucs2':
402 case 'ucs-2':
403 return decoders.utf16le;
404 case 'base64':
405 return decoders.base64;
406 default:
407 if (lc === undefined) {
408 lc = true;
409 charset = charset.toLowerCase();
410 continue;
411 }
412 return decoders.other.bind(charset);
413 }
414 }
415}
416
417const decoders = {
418 utf8: (data, hint) => {
419 if (data.length === 0)
420 return '';
421 if (typeof data === 'string') {
422 // If `data` never had any percent-encoded bytes or never had any that
423 // were outside of the ASCII range, then we can safely just return the
424 // input since UTF-8 is ASCII compatible
425 if (hint < 2)
426 return data;
427
428 data = Buffer.from(data, 'latin1');
429 }
430 return data.utf8Slice(0, data.length);
431 },
432
433 latin1: (data, hint) => {
434 if (data.length === 0)
435 return '';
436 if (typeof data === 'string')
437 return data;
438 return data.latin1Slice(0, data.length);
439 },
440
441 utf16le: (data, hint) => {
442 if (data.length === 0)
443 return '';
444 if (typeof data === 'string')
445 data = Buffer.from(data, 'latin1');
446 return data.ucs2Slice(0, data.length);
447 },
448
449 base64: (data, hint) => {
450 if (data.length === 0)
451 return '';
452 if (typeof data === 'string')
453 data = Buffer.from(data, 'latin1');
454 return data.base64Slice(0, data.length);
455 },
456
457 other: (data, hint) => {
458 if (data.length === 0)
459 return '';
460 if (typeof data === 'string')
461 data = Buffer.from(data, 'latin1');
462 try {
463 const decoder = new TextDecoder(this);
464 return decoder.decode(data);
465 } catch {}
466 },
467};
468
469function convertToUTF8(data, charset, hint) {
470 const decode = getDecoder(charset);
471 if (decode)
472 return decode(data, hint);
473}
474
475function basename(path) {
476 if (typeof path !== 'string')
477 return '';
478 for (let i = path.length - 1; i >= 0; --i) {
479 switch (path.charCodeAt(i)) {
480 case 0x2F: // '/'
481 case 0x5C: // '\'
482 path = path.slice(i + 1);
483 return (path === '..' || path === '.' ? '' : path);
484 }
485 }
486 return (path === '..' || path === '.' ? '' : path);
487}
488
489const TOKEN = [
490 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
491 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
492 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
493 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
494 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
495 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
496 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
497 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
498 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
499 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
500 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
501 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
502 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
503 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
504 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
505 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
506];
507
508const QDTEXT = [
509 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
510 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
511 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
512 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
513 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
514 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
515 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
516 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
517 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
518 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
519 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
520 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
521 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
522 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
523 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
524 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
525];
526
527const CHARSET = [
528 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
529 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
530 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0,
531 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
532 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
533 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
534 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
535 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0,
536 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
537 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
538 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
539 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
540 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
541 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
542 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
543 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
544];
545
546const EXTENDED_VALUE = [
547 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
548 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
549 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0,
550 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
551 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
552 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
553 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
554 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
555 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
556 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
557 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
558 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
559 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
560 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
561 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
562 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
563];
564
565/* eslint-disable no-multi-spaces */
566const HEX_VALUES = [
567 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
568 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
569 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
570 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
571 -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
572 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
573 -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
574 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
575 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
576 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
577 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
578 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
579 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
580 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
581 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
582 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
583];
584/* eslint-enable no-multi-spaces */
585
586module.exports = {
587 basename,
588 convertToUTF8,
589 getDecoder,
590 parseContentType,
591 parseDisposition,
592};