You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					1298 lines
				
				33 KiB
			
		
		
			
		
	
	
					1298 lines
				
				33 KiB
			| 
											3 years ago
										 | "use strict"; | ||
|  | const punycode = require("punycode"); | ||
|  | const tr46 = require("tr46"); | ||
|  | 
 | ||
|  | const specialSchemes = { | ||
|  |   ftp: 21, | ||
|  |   file: null, | ||
|  |   gopher: 70, | ||
|  |   http: 80, | ||
|  |   https: 443, | ||
|  |   ws: 80, | ||
|  |   wss: 443 | ||
|  | }; | ||
|  | 
 | ||
|  | const failure = Symbol("failure"); | ||
|  | 
 | ||
|  | function countSymbols(str) { | ||
|  |   return punycode.ucs2.decode(str).length; | ||
|  | } | ||
|  | 
 | ||
|  | function at(input, idx) { | ||
|  |   const c = input[idx]; | ||
|  |   return isNaN(c) ? undefined : String.fromCodePoint(c); | ||
|  | } | ||
|  | 
 | ||
|  | function isASCIIDigit(c) { | ||
|  |   return c >= 0x30 && c <= 0x39; | ||
|  | } | ||
|  | 
 | ||
|  | function isASCIIAlpha(c) { | ||
|  |   return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A); | ||
|  | } | ||
|  | 
 | ||
|  | function isASCIIAlphanumeric(c) { | ||
|  |   return isASCIIAlpha(c) || isASCIIDigit(c); | ||
|  | } | ||
|  | 
 | ||
|  | function isASCIIHex(c) { | ||
|  |   return isASCIIDigit(c) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66); | ||
|  | } | ||
|  | 
 | ||
|  | function isSingleDot(buffer) { | ||
|  |   return buffer === "." || buffer.toLowerCase() === "%2e"; | ||
|  | } | ||
|  | 
 | ||
|  | function isDoubleDot(buffer) { | ||
|  |   buffer = buffer.toLowerCase(); | ||
|  |   return buffer === ".." || buffer === "%2e." || buffer === ".%2e" || buffer === "%2e%2e"; | ||
|  | } | ||
|  | 
 | ||
|  | function isWindowsDriveLetterCodePoints(cp1, cp2) { | ||
|  |   return isASCIIAlpha(cp1) && (cp2 === 58 || cp2 === 124); | ||
|  | } | ||
|  | 
 | ||
|  | function isWindowsDriveLetterString(string) { | ||
|  |   return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && (string[1] === ":" || string[1] === "|"); | ||
|  | } | ||
|  | 
 | ||
|  | function isNormalizedWindowsDriveLetterString(string) { | ||
|  |   return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && string[1] === ":"; | ||
|  | } | ||
|  | 
 | ||
|  | function containsForbiddenHostCodePoint(string) { | ||
|  |   return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|%|\/|:|\?|@|\[|\\|\]/) !== -1; | ||
|  | } | ||
|  | 
 | ||
|  | function containsForbiddenHostCodePointExcludingPercent(string) { | ||
|  |   return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|\/|:|\?|@|\[|\\|\]/) !== -1; | ||
|  | } | ||
|  | 
 | ||
|  | function isSpecialScheme(scheme) { | ||
|  |   return specialSchemes[scheme] !== undefined; | ||
|  | } | ||
|  | 
 | ||
|  | function isSpecial(url) { | ||
|  |   return isSpecialScheme(url.scheme); | ||
|  | } | ||
|  | 
 | ||
|  | function defaultPort(scheme) { | ||
|  |   return specialSchemes[scheme]; | ||
|  | } | ||
|  | 
 | ||
|  | function percentEncode(c) { | ||
|  |   let hex = c.toString(16).toUpperCase(); | ||
|  |   if (hex.length === 1) { | ||
|  |     hex = "0" + hex; | ||
|  |   } | ||
|  | 
 | ||
|  |   return "%" + hex; | ||
|  | } | ||
|  | 
 | ||
|  | function utf8PercentEncode(c) { | ||
|  |   const buf = new Buffer(c); | ||
|  | 
 | ||
|  |   let str = ""; | ||
|  | 
 | ||
|  |   for (let i = 0; i < buf.length; ++i) { | ||
|  |     str += percentEncode(buf[i]); | ||
|  |   } | ||
|  | 
 | ||
|  |   return str; | ||
|  | } | ||
|  | 
 | ||
|  | function utf8PercentDecode(str) { | ||
|  |   const input = new Buffer(str); | ||
|  |   const output = []; | ||
|  |   for (let i = 0; i < input.length; ++i) { | ||
|  |     if (input[i] !== 37) { | ||
|  |       output.push(input[i]); | ||
|  |     } else if (input[i] === 37 && isASCIIHex(input[i + 1]) && isASCIIHex(input[i + 2])) { | ||
|  |       output.push(parseInt(input.slice(i + 1, i + 3).toString(), 16)); | ||
|  |       i += 2; | ||
|  |     } else { | ||
|  |       output.push(input[i]); | ||
|  |     } | ||
|  |   } | ||
|  |   return new Buffer(output).toString(); | ||
|  | } | ||
|  | 
 | ||
|  | function isC0ControlPercentEncode(c) { | ||
|  |   return c <= 0x1F || c > 0x7E; | ||
|  | } | ||
|  | 
 | ||
|  | const extraPathPercentEncodeSet = new Set([32, 34, 35, 60, 62, 63, 96, 123, 125]); | ||
|  | function isPathPercentEncode(c) { | ||
|  |   return isC0ControlPercentEncode(c) || extraPathPercentEncodeSet.has(c); | ||
|  | } | ||
|  | 
 | ||
|  | const extraUserinfoPercentEncodeSet = | ||
|  |   new Set([47, 58, 59, 61, 64, 91, 92, 93, 94, 124]); | ||
|  | function isUserinfoPercentEncode(c) { | ||
|  |   return isPathPercentEncode(c) || extraUserinfoPercentEncodeSet.has(c); | ||
|  | } | ||
|  | 
 | ||
