 aacb45156b
			
		
	
	aacb45156b
	
	
	
		
			
			- Install Jest for unit testing with React Testing Library - Install Playwright for end-to-end testing - Configure Jest with proper TypeScript support and module mapping - Create test setup files and utilities for both unit and e2e tests Components: * Jest configuration with coverage thresholds * Playwright configuration with browser automation * Unit tests for LoginForm, AuthContext, and useSocketIO hook * E2E tests for authentication, dashboard, and agents workflows * GitHub Actions workflow for automated testing * Mock data and API utilities for consistent testing * Test documentation with best practices Testing features: - Unit tests with 70% coverage threshold - E2E tests with API mocking and user journey testing - CI/CD integration for automated test runs - Cross-browser testing support with Playwright - Authentication system testing end-to-end 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			1275 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1275 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| const tr46 = require("tr46");
 | |
| 
 | |
| const infra = require("./infra");
 | |
| const { utf8DecodeWithoutBOM } = require("./encoding");
 | |
| const { percentDecodeString, utf8PercentEncodeCodePoint, utf8PercentEncodeString, isC0ControlPercentEncode,
 | |
|   isFragmentPercentEncode, isQueryPercentEncode, isSpecialQueryPercentEncode, isPathPercentEncode,
 | |
|   isUserinfoPercentEncode } = require("./percent-encoding");
 | |
| 
 | |
| function p(char) {
 | |
|   return char.codePointAt(0);
 | |
| }
 | |
| 
 | |
| const specialSchemes = {
 | |
|   ftp: 21,
 | |
|   file: null,
 | |
|   http: 80,
 | |
|   https: 443,
 | |
|   ws: 80,
 | |
|   wss: 443
 | |
| };
 | |
| 
 | |
| const failure = Symbol("failure");
 | |
| 
 | |
| function countSymbols(str) {
 | |
|   return [...str].length;
 | |
| }
 | |
| 
 | |
| function at(input, idx) {
 | |
|   const c = input[idx];
 | |
|   return isNaN(c) ? undefined : String.fromCodePoint(c);
 | |
| }
 | |
| 
 | |
| 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 infra.isASCIIAlpha(cp1) && (cp2 === p(":") || cp2 === p("|"));
 | |
| }
 | |
| 
 | |
| function isWindowsDriveLetterString(string) {
 | |
|   return string.length === 2 && infra.isASCIIAlpha(string.codePointAt(0)) && (string[1] === ":" || string[1] === "|");
 | |
| }
 | |
| 
 | |
| function isNormalizedWindowsDriveLetterString(string) {
 | |
|   return string.length === 2 && infra.isASCIIAlpha(string.codePointAt(0)) && string[1] === ":";
 | |
| }
 | |
| 
 | |
| function containsForbiddenHostCodePoint(string) {
 | |
|   return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|\/|:|<|>|\?|@|\[|\\|\]|\^|\|/u) !== -1;
 | |
| }
 | |
| 
 | |
| function containsForbiddenDomainCodePoint(string) {
 | |
|   return containsForbiddenHostCodePoint(string) || string.search(/[\u0000-\u001F]|%|\u007F/u) !== -1;
 | |
| }
 | |
| 
 | |
| function isSpecialScheme(scheme) {
 | |
|   return specialSchemes[scheme] !== undefined;
 | |
| }
 | |
| 
 | |
| function isSpecial(url) {
 | |
|   return isSpecialScheme(url.scheme);
 | |
| }
 | |
| 
 | |
| function isNotSpecial(url) {
 | |
|   return !isSpecialScheme(url.scheme);
 | |
| }
 | |
| 
 | |
| function defaultPort(scheme) {
 | |
|   return specialSchemes[scheme];
 | |
| }
 | |
| 
 | |
