Branch data Line data Source code
1 [ + ]: 348 : /**
2 : 348 : * @module whatwg-misc
3 : 348 : * @desc Helper module - Helper functions for the main module {@link module:whatwg-xhr whatwg-xhr}.
4 : 348 : * @version 1.0.0
5 : 348 : * @author Essam A. El-Sherif
6 : 348 : */
7 : 348 :
8 : 348 : /* Import nodeJS core modules */
9 : 348 : import assert from 'node:assert/strict';
10 : 348 : import { atob } from 'node:buffer';
11 : 348 :
12 : 348 : /*
13 : 348 : * HTTP token code point
14 : 348 : * url: https://mimesniff.spec.whatwg.org/#http-token-code-point
15 : 348 : *
16 : 348 : * @const HTTP_TOKEN_CODEPOINTS
17 : 348 : * @desc An HTTP token code point, as defined by {@link https://mimesniff.spec.whatwg.org/#http-token-code-point WHATWG MIME Sniffing Living Standard}.
18 : 348 : */
19 : 348 : const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-z0-9]+$/;
20 : 348 :
21 : 348 : /*
22 : 348 : * A whitespace byte (abbreviated 0xWS)
23 : 348 : * url: https://mimesniff.spec.whatwg.org/#whitespace-byte
24 : 348 : *
25 : 348 : * @const HTTP_WHITESPACE_REGEX
26 : 348 : * @desc A whitespace byte, as defined by {@link https://mimesniff.spec.whatwg.org/#whitespace-byte WHATWG MIME Sniffing Living Standard}.
27 : 348 : */
28 : 348 : const HTTP_WHITESPACE_REGEX = /(\u000A|\u000D|\u0009|\u0020)/;
29 : 348 :
30 : 348 : /*
31 : 348 : * HTTP quoted-string token code point
32 : 348 : * url: https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
33 : 348 : *
34 : 348 : * @const HTTP_QUOTED_STRING_TOKENS
35 : 348 : * @desc HTTP quoted-string token code point, as defined by {@link https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point WHATWG MIME Sniffing Living Standard}.
36 : 348 : */
37 : 348 : const HTTP_QUOTED_STRING_TOKENS = /^(\u0009|\x{0020}-\x{007E}|\x{0080}-\x{00FF})+$/;
38 : 348 :
39 : 348 : /**
40 : 348 : * The data: URL processor takes a URL dataURL and then runs these steps:
41 : 348 : * url: https://fetch.spec.whatwg.org/#data-url-processor
42 : 348 : *
43 : 348 : * 1. Assert: dataURL’s scheme is "data".
44 : 348 : * 2. Let input be the result of running the URL serializer on dataURL with exclude fragment set to true.
45 : 348 : * 3. Remove the leading "data:" from input.
46 : 348 : * 4. Let position point at the start of input.
47 : 348 : * 5. Let mimeType be the result of collecting a sequence of code points that are not equal to U+002C (,), given position.
48 : 348 : * 6. Strip leading and trailing ASCII whitespace from mimeType.
49 : 348 : * Note: This will only remove U+0020 SPACE code points, if any.
50 : 348 : * 7. If position is past the end of input, then return failure.
51 : 348 : * 8. Advance position by 1.
52 : 348 : * 9. Let encodedBody be the remainder of input.
53 : 348 : * 10. Let body be the percent-decoding of encodedBody.
54 : 348 : * 11. If mimeType ends with U+003B (;), followed by zero or more U+0020 SPACE, followed by an ASCII case-insensitive match for "base64", then:
55 : 348 : * 1. Let stringBody be the isomorphic decode of body.
56 : 348 : * 2. Set body to the forgiving-base64 decode of stringBody.
57 : 348 : * 3. If body is failure, then return failure.
58 : 348 : * 4. Remove the last 6 code points from mimeType.
59 : 348 : * 5. Remove trailing U+0020 SPACE code points from mimeType, if any.
60 : 348 : * 6. Remove the last U+003B (;) from mimeType.
61 : 348 : * 12. If mimeType starts with ";", then prepend "text/plain" to mimeType.
62 : 348 : * 13. Let mimeTypeRecord be the result of parsing mimeType.
63 : 348 : * 14. If mimeTypeRecord is failure, then set mimeTypeRecord to text/plain;charset=US-ASCII.
64 : 348 : * 15. Return a new data: URL struct whose MIME type is mimeTypeRecord and body is body.
65 : 348 : *
66 : 348 : * @func dataURLProcessor
67 : 348 : * @static
68 : 348 : * @param {URL} dataURL - URL object.
69 : 348 : * @return {object|string} An object of mimeType and body, or 'failure' string.
70 : 348 : * @desc A dataURLProcessor function, as defined by {@link https://fetch.spec.whatwg.org/#data-url-processor WHATWG Fetch Living Standard}.
71 : 348 : */
72 [ + ]: 348 : export function dataURLProcessor(dataURL){
73 : 13 :
74 : 13 : // fetch.spec.1. Assert: dataURL’s scheme is "data".
75 : 13 : assert(dataURL.protocol === 'data:');
76 : 13 :
77 : 13 : // fetch.spec.2. Let input be the result of running the URL
78 : 13 : // serializer on dataURL with exclude fragment
79 : 13 : // set to true.
80 : 13 : let input = urlSerializer(dataURL, true);
81 : 13 :
82 : 13 : // fetch.spec.3. Remove the leading "data:" string from input.
83 : 13 : input = input.slice(5);
84 : 13 :
85 : 13 : // fetch.spec.4. Let position point at the start of input.
86 : 13 : const position = { position: 0 };
87 : 13 :
88 : 13 : // fetch.spec.5. Let mimeType be the result of collecting a
89 : 13 : // sequence of code points that are not equal
90 : 13 : // to U+002C (,), given position.
91 : 13 : let mimeType = collectASequenceOfCodePointsFast(
92 : 13 : ',',
93 : 13 : input,
94 : 13 : position
95 : 13 : );
96 : 13 :
97 : 13 : // fetch.spec.6. Strip leading and trailing ASCII whitespace from mimeType.
98 : 13 : // Note: This will only remove U+0020 SPACE code points, if any.
99 : 13 : // Undici implementation note: we need to store the length because
100 : 13 : // if the mimetype has spaces removed, the wrong amount will be
101 : 13 : // sliced from the input in step #9.
102 : 13 : const mimeTypeLength = mimeType.length;
103 : 13 : mimeType = mimeType.replace(/^(\u0020)+|(\u0020)+$/g, '');
104 : 13 :
105 : 13 : // fetch.spec.7. If position is past the end of input, then return failure
106 : 13 : if(position.position >= input.length)
107 [ + ][ + ]: 13 : return 'failure';
108 : 11 :
109 : 11 : // fetch.spec.8. Advance position by 1.
110 : 11 : position.position++;
111 : 11 :
112 : 11 : // fetch.spec.9. Let encodedBody be the remainder of input.
113 : 11 : const encodedBody = input.slice(mimeTypeLength + 1);
114 : 11 :
115 : 11 : // fetch.spec.10. Let body be the percent-decoding of encodedBody.
116 : 11 : let body = stringPercentDecode(encodedBody);
117 : 11 :
118 : 11 : // fetch.spec.11. If mimeType ends with U+003B (;), followed by
119 : 11 : // zero or more U+0020 SPACE, followed by an ASCII
120 : 11 : // case-insensitive match for "base64", then:
121 [ + ]: 13 : if(/;(\u0020){0,}base64$/i.test(mimeType)){
122 : 1 :
123 : 1 : // fetch.spec.11.1. Let stringBody be the isomorphic decode of body.
124 : 1 : const stringBody = isomorphicDecode(body);
125 : 1 :
126 : 1 : // fetch.spec.11.2. Set body to the forgiving-base64 decode of stringBody.
127 : 1 : body = forgivingBase64(stringBody);
128 : 1 :
129 : 1 : // fetch.spec.11.3. If body is failure, then return failure.
130 : 1 : if (body === 'failure') return 'failure';
131 : 1 :
132 : 1 : // fetch.spec.11.4. Remove the last 6 code points from mimeType.
133 : 1 : mimeType = mimeType.slice(0, -6);
134 : 1 :
135 : 1 : // fetch.spec.11.5. Remove trailing U+0020 SPACE code points from mimeType, if any.
136 : 1 : mimeType = mimeType.replace(/(\u0020)+$/, '');
137 : 1 :
138 : 1 : // fetch.spec.11.6. Remove the last U+003B (;) code point from mimeType.
139 : 1 : mimeType = mimeType.slice(0, -1);
140 [ + ]: 1 : }
141 : 11 :
142 : 11 : // fetch.spec.12. If mimeType starts with U+003B (;), then prepend "text/plain" to mimeType.
143 : 11 : if(mimeType.startsWith(';'))
144 [ + ][ - ]: 13 : mimeType = 'text/plain' + mimeType;
145 : 11 :
146 : 11 : // fetch.spec.13. Let mimeTypeRecord be the result of parsing mimeType.
147 : 11 : let mimeTypeRecord = parseMIMEType(mimeType);
148 : 11 :
149 : 11 : // fetch.spec.14. If mimeTypeRecord is failure, then set
150 : 11 : // mimeTypeRecord to text/plain;charset=US-ASCII.
151 : 11 : if(mimeTypeRecord === 'failure')
152 [ + ][ + ]: 13 : mimeTypeRecord = parseMIMEType('text/plain;charset=US-ASCII');
153 : 11 :
154 : 11 : // fetch.spec.11.15. Return a new data: URL struct whose MIME
155 : 11 : // type is mimeTypeRecord and body is body.
156 : 11 :
157 : 11 : return { mimeType: mimeTypeRecord, body };
158 : 13 : }
159 : 348 :
160 : 348 : /*
161 : 348 : * URL serializer
162 : 348 : * url: https://url.spec.whatwg.org/#concept-url-serializer
163 : 348 : *
164 : 348 : * takes a URL url, with an optional boolean exclude fragment (default false)
165 : 348 : * They return an ASCII string.
166 : 348 : *
167 : 348 : * @func urlSerializer
168 : 348 : * @param {URL} url - URL object.
169 : 348 : * @param {boolean} excludeFragment - (Optional) Boolean parameter, defaulting to false, to exclude fragment.
170 : 348 : * @return {string} ASCII string.
171 : 348 : * @desc A URL serializer function, as defined by {@link https://url.spec.whatwg.org/#concept-url-serializer WHATWG URL Living Standard}.
172 : 348 : */
173 [ + ]: 13 : function urlSerializer(url, excludeFragment = false){
174 : 13 :
175 : 13 : if(!excludeFragment)
176 [ - ]: 13 : return url.href;
177 : 13 :
178 : 13 : const hash = url.href.lastIndexOf('#');
179 : 13 : if(hash === -1)
180 [ - ]: 13 : return url.href;
181 : 0 :
182 : 0 : return url.href.slice(0, hash);
183 : 13 : }
184 : 348 :
185 : 348 : /*
186 : 348 : * collect a sequence of code points
187 : 348 : * url: https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
188 : 348 : *
189 : 348 : * @func collectASequenceOfCodePoints
190 : 348 : * @param {function} condition - (string) => boolean.
191 : 348 : * @param {string} input
192 : 348 : * @param {{ position: number }} position
193 : 348 : * @return {string} Sequence of code points.
194 : 348 : * @desc A function To collect a sequence of code points meeting a condition from a string input, as defined by {@link https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points WHATWG Infra Living Standard}.
195 : 348 : */
196 [ + ]: 8 : function collectASequenceOfCodePoints(condition, input, position){
197 : 8 :
198 : 8 : // infra.spec.1. Let result be the empty string.
199 : 8 : let result = '';
200 : 8 :
201 : 8 : // infra.spec.2. While position doesn’t point past the end of input and the
202 : 8 : // code point at position within input meets the condition condition:
203 [ + ][ + ]: 8 : while (position.position < input.length && condition(input[position.position])){
204 : 28 :
205 : 28 : // infra.spec.2.1. Append that code point to the end of result.
206 : 28 : result += input[position.position];
207 : 28 :
208 : 28 : // infra.spec.2.2. Advance position by 1.
209 : 28 : position.position++;
210 : 28 : }
211 : 8 :
212 : 8 : // infra.spec.3. Return result.
213 : 8 : return result;
214 : 8 : }
215 : 348 :
216 : 348 : /*
217 : 348 : * A faster collectASequenceOfCodePoints that only works when comparing a single character.
218 : 348 : *
219 : 348 : * @param {string} char
220 : 348 : * @param {string} input
221 : 348 : * @param {{ position: number }} position
222 : 348 : */
223 [ + ]: 49 : function collectASequenceOfCodePointsFast (char, input, position){
224 : 49 :
225 : 49 : const idx = input.indexOf(char, position.position);
226 : 49 : const start = position.position;
227 : 49 :
228 [ + ]: 49 : if(idx === -1){
229 : 20 : position.position = input.length;
230 : 20 : return input.slice(start);
231 [ + ]: 20 : }
232 : 28 :
233 : 28 : position.position = idx;
234 : 28 :
235 : 28 : return input.slice(start, position.position);
236 : 49 : }
237 : 348 :
238 : 348 : /*
239 : 348 : * To percent-decode a scalar value string input:
240 : 348 : * url: https://url.spec.whatwg.org/#string-percent-decode
241 : 348 : *
242 : 348 : * @param {string} input
243 : 348 : */
244 [ + ]: 12 : function stringPercentDecode (input) {
245 : 12 :
246 : 12 : const encoder = new TextEncoder();
247 : 12 :
248 : 12 : // url.spec.1. Let bytes be the UTF-8 encoding of input.
249 : 12 : const bytes = encoder.encode(input);
250 : 12 :
251 : 12 : // url.spec.2. Return the percent-decoding of bytes.
252 : 12 : return percentDecode(bytes);
253 : 12 : }
254 : 348 :
255 : 348 : /*
256 : 348 : * percent-decode
257 : 348 : * url: https://url.spec.whatwg.org/#percent-decode
258 : 348 : *
259 : 348 : * To percent-decode a byte sequence input,
260 : 348 : *
261 : 348 : * @param {Uint8Array} input
262 : 348 : */
263 [ + ]: 12 : function percentDecode(input){
264 : 12 :
265 : 12 : // url.spec.1. Let output be an empty byte sequence.
266 : 12 : const output = [];
267 : 12 :
268 : 12 : // url.spec.2. For each byte byte in input:
269 [ + ]: 12 : for(let i = 0; i < input.length; i++){
270 : 162 : const byte = input[i];
271 : 162 :
272 : 162 : // url.spec.2.1. If byte is not 0x25 (%), then append byte to output.
273 [ + ]: 162 : if (byte !== 0x25) {
274 : 20 : output.push(byte);
275 [ + ]: 20 : }
276 : 1 : // url.spec.2.2. Otherwise, if byte is 0x25 (%) and the next two bytes
277 : 1 : // after byte in input are not in the ranges
278 : 1 : // 0x30 (0) to 0x39 (9), 0x41 (A) to 0x46 (F),
279 : 1 : // and 0x61 (a) to 0x66 (f), all inclusive, append byte
280 : 1 : // to output.
281 : 1 : else
282 : 1 : if(
283 : 1 : byte === 0x25 &&
284 : 1 : !/^[0-9A-Fa-f]{2}$/i.test(String.fromCharCode(input[i + 1], input[i + 2]))
285 : 1 : ){
286 : 1 : output.push(0x25);
287 : 1 : }
288 : 1 : // url.spec.2.3. Otherwise:
289 : 1 : else {
290 : 1 : // url.spec.2.3.1. Let bytePoint be the two bytes after byte in input,
291 : 1 : // decoded, and then interpreted as hexadecimal number.
292 : 1 :
293 : 1 : const nextTwoBytes = String.fromCharCode(input[i + 1], input[i + 2]);
294 : 1 : const bytePoint = Number.parseInt(nextTwoBytes, 16);
295 : 1 :
296 : 1 : // url.spec.2.3.2. Append a byte whose value is bytePoint to output.
297 : 1 : output.push(bytePoint);
298 : 1 :
299 : 1 : // url.spec.2.3.3. Skip the next two bytes in input.
300 : 1 : i += 2;
301 : 1 : }
302 : 162 : }
303 : 12 :
304 : 12 : // url.spec.3. Return output.
305 : 12 : return Uint8Array.from(output);
306 : 12 : }
307 : 348 :
308 : 348 : /**
309 : 348 : * parse a MIME type,
310 : 348 : * url: https://mimesniff.spec.whatwg.org/#parse-a-mime-type
311 : 348 : *
312 : 348 : * @param {string} input
313 : 348 : *
314 : 348 : * @func parseMIMEType
315 : 348 : * @static
316 : 348 : * @param {string} input - String input.
317 : 348 : * @return {object} Serialized MIME type record.
318 : 348 : * @desc Parse a MIME type, given a string input, as defined by {@link https://mimesniff.spec.whatwg.org/#parse-a-mime-type WHATWG MIME Sniffing Living Standard}.
319 : 348 : */
320 [ + ]: 348 : export function parseMIMEType(input){
321 : 17 :
322 : 17 : // mimesniff.spec.1. Remove any leading and trailing HTTP whitespace
323 : 17 : // from input.
324 : 17 : input = input.trim();
325 : 17 :
326 : 17 : // mimesniff.spec.2. Let position be a position variable for input,
327 : 17 : // initially pointing at the start of input.
328 : 17 : const position = { position: 0 };
329 : 17 :
330 : 17 : // mimesniff.spec.3. Let type be the result of collecting a sequence
331 : 17 : // of code points that are not U+002F (/) from
332 : 17 : // input, given position.
333 : 17 : const type = collectASequenceOfCodePointsFast(
334 : 17 : '/',
335 : 17 : input,
336 : 17 : position
337 : 17 : );
338 : 17 :
339 : 17 : // mimesniff.spec.4. If type is the empty string or does not solely
340 : 17 : // contain HTTP token code points, then return failure.
341 [ + ]: 17 : if(type.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(type))
342 [ + ][ + ]: 17 : return 'failure';
343 : 15 :
344 : 15 : // mimesniff.spec.5. If position is past the end of input,
345 : 15 : // then return failure
346 : 15 : if (position.position > input.length)
347 [ - ][ + ]: 17 : return 'failure';
348 : 15 :
349 : 15 : // mimesniff.spec.6. Advance position by 1. (This skips past U+002F (/).)
350 : 15 : position.position++;
351 : 15 :
352 : 15 : // mimesniff.spec.7. Let subtype be the result of collecting a sequence of
353 : 15 : // code points that are not U+003B (;) from input, given
354 : 15 : // position.
355 : 15 : let subtype = collectASequenceOfCodePointsFast(
356 : 15 : ';',
357 : 15 : input,
358 : 15 : position
359 : 15 : );
360 : 15 :
361 : 15 : // mimesniff.spec.8. Remove any trailing HTTP whitespace from subtype.
362 : 15 : subtype = subtype.trimEnd();
363 : 15 :
364 : 15 : // mimesniff.spec.9. If subtype is the empty string or does not solely
365 : 15 : // contain HTTP token code points, then return failure.
366 [ + ]: 17 : if(subtype.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(subtype))
367 [ + ][ + ]: 17 : return 'failure';
368 : 13 :
369 : 13 : // mimesniff.spec.10. Let mimeType be a new MIME type record whose type
370 : 13 : // is type, in ASCII lowercase, and subtype is subtype,
371 : 13 : // in ASCII lowercase.
372 : 13 : const mimeType = {
373 : 13 : type: type.toLowerCase(),
374 : 13 : subtype: subtype.toLowerCase(),
375 : 13 : parameters: new Map(),
376 : 13 : essence: `${type}/${subtype}`
377 : 13 : };
378 : 13 :
379 : 13 : // mimesniff.spec.11. While position is not past the end of input:
380 [ + ]: 17 : while (position.position < input.length){
381 : 4 :
382 : 4 : // mimesniff.spec.11.1. Advance position by 1. (This skips past U+003B (;).)
383 : 4 : position.position++;
384 : 4 :
385 : 4 : // mimesniff.spec.11.2. Collect a sequence of code points that are HTTP
386 : 4 : // whitespace from input given position.
387 : 4 : collectASequenceOfCodePoints(
388 [ + ]: 4 : char => HTTP_WHITESPACE_REGEX.test(char),
389 : 4 : input,
390 : 4 : position
391 : 4 : );
392 : 4 :
393 : 4 : // mimesniff.spec.11.3. Let parameterName be the result of collecting a
394 : 4 : // sequence of code points that are not U+003B (;)
395 : 4 : // or U+003D (=) from input, given position.
396 : 4 : let parameterName = collectASequenceOfCodePoints(
397 [ + ]: 4 : (char) => char !== ';' && char !== '=',
398 : 4 : input,
399 : 4 : position
400 : 4 : );
401 : 4 :
402 : 4 : // mimesniff.spec.11.4. Set parameterName to parameterName,
403 : 4 : // in ASCII lowercase.
404 : 4 : parameterName = parameterName.toLowerCase();
405 : 4 :
406 : 4 : // mimesniff.spec.11.5. If position is not past the end of input, then:
407 : 4 : if(position.position < input.length){
408 : 4 : // mimesniff.spec.11.5.1. If the code point at position within input is
409 : 4 : // U+003B (;), then continue.
410 : 4 : if(input[position.position] === ';')
411 [ - ]: 4 : continue;
412 : 4 :
413 : 4 : // mimesniff.spec.11.5.2. Advance position by 1. (This skips past U+003D (=).)
414 : 4 : position.position++;
415 : 4 : }
416 : 4 :
417 : 4 : // mimesniff.spec.11.6. If position is past the end of input, then break.
418 : 4 : if(position.position > input.length)
419 [ - ]: 4 : break;
420 : 4 :
421 : 4 : // mimesniff.spec.11.7. Let parameterValue be null.
422 : 4 : let parameterValue = null;
423 : 4 :
424 : 4 : // mimesniff.spec.11.8. If the code point at position within input is
425 : 4 : // U+0022 ("), then:
426 [ - ]: 4 : if(input[position.position] === '"'){
427 : 0 :
428 : 0 : // mimesniff.spec.11.8.1. Set parameterValue to the result of collecting
429 : 0 : // an HTTP quoted string from input, given position
430 : 0 : // and the extract-value flag.
431 : 0 : parameterValue = collectAnHTTPQuotedString(input, position, true);
432 : 0 :
433 : 0 : // mimesniff.spec.11.8.2. Collect a sequence of code points that are not
434 : 0 : // U+003B (;) from input, given position.
435 : 0 : collectASequenceOfCodePointsFast(
436 : 0 : ';',
437 : 0 : input,
438 : 0 : position
439 : 0 : );
440 : 0 : }
441 : 4 : // mimesniff.spec.11.9. Otherwise:
442 : 4 : else{
443 : 4 : // mimesniff.spec.11.9.1. Set parameterValue to the result of collecting
444 : 4 : // a sequence of code points that are not U+003B (;)
445 : 4 : // from input, given position.
446 : 4 : parameterValue = collectASequenceOfCodePointsFast(
447 : 4 : ';',
448 : 4 : input,
449 : 4 : position
450 : 4 : );
451 : 4 :
452 : 4 : // mimesniff.spec.11.9.2. Remove any trailing HTTP whitespace from parameterValue.
453 : 4 : // Note: it says "trailing" whitespace; leading is fine.
454 : 4 : parameterValue = parameterValue.trimEnd();
455 : 4 :
456 : 4 : // mimesniff.spec.11.9.3. If parameterValue is the empty string, then continue.
457 : 4 : if(parameterValue.length === 0)
458 [ - ]: 4 : continue;
459 : 4 : }
460 : 4 :
461 : 4 : // mimesniff.spec.11.10. If all of the following are true
462 : 4 : // - parameterName is not the empty string
463 : 4 : // - parameterName solely contains HTTP token code points
464 : 4 : // - parameterValue solely contains HTTP quoted-string token code points
465 : 4 : // - mimeType’s parameters[parameterName] does not exist
466 : 4 : // then set mimeType’s parameters[parameterName] to parameterValue.
467 : 4 : if(
468 : 4 : parameterName.length !== 0 &&
469 : 4 : HTTP_TOKEN_CODEPOINTS.test(parameterName) &&
470 : 4 : !HTTP_QUOTED_STRING_TOKENS.test(parameterValue) &&
471 : 4 : !mimeType.parameters.has(parameterName)
472 : 4 : ) {
473 : 4 : mimeType.parameters.set(parameterName, parameterValue);
474 : 4 : }
475 [ + ]: 4 : }
476 : 13 :
477 : 13 : // mimesniff.spec.12. Return mimeType.
478 : 13 : return mimeType;
479 : 17 : }
480 : 348 :
481 : 348 : /*
482 : 348 : * forgiving-base64 decode
483 : 348 : * url: https://infra.spec.whatwg.org/#forgiving-base64-decode
484 : 348 : *
485 : 348 : * @param {string} data
486 : 348 : */
487 [ + ]: 2 : function forgivingBase64 (data){
488 : 2 :
489 : 2 : // infra.spec.1. Remove all ASCII whitespace from data.
490 : 2 : data = data.replace(/[\u0009\u000A\u000C\u000D\u0020]/g, '');
491 : 2 :
492 : 2 : // infra.spec.2. If data’s code point length divides by 4 leaving
493 : 2 : // no remainder, then:
494 : 2 : if(data.length % 4 === 0){
495 : 2 : // infra.spec.2.1. If data ends with one or two U+003D (=) code points,
496 : 2 : // then remove them from data.
497 : 2 : data = data.replace(/=?=$/, '');
498 : 2 : }
499 : 2 :
500 : 2 : // infra.spec.3. If data’s code point length divides by 4 leaving
501 : 2 : // a remainder of 1, then return failure.
502 : 2 : if(data.length % 4 === 1){
503 : 2 : return 'failure';
504 : 2 : }
505 : 2 :
506 : 2 : // infra.spec.4. If data contains a code point that is not one of
507 : 2 : // U+002B (+)
508 : 2 : // U+002F (/)
509 : 2 : // ASCII alphanumeric
510 : 2 : // then return failure.
511 [ - ]: 2 : if(/[^+/0-9A-Za-z]/.test(data)){
512 : 0 : return 'failure';
513 : 0 : }
514 : 2 :
515 : 2 : const binary = atob(data);
516 : 2 : const bytes = new Uint8Array(binary.length);
517 : 2 :
518 [ + ]: 2 : for (let byte = 0; byte < binary.length; byte++) {
519 : 13 : bytes[byte] = binary.charCodeAt(byte);
520 : 13 : }
521 : 2 :
522 : 2 : return bytes;
523 : 2 : }
524 : 348 :
525 : 348 : /*
526 : 348 : * collect an HTTP quoted string
527 : 348 : * url: https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
528 : 348 : *
529 : 348 : * @param {string} input
530 : 348 : * @param {{ position: number }} position
531 : 348 : * @param {boolean?} extractValue
532 : 348 : */
533 : 0 : function collectAnHTTPQuotedString (input, position, extractValue) {
534 : 0 :
535 : 0 : // fetch.spec.1. Let positionStart be position.
536 : 0 : const positionStart = position.position;
537 : 0 :
538 : 0 : // fetch.spec.2. Let value be the empty string.
539 : 0 : let value = '';
540 : 0 :
541 : 0 : // fetch.spec.3. Assert: the code point at position within input
542 : 0 : // is U+0022 (").
543 : 0 : assert(input[position.position] === '"');
544 : 0 :
545 : 0 : // fetch.spec.4. Advance position by 1.
546 : 0 : position.position++;
547 : 0 :
548 : 0 : // fetch.spec.5. While true:
549 : 0 : while(true){
550 : 0 : // fetch.spec.5.1. Append the result of collecting a sequence of code points
551 : 0 : // that are not U+0022 (") or U+005C (\) from input, given
552 : 0 : // position, to value.
553 : 0 : value += collectASequenceOfCodePoints(
554 : 0 : (char) => char !== '"' && char !== '\\',
555 : 0 : input,
556 : 0 : position
557 : 0 : );
558 : 0 :
559 : 0 : // fetch.spec.5.2. If position is past the end of input, then break.
560 : 0 : if(position.position >= input.length)
561 : 0 : break;
562 : 0 :
563 : 0 : // fetch.spec.5.3. Let quoteOrBackslash be the code point at position
564 : 0 : // within input.
565 : 0 : const quoteOrBackslash = input[position.position];
566 : 0 :
567 : 0 : // fetch.spec.5.4. Advance position by 1.
568 : 0 : position.position++;
569 : 0 :
570 : 0 : // fetch.spec.5.5. If quoteOrBackslash is U+005C (\), then:
571 : 0 : if(quoteOrBackslash === '\\'){
572 : 0 : // fetch.spec.5.5.1. If position is past the end of input, then append
573 : 0 : // U+005C (\) to value and break.
574 : 0 : if (position.position >= input.length) {
575 : 0 : value += '\\'
576 : 0 : break
577 : 0 : }
578 : 0 :
579 : 0 : // fetch.spec.5.5.2. Append the code point at position within input to value.
580 : 0 : value += input[position.position];
581 : 0 :
582 : 0 : // fetch.spec.5.5.3. Advance position by 1.
583 : 0 : position.position++;
584 : 0 : }
585 : 0 : // fetch.spec.5.6. Otherwise:
586 : 0 : else{
587 : 0 : // fetch.spec.5.6.1. Assert: quoteOrBackslash is U+0022 (").
588 : 0 : assert(quoteOrBackslash === '"');
589 : 0 :
590 : 0 : // fetch.spec.5.6.2. Break.
591 : 0 : break;
592 : 0 : }
593 : 0 : }
594 : 0 :
595 : 0 : // fetch.spec.6. If the extract-value flag is set, then return value.
596 : 0 : if(extractValue)
597 : 0 : return value;
598 : 0 :
599 : 0 : // fetch.spec.7. Return the code points from positionStart to position,
600 : 0 : // inclusive, within input.
601 : 0 : return input.slice(positionStart, position.position)
602 : 0 : }
603 : 348 :
604 : 348 : /**
605 : 348 : * serialize a MIME type
606 : 348 : * url: https://mimesniff.spec.whatwg.org/#serialize-a-mime-type
607 : 348 : *
608 : 348 : * @func serializeAMimeType
609 : 348 : * @static
610 : 348 : * @param {object} mimeType - MIME type record.
611 : 348 : * @return {string} Serialized MIME type record.
612 : 348 : * @desc Serialize a MIME type, as defined by {@link https://mimesniff.spec.whatwg.org/#serialize-a-mime-type WHATWG MIME Sniffing Living Standard}.
613 : 348 : */
614 [ + ]: 348 : export function serializeAMimeType(mimeType){
615 : 13 :
616 : 13 : assert(mimeType !== 'failure');
617 : 13 :
618 : 13 : const { type, subtype, parameters } = mimeType;
619 : 13 :
620 : 13 : // mimesniff.spec.1. Let serialization be the concatenation of mimeType’s
621 : 13 : // type, U+002F (/), and mimeType’s subtype.
622 : 13 : let serialization = `${type}/${subtype}`;
623 : 13 :
624 : 13 : // mimesniff.spec.2. For each name → value of mimeType’s parameters:
625 [ + ]: 13 : for(let [name, value] of parameters.entries()){
626 : 4 :
627 : 4 : // mimesniff.spec.2.1. Append U+003B (;) to serialization.
628 : 4 : serialization += ';'
629 : 4 :
630 : 4 : // mimesniff.spec.2.2. Append name to serialization.
631 : 4 : serialization += name;
632 : 4 :
633 : 4 : // mimesniff.spec.2.3. Append U+003D (=) to serialization.
634 : 4 : serialization += '=';
635 : 4 :
636 : 4 : // mimesniff.spec.2.4. If value does not solely contain HTTP token code
637 : 4 : // points or value is the empty string, then:
638 [ - ]: 4 : if(!isValidHTTPToken(value)){
639 : 0 : // mimesniff.spec.2.4.1. Precede each occurence of U+0022 (") or
640 : 0 : // U+005C (\) in value with U+005C (\).
641 : 0 : value = value.replace(/(\\|")/g, '\\$1');
642 : 0 :
643 : 0 : // mimesniff.spec.2.4.2. Prepend U+0022 (") to value.
644 : 0 : value = '"' + value;
645 : 0 :
646 : 0 : // mimesniff.spec.2.4.3. Append U+0022 (") to value.
647 : 0 : value += '"';
648 : 0 : }
649 : 4 :
650 : 4 : // mimesniff.spec.2.5. Append value to serialization.
651 : 4 : serialization += value;
652 : 4 : }
653 : 13 :
654 : 13 : // mimesniff.spec.3. Return serialization.
655 : 13 : return serialization;
656 : 13 : }
657 : 348 :
658 : 348 : const MAXIMUM_ARGUMENT_LENGTH = 65535
659 : 348 :
660 : 348 : /*
661 : 348 : * isomorphic decode
662 : 348 : * url: https://infra.spec.whatwg.org/#isomorphic-decode
663 : 348 : *
664 : 348 : * To isomorphic decode a byte sequence input, return a string whose code point length
665 : 348 : * is equal to input’s length and whose code points have the same values as the values
666 : 348 : * of input’s bytes, in the same order.
667 : 348 : *
668 : 348 : * @param {number[]|Uint8Array} input
669 : 348 : */
670 [ + ]: 2 : function isomorphicDecode(input){
671 : 2 :
672 : 2 : if(input.length < MAXIMUM_ARGUMENT_LENGTH)
673 [ - ]: 2 : return String.fromCharCode(...input);
674 : 0 :
675 : 0 : return input.reduce((previous, current) => previous + String.fromCharCode(current), '');
676 : 2 : }
677 : 348 :
678 : 348 : // RFC 7230, Section 3.2.6.
679 : 348 : // https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/third_party/blink/renderer/platform/network/http_parsers.cc#L321
680 : 348 :
681 [ + ]: 4 : function isValidHTTPToken(characters){
682 : 4 :
683 : 4 : if(!characters || typeof characters !== 'string')
684 [ - ]: 4 : return false;
685 : 4 :
686 [ + ]: 4 : for(let i = 0; i < characters.length; ++i){
687 : 27 : const c = characters.charCodeAt(i);
688 : 27 : if(c > 0x7f || !isTokenChar(c))
689 [ - ]: 27 : return false;
690 : 27 : }
691 : 4 :
692 : 4 : return true;
693 : 4 : }
694 : 348 :
695 [ + ]: 27 : function isTokenChar(c){
696 : 27 : return !(
697 : 27 : c >= 0x7f ||
698 : 27 : c <= 0x20 ||
699 : 27 : c === '(' ||
700 : 27 : c === ')' ||
701 : 27 : c === '<' ||
702 : 27 : c === '>' ||
703 : 27 : c === '@' ||
704 : 27 : c === ',' ||
705 : 27 : c === ';' ||
706 : 27 : c === ':' ||
707 : 27 : c === '\\' ||
708 : 27 : c === '"' ||
709 : 27 : c === '/' ||
710 : 27 : c === '[' ||
711 : 27 : c === ']' ||
712 : 27 : c === '?' ||
713 : 27 : c === '=' ||
714 : 27 : c === '{' ||
715 : 27 : c === '}'
716 : 27 : );
717 : 27 : }
|