|  | function percentEncodeChar(c, encodeSetPredicate) { | ||
|  |   const cStr = String.fromCodePoint(c); | ||
|  | 
 | ||
|  |   if (encodeSetPredicate(c)) { | ||
|  |     return utf8PercentEncode(cStr); | ||
|  |   } | ||
|  | 
 | ||
|  |   return cStr; | ||
|  | } | ||
|  | 
 | ||
|  | function parseIPv4Number(input) { | ||
|  |   let R = 10; | ||
|  | 
 | ||
|  |   if (input.length >= 2 && input.charAt(0) === "0" && input.charAt(1).toLowerCase() === "x") { | ||
|  |     input = input.substring(2); | ||
|  |     R = 16; | ||
|  |   } else if (input.length >= 2 && input.charAt(0) === "0") { | ||
|  |     input = input.substring(1); | ||
|  |     R = 8; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (input === "") { | ||
|  |     return 0; | ||
|  |   } | ||
|  | 
 | ||
|  |   const regex = R === 10 ? /[^0-9]/ : (R === 16 ? /[^0-9A-Fa-f]/ : /[^0-7]/); | ||
|  |   if (regex.test(input)) { | ||
|  |     return failure; | ||
|  |   } | ||
|  | 
 | ||
|  |   return parseInt(input, R); | ||
|  | } | ||
|  | 
 | ||
|  | function parseIPv4(input) { | ||
|  |   const parts = input.split("."); | ||
|  |   if (parts[parts.length - 1] === "") { | ||
|  |     if (parts.length > 1) { | ||
|  |       parts.pop(); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   if (parts.length > 4) { | ||
|  |     return input; | ||
|  |   } | ||
|  | 
 | ||
|  |   const numbers = []; | ||
|  |   for (const part of parts) { | ||
|  |     if (part === "") { | ||
|  |       return input; | ||
|  |     } | ||
|  |     const n = parseIPv4Number(part); | ||
|  |     if (n === failure) { | ||
|  |       return input; | ||
|  |     } | ||
|  | 
 | ||
|  |     numbers.push(n); | ||
|  |   } | ||
|  | 
 | ||
|  |   for (let i = 0; i < numbers.length - 1; ++i) { | ||
|  |     if (numbers[i] > 255) { | ||
|  |       return failure; | ||
|  |     } | ||
|  |   } | ||
|  |   if (numbers[numbers.length - 1] >= Math.pow(256, 5 - numbers.length)) { | ||
|  |     return failure; | ||
|  |   } | ||
|  | 
 | ||
|  |   let ipv4 = numbers.pop(); | ||
|  |   let counter = 0; | ||
|  | 
 | ||
|  |   for (const n of numbers) { | ||
|  |     ipv4 += n * Math.pow(256, 3 - counter); | ||
|  |     ++counter; | ||
|  |   } | ||
|  | 
 | ||
|  |   return ipv4; | ||
|  | } | ||
|  | 
 | ||
|  | function serializeIPv4(address) { | ||
|  |   let output = ""; | ||
|  |   let n = address; | ||
|  | 
 | ||
|  |   for (let i = 1; i <= 4; ++i) { | ||
|  |     output = String(n % 256) + output; | ||
|  |     if (i !== 4) { | ||
|  |       output = "." + output; | ||
|  |     } | ||
|  |     n = Math.floor(n / 256); | ||
|  |   } | ||
|  | 
 | ||
|  |   return output; | ||
|  | } | ||
|  | 
 | ||
|  | function parseIPv6(input) { | ||
|  |   const address = [0, 0, 0, 0, 0, 0, 0, 0]; | ||
|  |   let pieceIndex = 0; | ||
|  |   let compress = null; | ||
|  |   let pointer = 0; | ||
|  | 
 | ||
|  |   input = punycode.ucs2.decode(input); | ||
|  | 
 | ||
|  |   if (input[pointer] === 58) { | ||
|  |     if (input[pointer + 1] !== 58) { | ||
|  |       return failure; | ||
|  |     } | ||
|  | 
 | ||
|  |     pointer += 2; | ||
|  |     ++pieceIndex; | ||
|  |     compress = pieceIndex; | ||
|  |   } | ||
|  | 
 | ||
|  |   while (pointer < input.length) { | ||
|  |     if (pieceIndex === 8) { | ||
|  |       return failure; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (input[pointer] === 58) { | ||
|  |       if (compress !== null) { | ||
|  |         return failure; | ||
|  |       } | ||
|  |       ++pointer; | ||
|  |       ++pieceIndex; | ||
|  |       compress = pieceIndex; | ||
|  |       continue; | ||
|  |     } | ||
|  | 
 | ||
|  |     let value = 0; | ||
|  |     let length = 0; | ||
|  | 
 | ||
|  |     while (length < 4 && isASCIIHex(input[pointer])) { | ||
|  |       value = value * 0x10 + parseInt(at(input, pointer), 16); | ||
|  |       ++pointer; | ||
|  |       ++length; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (input[pointer] === 46) { | ||
|  |       if (length === 0) { | ||
|  |         return failure; | ||
|  |       } | ||
|  | 
 | ||
|  |       pointer -= length; | ||
|  | 
 | ||
|  |       if (pieceIndex > 6) { | ||
|  |         return failure; | ||
|  |       } | ||
|  | 
 | ||
|  |       let numbersSeen = 0; | ||
|  | 
 | ||
|  |       while (input[pointer] !== undefined) { | ||
|  |         let ipv4Piece = null; | ||
|  | 
 | ||
|  |         if (numbersSeen > 0) { | ||
|  |           if (input[pointer] === 46 && numbersSeen < 4) { | ||
|  |             ++pointer; | ||
|  |           } else { | ||
|  |             return failure; | ||
|  |           } | ||
|  |         } | ||
|  | 
 | ||
|  |         if (!isASCIIDigit(input[pointer])) { | ||
|  |           return failure; | ||
|  |         } | ||
|  | 
 | ||
|  |         while (isASCIIDigit(input[pointer])) { | ||
|  |           const number = parseInt(at(input, pointer)); | ||
|  |           if (ipv4Piece === null) { | ||
|  |             ipv4Piece = number; | ||
|  |           } else if (ipv4Piece === 0) { | ||
|  |             return failure; | ||
|  |           } else { | ||
|  |             ipv4Piece = ipv4Piece * 10 + number; | ||
|  |           } | ||
|  |           if (ipv4Piece > 255) { | ||
|  |             return failure; | ||
|  |           } | ||
|  |           ++pointer; | ||
|  |         } | ||
|  | 
 | ||
|  |         address[pieceIndex] = address[pieceIndex] * 0x100 + ipv4Piece; | ||
|  | 
 | ||
|  |         ++numbersSeen; | ||
|  | 
 | ||
|  |         if (numbersSeen === 2 || numbersSeen === 4) { | ||
|  |           ++pieceIndex; | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       if (numbersSeen !== 4) { | ||
|  |         return failure; | ||
|  |       } | ||
|  | 
 | ||
|  |       break; | ||
|  |     } else if (input[pointer] === 58) { | ||
|  |       ++pointer; | ||
|  |       if (input[pointer] === undefined) { | ||
|  |         return failure; | ||
|  |       } | ||
|  |     } else if (input[pointer] !== undefined) { | ||
|  |       return failure; | ||
|  |     } | ||
|  | 
 | ||
|  |     address[pieceIndex] = value; | ||
|  |     ++pieceIndex; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (compress !== null) { | ||
|  |     let swaps = pieceIndex - compress; | ||
|  |     pieceIndex = 7; | ||
|  |     while (pieceIndex !== 0 && swaps > 0) { | ||
|  |       const temp = address[compress + swaps - 1]; | ||
|  |       address[compress + swaps - 1] = address[pieceIndex]; | ||
|  |       address[pieceIndex] = temp; | ||
|  |       --pieceIndex; | ||
|  |       --swaps; | ||
|  |     } | ||
|  |   } else if (compress === null && pieceIndex !== 8) { | ||
|  |     return failure; | ||
|  |   } | ||
|  | 
 | ||
|  |   return address; | ||
|  | } | ||
|  | 
 | ||
|  | function serializeIPv6(address) { | ||
|  |   let output = ""; | ||
|  |   const seqResult = findLongestZeroSequence(address); | ||
|  |   const compress = seqResult.idx; | ||
|  |   let ignore0 = false; | ||
|  | 
 | ||
|  |   for (let pieceIndex = 0; pieceIndex <= 7; ++pieceIndex) { | ||
|  |     if (ignore0 && address[pieceIndex] === 0) { | ||
|  |       continue; | ||
|  |     } else if (ignore0) { | ||
|  |       ignore0 = false; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (compress === pieceIndex) { | ||
|  |       const separator = pieceIndex === 0 ? "::" : ":"; | ||
|  |       output += separator; | ||
|  |       ignore0 = true; | ||
|  |       continue; | ||
|  |     } | ||
|  | 
 | ||
|  |     output += address[pieceIndex].toString(16); | ||
|  | 
 | ||
|  |     if (pieceIndex !== 7) { | ||
|  |       output += ":"; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return output; | ||
|  | } | ||
|  | 
 | ||
|  | function parseHost(input, isSpecialArg) { | ||
|  |   if (input[0] === "[") { | ||
|  |     if (input[input.length - 1] !== "]") { | ||
|  |       return failure; | ||
|  |     } | ||
|  | 
 | ||
|  |     return parseIPv6(input.substring(1, input.length - 1)); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!isSpecialArg) { | ||
|  |     return parseOpaqueHost(input); | ||
|  |   } | ||
|  | 
 | ||
|  |   const domain = utf8PercentDecode(input); | ||
|  |   const asciiDomain = tr46.toASCII(domain, false, tr46.PROCESSING_OPTIONS.NONTRANSITIONAL, false); | ||
|  |   if (asciiDomain === null) { | ||
|  |     return failure; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (containsForbiddenHostCodePoint(asciiDomain)) { | ||
|  |     return failure; | ||
|  |   } | ||
|  | 
 | ||
|  |   const ipv4Host = parseIPv4(asciiDomain); | ||
|  |   if (typeof ipv4Host === "number" || ipv4Host === failure) { | ||
|  |     return ipv4Host; | ||
|  |   } | ||
|  | 
 | ||
|  |   return asciiDomain; | ||
|  | } | ||
|  | 
 | ||
|  | function parseOpaqueHost(input) { | ||
|  |   if (containsForbiddenHostCodePointExcludingPercent(input)) { | ||
|  |     return failure; | ||
|  |   } | ||
|  | 
 | ||
|  |   let output = ""; | ||
|  |   const decoded = punycode.ucs2.decode(input); | ||
|  |   for (let i = 0; i < decoded.length; ++i) { | ||
|  |     output += percentEncodeChar(decoded[i], isC0ControlPercentEncode); | ||
|  |   } | ||
|  |   return output; | ||
|  | } | ||
|  | 
 | ||
|  | function findLongestZeroSequence(arr) { | ||
|  |   let maxIdx = null; | ||
|  |   let maxLen = 1; // only find elements > 1
 | ||
|  |   let currStart = null; | ||
|  |   let currLen = 0; | ||
|  | 
 | ||
|  |   for (let i = 0; i < arr.length; ++i) { | ||
|  |     if (arr[i] !== 0) { | ||
|  |       if (currLen > maxLen) { | ||
|  |         maxIdx = currStart; | ||
|  |         maxLen = currLen; | ||
|  |       } | ||
|  | 
 | ||
|  |       currStart = null; | ||
|  |       currLen = 0; | ||
|  |     } else { | ||
|  |       if (currStart === null) { | ||
|  |         currStart = i; | ||
|  |       } | ||
|  |       ++currLen; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   // if trailing zeros
 | ||
|  |   if (currLen > maxLen) { | ||
|  |     maxIdx = currStart; | ||
|  |     maxLen = currLen; | ||
|  |   } | ||
|  | 
 | ||
|  |   return { | ||
|  |     idx: maxIdx, | ||
|  |     len: maxLen | ||
|  |   }; | ||
|  | } | ||
|  | 
 | ||
|  | function serializeHost(host) { | ||
|  |   if (typeof host === "number") { | ||
|  |     return serializeIPv4(host); | ||
|  |   } | ||
|  | 
 | ||
|  |   // IPv6 serializer
 | ||
|  |   if (host instanceof Array) { | ||
|  |     return "[" + serializeIPv6(host) + "]"; | ||
|  |   } | ||
|  | 
 | ||
|  |   return host; | ||
|  | } | ||
|  | 
 | ||
|  | function trimControlChars(url) { | ||
|  |   return url.replace(/^[\u0000-\u001F\u0020]+|[\u0000-\u001F\u0020]+$/g, ""); | ||
|  | } | ||
|  | 
 | ||
|  | function trimTabAndNewline(url) { | ||
|  |   return url.replace(/\u0009|\u000A|\u000D/g, ""); | ||
|  | } | ||
|  | 
 | ||
|  | function shortenPath(url) { | ||
|  |   const path = url.path; | ||
|  |   if (path.length === 0) { | ||
|  |     return; | ||
|  |   } | ||
|  |   if (url.scheme === "file" && path.length === 1 && isNormalizedWindowsDriveLetter(path[0])) { | ||
|  |     return; | ||
|  |   } | ||
|  | 
 | ||
|  |   path.pop(); | ||
|  | } | ||
|  | 
 | ||
|  | function includesCredentials(url) { | ||
|  |   return url.username !== "" || url.password !== ""; | ||
|  | } | ||
|  | 
 | ||
|  | function cannotHaveAUsernamePasswordPort(url) { | ||
|  |   return url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file"; | ||
|  | } | ||
|  | 
 | ||
|  | function isNormalizedWindowsDriveLetter(string) { | ||
|  |   return /^[A-Za-z]:$/.test(string); | ||
|  | } | ||
|  | 
 | ||
|  | function URLStateMachine(input, base, encodingOverride, url, stateOverride) { | ||
|  |   this.pointer = 0; | ||
|  |   this.input = input; | ||
|  |   this.base = base || null; | ||
|  |   this.encodingOverride = encodingOverride || "utf-8"; | ||
|  |   this.stateOverride = stateOverride; | ||
|  |   this.url = url; | ||
|  |   this.failure = false; | ||
|  |   this.parseError = false; | ||
|  | 
 | ||
|  |   if (!this.url) { | ||
|  |     this.url = { | ||
|  |       scheme: "", | ||
|  |       username: "", | ||
|  |       password: "", | ||
|  |       host: null, | ||
|  |       port: null, | ||
|  |       path: [], | ||
|  |       query: null, | ||
|  |       fragment: null, | ||
|  | 
 | ||
|  |       cannotBeABaseURL: false | ||
|  |     }; | ||
|  | 
 | ||
|  |     const res = trimControlChars(this.input); | ||
|  |     if (res !== this.input) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  |     this.input = res; | ||
|  |   } | ||
|  | 
 | ||
|  |   const res = trimTabAndNewline(this.input); | ||
|  |   if (res !== this.input) { | ||
|  |     this.parseError = true; | ||
|  |   } | ||
|  |   this.input = res; | ||
|  | 
 | ||
|  |   this.state = stateOverride || "scheme start"; | ||
|  | 
 | ||
|  |   this.buffer = ""; | ||
|  |   this.atFlag = false; | ||
|  |   this.arrFlag = false; | ||
|  |   this.passwordTokenSeenFlag = false; | ||
|  | 
 | ||
|  |   this.input = punycode.ucs2.decode(this.input); | ||
|  | 
 | ||
|  |   for (; this.pointer <= this.input.length; ++this.pointer) { | ||
|  |     const c = this.input[this.pointer]; | ||
|  |     const cStr = isNaN(c) ? undefined : String.fromCodePoint(c); | ||
|  | 
 | ||
|  |     // exec state machine
 | ||
|  |     const ret = this["parse " + this.state](c, cStr); | ||
|  |     if (!ret) { | ||
|  |       break; // terminate algorithm
 | ||
|  |     } else if (ret === failure) { | ||
|  |       this.failure = true; | ||
|  |       break; | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse scheme start"] = function parseSchemeStart(c, cStr) { | ||
|  |   if (isASCIIAlpha(c)) { | ||
|  |     this.buffer += cStr.toLowerCase(); | ||
|  |     this.state = "scheme"; | ||
|  |   } else if (!this.stateOverride) { | ||
|  |     this.state = "no scheme"; | ||
|  |     --this.pointer; | ||
|  |   } else { | ||
|  |     this.parseError = true; | ||
|  |     return failure; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) { | ||
|  |   if (isASCIIAlphanumeric(c) || c === 43 || c === 45 || c === 46) { | ||
|  |     this.buffer += cStr.toLowerCase(); | ||
|  |   } else if (c === 58) { | ||
|  |     if (this.stateOverride) { | ||
|  |       if (isSpecial(this.url) && !isSpecialScheme(this.buffer)) { | ||
|  |         return false; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (!isSpecial(this.url) && isSpecialScheme(this.buffer)) { | ||
|  |         return false; | ||
|  |       } | ||
|  | 
 | ||
|  |       if ((includesCredentials(this.url) || this.url.port !== null) && this.buffer === "file") { | ||
|  |         return false; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (this.url.scheme === "file" && (this.url.host === "" || this.url.host === null)) { | ||
|  |         return false; | ||
|  |       } | ||
|  |     } | ||
|  |     this.url.scheme = this.buffer; | ||
|  |     this.buffer = ""; | ||
|  |     if (this.stateOverride) { | ||
|  |       return false; | ||
|  |     } | ||
|  |     if (this.url.scheme === "file") { | ||
|  |       if (this.input[this.pointer + 1] !== 47 || this.input[this.pointer + 2] !== 47) { | ||
|  |         this.parseError = true; | ||
|  |       } | ||
|  |       this.state = "file"; | ||
|  |     } else if (isSpecial(this.url) && this.base !== null && this.base.scheme === this.url.scheme) { | ||
|  |       this.state = "special relative or authority"; | ||
|  |     } else if (isSpecial(this.url)) { | ||
|  |       this.state = "special authority slashes"; | ||
|  |     } else if (this.input[this.pointer + 1] === 47) { | ||
|  |       this.state = "path or authority"; | ||
|  |       ++this.pointer; | ||
|  |     } else { | ||
|  |       this.url.cannotBeABaseURL = true; | ||
|  |       this.url.path.push(""); | ||
|  |       this.state = "cannot-be-a-base-URL path"; | ||
|  |     } | ||
|  |   } else if (!this.stateOverride) { | ||
|  |     this.buffer = ""; | ||
|  |     this.state = "no scheme"; | ||
|  |     this.pointer = -1; | ||
|  |   } else { | ||
|  |     this.parseError = true; | ||
|  |     return failure; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) { | ||
|  |   if (this.base === null || (this.base.cannotBeABaseURL && c !== 35)) { | ||
|  |     return failure; | ||
|  |   } else if (this.base.cannotBeABaseURL && c === 35) { | ||
|  |     this.url.scheme = this.base.scheme; | ||
|  |     this.url.path = this.base.path.slice(); | ||
|  |     this.url.query = this.base.query; | ||
|  |     this.url.fragment = ""; | ||
|  |     this.url.cannotBeABaseURL = true; | ||
|  |     this.state = "fragment"; | ||
|  |   } else if (this.base.scheme === "file") { | ||
|  |     this.state = "file"; | ||
|  |     --this.pointer; | ||
|  |   } else { | ||
|  |     this.state = "relative"; | ||
|  |     --this.pointer; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse special relative or authority"] = function parseSpecialRelativeOrAuthority(c) { | ||
|  |   if (c === 47 && this.input[this.pointer + 1] === 47) { | ||
|  |     this.state = "special authority ignore slashes"; | ||
|  |     ++this.pointer; | ||
|  |   } else { | ||
|  |     this.parseError = true; | ||
|  |     this.state = "relative"; | ||
|  |     --this.pointer; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse path or authority"] = function parsePathOrAuthority(c) { | ||
|  |   if (c === 47) { | ||
|  |     this.state = "authority"; | ||
|  |   } else { | ||
|  |     this.state = "path"; | ||
|  |     --this.pointer; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse relative"] = function parseRelative(c) { | ||
|  |   this.url.scheme = this.base.scheme; | ||
|  |   if (isNaN(c)) { | ||
|  |     this.url.username = this.base.username; | ||
|  |     this.url.password = this.base.password; | ||
|  |     this.url.host = this.base.host; | ||
|  |     this.url.port = this.base.port; | ||
|  |     this.url.path = this.base.path.slice(); | ||
|  |     this.url.query = this.base.query; | ||
|  |   } else if (c === 47) { | ||
|  |     this.state = "relative slash"; | ||
|  |   } else if (c === 63) { | ||
|  |     this.url.username = this.base.username; | ||
|  |     this.url.password = this.base.password; | ||
|  |     this.url.host = this.base.host; | ||
|  |     this.url.port = this.base.port; | ||
|  |     this.url.path = this.base.path.slice(); | ||
|  |     this.url.query = ""; | ||
|  |     this.state = "query"; | ||
|  |   } else if (c === 35) { | ||
|  |     this.url.username = this.base.username; | ||
|  |     this.url.password = this.base.password; | ||
|  |     this.url.host = this.base.host; | ||
|  |     this.url.port = this.base.port; | ||
|  |     this.url.path = this.base.path.slice(); | ||
|  |     this.url.query = this.base.query; | ||
|  |     this.url.fragment = ""; | ||
|  |     this.state = "fragment"; | ||
|  |   } else if (isSpecial(this.url) && c === 92) { | ||
|  |     this.parseError = true; | ||
|  |     this.state = "relative slash"; | ||
|  |   } else { | ||
|  |     this.url.username = this.base.username; | ||
|  |     this.url.password = this.base.password; | ||
|  |     this.url.host = this.base.host; | ||
|  |     this.url.port = this.base.port; | ||
|  |     this.url.path = this.base.path.slice(0, this.base.path.length - 1); | ||
|  | 
 | ||
|  |     this.state = "path"; | ||
|  |     --this.pointer; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) { | ||
|  |   if (isSpecial(this.url) && (c === 47 || c === 92)) { | ||
|  |     if (c === 92) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  |     this.state = "special authority ignore slashes"; | ||
|  |   } else if (c === 47) { | ||
|  |     this.state = "authority"; | ||
|  |   } else { | ||
|  |     this.url.username = this.base.username; | ||
|  |     this.url.password = this.base.password; | ||
|  |     this.url.host = this.base.host; | ||
|  |     this.url.port = this.base.port; | ||
|  |     this.state = "path"; | ||
|  |     --this.pointer; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse special authority slashes"] = function parseSpecialAuthoritySlashes(c) { | ||
|  |   if (c === 47 && this.input[this.pointer + 1] === 47) { | ||
|  |     this.state = "special authority ignore slashes"; | ||
|  |     ++this.pointer; | ||
|  |   } else { | ||
|  |     this.parseError = true; | ||
|  |     this.state = "special authority ignore slashes"; | ||
|  |     --this.pointer; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse special authority ignore slashes"] = function parseSpecialAuthorityIgnoreSlashes(c) { | ||
|  |   if (c !== 47 && c !== 92) { | ||
|  |     this.state = "authority"; | ||
|  |     --this.pointer; | ||
|  |   } else { | ||
|  |     this.parseError = true; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) { | ||
|  |   if (c === 64) { | ||
|  |     this.parseError = true; | ||
|  |     if (this.atFlag) { | ||
|  |       this.buffer = "%40" + this.buffer; | ||
|  |     } | ||
|  |     this.atFlag = true; | ||
|  | 
 | ||
|  |     // careful, this is based on buffer and has its own pointer (this.pointer != pointer) and inner chars
 | ||
|  |     const len = countSymbols(this.buffer); | ||
|  |     for (let pointer = 0; pointer < len; ++pointer) { | ||
|  |       const codePoint = this.buffer.codePointAt(pointer); | ||
|  | 
 | ||
|  |       if (codePoint === 58 && !this.passwordTokenSeenFlag) { | ||
|  |         this.passwordTokenSeenFlag = true; | ||
|  |         continue; | ||
|  |       } | ||
|  |       const encodedCodePoints = percentEncodeChar(codePoint, isUserinfoPercentEncode); | ||
|  |       if (this.passwordTokenSeenFlag) { | ||
|  |         this.url.password += encodedCodePoints; | ||
|  |       } else { | ||
|  |         this.url.username += encodedCodePoints; | ||
|  |       } | ||
|  |     } | ||
|  |     this.buffer = ""; | ||
|  |   } else if (isNaN(c) || c === 47 || c === 63 || c === 35 || | ||
|  |              (isSpecial(this.url) && c === 92)) { | ||
|  |     if (this.atFlag && this.buffer === "") { | ||
|  |       this.parseError = true; | ||
|  |       return failure; | ||
|  |     } | ||
|  |     this.pointer -= countSymbols(this.buffer) + 1; | ||
|  |     this.buffer = ""; | ||
|  |     this.state = "host"; | ||
|  |   } else { | ||
|  |     this.buffer += cStr; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse hostname"] = | ||
|  | URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) { | ||
|  |   if (this.stateOverride && this.url.scheme === "file") { | ||
|  |     --this.pointer; | ||
|  |     this.state = "file host"; | ||
|  |   } else if (c === 58 && !this.arrFlag) { | ||
|  |     if (this.buffer === "") { | ||
|  |       this.parseError = true; | ||
|  |       return failure; | ||
|  |     } | ||
|  | 
 | ||
|  |     const host = parseHost(this.buffer, isSpecial(this.url)); | ||
|  |     if (host === failure) { | ||
|  |       return failure; | ||
|  |     } | ||
|  | 
 | ||
|  |     this.url.host = host; | ||
|  |     this.buffer = ""; | ||
|  |     this.state = "port"; | ||
|  |     if (this.stateOverride === "hostname") { | ||
|  |       return false; | ||
|  |     } | ||
|  |   } else if (isNaN(c) || c === 47 || c === 63 || c === 35 || | ||
|  |              (isSpecial(this.url) && c === 92)) { | ||
|  |     --this.pointer; | ||
|  |     if (isSpecial(this.url) && this.buffer === "") { | ||
|  |       this.parseError = true; | ||
|  |       return failure; | ||
|  |     } else if (this.stateOverride && this.buffer === "" && | ||
|  |                (includesCredentials(this.url) || this.url.port !== null)) { | ||
|  |       this.parseError = true; | ||
|  |       return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     const host = parseHost(this.buffer, isSpecial(this.url)); | ||
|  |     if (host === failure) { | ||
|  |       return failure; | ||
|  |     } | ||
|  | 
 | ||
|  |     this.url.host = host; | ||
|  |     this.buffer = ""; | ||
|  |     this.state = "path start"; | ||
|  |     if (this.stateOverride) { | ||
|  |       return false; | ||
|  |     } | ||
|  |   } else { | ||
|  |     if (c === 91) { | ||
|  |       this.arrFlag = true; | ||
|  |     } else if (c === 93) { | ||
|  |       this.arrFlag = false; | ||
|  |     } | ||
|  |     this.buffer += cStr; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) { | ||
|  |   if (isASCIIDigit(c)) { | ||
|  |     this.buffer += cStr; | ||
|  |   } else if (isNaN(c) || c === 47 || c === 63 || c === 35 || | ||
|  |              (isSpecial(this.url) && c === 92) || | ||
|  |              this.stateOverride) { | ||
|  |     if (this.buffer !== "") { | ||
|  |       const port = parseInt(this.buffer); | ||
|  |       if (port > Math.pow(2, 16) - 1) { | ||
|  |         this.parseError = true; | ||
|  |         return failure; | ||
|  |       } | ||
|  |       this.url.port = port === defaultPort(this.url.scheme) ? null : port; | ||
|  |       this.buffer = ""; | ||
|  |     } | ||
|  |     if (this.stateOverride) { | ||
|  |       return false; | ||
|  |     } | ||
|  |     this.state = "path start"; | ||
|  |     --this.pointer; | ||
|  |   } else { | ||
|  |     this.parseError = true; | ||
|  |     return failure; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | const fileOtherwiseCodePoints = new Set([47, 92, 63, 35]); | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse file"] = function parseFile(c) { | ||
|  |   this.url.scheme = "file"; | ||
|  | 
 | ||
|  |   if (c === 47 || c === 92) { | ||
|  |     if (c === 92) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  |     this.state = "file slash"; | ||
|  |   } else if (this.base !== null && this.base.scheme === "file") { | ||
|  |     if (isNaN(c)) { | ||
|  |       this.url.host = this.base.host; | ||
|  |       this.url.path = this.base.path.slice(); | ||
|  |       this.url.query = this.base.query; | ||
|  |     } else if (c === 63) { | ||
|  |       this.url.host = this.base.host; | ||
|  |       this.url.path = this.base.path.slice(); | ||
|  |       this.url.query = ""; | ||
|  |       this.state = "query"; | ||
|  |     } else if (c === 35) { | ||
|  |       this.url.host = this.base.host; | ||
|  |       this.url.path = this.base.path.slice(); | ||
|  |       this.url.query = this.base.query; | ||
|  |       this.url.fragment = ""; | ||
|  |       this.state = "fragment"; | ||
|  |     } else { | ||
|  |       if (this.input.length - this.pointer - 1 === 0 || // remaining consists of 0 code points
 | ||
|  |           !isWindowsDriveLetterCodePoints(c, this.input[this.pointer + 1]) || | ||
|  |           (this.input.length - this.pointer - 1 >= 2 && // remaining has at least 2 code points
 | ||
|  |            !fileOtherwiseCodePoints.has(this.input[this.pointer + 2]))) { | ||
|  |         this.url.host = this.base.host; | ||
|  |         this.url.path = this.base.path.slice(); | ||
|  |         shortenPath(this.url); | ||
|  |       } else { | ||
|  |         this.parseError = true; | ||
|  |       } | ||
|  | 
 | ||
|  |       this.state = "path"; | ||
|  |       --this.pointer; | ||
|  |     } | ||
|  |   } else { | ||
|  |     this.state = "path"; | ||
|  |     --this.pointer; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) { | ||
|  |   if (c === 47 || c === 92) { | ||
|  |     if (c === 92) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  |     this.state = "file host"; | ||
|  |   } else { | ||
|  |     if (this.base !== null && this.base.scheme === "file") { | ||
|  |       if (isNormalizedWindowsDriveLetterString(this.base.path[0])) { | ||
|  |         this.url.path.push(this.base.path[0]); | ||
|  |       } else { | ||
|  |         this.url.host = this.base.host; | ||
|  |       } | ||
|  |     } | ||
|  |     this.state = "path"; | ||
|  |     --this.pointer; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) { | ||
|  |   if (isNaN(c) || c === 47 || c === 92 || c === 63 || c === 35) { | ||
|  |     --this.pointer; | ||
|  |     if (!this.stateOverride && isWindowsDriveLetterString(this.buffer)) { | ||
|  |       this.parseError = true; | ||
|  |       this.state = "path"; | ||
|  |     } else if (this.buffer === "") { | ||
|  |       this.url.host = ""; | ||
|  |       if (this.stateOverride) { | ||
|  |         return false; | ||
|  |       } | ||
|  |       this.state = "path start"; | ||
|  |     } else { | ||
|  |       let host = parseHost(this.buffer, isSpecial(this.url)); | ||
|  |       if (host === failure) { | ||
|  |         return failure; | ||
|  |       } | ||
|  |       if (host === "localhost") { | ||
|  |         host = ""; | ||
|  |       } | ||
|  |       this.url.host = host; | ||
|  | 
 | ||
|  |       if (this.stateOverride) { | ||
|  |         return false; | ||
|  |       } | ||
|  | 
 | ||
|  |       this.buffer = ""; | ||
|  |       this.state = "path start"; | ||
|  |     } | ||
|  |   } else { | ||
|  |     this.buffer += cStr; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse path start"] = function parsePathStart(c) { | ||
|  |   if (isSpecial(this.url)) { | ||
|  |     if (c === 92) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  |     this.state = "path"; | ||
|  | 
 | ||
|  |     if (c !== 47 && c !== 92) { | ||
|  |       --this.pointer; | ||
|  |     } | ||
|  |   } else if (!this.stateOverride && c === 63) { | ||
|  |     this.url.query = ""; | ||
|  |     this.state = "query"; | ||
|  |   } else if (!this.stateOverride && c === 35) { | ||
|  |     this.url.fragment = ""; | ||
|  |     this.state = "fragment"; | ||
|  |   } else if (c !== undefined) { | ||
|  |     this.state = "path"; | ||
|  |     if (c !== 47) { | ||
|  |       --this.pointer; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse path"] = function parsePath(c) { | ||
|  |   if (isNaN(c) || c === 47 || (isSpecial(this.url) && c === 92) || | ||
|  |       (!this.stateOverride && (c === 63 || c === 35))) { | ||
|  |     if (isSpecial(this.url) && c === 92) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (isDoubleDot(this.buffer)) { | ||
|  |       shortenPath(this.url); | ||
|  |       if (c !== 47 && !(isSpecial(this.url) && c === 92)) { | ||
|  |         this.url.path.push(""); | ||
|  |       } | ||
|  |     } else if (isSingleDot(this.buffer) && c !== 47 && | ||
|  |                !(isSpecial(this.url) && c === 92)) { | ||
|  |       this.url.path.push(""); | ||
|  |     } else if (!isSingleDot(this.buffer)) { | ||
|  |       if (this.url.scheme === "file" && this.url.path.length === 0 && isWindowsDriveLetterString(this.buffer)) { | ||
|  |         if (this.url.host !== "" && this.url.host !== null) { | ||
|  |           this.parseError = true; | ||
|  |           this.url.host = ""; | ||
|  |         } | ||
|  |         this.buffer = this.buffer[0] + ":"; | ||
|  |       } | ||
|  |       this.url.path.push(this.buffer); | ||
|  |     } | ||
|  |     this.buffer = ""; | ||
|  |     if (this.url.scheme === "file" && (c === undefined || c === 63 || c === 35)) { | ||
|  |       while (this.url.path.length > 1 && this.url.path[0] === "") { | ||
|  |         this.parseError = true; | ||
|  |         this.url.path.shift(); | ||
|  |       } | ||
|  |     } | ||
|  |     if (c === 63) { | ||
|  |       this.url.query = ""; | ||
|  |       this.state = "query"; | ||
|  |     } | ||
|  |     if (c === 35) { | ||
|  |       this.url.fragment = ""; | ||
|  |       this.state = "fragment"; | ||
|  |     } | ||
|  |   } else { | ||
|  |     // TODO: If c is not a URL code point and not "%", parse error.
 | ||
|  | 
 | ||
|  |     if (c === 37 && | ||
|  |       (!isASCIIHex(this.input[this.pointer + 1]) || | ||
|  |         !isASCIIHex(this.input[this.pointer + 2]))) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  | 
 | ||
|  |     this.buffer += percentEncodeChar(c, isPathPercentEncode); | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse cannot-be-a-base-URL path"] = function parseCannotBeABaseURLPath(c) { | ||
|  |   if (c === 63) { | ||
|  |     this.url.query = ""; | ||
|  |     this.state = "query"; | ||
|  |   } else if (c === 35) { | ||
|  |     this.url.fragment = ""; | ||
|  |     this.state = "fragment"; | ||
|  |   } else { | ||
|  |     // TODO: Add: not a URL code point
 | ||
|  |     if (!isNaN(c) && c !== 37) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (c === 37 && | ||
|  |         (!isASCIIHex(this.input[this.pointer + 1]) || | ||
|  |          !isASCIIHex(this.input[this.pointer + 2]))) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!isNaN(c)) { | ||
|  |       this.url.path[0] = this.url.path[0] + percentEncodeChar(c, isC0ControlPercentEncode); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) { | ||
|  |   if (isNaN(c) || (!this.stateOverride && c === 35)) { | ||
|  |     if (!isSpecial(this.url) || this.url.scheme === "ws" || this.url.scheme === "wss") { | ||
|  |       this.encodingOverride = "utf-8"; | ||
|  |     } | ||
|  | 
 | ||
|  |     const buffer = new Buffer(this.buffer); // TODO: Use encoding override instead
 | ||
|  |     for (let i = 0; i < buffer.length; ++i) { | ||
|  |       if (buffer[i] < 0x21 || buffer[i] > 0x7E || buffer[i] === 0x22 || buffer[i] === 0x23 || | ||
|  |           buffer[i] === 0x3C || buffer[i] === 0x3E) { | ||
|  |         this.url.query += percentEncode(buffer[i]); | ||
|  |       } else { | ||
|  |         this.url.query += String.fromCodePoint(buffer[i]); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     this.buffer = ""; | ||
|  |     if (c === 35) { | ||
|  |       this.url.fragment = ""; | ||
|  |       this.state = "fragment"; | ||
|  |     } | ||
|  |   } else { | ||
|  |     // TODO: If c is not a URL code point and not "%", parse error.
 | ||
|  |     if (c === 37 && | ||
|  |       (!isASCIIHex(this.input[this.pointer + 1]) || | ||
|  |         !isASCIIHex(this.input[this.pointer + 2]))) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  | 
 | ||
|  |     this.buffer += cStr; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | URLStateMachine.prototype["parse fragment"] = function parseFragment(c) { | ||
|  |   if (isNaN(c)) { // do nothing
 | ||
|  |   } else if (c === 0x0) { | ||
|  |     this.parseError = true; | ||
|  |   } else { | ||
|  |     // TODO: If c is not a URL code point and not "%", parse error.
 | ||
|  |     if (c === 37 && | ||
|  |       (!isASCIIHex(this.input[this.pointer + 1]) || | ||
|  |         !isASCIIHex(this.input[this.pointer + 2]))) { | ||
|  |       this.parseError = true; | ||
|  |     } | ||
|  | 
 | ||
|  |     this.url.fragment += percentEncodeChar(c, isC0ControlPercentEncode); | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | function serializeURL(url, excludeFragment) { | ||
|  |   let output = url.scheme + ":"; | ||
|  |   if (url.host !== null) { | ||
|  |     output += "//"; | ||
|  | 
 | ||
|  |     if (url.username !== "" || url.password !== "") { | ||
|  |       output += url.username; | ||
|  |       if (url.password !== "") { | ||
|  |         output += ":" + url.password; | ||
|  |       } | ||
|  |       output += "@"; | ||
|  |     } | ||
|  | 
 | ||
|  |     output += serializeHost(url.host); | ||
|  | 
 | ||
|  |     if (url.port !== null) { | ||
|  |       output += ":" + url.port; | ||
|  |     } | ||
|  |   } else if (url.host === null && url.scheme === "file") { | ||
|  |     output += "//"; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (url.cannotBeABaseURL) { | ||
|  |     output += url.path[0]; | ||
|  |   } else { | ||
|  |     for (const string of url.path) { | ||
|  |       output += "/" + string; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   if (url.query !== null) { | ||
|  |     output += "?" + url.query; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!excludeFragment && url.fragment !== null) { | ||
|  |     output += "#" + url.fragment; | ||
|  |   } | ||
|  | 
 | ||
|  |   return output; | ||
|  | } | ||
|  | 
 | ||
|  | function serializeOrigin(tuple) { | ||
|  |   let result = tuple.scheme + "://"; | ||
|  |   result += serializeHost(tuple.host); | ||
|  | 
 | ||
|  |   if (tuple.port !== null) { | ||
|  |     result += ":" + tuple.port; | ||
|  |   } | ||
|  | 
 | ||
|  |   return result; | ||
|  | } | ||
|  | 
 | ||
|  | module.exports.serializeURL = serializeURL; | ||
|  | 
 | ||
|  | module.exports.serializeURLOrigin = function (url) { | ||
|  |   // https://url.spec.whatwg.org/#concept-url-origin
 | ||
|  |   switch (url.scheme) { | ||
|  |     case "blob": | ||
|  |       try { | ||
|  |         return module.exports.serializeURLOrigin(module.exports.parseURL(url.path[0])); | ||
|  |       } catch (e) { | ||
|  |         // serializing an opaque origin returns "null"
 | ||
|  |         return "null"; | ||
|  |       } | ||
|  |     case "ftp": | ||
|  |     case "gopher": | ||
|  |     case "http": | ||
|  |     case "https": | ||
|  |     case "ws": | ||
|  |     case "wss": | ||
|  |       return serializeOrigin({ | ||
|  |         scheme: url.scheme, | ||
|  |         host: url.host, | ||
|  |         port: url.port | ||
|  |       }); | ||
|  |     case "file": | ||
|  |       // spec says "exercise to the reader", chrome says "file://"
 | ||
|  |       return "file://"; | ||
|  |     default: | ||
|  |       // serializing an opaque origin returns "null"
 | ||
|  |       return "null"; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports.basicURLParse = function (input, options) { | ||
|  |   if (options === undefined) { | ||
|  |     options = {}; | ||
|  |   } | ||
|  | 
 | ||
|  |   const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride); | ||
|  |   if (usm.failure) { | ||
|  |     return "failure"; | ||
|  |   } | ||
|  | 
 | ||
|  |   return usm.url; | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports.setTheUsername = function (url, username) { | ||
|  |   url.username = ""; | ||
|  |   const decoded = punycode.ucs2.decode(username); | ||
|  |   for (let i = 0; i < decoded.length; ++i) { | ||
|  |     url.username += percentEncodeChar(decoded[i], isUserinfoPercentEncode); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports.setThePassword = function (url, password) { | ||
|  |   url.password = ""; | ||
|  |   const decoded = punycode.ucs2.decode(password); | ||
|  |   for (let i = 0; i < decoded.length; ++i) { | ||
|  |     url.password += percentEncodeChar(decoded[i], isUserinfoPercentEncode); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports.serializeHost = serializeHost; | ||
|  | 
 | ||
|  | module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort; | ||
|  | 
 | ||
|  | module.exports.serializeInteger = function (integer) { | ||
|  |   return String(integer); | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports.parseURL = function (input, options) { | ||
|  |   if (options === undefined) { | ||
|  |     options = {}; | ||
|  |   } | ||
|  | 
 | ||
|  |   // We don't handle blobs, so this just delegates:
 | ||
|  |   return module.exports.basicURLParse(input, { baseURL: options.baseURL, encodingOverride: options.encodingOverride }); | ||
|  | }; |