2 const punycode = require("punycode");
\r
3 const tr46 = require("tr46");
\r
5 const specialSchemes = {
\r
15 const failure = Symbol("failure");
\r
17 function countSymbols(str) {
\r
18 return punycode.ucs2.decode(str).length;
\r
21 function at(input, idx) {
\r
22 const c = input[idx];
\r
23 return isNaN(c) ? undefined : String.fromCodePoint(c);
\r
26 function isASCIIDigit(c) {
\r
27 return c >= 0x30 && c <= 0x39;
\r
30 function isASCIIAlpha(c) {
\r
31 return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);
\r
34 function isASCIIAlphanumeric(c) {
\r
35 return isASCIIAlpha(c) || isASCIIDigit(c);
\r
38 function isASCIIHex(c) {
\r
39 return isASCIIDigit(c) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66);
\r
42 function isSingleDot(buffer) {
\r
43 return buffer === "." || buffer.toLowerCase() === "%2e";
\r
46 function isDoubleDot(buffer) {
\r
47 buffer = buffer.toLowerCase();
\r
48 return buffer === ".." || buffer === "%2e." || buffer === ".%2e" || buffer === "%2e%2e";
\r
51 function isWindowsDriveLetterCodePoints(cp1, cp2) {
\r
52 return isASCIIAlpha(cp1) && (cp2 === 58 || cp2 === 124);
\r
55 function isWindowsDriveLetterString(string) {
\r
56 return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && (string[1] === ":" || string[1] === "|");
\r
59 function isNormalizedWindowsDriveLetterString(string) {
\r
60 return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && string[1] === ":";
\r
63 function containsForbiddenHostCodePoint(string) {
\r
64 return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|%|\/|:|\?|@|\[|\\|\]/) !== -1;
\r
67 function containsForbiddenHostCodePointExcludingPercent(string) {
\r
68 return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|\/|:|\?|@|\[|\\|\]/) !== -1;
\r
71 function isSpecialScheme(scheme) {
\r
72 return specialSchemes[scheme] !== undefined;
\r
75 function isSpecial(url) {
\r
76 return isSpecialScheme(url.scheme);
\r
79 function defaultPort(scheme) {
\r
80 return specialSchemes[scheme];
\r
83 function percentEncode(c) {
\r
84 let hex = c.toString(16).toUpperCase();
\r
85 if (hex.length === 1) {
\r
92 function utf8PercentEncode(c) {
\r
93 const buf = new Buffer(c);
\r
97 for (let i = 0; i < buf.length; ++i) {
\r
98 str += percentEncode(buf[i]);
\r
104 function utf8PercentDecode(str) {
\r
105 const input = new Buffer(str);
\r
107 for (let i = 0; i < input.length; ++i) {
\r
108 if (input[i] !== 37) {
\r
109 output.push(input[i]);
\r
110 } else if (input[i] === 37 && isASCIIHex(input[i + 1]) && isASCIIHex(input[i + 2])) {
\r
111 output.push(parseInt(input.slice(i + 1, i + 3).toString(), 16));
\r
114 output.push(input[i]);
\r
117 return new Buffer(output).toString();
\r
120 function isC0ControlPercentEncode(c) {
\r
121 return c <= 0x1F || c > 0x7E;
\r
124 const extraPathPercentEncodeSet = new Set([32, 34, 35, 60, 62, 63, 96, 123, 125]);
\r
125 function isPathPercentEncode(c) {
\r
126 return isC0ControlPercentEncode(c) || extraPathPercentEncodeSet.has(c);
\r
129 const extraUserinfoPercentEncodeSet =
\r
130 new Set([47, 58, 59, 61, 64, 91, 92, 93, 94, 124]);
\r
131 function isUserinfoPercentEncode(c) {
\r
132 return isPathPercentEncode(c) || extraUserinfoPercentEncodeSet.has(c);
\r
135 function percentEncodeChar(c, encodeSetPredicate) {
\r
136 const cStr = String.fromCodePoint(c);
\r
138 if (encodeSetPredicate(c)) {
\r
139 return utf8PercentEncode(cStr);
\r
145 function parseIPv4Number(input) {
\r
148 if (input.length >= 2 && input.charAt(0) === "0" && input.charAt(1).toLowerCase() === "x") {
\r
149 input = input.substring(2);
\r
151 } else if (input.length >= 2 && input.charAt(0) === "0") {
\r
152 input = input.substring(1);
\r
156 if (input === "") {
\r
160 const regex = R === 10 ? /[^0-9]/ : (R === 16 ? /[^0-9A-Fa-f]/ : /[^0-7]/);
\r
161 if (regex.test(input)) {
\r
165 return parseInt(input, R);
\r
168 function parseIPv4(input) {
\r
169 const parts = input.split(".");
\r
170 if (parts[parts.length - 1] === "") {
\r
171 if (parts.length > 1) {
\r
176 if (parts.length > 4) {
\r
180 const numbers = [];
\r
181 for (const part of parts) {
\r
185 const n = parseIPv4Number(part);
\r
186 if (n === failure) {
\r
193 for (let i = 0; i < numbers.length - 1; ++i) {
\r
194 if (numbers[i] > 255) {
\r
198 if (numbers[numbers.length - 1] >= Math.pow(256, 5 - numbers.length)) {
\r
202 let ipv4 = numbers.pop();
\r
205 for (const n of numbers) {
\r
206 ipv4 += n * Math.pow(256, 3 - counter);
\r
213 function serializeIPv4(address) {
\r
217 for (let i = 1; i <= 4; ++i) {
\r
218 output = String(n % 256) + output;
\r
220 output = "." + output;
\r
222 n = Math.floor(n / 256);
\r
228 function parseIPv6(input) {
\r
229 const address = [0, 0, 0, 0, 0, 0, 0, 0];
\r
230 let pieceIndex = 0;
\r
231 let compress = null;
\r
234 input = punycode.ucs2.decode(input);
\r
236 if (input[pointer] === 58) {
\r
237 if (input[pointer + 1] !== 58) {
\r
243 compress = pieceIndex;
\r
246 while (pointer < input.length) {
\r
247 if (pieceIndex === 8) {
\r
251 if (input[pointer] === 58) {
\r
252 if (compress !== null) {
\r
257 compress = pieceIndex;
\r
264 while (length < 4 && isASCIIHex(input[pointer])) {
\r
265 value = value * 0x10 + parseInt(at(input, pointer), 16);
\r
270 if (input[pointer] === 46) {
\r
271 if (length === 0) {
\r
277 if (pieceIndex > 6) {
\r
281 let numbersSeen = 0;
\r
283 while (input[pointer] !== undefined) {
\r
284 let ipv4Piece = null;
\r
286 if (numbersSeen > 0) {
\r
287 if (input[pointer] === 46 && numbersSeen < 4) {
\r
294 if (!isASCIIDigit(input[pointer])) {
\r
298 while (isASCIIDigit(input[pointer])) {
\r
299 const number = parseInt(at(input, pointer));
\r
300 if (ipv4Piece === null) {
\r
301 ipv4Piece = number;
\r
302 } else if (ipv4Piece === 0) {
\r
305 ipv4Piece = ipv4Piece * 10 + number;
\r
307 if (ipv4Piece > 255) {
\r
313 address[pieceIndex] = address[pieceIndex] * 0x100 + ipv4Piece;
\r
317 if (numbersSeen === 2 || numbersSeen === 4) {
\r
322 if (numbersSeen !== 4) {
\r
327 } else if (input[pointer] === 58) {
\r
329 if (input[pointer] === undefined) {
\r
332 } else if (input[pointer] !== undefined) {
\r
336 address[pieceIndex] = value;
\r
340 if (compress !== null) {
\r
341 let swaps = pieceIndex - compress;
\r
343 while (pieceIndex !== 0 && swaps > 0) {
\r
344 const temp = address[compress + swaps - 1];
\r
345 address[compress + swaps - 1] = address[pieceIndex];
\r
346 address[pieceIndex] = temp;
\r
350 } else if (compress === null && pieceIndex !== 8) {
\r
357 function serializeIPv6(address) {
\r
359 const seqResult = findLongestZeroSequence(address);
\r
360 const compress = seqResult.idx;
\r
361 let ignore0 = false;
\r
363 for (let pieceIndex = 0; pieceIndex <= 7; ++pieceIndex) {
\r
364 if (ignore0 && address[pieceIndex] === 0) {
\r
366 } else if (ignore0) {
\r
370 if (compress === pieceIndex) {
\r
371 const separator = pieceIndex === 0 ? "::" : ":";
\r
372 output += separator;
\r
377 output += address[pieceIndex].toString(16);
\r
379 if (pieceIndex !== 7) {
\r
387 function parseHost(input, isSpecialArg) {
\r
388 if (input[0] === "[") {
\r
389 if (input[input.length - 1] !== "]") {
\r
393 return parseIPv6(input.substring(1, input.length - 1));
\r
396 if (!isSpecialArg) {
\r
397 return parseOpaqueHost(input);
\r
400 const domain = utf8PercentDecode(input);
\r
401 const asciiDomain = tr46.toASCII(domain, false, tr46.PROCESSING_OPTIONS.NONTRANSITIONAL, false);
\r
402 if (asciiDomain === null) {
\r
406 if (containsForbiddenHostCodePoint(asciiDomain)) {
\r
410 const ipv4Host = parseIPv4(asciiDomain);
\r
411 if (typeof ipv4Host === "number" || ipv4Host === failure) {
\r
415 return asciiDomain;
\r
418 function parseOpaqueHost(input) {
\r
419 if (containsForbiddenHostCodePointExcludingPercent(input)) {
\r
424 const decoded = punycode.ucs2.decode(input);
\r
425 for (let i = 0; i < decoded.length; ++i) {
\r
426 output += percentEncodeChar(decoded[i], isC0ControlPercentEncode);
\r
431 function findLongestZeroSequence(arr) {
\r
433 let maxLen = 1; // only find elements > 1
\r
434 let currStart = null;
\r
437 for (let i = 0; i < arr.length; ++i) {
\r
438 if (arr[i] !== 0) {
\r
439 if (currLen > maxLen) {
\r
440 maxIdx = currStart;
\r
447 if (currStart === null) {
\r
454 // if trailing zeros
\r
455 if (currLen > maxLen) {
\r
456 maxIdx = currStart;
\r
466 function serializeHost(host) {
\r
467 if (typeof host === "number") {
\r
468 return serializeIPv4(host);
\r
472 if (host instanceof Array) {
\r
473 return "[" + serializeIPv6(host) + "]";
\r
479 function trimControlChars(url) {
\r
480 return url.replace(/^[\u0000-\u001F\u0020]+|[\u0000-\u001F\u0020]+$/g, "");
\r
483 function trimTabAndNewline(url) {
\r
484 return url.replace(/\u0009|\u000A|\u000D/g, "");
\r
487 function shortenPath(url) {
\r
488 const path = url.path;
\r
489 if (path.length === 0) {
\r
492 if (url.scheme === "file" && path.length === 1 && isNormalizedWindowsDriveLetter(path[0])) {
\r
499 function includesCredentials(url) {
\r
500 return url.username !== "" || url.password !== "";
\r
503 function cannotHaveAUsernamePasswordPort(url) {
\r
504 return url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file";
\r
507 function isNormalizedWindowsDriveLetter(string) {
\r
508 return /^[A-Za-z]:$/.test(string);
\r
511 function URLStateMachine(input, base, encodingOverride, url, stateOverride) {
\r
513 this.input = input;
\r
514 this.base = base || null;
\r
515 this.encodingOverride = encodingOverride || "utf-8";
\r
516 this.stateOverride = stateOverride;
\r
518 this.failure = false;
\r
519 this.parseError = false;
\r
532 cannotBeABaseURL: false
\r
535 const res = trimControlChars(this.input);
\r
536 if (res !== this.input) {
\r
537 this.parseError = true;
\r
542 const res = trimTabAndNewline(this.input);
\r
543 if (res !== this.input) {
\r
544 this.parseError = true;
\r
548 this.state = stateOverride || "scheme start";
\r
551 this.atFlag = false;
\r
552 this.arrFlag = false;
\r
553 this.passwordTokenSeenFlag = false;
\r
555 this.input = punycode.ucs2.decode(this.input);
\r
557 for (; this.pointer <= this.input.length; ++this.pointer) {
\r
558 const c = this.input[this.pointer];
\r
559 const cStr = isNaN(c) ? undefined : String.fromCodePoint(c);
\r
561 // exec state machine
\r
562 const ret = this["parse " + this.state](c, cStr);
\r
564 break; // terminate algorithm
\r
565 } else if (ret === failure) {
\r
566 this.failure = true;
\r
572 URLStateMachine.prototype["parse scheme start"] = function parseSchemeStart(c, cStr) {
\r
573 if (isASCIIAlpha(c)) {
\r
574 this.buffer += cStr.toLowerCase();
\r
575 this.state = "scheme";
\r
576 } else if (!this.stateOverride) {
\r
577 this.state = "no scheme";
\r
580 this.parseError = true;
\r
587 URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) {
\r
588 if (isASCIIAlphanumeric(c) || c === 43 || c === 45 || c === 46) {
\r
589 this.buffer += cStr.toLowerCase();
\r
590 } else if (c === 58) {
\r
591 if (this.stateOverride) {
\r
592 if (isSpecial(this.url) && !isSpecialScheme(this.buffer)) {
\r
596 if (!isSpecial(this.url) && isSpecialScheme(this.buffer)) {
\r
600 if ((includesCredentials(this.url) || this.url.port !== null) && this.buffer === "file") {
\r
604 if (this.url.scheme === "file" && (this.url.host === "" || this.url.host === null)) {
\r
608 this.url.scheme = this.buffer;
\r
610 if (this.stateOverride) {
\r
613 if (this.url.scheme === "file") {
\r
614 if (this.input[this.pointer + 1] !== 47 || this.input[this.pointer + 2] !== 47) {
\r
615 this.parseError = true;
\r
617 this.state = "file";
\r
618 } else if (isSpecial(this.url) && this.base !== null && this.base.scheme === this.url.scheme) {
\r
619 this.state = "special relative or authority";
\r
620 } else if (isSpecial(this.url)) {
\r
621 this.state = "special authority slashes";
\r
622 } else if (this.input[this.pointer + 1] === 47) {
\r
623 this.state = "path or authority";
\r
626 this.url.cannotBeABaseURL = true;
\r
627 this.url.path.push("");
\r
628 this.state = "cannot-be-a-base-URL path";
\r
630 } else if (!this.stateOverride) {
\r
632 this.state = "no scheme";
\r
635 this.parseError = true;
\r
642 URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) {
\r
643 if (this.base === null || (this.base.cannotBeABaseURL && c !== 35)) {
\r
645 } else if (this.base.cannotBeABaseURL && c === 35) {
\r
646 this.url.scheme = this.base.scheme;
\r
647 this.url.path = this.base.path.slice();
\r
648 this.url.query = this.base.query;
\r
649 this.url.fragment = "";
\r
650 this.url.cannotBeABaseURL = true;
\r
651 this.state = "fragment";
\r
652 } else if (this.base.scheme === "file") {
\r
653 this.state = "file";
\r
656 this.state = "relative";
\r
663 URLStateMachine.prototype["parse special relative or authority"] = function parseSpecialRelativeOrAuthority(c) {
\r
664 if (c === 47 && this.input[this.pointer + 1] === 47) {
\r
665 this.state = "special authority ignore slashes";
\r
668 this.parseError = true;
\r
669 this.state = "relative";
\r
676 URLStateMachine.prototype["parse path or authority"] = function parsePathOrAuthority(c) {
\r
678 this.state = "authority";
\r
680 this.state = "path";
\r
687 URLStateMachine.prototype["parse relative"] = function parseRelative(c) {
\r
688 this.url.scheme = this.base.scheme;
\r
690 this.url.username = this.base.username;
\r
691 this.url.password = this.base.password;
\r
692 this.url.host = this.base.host;
\r
693 this.url.port = this.base.port;
\r
694 this.url.path = this.base.path.slice();
\r
695 this.url.query = this.base.query;
\r
696 } else if (c === 47) {
\r
697 this.state = "relative slash";
\r
698 } else if (c === 63) {
\r
699 this.url.username = this.base.username;
\r
700 this.url.password = this.base.password;
\r
701 this.url.host = this.base.host;
\r
702 this.url.port = this.base.port;
\r
703 this.url.path = this.base.path.slice();
\r
704 this.url.query = "";
\r
705 this.state = "query";
\r
706 } else if (c === 35) {
\r
707 this.url.username = this.base.username;
\r
708 this.url.password = this.base.password;
\r
709 this.url.host = this.base.host;
\r
710 this.url.port = this.base.port;
\r
711 this.url.path = this.base.path.slice();
\r
712 this.url.query = this.base.query;
\r
713 this.url.fragment = "";
\r
714 this.state = "fragment";
\r
715 } else if (isSpecial(this.url) && c === 92) {
\r
716 this.parseError = true;
\r
717 this.state = "relative slash";
\r
719 this.url.username = this.base.username;
\r
720 this.url.password = this.base.password;
\r
721 this.url.host = this.base.host;
\r
722 this.url.port = this.base.port;
\r
723 this.url.path = this.base.path.slice(0, this.base.path.length - 1);
\r
725 this.state = "path";
\r
732 URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) {
\r
733 if (isSpecial(this.url) && (c === 47 || c === 92)) {
\r
735 this.parseError = true;
\r
737 this.state = "special authority ignore slashes";
\r
738 } else if (c === 47) {
\r
739 this.state = "authority";
\r
741 this.url.username = this.base.username;
\r
742 this.url.password = this.base.password;
\r
743 this.url.host = this.base.host;
\r
744 this.url.port = this.base.port;
\r
745 this.state = "path";
\r
752 URLStateMachine.prototype["parse special authority slashes"] = function parseSpecialAuthoritySlashes(c) {
\r
753 if (c === 47 && this.input[this.pointer + 1] === 47) {
\r
754 this.state = "special authority ignore slashes";
\r
757 this.parseError = true;
\r
758 this.state = "special authority ignore slashes";
\r
765 URLStateMachine.prototype["parse special authority ignore slashes"] = function parseSpecialAuthorityIgnoreSlashes(c) {
\r
766 if (c !== 47 && c !== 92) {
\r
767 this.state = "authority";
\r
770 this.parseError = true;
\r
776 URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) {
\r
778 this.parseError = true;
\r
780 this.buffer = "%40" + this.buffer;
\r
782 this.atFlag = true;
\r
784 // careful, this is based on buffer and has its own pointer (this.pointer != pointer) and inner chars
\r
785 const len = countSymbols(this.buffer);
\r
786 for (let pointer = 0; pointer < len; ++pointer) {
\r
787 const codePoint = this.buffer.codePointAt(pointer);
\r
789 if (codePoint === 58 && !this.passwordTokenSeenFlag) {
\r
790 this.passwordTokenSeenFlag = true;
\r
793 const encodedCodePoints = percentEncodeChar(codePoint, isUserinfoPercentEncode);
\r
794 if (this.passwordTokenSeenFlag) {
\r
795 this.url.password += encodedCodePoints;
\r
797 this.url.username += encodedCodePoints;
\r
801 } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
\r
802 (isSpecial(this.url) && c === 92)) {
\r
803 if (this.atFlag && this.buffer === "") {
\r
804 this.parseError = true;
\r
807 this.pointer -= countSymbols(this.buffer) + 1;
\r
809 this.state = "host";
\r
811 this.buffer += cStr;
\r
817 URLStateMachine.prototype["parse hostname"] =
\r
818 URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) {
\r
819 if (this.stateOverride && this.url.scheme === "file") {
\r
821 this.state = "file host";
\r
822 } else if (c === 58 && !this.arrFlag) {
\r
823 if (this.buffer === "") {
\r
824 this.parseError = true;
\r
828 const host = parseHost(this.buffer, isSpecial(this.url));
\r
829 if (host === failure) {
\r
833 this.url.host = host;
\r
835 this.state = "port";
\r
836 if (this.stateOverride === "hostname") {
\r
839 } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
\r
840 (isSpecial(this.url) && c === 92)) {
\r
842 if (isSpecial(this.url) && this.buffer === "") {
\r
843 this.parseError = true;
\r
845 } else if (this.stateOverride && this.buffer === "" &&
\r
846 (includesCredentials(this.url) || this.url.port !== null)) {
\r
847 this.parseError = true;
\r
851 const host = parseHost(this.buffer, isSpecial(this.url));
\r
852 if (host === failure) {
\r
856 this.url.host = host;
\r
858 this.state = "path start";
\r
859 if (this.stateOverride) {
\r
864 this.arrFlag = true;
\r
865 } else if (c === 93) {
\r
866 this.arrFlag = false;
\r
868 this.buffer += cStr;
\r
874 URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) {
\r
875 if (isASCIIDigit(c)) {
\r
876 this.buffer += cStr;
\r
877 } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
\r
878 (isSpecial(this.url) && c === 92) ||
\r
879 this.stateOverride) {
\r
880 if (this.buffer !== "") {
\r
881 const port = parseInt(this.buffer);
\r
882 if (port > Math.pow(2, 16) - 1) {
\r
883 this.parseError = true;
\r
886 this.url.port = port === defaultPort(this.url.scheme) ? null : port;
\r
889 if (this.stateOverride) {
\r
892 this.state = "path start";
\r
895 this.parseError = true;
\r
902 const fileOtherwiseCodePoints = new Set([47, 92, 63, 35]);
\r
904 URLStateMachine.prototype["parse file"] = function parseFile(c) {
\r
905 this.url.scheme = "file";
\r
907 if (c === 47 || c === 92) {
\r
909 this.parseError = true;
\r
911 this.state = "file slash";
\r
912 } else if (this.base !== null && this.base.scheme === "file") {
\r
914 this.url.host = this.base.host;
\r
915 this.url.path = this.base.path.slice();
\r
916 this.url.query = this.base.query;
\r
917 } else if (c === 63) {
\r
918 this.url.host = this.base.host;
\r
919 this.url.path = this.base.path.slice();
\r
920 this.url.query = "";
\r
921 this.state = "query";
\r
922 } else if (c === 35) {
\r
923 this.url.host = this.base.host;
\r
924 this.url.path = this.base.path.slice();
\r
925 this.url.query = this.base.query;
\r
926 this.url.fragment = "";
\r
927 this.state = "fragment";
\r
929 if (this.input.length - this.pointer - 1 === 0 || // remaining consists of 0 code points
\r
930 !isWindowsDriveLetterCodePoints(c, this.input[this.pointer + 1]) ||
\r
931 (this.input.length - this.pointer - 1 >= 2 && // remaining has at least 2 code points
\r
932 !fileOtherwiseCodePoints.has(this.input[this.pointer + 2]))) {
\r
933 this.url.host = this.base.host;
\r
934 this.url.path = this.base.path.slice();
\r
935 shortenPath(this.url);
\r
937 this.parseError = true;
\r
940 this.state = "path";
\r
944 this.state = "path";
\r
951 URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) {
\r
952 if (c === 47 || c === 92) {
\r
954 this.parseError = true;
\r
956 this.state = "file host";
\r
958 if (this.base !== null && this.base.scheme === "file") {
\r
959 if (isNormalizedWindowsDriveLetterString(this.base.path[0])) {
\r
960 this.url.path.push(this.base.path[0]);
\r
962 this.url.host = this.base.host;
\r
965 this.state = "path";
\r
972 URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) {
\r
973 if (isNaN(c) || c === 47 || c === 92 || c === 63 || c === 35) {
\r
975 if (!this.stateOverride && isWindowsDriveLetterString(this.buffer)) {
\r
976 this.parseError = true;
\r
977 this.state = "path";
\r
978 } else if (this.buffer === "") {
\r
979 this.url.host = "";
\r
980 if (this.stateOverride) {
\r
983 this.state = "path start";
\r
985 let host = parseHost(this.buffer, isSpecial(this.url));
\r
986 if (host === failure) {
\r
989 if (host === "localhost") {
\r
992 this.url.host = host;
\r
994 if (this.stateOverride) {
\r
999 this.state = "path start";
\r
1002 this.buffer += cStr;
\r
1008 URLStateMachine.prototype["parse path start"] = function parsePathStart(c) {
\r
1009 if (isSpecial(this.url)) {
\r
1011 this.parseError = true;
\r
1013 this.state = "path";
\r
1015 if (c !== 47 && c !== 92) {
\r
1018 } else if (!this.stateOverride && c === 63) {
\r
1019 this.url.query = "";
\r
1020 this.state = "query";
\r
1021 } else if (!this.stateOverride && c === 35) {
\r
1022 this.url.fragment = "";
\r
1023 this.state = "fragment";
\r
1024 } else if (c !== undefined) {
\r
1025 this.state = "path";
\r
1034 URLStateMachine.prototype["parse path"] = function parsePath(c) {
\r
1035 if (isNaN(c) || c === 47 || (isSpecial(this.url) && c === 92) ||
\r
1036 (!this.stateOverride && (c === 63 || c === 35))) {
\r
1037 if (isSpecial(this.url) && c === 92) {
\r
1038 this.parseError = true;
\r
1041 if (isDoubleDot(this.buffer)) {
\r
1042 shortenPath(this.url);
\r
1043 if (c !== 47 && !(isSpecial(this.url) && c === 92)) {
\r
1044 this.url.path.push("");
\r
1046 } else if (isSingleDot(this.buffer) && c !== 47 &&
\r
1047 !(isSpecial(this.url) && c === 92)) {
\r
1048 this.url.path.push("");
\r
1049 } else if (!isSingleDot(this.buffer)) {
\r
1050 if (this.url.scheme === "file" && this.url.path.length === 0 && isWindowsDriveLetterString(this.buffer)) {
\r
1051 if (this.url.host !== "" && this.url.host !== null) {
\r
1052 this.parseError = true;
\r
1053 this.url.host = "";
\r
1055 this.buffer = this.buffer[0] + ":";
\r
1057 this.url.path.push(this.buffer);
\r
1060 if (this.url.scheme === "file" && (c === undefined || c === 63 || c === 35)) {
\r
1061 while (this.url.path.length > 1 && this.url.path[0] === "") {
\r
1062 this.parseError = true;
\r
1063 this.url.path.shift();
\r
1067 this.url.query = "";
\r
1068 this.state = "query";
\r
1071 this.url.fragment = "";
\r
1072 this.state = "fragment";
\r
1075 // TODO: If c is not a URL code point and not "%", parse error.
\r
1078 (!isASCIIHex(this.input[this.pointer + 1]) ||
\r
1079 !isASCIIHex(this.input[this.pointer + 2]))) {
\r
1080 this.parseError = true;
\r
1083 this.buffer += percentEncodeChar(c, isPathPercentEncode);
\r
1089 URLStateMachine.prototype["parse cannot-be-a-base-URL path"] = function parseCannotBeABaseURLPath(c) {
\r
1091 this.url.query = "";
\r
1092 this.state = "query";
\r
1093 } else if (c === 35) {
\r
1094 this.url.fragment = "";
\r
1095 this.state = "fragment";
\r
1097 // TODO: Add: not a URL code point
\r
1098 if (!isNaN(c) && c !== 37) {
\r
1099 this.parseError = true;
\r
1103 (!isASCIIHex(this.input[this.pointer + 1]) ||
\r
1104 !isASCIIHex(this.input[this.pointer + 2]))) {
\r
1105 this.parseError = true;
\r
1109 this.url.path[0] = this.url.path[0] + percentEncodeChar(c, isC0ControlPercentEncode);
\r
1116 URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) {
\r
1117 if (isNaN(c) || (!this.stateOverride && c === 35)) {
\r
1118 if (!isSpecial(this.url) || this.url.scheme === "ws" || this.url.scheme === "wss") {
\r
1119 this.encodingOverride = "utf-8";
\r
1122 const buffer = new Buffer(this.buffer); // TODO: Use encoding override instead
\r
1123 for (let i = 0; i < buffer.length; ++i) {
\r
1124 if (buffer[i] < 0x21 || buffer[i] > 0x7E || buffer[i] === 0x22 || buffer[i] === 0x23 ||
\r
1125 buffer[i] === 0x3C || buffer[i] === 0x3E) {
\r
1126 this.url.query += percentEncode(buffer[i]);
\r
1128 this.url.query += String.fromCodePoint(buffer[i]);
\r
1134 this.url.fragment = "";
\r
1135 this.state = "fragment";
\r
1138 // TODO: If c is not a URL code point and not "%", parse error.
\r
1140 (!isASCIIHex(this.input[this.pointer + 1]) ||
\r
1141 !isASCIIHex(this.input[this.pointer + 2]))) {
\r
1142 this.parseError = true;
\r
1145 this.buffer += cStr;
\r
1151 URLStateMachine.prototype["parse fragment"] = function parseFragment(c) {
\r
1152 if (isNaN(c)) { // do nothing
\r
1153 } else if (c === 0x0) {
\r
1154 this.parseError = true;
\r
1156 // TODO: If c is not a URL code point and not "%", parse error.
\r
1158 (!isASCIIHex(this.input[this.pointer + 1]) ||
\r
1159 !isASCIIHex(this.input[this.pointer + 2]))) {
\r
1160 this.parseError = true;
\r
1163 this.url.fragment += percentEncodeChar(c, isC0ControlPercentEncode);
\r
1169 function serializeURL(url, excludeFragment) {
\r
1170 let output = url.scheme + ":";
\r
1171 if (url.host !== null) {
\r
1174 if (url.username !== "" || url.password !== "") {
\r
1175 output += url.username;
\r
1176 if (url.password !== "") {
\r
1177 output += ":" + url.password;
\r
1182 output += serializeHost(url.host);
\r
1184 if (url.port !== null) {
\r
1185 output += ":" + url.port;
\r
1187 } else if (url.host === null && url.scheme === "file") {
\r
1191 if (url.cannotBeABaseURL) {
\r
1192 output += url.path[0];
\r
1194 for (const string of url.path) {
\r
1195 output += "/" + string;
\r
1199 if (url.query !== null) {
\r
1200 output += "?" + url.query;
\r
1203 if (!excludeFragment && url.fragment !== null) {
\r
1204 output += "#" + url.fragment;
\r
1210 function serializeOrigin(tuple) {
\r
1211 let result = tuple.scheme + "://";
\r
1212 result += serializeHost(tuple.host);
\r
1214 if (tuple.port !== null) {
\r
1215 result += ":" + tuple.port;
\r
1221 module.exports.serializeURL = serializeURL;
\r
1223 module.exports.serializeURLOrigin = function (url) {
\r
1224 // https://url.spec.whatwg.org/#concept-url-origin
\r
1225 switch (url.scheme) {
\r
1228 return module.exports.serializeURLOrigin(module.exports.parseURL(url.path[0]));
\r
1230 // serializing an opaque origin returns "null"
\r
1239 return serializeOrigin({
\r
1240 scheme: url.scheme,
\r
1245 // spec says "exercise to the reader", chrome says "file://"
\r
1248 // serializing an opaque origin returns "null"
\r
1253 module.exports.basicURLParse = function (input, options) {
\r
1254 if (options === undefined) {
\r
1258 const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride);
\r
1259 if (usm.failure) {
\r
1266 module.exports.setTheUsername = function (url, username) {
\r
1267 url.username = "";
\r
1268 const decoded = punycode.ucs2.decode(username);
\r
1269 for (let i = 0; i < decoded.length; ++i) {
\r
1270 url.username += percentEncodeChar(decoded[i], isUserinfoPercentEncode);
\r
1274 module.exports.setThePassword = function (url, password) {
\r
1275 url.password = "";
\r
1276 const decoded = punycode.ucs2.decode(password);
\r
1277 for (let i = 0; i < decoded.length; ++i) {
\r
1278 url.password += percentEncodeChar(decoded[i], isUserinfoPercentEncode);
\r
1282 module.exports.serializeHost = serializeHost;
\r
1284 module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort;
\r
1286 module.exports.serializeInteger = function (integer) {
\r
1287 return String(integer);
\r
1290 module.exports.parseURL = function (input, options) {
\r
1291 if (options === undefined) {
\r
1295 // We don't handle blobs, so this just delegates:
\r
1296 return module.exports.basicURLParse(input, { baseURL: options.baseURL, encodingOverride: options.encodingOverride });
\r