| function parseIPv4Number(input) {
 | |
|   if (input === "") {
 | |
|     return failure;
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   let regex = /[^0-7]/u;
 | |
|   if (R === 10) {
 | |
|     regex = /[^0-9]/u;
 | |
|   }
 | |
|   if (R === 16) {
 | |
|     regex = /[^0-9A-Fa-f]/u;
 | |
|   }
 | |
| 
 | |
|   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 failure;
 | |
|   }
 | |
| 
 | |
|   const numbers = [];
 | |
|   for (const part of parts) {
 | |
|     const n = parseIPv4Number(part);
 | |
|     if (n === failure) {
 | |
|       return failure;
 | |
|     }
 | |
| 
 | |
|     numbers.push(n);
 | |
|   }
 | |
| 
 | |
|   for (let i = 0; i < numbers.length - 1; ++i) {
 | |
|     if (numbers[i] > 255) {
 | |
|       return failure;
 | |
|     }
 | |
|   }
 | |
|   if (numbers[numbers.length - 1] >= 256 ** (5 - numbers.length)) {
 | |
|     return failure;
 | |
|   }
 | |
| 
 | |
|   let ipv4 = numbers.pop();
 | |
|   let counter = 0;
 | |
| 
 | |
|   for (const n of numbers) {
 | |
|     ipv4 += n * 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 = Array.from(input, c => c.codePointAt(0));
 | |
| 
 | |
|   if (input[pointer] === p(":")) {
 | |
|     if (input[pointer + 1] !== p(":")) {
 | |
|       return failure;
 | |
|     }
 | |
| 
 | |
|     pointer += 2;
 | |
|     ++pieceIndex;
 | |
|     compress = pieceIndex;
 | |
|   }
 | |
| 
 | |
|   while (pointer < input.length) {
 | |
|     if (pieceIndex === 8) {
 | |
|       return failure;
 | |
|     }
 | |
| 
 | |
|     if (input[pointer] === p(":")) {
 | |
|       if (compress !== null) {
 | |
|         return failure;
 | |
|       }
 | |
|       ++pointer;
 | |
|       ++pieceIndex;
 | |
|       compress = pieceIndex;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     let value = 0;
 | |
|     let length = 0;
 | |
| 
 | |
|     while (length < 4 && infra.isASCIIHex(input[pointer])) {
 | |
|       value = value * 0x10 + parseInt(at(input, pointer), 16);
 | |
|       ++pointer;
 | |
|       ++length;
 | |
|     }
 | |
| 
 | |
|     if (input[pointer] === p(".")) {
 | |
|       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] === p(".") && numbersSeen < 4) {
 | |
|             ++pointer;
 | |
|           } else {
 | |
|             return failure;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         if (!infra.isASCIIDigit(input[pointer])) {
 | |
|           return failure;
 | |
|         }
 | |
| 
 | |
|         while (infra.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] === p(":")) {
 | |
|       ++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 compress = findTheIPv6AddressCompressedPieceIndex(address);
 | |
|   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, isOpaque = false) {
 | |
|   if (input[0] === "[") {
 | |
|     if (input[input.length - 1] !== "]") {
 | |
|       return failure;
 | |
|     }
 | |
| 
 | |
|     return parseIPv6(input.substring(1, input.length - 1));
 | |
|   }
 | |
| 
 | |
|   if (isOpaque) {
 | |
|     return parseOpaqueHost(input);
 | |
|   }
 | |
| 
 | |
|   const domain = utf8DecodeWithoutBOM(percentDecodeString(input));
 | |
|   const asciiDomain = domainToASCII(domain);
 | |
|   if (asciiDomain === failure) {
 | |
|     return failure;
 | |
|   }
 | |
| 
 | |
|   if (endsInANumber(asciiDomain)) {
 | |
|     return parseIPv4(asciiDomain);
 | |
|   }
 | |
| 
 | |
|   return asciiDomain;
 | |
| }
 | |
| 
 | |
| function endsInANumber(input) {
 | |
|   const parts = input.split(".");
 | |
|   if (parts[parts.length - 1] === "") {
 | |
|     if (parts.length === 1) {
 | |
|       return false;
 | |
|     }
 | |
|     parts.pop();
 | |
|   }
 | |
| 
 | |
|   const last = parts[parts.length - 1];
 | |
|   if (parseIPv4Number(last) !== failure) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (/^[0-9]+$/u.test(last)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| function parseOpaqueHost(input) {
 | |
|   if (containsForbiddenHostCodePoint(input)) {
 | |
|     return failure;
 | |
|   }
 | |
| 
 | |
|   return utf8PercentEncodeString(input, isC0ControlPercentEncode);
 | |
| }
 | |
| 
 | |
| function findTheIPv6AddressCompressedPieceIndex(address) {
 | |
|   let longestIndex = null;
 | |
|   let longestSize = 1; // only find elements > 1
 | |
|   let foundIndex = null;
 | |
|   let foundSize = 0;
 | |
| 
 | |
|   for (let pieceIndex = 0; pieceIndex < address.length; ++pieceIndex) {
 | |
|     if (address[pieceIndex] !== 0) {
 | |
|       if (foundSize > longestSize) {
 | |
|         longestIndex = foundIndex;
 | |
|         longestSize = foundSize;
 | |
|       }
 | |
| 
 | |
|       foundIndex = null;
 | |
|       foundSize = 0;
 | |
|     } else {
 | |
|       if (foundIndex === null) {
 | |
|         foundIndex = pieceIndex;
 | |
|       }
 | |
|       ++foundSize;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (foundSize > longestSize) {
 | |
|     return foundIndex;
 | |
|   }
 | |
| 
 | |
|   return longestIndex;
 | |
| }
 | |
| 
 | |
| function serializeHost(host) {
 | |
|   if (typeof host === "number") {
 | |
|     return serializeIPv4(host);
 | |
|   }
 | |
| 
 | |
|   // IPv6 serializer
 | |
|   if (host instanceof Array) {
 | |
|     return `[${serializeIPv6(host)}]`;
 | |
|   }
 | |
| 
 | |
|   return host;
 | |
| }
 | |
| 
 | |
| function domainToASCII(domain, beStrict = false) {
 | |
|   const result = tr46.toASCII(domain, {
 | |
|     checkHyphens: beStrict,
 | |
|     checkBidi: true,
 | |
|     checkJoiners: true,
 | |
|     useSTD3ASCIIRules: beStrict,
 | |
|     transitionalProcessing: false,
 | |
|     verifyDNSLength: beStrict,
 | |
|     ignoreInvalidPunycode: false
 | |
|   });
 | |
|   if (result === null) {
 | |
|     return failure;
 | |
|   }
 | |
| 
 | |
|   if (!beStrict) {
 | |
|     if (result === "") {
 | |
|       return failure;
 | |
|     }
 | |
|     if (containsForbiddenDomainCodePoint(result)) {
 | |
|       return failure;
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| function trimControlChars(string) {
 | |
|   // Avoid using regexp because of this V8 bug: https://issues.chromium.org/issues/42204424
 | |
| 
 | |
|   let start = 0;
 | |
|   let end = string.length;
 | |
|   for (; start < end; ++start) {
 | |
|     if (string.charCodeAt(start) > 0x20) {
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   for (; end > start; --end) {
 | |
|     if (string.charCodeAt(end - 1) > 0x20) {
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return string.substring(start, end);
 | |
| }
 | |
| 
 | |
| function trimTabAndNewline(url) {
 | |
|   return url.replace(/\u0009|\u000A|\u000D/ug, "");
 | |
| }
 | |
| 
 | |
| function shortenPath(url) {
 | |
|   const { path } = url;
 | |
|   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.scheme === "file";
 | |
| }
 | |
| 
 | |
| function hasAnOpaquePath(url) {
 | |
|   return typeof url.path === "string";
 | |
| }
 | |
| 
 | |
| function isNormalizedWindowsDriveLetter(string) {
 | |
|   return /^[A-Za-z]:$/u.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
 | |
|     };
 | |
| 
 | |
|     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 = Array.from(this.input, c => c.codePointAt(0));
 | |
| 
 | |
|   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 (infra.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 (infra.isASCIIAlphanumeric(c) || c === p("+") || c === p("-") || c === p(".")) {
 | |
|     this.buffer += cStr.toLowerCase();
 | |
|   } else if (c === p(":")) {
 | |
|     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 === "") {
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
|     this.url.scheme = this.buffer;
 | |
|     if (this.stateOverride) {
 | |
|       if (this.url.port === defaultPort(this.url.scheme)) {
 | |
|         this.url.port = null;
 | |
|       }
 | |
|       return false;
 | |
|     }
 | |
|     this.buffer = "";
 | |
|     if (this.url.scheme === "file") {
 | |
|       if (this.input[this.pointer + 1] !== p("/") || this.input[this.pointer + 2] !== p("/")) {
 | |
|         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] === p("/")) {
 | |
|       this.state = "path or authority";
 | |
|       ++this.pointer;
 | |
|     } else {
 | |
|       this.url.path = "";
 | |
|       this.state = "opaque 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 || (hasAnOpaquePath(this.base) && c !== p("#"))) {
 | |
|     return failure;
 | |
|   } else if (hasAnOpaquePath(this.base) && c === p("#")) {
 | |
|     this.url.scheme = this.base.scheme;
 | |
|     this.url.path = this.base.path;
 | |
|     this.url.query = this.base.query;
 | |
|     this.url.fragment = "";
 | |
|     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 === p("/") && this.input[this.pointer + 1] === p("/")) {
 | |
|     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 === p("/")) {
 | |
|     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 (c === p("/")) {
 | |
|     this.state = "relative slash";
 | |
|   } else if (isSpecial(this.url) && c === p("\\")) {
 | |
|     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();
 | |
|     this.url.query = this.base.query;
 | |
|     if (c === p("?")) {
 | |
|       this.url.query = "";
 | |
|       this.state = "query";
 | |
|     } else if (c === p("#")) {
 | |
|       this.url.fragment = "";
 | |
|       this.state = "fragment";
 | |
|     } else if (!isNaN(c)) {
 | |
|       this.url.query = null;
 | |
|       this.url.path.pop();
 | |
|       this.state = "path";
 | |
|       --this.pointer;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) {
 | |
|   if (isSpecial(this.url) && (c === p("/") || c === p("\\"))) {
 | |
|     if (c === p("\\")) {
 | |
|       this.parseError = true;
 | |
|     }
 | |
|     this.state = "special authority ignore slashes";
 | |
|   } else if (c === p("/")) {
 | |
|     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 === p("/") && this.input[this.pointer + 1] === p("/")) {
 | |
|     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 !== p("/") && c !== p("\\")) {
 | |
|     this.state = "authority";
 | |
|     --this.pointer;
 | |
|   } else {
 | |
|     this.parseError = true;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) {
 | |
|   if (c === p("@")) {
 | |
|     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 === p(":") && !this.passwordTokenSeenFlag) {
 | |
|         this.passwordTokenSeenFlag = true;
 | |
|         continue;
 | |
|       }
 | |
|       const encodedCodePoints = utf8PercentEncodeCodePoint(codePoint, isUserinfoPercentEncode);
 | |
|       if (this.passwordTokenSeenFlag) {
 | |
|         this.url.password += encodedCodePoints;
 | |
|       } else {
 | |
|         this.url.username += encodedCodePoints;
 | |
|       }
 | |
|     }
 | |
|     this.buffer = "";
 | |
|   } else if (isNaN(c) || c === p("/") || c === p("?") || c === p("#") ||
 | |
|              (isSpecial(this.url) && c === p("\\"))) {
 | |
|     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 === p(":") && !this.arrFlag) {
 | |
|     if (this.buffer === "") {
 | |
|       this.parseError = true;
 | |
|       return failure;
 | |
|     }
 | |
| 
 | |
|     if (this.stateOverride === "hostname") {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     const host = parseHost(this.buffer, isNotSpecial(this.url));
 | |
|     if (host === failure) {
 | |
|       return failure;
 | |
|     }
 | |
| 
 | |
|     this.url.host = host;
 | |
|     this.buffer = "";
 | |
|     this.state = "port";
 | |
|   } else if (isNaN(c) || c === p("/") || c === p("?") || c === p("#") ||
 | |
|              (isSpecial(this.url) && c === p("\\"))) {
 | |
|     --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, isNotSpecial(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 === p("[")) {
 | |
|       this.arrFlag = true;
 | |
|     } else if (c === p("]")) {
 | |
|       this.arrFlag = false;
 | |
|     }
 | |
|     this.buffer += cStr;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) {
 | |
|   if (infra.isASCIIDigit(c)) {
 | |
|     this.buffer += cStr;
 | |
|   } else if (isNaN(c) || c === p("/") || c === p("?") || c === p("#") ||
 | |
|              (isSpecial(this.url) && c === p("\\")) ||
 | |
|              this.stateOverride) {
 | |
|     if (this.buffer !== "") {
 | |
|       const port = parseInt(this.buffer);
 | |
|       if (port > 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([p("/"), p("\\"), p("?"), p("#")]);
 | |
| 
 | |
| function startsWithWindowsDriveLetter(input, pointer) {
 | |
|   const length = input.length - pointer;
 | |
|   return length >= 2 &&
 | |
|     isWindowsDriveLetterCodePoints(input[pointer], input[pointer + 1]) &&
 | |
|     (length === 2 || fileOtherwiseCodePoints.has(input[pointer + 2]));
 | |
| }
 | |
| 
 | |
| URLStateMachine.prototype["parse file"] = function parseFile(c) {
 | |
|   this.url.scheme = "file";
 | |
|   this.url.host = "";
 | |
| 
 | |
|   if (c === p("/") || c === p("\\")) {
 | |
|     if (c === p("\\")) {
 | |
|       this.parseError = true;
 | |
|     }
 | |
|     this.state = "file slash";
 | |
|   } else if (this.base !== null && this.base.scheme === "file") {
 | |
|     this.url.host = this.base.host;
 | |
|     this.url.path = this.base.path.slice();
 | |
|     this.url.query = this.base.query;
 | |
|     if (c === p("?")) {
 | |
|       this.url.query = "";
 | |
|       this.state = "query";
 | |
|     } else if (c === p("#")) {
 | |
|       this.url.fragment = "";
 | |
|       this.state = "fragment";
 | |
|     } else if (!isNaN(c)) {
 | |
|       this.url.query = null;
 | |
|       if (!startsWithWindowsDriveLetter(this.input, this.pointer)) {
 | |
|         shortenPath(this.url);
 | |
|       } else {
 | |
|         this.parseError = true;
 | |
|         this.url.path = [];
 | |
|       }
 | |
| 
 | |
|       this.state = "path";
 | |
|       --this.pointer;
 | |
|     }
 | |
|   } else {
 | |
|     this.state = "path";
 | |
|     --this.pointer;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) {
 | |
|   if (c === p("/") || c === p("\\")) {
 | |
|     if (c === p("\\")) {
 | |
|       this.parseError = true;
 | |
|     }
 | |
|     this.state = "file host";
 | |
|   } else {
 | |
|     if (this.base !== null && this.base.scheme === "file") {
 | |
|       if (!startsWithWindowsDriveLetter(this.input, this.pointer) &&
 | |
|           isNormalizedWindowsDriveLetterString(this.base.path[0])) {
 | |
|         this.url.path.push(this.base.path[0]);
 | |
|       }
 | |
|       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 === p("/") || c === p("\\") || c === p("?") || c === p("#")) {
 | |
|     --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, isNotSpecial(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 === p("\\")) {
 | |
|       this.parseError = true;
 | |
|     }
 | |
|     this.state = "path";
 | |
| 
 | |
|     if (c !== p("/") && c !== p("\\")) {
 | |
|       --this.pointer;
 | |
|     }
 | |
|   } else if (!this.stateOverride && c === p("?")) {
 | |
|     this.url.query = "";
 | |
|     this.state = "query";
 | |
|   } else if (!this.stateOverride && c === p("#")) {
 | |
|     this.url.fragment = "";
 | |
|     this.state = "fragment";
 | |
|   } else if (c !== undefined) {
 | |
|     this.state = "path";
 | |
|     if (c !== p("/")) {
 | |
|       --this.pointer;
 | |
|     }
 | |
|   } else if (this.stateOverride && this.url.host === null) {
 | |
|     this.url.path.push("");
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| URLStateMachine.prototype["parse path"] = function parsePath(c) {
 | |
|   if (isNaN(c) || c === p("/") || (isSpecial(this.url) && c === p("\\")) ||
 | |
|       (!this.stateOverride && (c === p("?") || c === p("#")))) {
 | |
|     if (isSpecial(this.url) && c === p("\\")) {
 | |
|       this.parseError = true;
 | |
|     }
 | |
| 
 | |
|     if (isDoubleDot(this.buffer)) {
 | |
|       shortenPath(this.url);
 | |
|       if (c !== p("/") && !(isSpecial(this.url) && c === p("\\"))) {
 | |
|         this.url.path.push("");
 | |
|       }
 | |
|     } else if (isSingleDot(this.buffer) && c !== p("/") &&
 | |
|                !(isSpecial(this.url) && c === p("\\"))) {
 | |
|       this.url.path.push("");
 | |
|     } else if (!isSingleDot(this.buffer)) {
 | |
|       if (this.url.scheme === "file" && this.url.path.length === 0 && isWindowsDriveLetterString(this.buffer)) {
 | |
|         this.buffer = `${this.buffer[0]}:`;
 | |
|       }
 | |
|       this.url.path.push(this.buffer);
 | |
|     }
 | |
|     this.buffer = "";
 | |
|     if (c === p("?")) {
 | |
|       this.url.query = "";
 | |
|       this.state = "query";
 | |
|     }
 | |
|     if (c === p("#")) {
 | |
|       this.url.fragment = "";
 | |
|       this.state = "fragment";
 | |
|     }
 | |
|   } else {
 | |
|     // TODO: If c is not a URL code point and not "%", parse error.
 | |
| 
 | |
|     if (c === p("%") &&
 | |
|       (!infra.isASCIIHex(this.input[this.pointer + 1]) ||
 | |
|         !infra.isASCIIHex(this.input[this.pointer + 2]))) {
 | |
|       this.parseError = true;
 | |
|     }
 | |
| 
 | |
|     this.buffer += utf8PercentEncodeCodePoint(c, isPathPercentEncode);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| URLStateMachine.prototype["parse opaque path"] = function parseOpaquePath(c) {
 | |
|   if (c === p("?")) {
 | |
|     this.url.query = "";
 | |
|     this.state = "query";
 | |
|   } else if (c === p("#")) {
 | |
|     this.url.fragment = "";
 | |
|     this.state = "fragment";
 | |
|   } else if (c === p(" ")) {
 | |
|     const remaining = this.input[this.pointer + 1];
 | |
|     if (remaining === p("?") || remaining === p("#")) {
 | |
|       this.url.path += "%20";
 | |
|     } else {
 | |
|       this.url.path += " ";
 | |
|     }
 | |
|   } else {
 | |
|     // TODO: Add: not a URL code point
 | |
|     if (!isNaN(c) && c !== p("%")) {
 | |
|       this.parseError = true;
 | |
|     }
 | |
| 
 | |
|     if (c === p("%") &&
 | |
|         (!infra.isASCIIHex(this.input[this.pointer + 1]) ||
 | |
|          !infra.isASCIIHex(this.input[this.pointer + 2]))) {
 | |
|       this.parseError = true;
 | |
|     }
 | |
| 
 | |
|     if (!isNaN(c)) {
 | |
|       this.url.path += utf8PercentEncodeCodePoint(c, isC0ControlPercentEncode);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) {
 | |
|   if (!isSpecial(this.url) || this.url.scheme === "ws" || this.url.scheme === "wss") {
 | |
|     this.encodingOverride = "utf-8";
 | |
|   }
 | |
| 
 | |
|   if ((!this.stateOverride && c === p("#")) || isNaN(c)) {
 | |
|     const queryPercentEncodePredicate = isSpecial(this.url) ? isSpecialQueryPercentEncode : isQueryPercentEncode;
 | |
|     this.url.query += utf8PercentEncodeString(this.buffer, queryPercentEncodePredicate);
 | |
| 
 | |
|     this.buffer = "";
 | |
| 
 | |
|     if (c === p("#")) {
 | |
|       this.url.fragment = "";
 | |
|       this.state = "fragment";
 | |
|     }
 | |
|   } else if (!isNaN(c)) {
 | |
|     // TODO: If c is not a URL code point and not "%", parse error.
 | |
| 
 | |
|     if (c === p("%") &&
 | |
|       (!infra.isASCIIHex(this.input[this.pointer + 1]) ||
 | |
|         !infra.isASCIIHex(this.input[this.pointer + 2]))) {
 | |
|       this.parseError = true;
 | |
|     }
 | |
| 
 | |
|     this.buffer += cStr;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| URLStateMachine.prototype["parse fragment"] = function parseFragment(c) {
 | |
|   if (!isNaN(c)) {
 | |
|     // TODO: If c is not a URL code point and not "%", parse error.
 | |
|     if (c === p("%") &&
 | |
|       (!infra.isASCIIHex(this.input[this.pointer + 1]) ||
 | |
|         !infra.isASCIIHex(this.input[this.pointer + 2]))) {
 | |
|       this.parseError = true;
 | |
|     }
 | |
| 
 | |
|     this.url.fragment += utf8PercentEncodeCodePoint(c, isFragmentPercentEncode);
 | |
|   }
 | |
| 
 | |
|   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}`;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (url.host === null && !hasAnOpaquePath(url) && url.path.length > 1 && url.path[0] === "") {
 | |
|     output += "/.";
 | |
|   }
 | |
|   output += serializePath(url);
 | |
| 
 | |
|   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;
 | |
| }
 | |
| 
 | |
| function serializePath(url) {
 | |
|   if (hasAnOpaquePath(url)) {
 | |
|     return url.path;
 | |
|   }
 | |
| 
 | |
|   let output = "";
 | |
|   for (const segment of url.path) {
 | |
|     output += `/${segment}`;
 | |
|   }
 | |
|   return output;
 | |
| }
 | |
| 
 | |
| module.exports.serializeURL = serializeURL;
 | |
| 
 | |
| module.exports.serializePath = serializePath;
 | |
| 
 | |
| module.exports.serializeURLOrigin = function (url) {
 | |
|   // https://url.spec.whatwg.org/#concept-url-origin
 | |
|   switch (url.scheme) {
 | |
|     case "blob": {
 | |
|       const pathURL = module.exports.parseURL(serializePath(url));
 | |
|       if (pathURL === null) {
 | |
|         return "null";
 | |
|       }
 | |
|       if (pathURL.scheme !== "http" && pathURL.scheme !== "https") {
 | |
|         return "null";
 | |
|       }
 | |
|       return module.exports.serializeURLOrigin(pathURL);
 | |
|     }
 | |
|     case "ftp":
 | |
|     case "http":
 | |
|     case "https":
 | |
|     case "ws":
 | |
|     case "wss":
 | |
|       return serializeOrigin({
 | |
|         scheme: url.scheme,
 | |
|         host: url.host,
 | |
|         port: url.port
 | |
|       });
 | |
|     case "file":
 | |
|       // The spec says:
 | |
|       // > Unfortunate as it is, this is left as an exercise to the reader. When in doubt, return a new opaque origin.
 | |
|       // Browsers tested so far:
 | |
|       // - Chrome says "file://", but treats file: URLs as cross-origin for most (all?) purposes; see e.g.
 | |
|       //   https://bugs.chromium.org/p/chromium/issues/detail?id=37586
 | |
|       // - Firefox says "null", but treats file: URLs as same-origin sometimes based on directory stuff; see
 | |
|       //   https://developer.mozilla.org/en-US/docs/Archive/Misc_top_level/Same-origin_policy_for_file:_URIs
 | |
|       return "null";
 | |
|     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 null;
 | |
|   }
 | |
| 
 | |
|   return usm.url;
 | |
| };
 | |
| 
 | |
| module.exports.setTheUsername = function (url, username) {
 | |
|   url.username = utf8PercentEncodeString(username, isUserinfoPercentEncode);
 | |
| };
 | |
| 
 | |
| module.exports.setThePassword = function (url, password) {
 | |
|   url.password = utf8PercentEncodeString(password, isUserinfoPercentEncode);
 | |
| };
 | |
| 
 | |
| module.exports.serializeHost = serializeHost;
 | |
| 
 | |
| module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort;
 | |
| 
 | |
| module.exports.hasAnOpaquePath = hasAnOpaquePath;
 | |
| 
 | |
| 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 });
 | |
| };
 |