source: frontend/node_modules/whatwg-url/dist/url-state-machine.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 29.4 KB
Line 
1"use strict";
2const punycode = require("punycode");
3const tr46 = require("tr46");
4
5const infra = require("./infra");
6const { utf8DecodeWithoutBOM } = require("./encoding");
7const { percentDecodeString, utf8PercentEncodeCodePoint, utf8PercentEncodeString, isC0ControlPercentEncode,
8 isFragmentPercentEncode, isQueryPercentEncode, isSpecialQueryPercentEncode, isPathPercentEncode,
9 isUserinfoPercentEncode } = require("./percent-encoding");
10
11const specialSchemes = {
12 ftp: 21,
13 file: null,
14 http: 80,
15 https: 443,
16 ws: 80,
17 wss: 443
18};
19
20const failure = Symbol("failure");
21
22function countSymbols(str) {
23 return [...str].length;
24}
25
26function at(input, idx) {
27 const c = input[idx];
28 return isNaN(c) ? undefined : String.fromCodePoint(c);
29}
30
31function isSingleDot(buffer) {
32 return buffer === "." || buffer.toLowerCase() === "%2e";
33}
34
35function isDoubleDot(buffer) {
36 buffer = buffer.toLowerCase();
37 return buffer === ".." || buffer === "%2e." || buffer === ".%2e" || buffer === "%2e%2e";
38}
39
40function isWindowsDriveLetterCodePoints(cp1, cp2) {
41 return infra.isASCIIAlpha(cp1) && (cp2 === 58 || cp2 === 124);
42}
43
44function isWindowsDriveLetterString(string) {
45 return string.length === 2 && infra.isASCIIAlpha(string.codePointAt(0)) && (string[1] === ":" || string[1] === "|");
46}
47
48function isNormalizedWindowsDriveLetterString(string) {
49 return string.length === 2 && infra.isASCIIAlpha(string.codePointAt(0)) && string[1] === ":";
50}
51
52function containsForbiddenHostCodePoint(string) {
53 return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|%|\/|:|<|>|\?|@|\[|\\|\]|\^|\|/u) !== -1;
54}
55
56function containsForbiddenHostCodePointExcludingPercent(string) {
57 return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|\/|:|<|>|\?|@|\[|\\|\]|\^|\|/u) !== -1;
58}
59
60function isSpecialScheme(scheme) {
61 return specialSchemes[scheme] !== undefined;
62}
63
64function isSpecial(url) {
65 return isSpecialScheme(url.scheme);
66}
67
68function isNotSpecial(url) {
69 return !isSpecialScheme(url.scheme);
70}
71
72function defaultPort(scheme) {
73 return specialSchemes[scheme];
74}
75
76function parseIPv4Number(input) {
77 let R = 10;
78
79 if (input.length >= 2 && input.charAt(0) === "0" && input.charAt(1).toLowerCase() === "x") {
80 input = input.substring(2);
81 R = 16;
82 } else if (input.length >= 2 && input.charAt(0) === "0") {
83 input = input.substring(1);
84 R = 8;
85 }
86
87 if (input === "") {
88 return 0;
89 }
90
91 let regex = /[^0-7]/u;
92 if (R === 10) {
93 regex = /[^0-9]/u;
94 }
95 if (R === 16) {
96 regex = /[^0-9A-Fa-f]/u;
97 }
98
99 if (regex.test(input)) {
100 return failure;
101 }
102
103 return parseInt(input, R);
104}
105
106function parseIPv4(input) {
107 const parts = input.split(".");
108 if (parts[parts.length - 1] === "") {
109 if (parts.length > 1) {
110 parts.pop();
111 }
112 }
113
114 if (parts.length > 4) {
115 return input;
116 }
117
118 const numbers = [];
119 for (const part of parts) {
120 if (part === "") {
121 return input;
122 }
123 const n = parseIPv4Number(part);
124 if (n === failure) {
125 return input;
126 }
127
128 numbers.push(n);
129 }
130
131 for (let i = 0; i < numbers.length - 1; ++i) {
132 if (numbers[i] > 255) {
133 return failure;
134 }
135 }
136 if (numbers[numbers.length - 1] >= 256 ** (5 - numbers.length)) {
137 return failure;
138 }
139
140 let ipv4 = numbers.pop();
141 let counter = 0;
142
143 for (const n of numbers) {
144 ipv4 += n * 256 ** (3 - counter);
145 ++counter;
146 }
147
148 return ipv4;
149}
150
151function serializeIPv4(address) {
152 let output = "";
153 let n = address;
154
155 for (let i = 1; i <= 4; ++i) {
156 output = String(n % 256) + output;
157 if (i !== 4) {
158 output = `.${output}`;
159 }
160 n = Math.floor(n / 256);
161 }
162
163 return output;
164}
165
166function parseIPv6(input) {
167 const address = [0, 0, 0, 0, 0, 0, 0, 0];
168 let pieceIndex = 0;
169 let compress = null;
170 let pointer = 0;
171
172 input = punycode.ucs2.decode(input);
173
174 if (input[pointer] === 58) {
175 if (input[pointer + 1] !== 58) {
176 return failure;
177 }
178
179 pointer += 2;
180 ++pieceIndex;
181 compress = pieceIndex;
182 }
183
184 while (pointer < input.length) {
185 if (pieceIndex === 8) {
186 return failure;
187 }
188
189 if (input[pointer] === 58) {
190 if (compress !== null) {
191 return failure;
192 }
193 ++pointer;
194 ++pieceIndex;
195 compress = pieceIndex;
196 continue;
197 }
198
199 let value = 0;
200 let length = 0;
201
202 while (length < 4 && infra.isASCIIHex(input[pointer])) {
203 value = value * 0x10 + parseInt(at(input, pointer), 16);
204 ++pointer;
205 ++length;
206 }
207
208 if (input[pointer] === 46) {
209 if (length === 0) {
210 return failure;
211 }
212
213 pointer -= length;
214
215 if (pieceIndex > 6) {
216 return failure;
217 }
218
219 let numbersSeen = 0;
220
221 while (input[pointer] !== undefined) {
222 let ipv4Piece = null;
223
224 if (numbersSeen > 0) {
225 if (input[pointer] === 46 && numbersSeen < 4) {
226 ++pointer;
227 } else {
228 return failure;
229 }
230 }
231
232 if (!infra.isASCIIDigit(input[pointer])) {
233 return failure;
234 }
235
236 while (infra.isASCIIDigit(input[pointer])) {
237 const number = parseInt(at(input, pointer));
238 if (ipv4Piece === null) {
239 ipv4Piece = number;
240 } else if (ipv4Piece === 0) {
241 return failure;
242 } else {
243 ipv4Piece = ipv4Piece * 10 + number;
244 }
245 if (ipv4Piece > 255) {
246 return failure;
247 }
248 ++pointer;
249 }
250
251 address[pieceIndex] = address[pieceIndex] * 0x100 + ipv4Piece;
252
253 ++numbersSeen;
254
255 if (numbersSeen === 2 || numbersSeen === 4) {
256 ++pieceIndex;
257 }
258 }
259
260 if (numbersSeen !== 4) {
261 return failure;
262 }
263
264 break;
265 } else if (input[pointer] === 58) {
266 ++pointer;
267 if (input[pointer] === undefined) {
268 return failure;
269 }
270 } else if (input[pointer] !== undefined) {
271 return failure;
272 }
273
274 address[pieceIndex] = value;
275 ++pieceIndex;
276 }
277
278 if (compress !== null) {
279 let swaps = pieceIndex - compress;
280 pieceIndex = 7;
281 while (pieceIndex !== 0 && swaps > 0) {
282 const temp = address[compress + swaps - 1];
283 address[compress + swaps - 1] = address[pieceIndex];
284 address[pieceIndex] = temp;
285 --pieceIndex;
286 --swaps;
287 }
288 } else if (compress === null && pieceIndex !== 8) {
289 return failure;
290 }
291
292 return address;
293}
294
295function serializeIPv6(address) {
296 let output = "";
297 const compress = findLongestZeroSequence(address);
298 let ignore0 = false;
299
300 for (let pieceIndex = 0; pieceIndex <= 7; ++pieceIndex) {
301 if (ignore0 && address[pieceIndex] === 0) {
302 continue;
303 } else if (ignore0) {
304 ignore0 = false;
305 }
306
307 if (compress === pieceIndex) {
308 const separator = pieceIndex === 0 ? "::" : ":";
309 output += separator;
310 ignore0 = true;
311 continue;
312 }
313
314 output += address[pieceIndex].toString(16);
315
316 if (pieceIndex !== 7) {
317 output += ":";
318 }
319 }
320
321 return output;
322}
323
324function parseHost(input, isNotSpecialArg = false) {
325 if (input[0] === "[") {
326 if (input[input.length - 1] !== "]") {
327 return failure;
328 }
329
330 return parseIPv6(input.substring(1, input.length - 1));
331 }
332
333 if (isNotSpecialArg) {
334 return parseOpaqueHost(input);
335 }
336
337 const domain = utf8DecodeWithoutBOM(percentDecodeString(input));
338 const asciiDomain = domainToASCII(domain);
339 if (asciiDomain === failure) {
340 return failure;
341 }
342
343 if (containsForbiddenHostCodePoint(asciiDomain)) {
344 return failure;
345 }
346
347 const ipv4Host = parseIPv4(asciiDomain);
348 if (typeof ipv4Host === "number" || ipv4Host === failure) {
349 return ipv4Host;
350 }
351
352 return asciiDomain;
353}
354
355function parseOpaqueHost(input) {
356 if (containsForbiddenHostCodePointExcludingPercent(input)) {
357 return failure;
358 }
359
360 return utf8PercentEncodeString(input, isC0ControlPercentEncode);
361}
362
363function findLongestZeroSequence(arr) {
364 let maxIdx = null;
365 let maxLen = 1; // only find elements > 1
366 let currStart = null;
367 let currLen = 0;
368
369 for (let i = 0; i < arr.length; ++i) {
370 if (arr[i] !== 0) {
371 if (currLen > maxLen) {
372 maxIdx = currStart;
373 maxLen = currLen;
374 }
375
376 currStart = null;
377 currLen = 0;
378 } else {
379 if (currStart === null) {
380 currStart = i;
381 }
382 ++currLen;
383 }
384 }
385
386 // if trailing zeros
387 if (currLen > maxLen) {
388 return currStart;
389 }
390
391 return maxIdx;
392}
393
394function serializeHost(host) {
395 if (typeof host === "number") {
396 return serializeIPv4(host);
397 }
398
399 // IPv6 serializer
400 if (host instanceof Array) {
401 return `[${serializeIPv6(host)}]`;
402 }
403
404 return host;
405}
406
407function domainToASCII(domain, beStrict = false) {
408 const result = tr46.toASCII(domain, {
409 checkBidi: true,
410 checkHyphens: false,
411 checkJoiners: true,
412 useSTD3ASCIIRules: beStrict,
413 verifyDNSLength: beStrict
414 });
415 if (result === null || result === "") {
416 return failure;
417 }
418 return result;
419}
420
421function trimControlChars(url) {
422 return url.replace(/^[\u0000-\u001F\u0020]+|[\u0000-\u001F\u0020]+$/ug, "");
423}
424
425function trimTabAndNewline(url) {
426 return url.replace(/\u0009|\u000A|\u000D/ug, "");
427}
428
429function shortenPath(url) {
430 const { path } = url;
431 if (path.length === 0) {
432 return;
433 }
434 if (url.scheme === "file" && path.length === 1 && isNormalizedWindowsDriveLetter(path[0])) {
435 return;
436 }
437
438 path.pop();
439}
440
441function includesCredentials(url) {
442 return url.username !== "" || url.password !== "";
443}
444
445function cannotHaveAUsernamePasswordPort(url) {
446 return url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file";
447}
448
449function isNormalizedWindowsDriveLetter(string) {
450 return /^[A-Za-z]:$/u.test(string);
451}
452
453function URLStateMachine(input, base, encodingOverride, url, stateOverride) {
454 this.pointer = 0;
455 this.input = input;
456 this.base = base || null;
457 this.encodingOverride = encodingOverride || "utf-8";
458 this.stateOverride = stateOverride;
459 this.url = url;
460 this.failure = false;
461 this.parseError = false;
462
463 if (!this.url) {
464 this.url = {
465 scheme: "",
466 username: "",
467 password: "",
468 host: null,
469 port: null,
470 path: [],
471 query: null,
472 fragment: null,
473
474 cannotBeABaseURL: false
475 };
476
477 const res = trimControlChars(this.input);
478 if (res !== this.input) {
479 this.parseError = true;
480 }
481 this.input = res;
482 }
483
484 const res = trimTabAndNewline(this.input);
485 if (res !== this.input) {
486 this.parseError = true;
487 }
488 this.input = res;
489
490 this.state = stateOverride || "scheme start";
491
492 this.buffer = "";
493 this.atFlag = false;
494 this.arrFlag = false;
495 this.passwordTokenSeenFlag = false;
496
497 this.input = punycode.ucs2.decode(this.input);
498
499 for (; this.pointer <= this.input.length; ++this.pointer) {
500 const c = this.input[this.pointer];
501 const cStr = isNaN(c) ? undefined : String.fromCodePoint(c);
502
503 // exec state machine
504 const ret = this[`parse ${this.state}`](c, cStr);
505 if (!ret) {
506 break; // terminate algorithm
507 } else if (ret === failure) {
508 this.failure = true;
509 break;
510 }
511 }
512}
513
514URLStateMachine.prototype["parse scheme start"] = function parseSchemeStart(c, cStr) {
515 if (infra.isASCIIAlpha(c)) {
516 this.buffer += cStr.toLowerCase();
517 this.state = "scheme";
518 } else if (!this.stateOverride) {
519 this.state = "no scheme";
520 --this.pointer;
521 } else {
522 this.parseError = true;
523 return failure;
524 }
525
526 return true;
527};
528
529URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) {
530 if (infra.isASCIIAlphanumeric(c) || c === 43 || c === 45 || c === 46) {
531 this.buffer += cStr.toLowerCase();
532 } else if (c === 58) {
533 if (this.stateOverride) {
534 if (isSpecial(this.url) && !isSpecialScheme(this.buffer)) {
535 return false;
536 }
537
538 if (!isSpecial(this.url) && isSpecialScheme(this.buffer)) {
539 return false;
540 }
541
542 if ((includesCredentials(this.url) || this.url.port !== null) && this.buffer === "file") {
543 return false;
544 }
545
546 if (this.url.scheme === "file" && this.url.host === "") {
547 return false;
548 }
549 }
550 this.url.scheme = this.buffer;
551 if (this.stateOverride) {
552 if (this.url.port === defaultPort(this.url.scheme)) {
553 this.url.port = null;
554 }
555 return false;
556 }
557 this.buffer = "";
558 if (this.url.scheme === "file") {
559 if (this.input[this.pointer + 1] !== 47 || this.input[this.pointer + 2] !== 47) {
560 this.parseError = true;
561 }
562 this.state = "file";
563 } else if (isSpecial(this.url) && this.base !== null && this.base.scheme === this.url.scheme) {
564 this.state = "special relative or authority";
565 } else if (isSpecial(this.url)) {
566 this.state = "special authority slashes";
567 } else if (this.input[this.pointer + 1] === 47) {
568 this.state = "path or authority";
569 ++this.pointer;
570 } else {
571 this.url.cannotBeABaseURL = true;
572 this.url.path.push("");
573 this.state = "cannot-be-a-base-URL path";
574 }
575 } else if (!this.stateOverride) {
576 this.buffer = "";
577 this.state = "no scheme";
578 this.pointer = -1;
579 } else {
580 this.parseError = true;
581 return failure;
582 }
583
584 return true;
585};
586
587URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) {
588 if (this.base === null || (this.base.cannotBeABaseURL && c !== 35)) {
589 return failure;
590 } else if (this.base.cannotBeABaseURL && c === 35) {
591 this.url.scheme = this.base.scheme;
592 this.url.path = this.base.path.slice();
593 this.url.query = this.base.query;
594 this.url.fragment = "";
595 this.url.cannotBeABaseURL = true;
596 this.state = "fragment";
597 } else if (this.base.scheme === "file") {
598 this.state = "file";
599 --this.pointer;
600 } else {
601 this.state = "relative";
602 --this.pointer;
603 }
604
605 return true;
606};
607
608URLStateMachine.prototype["parse special relative or authority"] = function parseSpecialRelativeOrAuthority(c) {
609 if (c === 47 && this.input[this.pointer + 1] === 47) {
610 this.state = "special authority ignore slashes";
611 ++this.pointer;
612 } else {
613 this.parseError = true;
614 this.state = "relative";
615 --this.pointer;
616 }
617
618 return true;
619};
620
621URLStateMachine.prototype["parse path or authority"] = function parsePathOrAuthority(c) {
622 if (c === 47) {
623 this.state = "authority";
624 } else {
625 this.state = "path";
626 --this.pointer;
627 }
628
629 return true;
630};
631
632URLStateMachine.prototype["parse relative"] = function parseRelative(c) {
633 this.url.scheme = this.base.scheme;
634 if (c === 47) {
635 this.state = "relative slash";
636 } else if (isSpecial(this.url) && c === 92) {
637 this.parseError = true;
638 this.state = "relative slash";
639 } else {
640 this.url.username = this.base.username;
641 this.url.password = this.base.password;
642 this.url.host = this.base.host;
643 this.url.port = this.base.port;
644 this.url.path = this.base.path.slice();
645 this.url.query = this.base.query;
646 if (c === 63) {
647 this.url.query = "";
648 this.state = "query";
649 } else if (c === 35) {
650 this.url.fragment = "";
651 this.state = "fragment";
652 } else if (!isNaN(c)) {
653 this.url.query = null;
654 this.url.path.pop();
655 this.state = "path";
656 --this.pointer;
657 }
658 }
659
660 return true;
661};
662
663URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) {
664 if (isSpecial(this.url) && (c === 47 || c === 92)) {
665 if (c === 92) {
666 this.parseError = true;
667 }
668 this.state = "special authority ignore slashes";
669 } else if (c === 47) {
670 this.state = "authority";
671 } else {
672 this.url.username = this.base.username;
673 this.url.password = this.base.password;
674 this.url.host = this.base.host;
675 this.url.port = this.base.port;
676 this.state = "path";
677 --this.pointer;
678 }
679
680 return true;
681};
682
683URLStateMachine.prototype["parse special authority slashes"] = function parseSpecialAuthoritySlashes(c) {
684 if (c === 47 && this.input[this.pointer + 1] === 47) {
685 this.state = "special authority ignore slashes";
686 ++this.pointer;
687 } else {
688 this.parseError = true;
689 this.state = "special authority ignore slashes";
690 --this.pointer;
691 }
692
693 return true;
694};
695
696URLStateMachine.prototype["parse special authority ignore slashes"] = function parseSpecialAuthorityIgnoreSlashes(c) {
697 if (c !== 47 && c !== 92) {
698 this.state = "authority";
699 --this.pointer;
700 } else {
701 this.parseError = true;
702 }
703
704 return true;
705};
706
707URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) {
708 if (c === 64) {
709 this.parseError = true;
710 if (this.atFlag) {
711 this.buffer = `%40${this.buffer}`;
712 }
713 this.atFlag = true;
714
715 // careful, this is based on buffer and has its own pointer (this.pointer != pointer) and inner chars
716 const len = countSymbols(this.buffer);
717 for (let pointer = 0; pointer < len; ++pointer) {
718 const codePoint = this.buffer.codePointAt(pointer);
719
720 if (codePoint === 58 && !this.passwordTokenSeenFlag) {
721 this.passwordTokenSeenFlag = true;
722 continue;
723 }
724 const encodedCodePoints = utf8PercentEncodeCodePoint(codePoint, isUserinfoPercentEncode);
725 if (this.passwordTokenSeenFlag) {
726 this.url.password += encodedCodePoints;
727 } else {
728 this.url.username += encodedCodePoints;
729 }
730 }
731 this.buffer = "";
732 } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
733 (isSpecial(this.url) && c === 92)) {
734 if (this.atFlag && this.buffer === "") {
735 this.parseError = true;
736 return failure;
737 }
738 this.pointer -= countSymbols(this.buffer) + 1;
739 this.buffer = "";
740 this.state = "host";
741 } else {
742 this.buffer += cStr;
743 }
744
745 return true;
746};
747
748URLStateMachine.prototype["parse hostname"] =
749URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) {
750 if (this.stateOverride && this.url.scheme === "file") {
751 --this.pointer;
752 this.state = "file host";
753 } else if (c === 58 && !this.arrFlag) {
754 if (this.buffer === "") {
755 this.parseError = true;
756 return failure;
757 }
758
759 if (this.stateOverride === "hostname") {
760 return false;
761 }
762
763 const host = parseHost(this.buffer, isNotSpecial(this.url));
764 if (host === failure) {
765 return failure;
766 }
767
768 this.url.host = host;
769 this.buffer = "";
770 this.state = "port";
771 } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
772 (isSpecial(this.url) && c === 92)) {
773 --this.pointer;
774 if (isSpecial(this.url) && this.buffer === "") {
775 this.parseError = true;
776 return failure;
777 } else if (this.stateOverride && this.buffer === "" &&
778 (includesCredentials(this.url) || this.url.port !== null)) {
779 this.parseError = true;
780 return false;
781 }
782
783 const host = parseHost(this.buffer, isNotSpecial(this.url));
784 if (host === failure) {
785 return failure;
786 }
787
788 this.url.host = host;
789 this.buffer = "";
790 this.state = "path start";
791 if (this.stateOverride) {
792 return false;
793 }
794 } else {
795 if (c === 91) {
796 this.arrFlag = true;
797 } else if (c === 93) {
798 this.arrFlag = false;
799 }
800 this.buffer += cStr;
801 }
802
803 return true;
804};
805
806URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) {
807 if (infra.isASCIIDigit(c)) {
808 this.buffer += cStr;
809 } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
810 (isSpecial(this.url) && c === 92) ||
811 this.stateOverride) {
812 if (this.buffer !== "") {
813 const port = parseInt(this.buffer);
814 if (port > 2 ** 16 - 1) {
815 this.parseError = true;
816 return failure;
817 }
818 this.url.port = port === defaultPort(this.url.scheme) ? null : port;
819 this.buffer = "";
820 }
821 if (this.stateOverride) {
822 return false;
823 }
824 this.state = "path start";
825 --this.pointer;
826 } else {
827 this.parseError = true;
828 return failure;
829 }
830
831 return true;
832};
833
834const fileOtherwiseCodePoints = new Set([47, 92, 63, 35]);
835
836function startsWithWindowsDriveLetter(input, pointer) {
837 const length = input.length - pointer;
838 return length >= 2 &&
839 isWindowsDriveLetterCodePoints(input[pointer], input[pointer + 1]) &&
840 (length === 2 || fileOtherwiseCodePoints.has(input[pointer + 2]));
841}
842
843URLStateMachine.prototype["parse file"] = function parseFile(c) {
844 this.url.scheme = "file";
845 this.url.host = "";
846
847 if (c === 47 || c === 92) {
848 if (c === 92) {
849 this.parseError = true;
850 }
851 this.state = "file slash";
852 } else if (this.base !== null && this.base.scheme === "file") {
853 this.url.host = this.base.host;
854 this.url.path = this.base.path.slice();
855 this.url.query = this.base.query;
856 if (c === 63) {
857 this.url.query = "";
858 this.state = "query";
859 } else if (c === 35) {
860 this.url.fragment = "";
861 this.state = "fragment";
862 } else if (!isNaN(c)) {
863 this.url.query = null;
864 if (!startsWithWindowsDriveLetter(this.input, this.pointer)) {
865 shortenPath(this.url);
866 } else {
867 this.parseError = true;
868 this.url.path = [];
869 }
870
871 this.state = "path";
872 --this.pointer;
873 }
874 } else {
875 this.state = "path";
876 --this.pointer;
877 }
878
879 return true;
880};
881
882URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) {
883 if (c === 47 || c === 92) {
884 if (c === 92) {
885 this.parseError = true;
886 }
887 this.state = "file host";
888 } else {
889 if (this.base !== null && this.base.scheme === "file") {
890 if (!startsWithWindowsDriveLetter(this.input, this.pointer) &&
891 isNormalizedWindowsDriveLetterString(this.base.path[0])) {
892 this.url.path.push(this.base.path[0]);
893 }
894 this.url.host = this.base.host;
895 }
896 this.state = "path";
897 --this.pointer;
898 }
899
900 return true;
901};
902
903URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) {
904 if (isNaN(c) || c === 47 || c === 92 || c === 63 || c === 35) {
905 --this.pointer;
906 if (!this.stateOverride && isWindowsDriveLetterString(this.buffer)) {
907 this.parseError = true;
908 this.state = "path";
909 } else if (this.buffer === "") {
910 this.url.host = "";
911 if (this.stateOverride) {
912 return false;
913 }
914 this.state = "path start";
915 } else {
916 let host = parseHost(this.buffer, isNotSpecial(this.url));
917 if (host === failure) {
918 return failure;
919 }
920 if (host === "localhost") {
921 host = "";
922 }
923 this.url.host = host;
924
925 if (this.stateOverride) {
926 return false;
927 }
928
929 this.buffer = "";
930 this.state = "path start";
931 }
932 } else {
933 this.buffer += cStr;
934 }
935
936 return true;
937};
938
939URLStateMachine.prototype["parse path start"] = function parsePathStart(c) {
940 if (isSpecial(this.url)) {
941 if (c === 92) {
942 this.parseError = true;
943 }
944 this.state = "path";
945
946 if (c !== 47 && c !== 92) {
947 --this.pointer;
948 }
949 } else if (!this.stateOverride && c === 63) {
950 this.url.query = "";
951 this.state = "query";
952 } else if (!this.stateOverride && c === 35) {
953 this.url.fragment = "";
954 this.state = "fragment";
955 } else if (c !== undefined) {
956 this.state = "path";
957 if (c !== 47) {
958 --this.pointer;
959 }
960 } else if (this.stateOverride && this.url.host === null) {
961 this.url.path.push("");
962 }
963
964 return true;
965};
966
967URLStateMachine.prototype["parse path"] = function parsePath(c) {
968 if (isNaN(c) || c === 47 || (isSpecial(this.url) && c === 92) ||
969 (!this.stateOverride && (c === 63 || c === 35))) {
970 if (isSpecial(this.url) && c === 92) {
971 this.parseError = true;
972 }
973
974 if (isDoubleDot(this.buffer)) {
975 shortenPath(this.url);
976 if (c !== 47 && !(isSpecial(this.url) && c === 92)) {
977 this.url.path.push("");
978 }
979 } else if (isSingleDot(this.buffer) && c !== 47 &&
980 !(isSpecial(this.url) && c === 92)) {
981 this.url.path.push("");
982 } else if (!isSingleDot(this.buffer)) {
983 if (this.url.scheme === "file" && this.url.path.length === 0 && isWindowsDriveLetterString(this.buffer)) {
984 this.buffer = `${this.buffer[0]}:`;
985 }
986 this.url.path.push(this.buffer);
987 }
988 this.buffer = "";
989 if (c === 63) {
990 this.url.query = "";
991 this.state = "query";
992 }
993 if (c === 35) {
994 this.url.fragment = "";
995 this.state = "fragment";
996 }
997 } else {
998 // TODO: If c is not a URL code point and not "%", parse error.
999
1000 if (c === 37 &&
1001 (!infra.isASCIIHex(this.input[this.pointer + 1]) ||
1002 !infra.isASCIIHex(this.input[this.pointer + 2]))) {
1003 this.parseError = true;
1004 }
1005
1006 this.buffer += utf8PercentEncodeCodePoint(c, isPathPercentEncode);
1007 }
1008
1009 return true;
1010};
1011
1012URLStateMachine.prototype["parse cannot-be-a-base-URL path"] = function parseCannotBeABaseURLPath(c) {
1013 if (c === 63) {
1014 this.url.query = "";
1015 this.state = "query";
1016 } else if (c === 35) {
1017 this.url.fragment = "";
1018 this.state = "fragment";
1019 } else {
1020 // TODO: Add: not a URL code point
1021 if (!isNaN(c) && c !== 37) {
1022 this.parseError = true;
1023 }
1024
1025 if (c === 37 &&
1026 (!infra.isASCIIHex(this.input[this.pointer + 1]) ||
1027 !infra.isASCIIHex(this.input[this.pointer + 2]))) {
1028 this.parseError = true;
1029 }
1030
1031 if (!isNaN(c)) {
1032 this.url.path[0] += utf8PercentEncodeCodePoint(c, isC0ControlPercentEncode);
1033 }
1034 }
1035
1036 return true;
1037};
1038
1039URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) {
1040 if (!isSpecial(this.url) || this.url.scheme === "ws" || this.url.scheme === "wss") {
1041 this.encodingOverride = "utf-8";
1042 }
1043
1044 if ((!this.stateOverride && c === 35) || isNaN(c)) {
1045 const queryPercentEncodePredicate = isSpecial(this.url) ? isSpecialQueryPercentEncode : isQueryPercentEncode;
1046 this.url.query += utf8PercentEncodeString(this.buffer, queryPercentEncodePredicate);
1047
1048 this.buffer = "";
1049
1050 if (c === 35) {
1051 this.url.fragment = "";
1052 this.state = "fragment";
1053 }
1054 } else if (!isNaN(c)) {
1055 // TODO: If c is not a URL code point and not "%", parse error.
1056
1057 if (c === 37 &&
1058 (!infra.isASCIIHex(this.input[this.pointer + 1]) ||
1059 !infra.isASCIIHex(this.input[this.pointer + 2]))) {
1060 this.parseError = true;
1061 }
1062
1063 this.buffer += cStr;
1064 }
1065
1066 return true;
1067};
1068
1069URLStateMachine.prototype["parse fragment"] = function parseFragment(c) {
1070 if (!isNaN(c)) {
1071 // TODO: If c is not a URL code point and not "%", parse error.
1072 if (c === 37 &&
1073 (!infra.isASCIIHex(this.input[this.pointer + 1]) ||
1074 !infra.isASCIIHex(this.input[this.pointer + 2]))) {
1075 this.parseError = true;
1076 }
1077
1078 this.url.fragment += utf8PercentEncodeCodePoint(c, isFragmentPercentEncode);
1079 }
1080
1081 return true;
1082};
1083
1084function serializeURL(url, excludeFragment) {
1085 let output = `${url.scheme}:`;
1086 if (url.host !== null) {
1087 output += "//";
1088
1089 if (url.username !== "" || url.password !== "") {
1090 output += url.username;
1091 if (url.password !== "") {
1092 output += `:${url.password}`;
1093 }
1094 output += "@";
1095 }
1096
1097 output += serializeHost(url.host);
1098
1099 if (url.port !== null) {
1100 output += `:${url.port}`;
1101 }
1102 }
1103
1104 if (url.cannotBeABaseURL) {
1105 output += url.path[0];
1106 } else {
1107 if (url.host === null && url.path.length > 1 && url.path[0] === "") {
1108 output += "/.";
1109 }
1110 for (const segment of url.path) {
1111 output += `/${segment}`;
1112 }
1113 }
1114
1115 if (url.query !== null) {
1116 output += `?${url.query}`;
1117 }
1118
1119 if (!excludeFragment && url.fragment !== null) {
1120 output += `#${url.fragment}`;
1121 }
1122
1123 return output;
1124}
1125
1126function serializeOrigin(tuple) {
1127 let result = `${tuple.scheme}://`;
1128 result += serializeHost(tuple.host);
1129
1130 if (tuple.port !== null) {
1131 result += `:${tuple.port}`;
1132 }
1133
1134 return result;
1135}
1136
1137module.exports.serializeURL = serializeURL;
1138
1139module.exports.serializeURLOrigin = function (url) {
1140 // https://url.spec.whatwg.org/#concept-url-origin
1141 switch (url.scheme) {
1142 case "blob":
1143 try {
1144 return module.exports.serializeURLOrigin(module.exports.parseURL(url.path[0]));
1145 } catch (e) {
1146 // serializing an opaque origin returns "null"
1147 return "null";
1148 }
1149 case "ftp":
1150 case "http":
1151 case "https":
1152 case "ws":
1153 case "wss":
1154 return serializeOrigin({
1155 scheme: url.scheme,
1156 host: url.host,
1157 port: url.port
1158 });
1159 case "file":
1160 // The spec says:
1161 // > Unfortunate as it is, this is left as an exercise to the reader. When in doubt, return a new opaque origin.
1162 // Browsers tested so far:
1163 // - Chrome says "file://", but treats file: URLs as cross-origin for most (all?) purposes; see e.g.
1164 // https://bugs.chromium.org/p/chromium/issues/detail?id=37586
1165 // - Firefox says "null", but treats file: URLs as same-origin sometimes based on directory stuff; see
1166 // https://developer.mozilla.org/en-US/docs/Archive/Misc_top_level/Same-origin_policy_for_file:_URIs
1167 return "null";
1168 default:
1169 // serializing an opaque origin returns "null"
1170 return "null";
1171 }
1172};
1173
1174module.exports.basicURLParse = function (input, options) {
1175 if (options === undefined) {
1176 options = {};
1177 }
1178
1179 const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride);
1180 if (usm.failure) {
1181 return null;
1182 }
1183
1184 return usm.url;
1185};
1186
1187module.exports.setTheUsername = function (url, username) {
1188 url.username = utf8PercentEncodeString(username, isUserinfoPercentEncode);
1189};
1190
1191module.exports.setThePassword = function (url, password) {
1192 url.password = utf8PercentEncodeString(password, isUserinfoPercentEncode);
1193};
1194
1195module.exports.serializeHost = serializeHost;
1196
1197module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort;
1198
1199module.exports.serializeInteger = function (integer) {
1200 return String(integer);
1201};
1202
1203module.exports.parseURL = function (input, options) {
1204 if (options === undefined) {
1205 options = {};
1206 }
1207
1208 // We don't handle blobs, so this just delegates:
1209 return module.exports.basicURLParse(input, { baseURL: options.baseURL, encodingOverride: options.encodingOverride });
1210};
Note: See TracBrowser for help on using the repository browser.