projen
Version:
CDK for software projects
2,585 lines • 106 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setValidationErrorCallback = setValidationErrorCallback;
exports.newURL = newURL;
exports.isSpecialScheme = isSpecialScheme;
exports.isSpecial = isSpecial;
exports.defaultPort = defaultPort;
exports.includesCredentials = includesCredentials;
exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort;
exports.urlSerializer = urlSerializer;
exports.hostSerializer = hostSerializer;
exports.iPv4Serializer = iPv4Serializer;
exports.iPv6Serializer = iPv6Serializer;
exports.urlParser = urlParser;
exports.basicURLParser = basicURLParser;
exports.setTheUsername = setTheUsername;
exports.setThePassword = setThePassword;
exports.isSingleDotPathSegment = isSingleDotPathSegment;
exports.isDoubleDotPathSegment = isDoubleDotPathSegment;
exports.shorten = shorten;
exports.isNormalizedWindowsDriveLetter = isNormalizedWindowsDriveLetter;
exports.isWindowsDriveLetter = isWindowsDriveLetter;
exports.startsWithAWindowsDriveLetter = startsWithAWindowsDriveLetter;
exports.hostParser = hostParser;
exports.iPv4NumberParser = iPv4NumberParser;
exports.iPv4Parser = iPv4Parser;
exports.iPv6Parser = iPv6Parser;
exports.opaqueHostParser = opaqueHostParser;
exports.resolveABlobURL = resolveABlobURL;
exports.percentEncode = percentEncode;
exports.percentDecode = percentDecode;
exports.stringPercentDecode = stringPercentDecode;
exports.utf8PercentEncode = utf8PercentEncode;
exports.hostEquals = hostEquals;
exports.urlEquals = urlEquals;
exports.urlEncodedStringParser = urlEncodedStringParser;
exports.urlEncodedParser = urlEncodedParser;
exports.urlEncodedByteSerializer = urlEncodedByteSerializer;
exports.urlEncodedSerializer = urlEncodedSerializer;
exports.origin = origin;
exports.domainToASCII = domainToASCII;
exports.domainToUnicode = domainToUnicode;
exports.asciiSerializationOfAnOrigin = asciiSerializationOfAnOrigin;
const util_1 = require("@oozcitak/util");
const interfaces_1 = require("./interfaces");
const infra_1 = require("@oozcitak/infra");
const url_1 = require("url");
let _validationErrorCallback;
/**
* Default ports for a special URL scheme.
*/
const _defaultPorts = {
"ftp": 21,
"file": null,
"http": 80,
"https": 443,
"ws": 80,
"wss": 443
};
/**
* The C0 control percent-encode set are the C0 controls and all code points
* greater than U+007E (~).
*/
const _c0ControlPercentEncodeSet = /[\0-\x1F\x7F-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/;
/**
* The fragment percent-encode set is the C0 control percent-encode set and
* U+0020 SPACE, U+0022 ("), U+003C (<), U+003E (>), and U+0060 (`).
*/
const _fragmentPercentEncodeSet = /[ "<>`]|[\0-\x1F\x7F-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/;
/**
* The path percent-encode set is the fragment percent-encode set and
* U+0023 (#), U+003F (?), U+007B ({), and U+007D (}).
*/
const _pathPercentEncodeSet = /[ "<>`#?{}]|[\0-\x1F\x7F-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/;
/**
* The userinfo percent-encode set is the path percent-encode set and
* U+002F (/), U+003A (:), U+003B (;), U+003D (=), U+0040 (@), U+005B ([),
* U+005C (\), U+005D (]), U+005E (^), and U+007C (|).
*/
const _userInfoPercentEncodeSet = /[ "<>`#?{}/:;=@\[\]\\\^\|]|[\0-\x1F\x7F-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/;
/**
* The URL code points are ASCII alphanumeric, U+0021 (!), U+0024 ($),
* U+0026 (&), U+0027 ('), U+0028 LEFT PARENTHESIS, U+0029 RIGHT PARENTHESIS,
* U+002A (*), U+002B (+), U+002C (,), U+002D (-), U+002E (.), U+002F (/),
* U+003A (:), U+003B (;), U+003D (=), U+003F (?), U+0040 (@), U+005F (_),
* U+007E (~), and code points in the range U+00A0 to U+10FFFD, inclusive,
* excluding surrogates and noncharacters.
*/
const _urlCodePoints = /[0-9A-Za-z!\$&-\/:;=\?@_~\xA0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFD]|[\uD800-\uD83E\uD840-\uD87E\uD880-\uD8BE\uD8C0-\uD8FE\uD900-\uD93E\uD940-\uD97E\uD980-\uD9BE\uD9C0-\uD9FE\uDA00-\uDA3E\uDA40-\uDA7E\uDA80-\uDABE\uDAC0-\uDAFE\uDB00-\uDB3E\uDB40-\uDB7E\uDB80-\uDBBE\uDBC0-\uDBFE][\uDC00-\uDFFF]|[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDC00-\uDFFD]/;
/**
* A forbidden host code point is U+0000 NULL, U+0009 TAB, U+000A LF,
* U+000D CR, U+0020 SPACE, U+0023 (#), U+0025 (%), U+002F (/), U+003A (:),
* U+003F (?), U+0040 (@), U+005B ([), U+005C (\), or U+005D (]).
*/
const _forbiddenHostCodePoint = /[\0\t\f\r #%/:?@\[\\\]]/;
/**
* Sets the callback function for validation errors.
*
* @param validationErrorCallback - a callback function to be called when a
* validation error occurs
*/
function setValidationErrorCallback(validationErrorCallback) {
_validationErrorCallback = validationErrorCallback;
}
/**
* Generates a validation error.
*
* @param message - error message
*/
function validationError(message) {
if (_validationErrorCallback !== undefined) {
_validationErrorCallback.call(null, "Validation Error: " + message);
}
}
/**
* Creates a new URL.
*/
function newURL() {
return {
scheme: '',
username: '',
password: '',
host: null,
port: null,
path: [],
query: null,
fragment: null,
_cannotBeABaseURLFlag: false,
_blobURLEntry: null
};
}
/**
* Determines if the scheme is a special scheme.
*
* @param scheme - a scheme
*/
function isSpecialScheme(scheme) {
return (scheme in _defaultPorts);
}
/**
* Determines if the URL has a special scheme.
*
* @param url - an URL
*/
function isSpecial(url) {
return isSpecialScheme(url.scheme);
}
/**
* Returns the default port for a special scheme.
*
* @param scheme - a scheme
*/
function defaultPort(scheme) {
return _defaultPorts[scheme] || null;
}
/**
* Determines if the URL has credentials.
*
* @param url - an URL
*/
function includesCredentials(url) {
return url.username !== '' || url.password !== '';
}
/**
* Determines if an URL cannot have credentials.
*
* @param url - an URL
*/
function cannotHaveAUsernamePasswordPort(url) {
/**
* A URL cannot have a username/password/port if its host is null or the
* empty string, its cannot-be-a-base-URL flag is set, or its scheme is
* "file".
*/
return (url.host === null || url.host === "" || url._cannotBeABaseURLFlag ||
url.scheme === "file");
}
/**
* Serializes an URL into a string.
*
* @param url - an URL
*/
function urlSerializer(url, excludeFragmentFlag = false) {
/**
* 1. Let output be url’s scheme and U+003A (:) concatenated.
*/
let output = url.scheme + ':';
/**
* 2. If url’s host is non-null:
*/
if (url.host !== null) {
/**
* 2.1. Append "//" to output.
*/
output += '//';
/**
* 2.2. If url includes credentials, then:
*/
if (includesCredentials(url)) {
/**
* 2.2.1. Append url’s username to output.
* 2.2.2. If url’s password is not the empty string, then append U+003A (:),
* followed by url’s password, to output.
* 2.2.3. Append U+0040 (@) to output.
*/
output += url.username;
if (url.password !== '') {
output += ':' + url.password;
}
output += '@';
}
/**
* 2.3. Append url’s host, serialized, to output.
* 2.4. If url’s port is non-null, append U+003A (:) followed by url’s port,
* serialized, to output.
*/
output += hostSerializer(url.host);
if (url.port !== null) {
output += ':' + url.port;
}
}
else if (url.host === null && url.scheme === "file") {
/**
* 3. Otherwise, if url’s host is null and url’s scheme is "file", append "//" to output.
*/
output += '//';
}
/**
* 4. If url’s cannot-be-a-base-URL flag is set, append url’s path[0] to
* output.
* 5. Otherwise, then for each string in url’s path, append U+002F (/)
* followed by the string to output.
*/
if (url._cannotBeABaseURLFlag) {
output += url.path[0];
}
else {
for (const str of url.path) {
output += '/' + str;
}
}
/**
* 6. If url’s query is non-null, append U+003F (?), followed by url’s
* query, to output.
* 7. If the exclude fragment flag is unset and url’s fragment is non-null,
* append U+0023 (#), followed by url’s fragment, to output.
* 8. Return output.
*/
if (url.query !== null) {
output += '?' + url.query;
}
if (!excludeFragmentFlag && url.fragment !== null) {
output += '#' + url.fragment;
}
return output;
}
/**
* Serializes a host into a string.
*
* @param host - a host
*/
function hostSerializer(host) {
/**
* 1. If host is an IPv4 address, return the result of running the IPv4
* serializer on host.
* 2. Otherwise, if host is an IPv6 address, return U+005B ([), followed
* by the result of running the IPv6 serializer on host, followed by
* U+005D (]).
* 3. Otherwise, host is a domain, opaque host, or empty host, return host.
*/
if ((0, util_1.isNumber)(host)) {
return iPv4Serializer(host);
}
else if ((0, util_1.isArray)(host)) {
return '[' + iPv6Serializer(host) + ']';
}
else {
return host;
}
}
/**
* Serializes an IPv4 address into a string.
*
* @param address - an IPv4 address
*/
function iPv4Serializer(address) {
/**
* 1. Let output be the empty string.
* 2. Let n be the value of address.
* 3. For each i in the range 1 to 4, inclusive:
* 3.1. Prepend n % 256, serialized, to output.
* 3.2. If i is not 4, then prepend U+002E (.) to output.
* 3.3. Set n to floor(n / 256).
* 4. Return output.
*/
let output = "";
let n = address;
for (let i = 1; i <= 4; i++) {
output = (n % 256).toString() + output;
if (i !== 4) {
output = '.' + output;
}
n = Math.floor(n / 256);
}
return output;
}
/**
* Serializes an IPv6 address into a string.
*
* @param address - an IPv6 address represented as a list of eight numbers
*/
function iPv6Serializer(address) {
/**
* 1. Let output be the empty string.
* 2. Let compress be an index to the first IPv6 piece in the first longest
* sequences of address’s IPv6 pieces that are 0.
* In 0:f:0:0:f:f:0:0 it would point to the second 0.
* 3. If there is no sequence of address’s IPv6 pieces that are 0 that is
* longer than 1, then set compress to null.
*/
let output = "";
let compress = null;
let lastIndex = -1;
let count = 0;
let lastCount = 0;
for (let i = 0; i < 8; i++) {
if (address[i] !== 0)
continue;
count = 1;
for (let j = i + 1; j < 8; j++) {
if (address[j] !== 0)
break;
count++;
continue;
}
if (count > lastCount) {
lastCount = count;
lastIndex = i;
}
}
if (lastCount > 1)
compress = lastIndex;
/**
* 4. Let ignore0 be false.
* 5. For each pieceIndex in the range 0 to 7, inclusive:
*/
let ignore0 = false;
for (let pieceIndex = 0; pieceIndex < 8; pieceIndex++) {
/**
* 5.1. If ignore0 is true and address[pieceIndex] is 0, then continue.
* 5.2. Otherwise, if ignore0 is true, set ignore0 to false.
* 5.3. If compress is pieceIndex, then:
*/
if (ignore0 && address[pieceIndex] === 0)
continue;
if (ignore0)
ignore0 = false;
if (compress === pieceIndex) {
/**
* 5.3.1. Let separator be "::" if pieceIndex is 0, and U+003A (:) otherwise.
* 5.3.2. Append separator to output.
* 5.3.3. Set ignore0 to true and continue.
*/
output += (pieceIndex === 0 ? '::' : ':');
ignore0 = true;
continue;
}
/**
* 5.4. Append address[pieceIndex], represented as the shortest possible
* lowercase hexadecimal number, to output.
* 5.5. If pieceIndex is not 7, then append U+003A (:) to output.
*/
output += address[pieceIndex].toString(16);
if (pieceIndex !== 7)
output += ':';
}
/**
* 6. Return output.
*/
return output;
}
/**
* Parses an URL string.
*
* @param input - input string
* @param baseURL - base URL
* @param encodingOverride - encoding override
*/
function urlParser(input, baseURL, encodingOverride) {
/**
* 1. Let url be the result of running the basic URL parser on input with
* base, and encoding override as provided.
* 2. If url is failure, return failure.
* 3. If url’s scheme is not "blob", return url.
* 4. Set url’s blob URL entry to the result of resolving the blob URL url,
* if that did not return failure, and null otherwise.
* 5. Return url.
*/
const url = basicURLParser(input, baseURL, encodingOverride);
if (url === null)
return null;
if (url.scheme !== "blob")
return url;
const entry = resolveABlobURL(url);
if (entry !== null) {
url._blobURLEntry = entry;
}
else {
url._blobURLEntry = null;
}
return url;
}
/**
* Parses an URL string.
*
* @param input - input string
* @param baseURL - base URL
* @param encodingOverride - encoding override
*/
function basicURLParser(input, baseURL, encodingOverride, url, stateOverride) {
/**
* 1. If url is not given:
* 1.1. Set url to a new URL.
* 1.2. If input contains any leading or trailing C0 control or space,
* validation error.
* 1.3. Remove any leading and trailing C0 control or space from input.
*/
if (url === undefined) {
url = newURL();
// leading
const leadingControlOrSpace = /^[\u0000-\u001F\u0020]+/;
const trailingControlOrSpace = /[\u0000-\u001F\u0020]+$/;
if (leadingControlOrSpace.test(input) || trailingControlOrSpace.test(input)) {
validationError("Input string contains leading or trailing control characters or space.");
}
input = input.replace(leadingControlOrSpace, '');
input = input.replace(trailingControlOrSpace, '');
}
/**
* 2. If input contains any ASCII tab or newline, validation error.
* 3. Remove all ASCII tab or newline from input.
*/
const tabOrNewline = /[\u0009\u000A\u000D]/g;
if (tabOrNewline.test(input)) {
validationError("Input string contains tab or newline characters.");
}
input = input.replace(tabOrNewline, '');
/**
* 4. Let state be state override if given, or scheme start state otherwise.
* 5. If base is not given, set it to null.
* 6. Let encoding be UTF-8.
* 7. If encoding override is given, set encoding to the result of getting
* an output encoding from encoding override.
*/
let state = (stateOverride === undefined ? interfaces_1.ParserState.SchemeStart : stateOverride);
if (baseURL === undefined)
baseURL = null;
let encoding = (encodingOverride === undefined ||
encodingOverride === "replacement" || encodingOverride === "UTF-16BE" ||
encodingOverride === "UTF-16LE" ? "UTF-8" : encodingOverride);
/**
* 8. Let buffer be the empty string.
* 9. Let the @ flag, [] flag, and passwordTokenSeenFlag be unset.
* 10. Let pointer be a pointer to first code point in input.
*/
let buffer = "";
let atFlag = false;
let arrayFlag = false;
let passwordTokenSeenFlag = false;
const EOF = "";
const walker = new util_1.StringWalker(input);
/**
* 11. Keep running the following state machine by switching on state. If
* after a run pointer points to the EOF code point, go to the next step.
* Otherwise, increase pointer by one and continue with the state machine.
*/
while (true) {
switch (state) {
case interfaces_1.ParserState.SchemeStart:
/**
* 1. If c is an ASCII alpha, append c, lowercased, to buffer, and set
* state to scheme state.
* 2. Otherwise, if state override is not given, set state to no scheme
* state, and decrease pointer by one.
* 3. Otherwise, validation error, return failure.
*/
if (infra_1.codePoint.ASCIIAlpha.test(walker.c())) {
buffer += walker.c().toLowerCase();
state = interfaces_1.ParserState.Scheme;
}
else if (stateOverride === undefined) {
state = interfaces_1.ParserState.NoScheme;
walker.pointer--;
}
else {
validationError("Invalid scheme start character.");
return null;
}
break;
case interfaces_1.ParserState.Scheme:
/**
* 1. If c is an ASCII alphanumeric, U+002B (+), U+002D (-), or U+002E
* (.), append c, lowercased, to buffer.
*/
if (infra_1.codePoint.ASCIIAlphanumeric.test(walker.c()) ||
walker.c() === '+' || walker.c() === '-' || walker.c() === '.') {
buffer += walker.c().toLowerCase();
}
else if (walker.c() === ':') {
/**
* 2. Otherwise, if c is U+003A (:), then:
* 2.1. If state override is given, then:
* 2.1.1. If url’s scheme is a special scheme and buffer is not a
* special scheme, then return.
* 2.1.2. If url’s scheme is not a special scheme and buffer is a
* special scheme, then return.
* 2.1.3. If url includes credentials or has a non-null port, and
* buffer is "file", then return.
* 2.1.4. If url’s scheme is "file" and its host is an empty host or
* null, then return.
*/
if (stateOverride !== undefined) {
if (isSpecialScheme(url.scheme) && !isSpecialScheme(buffer))
return url;
if (!isSpecialScheme(url.scheme) && isSpecialScheme(buffer))
return url;
if ((includesCredentials(url) || url.port !== null) && buffer === "file")
return url;
if (url.scheme === "file" && (url.host === "" || url.host === null))
return url;
}
/**
* 2.2. Set url’s scheme to buffer.
*/
url.scheme = buffer;
/**
* 2.3. If state override is given, then:
* 2.3.1. If url’s port is url’s scheme’s default port, then set
* url’s port to null.
* 2.3.2. Return.
*/
if (stateOverride !== undefined) {
if (url.port === defaultPort(url.scheme)) {
url.port = null;
}
return url;
}
/**
* 2.4. Set buffer to the empty string.
*/
buffer = "";
if (url.scheme === "file") {
/**
* 2.5. If url’s scheme is "file", then:
* 2.5.1. If remaining does not start with "//", validation error.
* 2.5.2. Set state to file state.
*/
if (!walker.remaining().startsWith("//")) {
validationError("Invalid file URL scheme, '//' expected.");
}
state = interfaces_1.ParserState.File;
}
else if (isSpecial(url) && baseURL !== null && baseURL.scheme === url.scheme) {
/**
* 2.6. Otherwise, if url is special, base is non-null, and base’s
* scheme is equal to url’s scheme, set state to special relative
* or authority state.
*/
state = interfaces_1.ParserState.SpecialRelativeOrAuthority;
}
else if (isSpecial(url)) {
/**
* 2.7. Otherwise, if url is special, set state to special
* authority slashes state.
*/
state = interfaces_1.ParserState.SpecialAuthoritySlashes;
}
else if (walker.remaining().startsWith("/")) {
/**
* 2.8. Otherwise, if remaining starts with an U+002F (/), set state
* to path or authority state and increase pointer by one.
*/
state = interfaces_1.ParserState.PathOrAuthority;
walker.pointer++;
}
else {
/**
* 2.9. Otherwise, set url’s cannot-be-a-base-URL flag, append an
* empty string to url’s path, and set state to
* cannot-be-a-base-URL path state.
*/
url._cannotBeABaseURLFlag = true;
url.path.push("");
state = interfaces_1.ParserState.CannotBeABaseURLPath;
}
}
else if (stateOverride === undefined) {
/**
* 3. Otherwise, if state override is not given, set buffer to the
* empty string, state to no scheme state, and start over (from the
* first code point in input).
*/
buffer = "";
state = interfaces_1.ParserState.NoScheme;
walker.pointer = 0;
continue;
}
else {
/**
* 4. Otherwise, validation error, return failure.
*/
validationError("Invalid input string.");
return null;
}
break;
case interfaces_1.ParserState.NoScheme:
/**
* 1. If base is null, or base’s cannot-be-a-base-URL flag is set
* and c is not U+0023 (#), validation error, return failure.
* 2. Otherwise, if base’s cannot-be-a-base-URL flag is set and
* c is U+0023 (#), set url’s scheme to base’s scheme, url’s path to
* a copy of base’s path, url’s query to base’s query, url’s
* fragment to the empty string, set url’s cannot-be-a-base-URL
* flag, and set state to fragment state.
* 3. Otherwise, if base’s scheme is not "file", set state to
* relative state and decrease pointer by one.
* 4. Otherwise, set state to file state and decrease pointer by one.
*/
if (baseURL === null || (baseURL._cannotBeABaseURLFlag && walker.c() !== '#')) {
validationError("Invalid input string.");
return null;
}
else if (baseURL._cannotBeABaseURLFlag && walker.c() === '#') {
url.scheme = baseURL.scheme;
url.path = infra_1.list.clone(baseURL.path);
url.query = baseURL.query;
url.fragment = "";
url._cannotBeABaseURLFlag = true;
state = interfaces_1.ParserState.Fragment;
}
else if (baseURL.scheme !== "file") {
state = interfaces_1.ParserState.Relative;
walker.pointer--;
}
else {
state = interfaces_1.ParserState.File;
walker.pointer--;
}
break;
case interfaces_1.ParserState.SpecialRelativeOrAuthority:
/**
* If c is U+002F (/) and remaining starts with U+002F (/), then set
* state to special authority ignore slashes state and increase
* pointer by one.
* Otherwise, validation error, set state to relative state and
* decrease pointer by one.
*/
if (walker.c() === '/' && walker.remaining().startsWith('/')) {
state = interfaces_1.ParserState.SpecialAuthorityIgnoreSlashes;
walker.pointer++;
}
else {
validationError("Invalid input string.");
state = interfaces_1.ParserState.Relative;
walker.pointer--;
}
break;
case interfaces_1.ParserState.PathOrAuthority:
/**
* If c is U+002F (/), then set state to authority state.
* Otherwise, set state to path state, and decrease pointer by one.
*/
if (walker.c() === '/') {
state = interfaces_1.ParserState.Authority;
}
else {
state = interfaces_1.ParserState.Path;
walker.pointer--;
}
break;
case interfaces_1.ParserState.Relative:
/**
* Set url’s scheme to base’s scheme, and then, switching on c:
*/
if (baseURL === null) {
throw new Error("Invalid parser state. Base URL is null.");
}
url.scheme = baseURL.scheme;
switch (walker.c()) {
case EOF: // EOF
/**
* Set url’s username to base’s username, url’s password to base’s
* password, url’s host to base’s host, url’s port to base’s port,
* url’s path to a copy of base’s path, and url’s query to base’s
* query.
*/
url.username = baseURL.username;
url.password = baseURL.password;
url.host = baseURL.host;
url.port = baseURL.port;
url.path = infra_1.list.clone(baseURL.path);
url.query = baseURL.query;
break;
case '/':
/**
* Set state to relative slash state.
*/
state = interfaces_1.ParserState.RelativeSlash;
break;
case '?':
/**
* Set url’s username to base’s username, url’s password to base’s
* password, url’s host to base’s host, url’s port to base’s port,
* url’s path to a copy of base’s path, url’s query to the empty
* string, and state to query state.
*/
url.username = baseURL.username;
url.password = baseURL.password;
url.host = baseURL.host;
url.port = baseURL.port;
url.path = infra_1.list.clone(baseURL.path);
url.query = "";
state = interfaces_1.ParserState.Query;
break;
case '#':
/**
* Set url’s username to base’s username, url’s password to base’s
* password, url’s host to base’s host, url’s port to base’s port,
* url’s path to a copy of base’s path, url’s query to base’s
* query, url’s fragment to the empty string, and state to
* fragment state.
*/
url.username = baseURL.username;
url.password = baseURL.password;
url.host = baseURL.host;
url.port = baseURL.port;
url.path = infra_1.list.clone(baseURL.path);
url.query = baseURL.query;
url.fragment = "";
state = interfaces_1.ParserState.Fragment;
break;
default:
/**
* If url is special and c is U+005C (\), validation error,
* set state to relative slash state.
* Otherwise, run these steps:
* 1. Set url’s username to base’s username, url’s password to
* base’s password, url’s host to base’s host, url’s port to
* base’s port, url’s path to a copy of base’s path, and then
* remove url’s path’s last item, if any.
* 2. Set state to path state, and decrease pointer by one.
*/
if (isSpecial(url) && walker.c() === '\\') {
validationError("Invalid input string.");
state = interfaces_1.ParserState.RelativeSlash;
}
else {
url.username = baseURL.username;
url.password = baseURL.password;
url.host = baseURL.host;
url.port = baseURL.port;
url.path = infra_1.list.clone(baseURL.path);
if (url.path.length !== 0)
url.path.splice(url.path.length - 1, 1);
state = interfaces_1.ParserState.Path;
walker.pointer--;
}
break;
}
break;
case interfaces_1.ParserState.RelativeSlash:
/**
* 1. If url is special and c is U+002F (/) or U+005C (\), then:
* 1.1. If c is U+005C (\), validation error.
* 1.2. Set state to special authority ignore slashes state.
* 2. Otherwise, if c is U+002F (/), then set state to authority state.
* 3. Otherwise, set url’s username to base’s username, url’s password
* to base’s password, url’s host to base’s host, url’s port to base’s
* port, state to path state, and then, decrease pointer by one.
*/
if (isSpecial(url) && (walker.c() === '/' || walker.c() === '\\')) {
if (walker.c() === '\\') {
validationError("Invalid input string.");
}
state = interfaces_1.ParserState.SpecialAuthorityIgnoreSlashes;
}
else if (walker.c() === '/') {
state = interfaces_1.ParserState.Authority;
}
else {
if (baseURL === null) {
throw new Error("Invalid parser state. Base URL is null.");
}
url.username = baseURL.username;
url.password = baseURL.password;
url.host = baseURL.host;
url.port = baseURL.port;
state = interfaces_1.ParserState.Path;
walker.pointer--;
}
break;
case interfaces_1.ParserState.SpecialAuthoritySlashes:
/**
* If c is U+002F (/) and remaining starts with U+002F (/), then set
* state to special authority ignore slashes state and increase
* pointer by one.
* Otherwise, validation error, set state to special authority ignore
* slashes state, and decrease pointer by one.
*/
if (walker.c() === '/' && walker.remaining().startsWith('/')) {
state = interfaces_1.ParserState.SpecialAuthorityIgnoreSlashes;
walker.pointer++;
}
else {
validationError("Expected '//'.");
state = interfaces_1.ParserState.SpecialAuthorityIgnoreSlashes;
walker.pointer--;
}
break;
case interfaces_1.ParserState.SpecialAuthorityIgnoreSlashes:
/**
* If c is neither U+002F (/) nor U+005C (\), then set state to
* authority state and decrease pointer by one.
* Otherwise, validation error.
*/
if (walker.c() !== '/' && walker.c() !== '\\') {
state = interfaces_1.ParserState.Authority;
walker.pointer--;
}
else {
validationError("Unexpected '/' or '\\'.");
}
break;
case interfaces_1.ParserState.Authority:
/**
* 1. If c is U+0040 (@), then:
*/
if (walker.c() === '@') {
/**
* 1.1. Validation error.
* 1.2. If the @ flag is set, prepend "%40" to buffer.
* 1.3. Set the @ flag.
* 1.4. For each codePoint in buffer:
*/
validationError("Unexpected '@'.");
if (atFlag)
buffer = '%40' + buffer;
atFlag = true;
for (const codePoint of buffer) {
/**
* 1.4.1. If codePoint is U+003A (:) and passwordTokenSeenFlag is
* unset, then set passwordTokenSeenFlag and continue.
* 1.4.2. Let encodedCodePoints be the result of running UTF-8
* percent encode codePoint using the userinfo percent-encode set.
* 1.4.3. If passwordTokenSeenFlag is set, then append
* encodedCodePoints to url’s password.
* 1.4.4. Otherwise, append encodedCodePoints to url’s username.
*/
if (codePoint === ':' && !passwordTokenSeenFlag) {
passwordTokenSeenFlag = true;
continue;
}
const encodedCodePoints = utf8PercentEncode(codePoint, _userInfoPercentEncodeSet);
if (passwordTokenSeenFlag) {
url.password += encodedCodePoints;
}
else {
url.username += encodedCodePoints;
}
}
/**
* 1.5. Set buffer to the empty string.
*/
buffer = "";
}
else if (walker.c() === EOF || walker.c() === '/' || walker.c() === '?' || walker.c() === '#' ||
(isSpecial(url) && walker.c() === '\\')) {
/**
* 2. Otherwise, if one of the following is true
* - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#)
* - url is special and c is U+005C (\)
* then:
* 2.1. If @ flag is set and buffer is the empty string, validation
* error, return failure.
* 2.2. Decrease pointer by the number of code points in buffer plus
* one, set buffer to the empty string, and set state to host state.
*/
if (atFlag && buffer === "") {
validationError("Invalid input string.");
return null;
}
walker.pointer -= (buffer.length + 1);
buffer = "";
state = interfaces_1.ParserState.Host;
}
else {
/**
* 3. Otherwise, append c to buffer.
*/
buffer += walker.c();
}
break;
case interfaces_1.ParserState.Host:
case interfaces_1.ParserState.Hostname:
if (stateOverride !== undefined && url.scheme === "file") {
/**
* 1. If state override is given and url’s scheme is "file", then
* decrease pointer by one and set state to file host state.
*/
walker.pointer--;
state = interfaces_1.ParserState.FileHost;
}
else if (walker.c() === ':' && !arrayFlag) {
/**
* 2. Otherwise, if c is U+003A (:) and the [] flag is unset, then:
* 2.1. If buffer is the empty string, validation error, return
* failure.
* 2.2. Let host be the result of host parsing buffer with url is
* not special.
* 2.3. If host is failure, then return failure.
* 2.4. Set url’s host to host, buffer to the empty string, and
* state to port state.
* 2.5. If state override is given and state override is hostname
* state, then return.
*/
if (buffer === "") {
validationError("Invalid input string.");
return null;
}
const host = hostParser(buffer, !isSpecial(url));
if (host === null)
return null;
url.host = host;
buffer = "";
state = interfaces_1.ParserState.Port;
if (stateOverride === interfaces_1.ParserState.Hostname)
return url;
}
else if (walker.c() === EOF || walker.c() === '/' || walker.c() === '?' || walker.c() === '#' ||
(isSpecial(url) && walker.c() === '\\')) {
/**
* 3. Otherwise, if one of the following is true
* - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#)
* - url is special and c is U+005C (\)
* then decrease pointer by one, and then:
* 3.1. If url is special and buffer is the empty string, validation
* error, return failure.
* 3.2. Otherwise, if state override is given, buffer is the empty
* string, and either url includes credentials or url’s port is
* non-null, validation error, return.
* 3.3. Let host be the result of host parsing buffer with url is
* not special.
* 3.4. If host is failure, then return failure.
* 3.5. Set url’s host to host, buffer to the empty string, and
* state to path start state.
* 3.6. If state override is given, then return.
*/
walker.pointer--;
if (isSpecial(url) && buffer === "") {
validationError("Invalid input string.");
return null;
}
else if (stateOverride !== undefined && buffer === "" &&
(includesCredentials(url) || url.port !== null)) {
validationError("Invalid input string.");
return url;
}
const host = hostParser(buffer, !isSpecial(url));
if (host === null)
return null;
url.host = host;
buffer = "";
state = interfaces_1.ParserState.PathStart;
if (stateOverride !== undefined)
return url;
}
else {
/**
* 4. Otherwise:
* 4.1. If c is U+005B ([), then set the [] flag.
* 4.2. If c is U+005D (]), then unset the [] flag.
* 4.3. Append c to buffer.
*/
if (walker.c() === '[')
arrayFlag = true;
if (walker.c() === ']')
arrayFlag = false;
buffer += walker.c();
}
break;
case interfaces_1.ParserState.Port:
if (infra_1.codePoint.ASCIIDigit.test(walker.c())) {
/**
* 1. If c is an ASCII digit, append c to buffer.
*/
buffer += walker.c();
}
else if (walker.c() === EOF || walker.c() === '/' || walker.c() === '?' || walker.c() === '#' ||
(isSpecial(url) && walker.c() === '\\') || stateOverride) {
/**
* 2. Otherwise, if one of the following is true
* - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#)
* - url is special and c is U+005C (\)
* - state override is given
* then:
*/
if (buffer !== "") {
/**
* 2.1. If buffer is not the empty string, then:
* 2.1.1. Let port be the mathematical integer value that is
* represented by buffer in radix-10 using ASCII digits for digits
* with values 0 through 9.
* 2.1.2. If port is greater than 2**16 − 1, validation error,
* return failure.
* 2.1.3. Set url’s port to null, if port is url’s scheme’s default
* port, and to port otherwise.
* 2.1.4. Set buffer to the empty string.
*/
if (buffer !== "") {
const port = parseInt(buffer, 10);
if (port > Math.pow(2, 16) - 1) {
validationError("Invalid port number.");
return null;
}
url.port = (port === defaultPort(url.scheme) ? null : port);
buffer = "";
}
}
/**
* 2.2. If state override is given, then return.
* 2.3. Set state to path start state, and decrease pointer by one.
*/
if (stateOverride !== undefined) {
return url;
}
state = interfaces_1.ParserState.PathStart;
walker.pointer--;
}
else {
/**
* 3. Otherwise, validation error, return failure.
*/
validationError("Invalid input string.");
return null;
}
break;
case interfaces_1.ParserState.File:
/**
* 1. Set url’s scheme to "file".
*/
url.scheme = "file";
if (walker.c() === '/' || walker.c() === '\\') {
/**
* 2. If c is U+002F (/) or U+005C (\), then:
* 2.1. If c is U+005C (\), validation error.
* 2.2. Set state to file slash state.
*/
if (walker.c() === '\\') {
validationError("Invalid input string.");
}
state = interfaces_1.ParserState.FileSlash;
}
else if (baseURL !== null && baseURL.scheme === "file") {
/**
* 3. Otherwise, if base is non-null and base’s scheme is "file",
* switch on c:
*/
switch (walker.c()) {
case EOF:
/**
* Set url’s host to base’s host, url’s path to a copy of base’s
* path, and url’s query to base’s query.
*/
url.host = baseURL.host;
url.path = infra_1.list.clone(baseURL.path);
url.query = baseURL.query;
break;
case '?':
/**
* Set url’s host to base’s host, url’s path to a copy of base’s
* path, url’s query to the empty string, and state to query
* state.
*/
url.host = baseURL.host;
url.path = infra_1.list.clone(baseURL.path);
url.query = "";
state = interfaces_1.ParserState.Query;
break;
case '#':
/**
* Set url’s host to base’s host, url’s path to a copy of base’s
* path, url’s query to base’s query, url’s fragment to the
* empty string, and state to fragment state.
*/
url.host = baseURL.host;
url.path = infra_1.list.clone(baseURL.path);
url.query = baseURL.query;
url.fragment = "";
state = interfaces_1.ParserState.Fragment;
break;
default:
/**
* 1. If the substring from pointer in input does not start
* with a Windows drive letter, then set url’s host to base’s
* host, url’s path to a copy of base’s path, and then shorten
* url’s path.
* _Note:_ is a (platform-independent) Windows drive letter
* quirk.
* 2. Otherwise, validation error.
* 3. Set state to path state, and decrease pointer by one.
*/
if (!startsWithAWindowsDriveLetter(walker.substring())) {
url.host = baseURL.host;
url.path = infra_1.list.clone(baseURL.path);
shorten(url);
}
else {
validationError("Unexpected windows drive letter in input string.");
}
state = interfaces_1.ParserState.Path;
walker.pointer--;
break;
}
}
else {
/**
* 4. Otherwise, set state to path state, and decrease pointer by
* one.
*/
state = interfaces_1.ParserState.Path;
walker.pointer--;
}
break;
case interfaces_1.ParserState.FileSlash:
if (walker.c() === '/' || walker.c() === '\\') {
/**
* 1. If c is U+002F (/) or U+005C (\), then:
* 1.1. If c is U+005C (\), validation error.
* 1.2. Set state to file host state.
*/
if (walker.c() === '\\') {
validationError("Invalid input string.");
}
state = interfaces_1.ParserState.FileHost;
}
else {
/**
* 2. Otherwise:
* 2.1. If base is non-null, base’s scheme is "file", and the
* substring from pointer in input does not start with a Windows
* drive letter, then:
* 2.1.1. If base’s path[0] is a normalized Windows drive letter,
* then append base’s path[0] to url’s path.
* _Note:_ is a (platform-independent) Windows drive letter
* quirk. Both url’s and base’s host are null under these conditions
* and therefore not copied.
* 2.1.2. Otherwise, set url’s host to base’s host.
* 2.2. Set state to path state, and decrease pointer by one.
*/
if (baseURL !== null && baseURL.scheme === "file" &&
!startsWithAWindowsDriveLetter(walker.substring())) {
if (isNormalizedWindowsDriveLetter(baseURL.path[0])) {
url.path.push(baseURL.path[0]);
}
else {
url.host = baseURL.host;
}
}
state = interfaces_1.ParserState.Path;
walker.pointer--;
}
break;
case interfaces_1.ParserState.FileHost:
if (walker.c() === EOF || walker.c() === '/' || walker.c() === '\\' ||
walker.c() === '?' || walker.c() === '#') {
/**
* 1. If c is the EOF code point, U+002F (/), U+005C (\), U+003F (?),
* or U+0023 (#), then decrease pointer by one and then:
*/
walker.pointer--;
if (stateOverride === undefined && isWindowsDriveLetter(buffer)) {
/**
* 1.1. If state override is not given and buffer is a Windows drive
* letter, validation error, set state to path state.
* _Note:_ is a (platform-independent) Windows drive letter
* quirk. buffer is not reset here and instead used in the path state.
*/
validationError("Unexpected windows drive letter in input string.");
state = interfaces_1.ParserState.Path;
}
else if (buffer === "") {
/**
* 1.2. Otherwise, if buffer is the empty string, then:
* 1.2.1. Set url’s host to the empty string.
* 1.2.2. If state override is given, then return.
* 1.2.3. Set state to path start state.
*/
url.host = "";
if (stateOverride !== undefined)
return url;
state = interfaces_1.ParserState.PathStart;
}
else {
/**
* 1.3. Otherwise, run these steps:
* 1.3.1. Let host be the result of host parsing buffer with url
* is not special.
* 1.3.2. If host is failure, then return failure.
* 1.3.3. If host is "localhost", then set host to the empty
* string.
* 1.3.4. Set url’s host to host.
* 1.3.5. If state override is given, then return.
* 1.3.6. Set buffer to the empty string and state to path start
* state.
*/
let host = hostParser(buffer, !isSpecial(url));
if (host === null)
return null;
if (host === "localhost")
host = "";
url.host = host;
if (stateOverride !== undefined)
return url;
buffer = "";
state = interfaces_1.ParserState.PathStart;
}
}
else {
/**
* 2. Otherwise, append c to buffer.
*/
buffer += walker.c();
}
break;
case interfaces_1.ParserState.PathStart:
if (isSpecial(url)) {
/**
* 1. If url is special, then:
* 1.1. If c is U+005C (\), validation error.
* 1.2. Set state to path state.
* 1.3. If c is neither U+002F (/) nor U+005C (\), then decrease
* pointer by one.
*/
if (walker.c() === '\\') {
validationError("Invalid input string.");
}
state = interfaces_1.ParserState.Path;
if (walker.c() !== '/' && walker.c() !== '\\')
walker.pointer--;
}
else if (stateOverride === undefined && walker.c() === '?') {
/**
* 2. Otherwise, if state override is not given and c is U+003F (?),
* set url’s query to the empty string and state to query state.
*/
url.query = "";
state = interfaces_1.ParserState.Query;
}
else if (stateOverride === undefined && walker.c() === '#') {
/**
* 3. Otherwise, if state override is not given and c is U+0023 (#),
* set url’s fragment to the empty string and state to fragment
* state.
*/
url.fragment = "";
state = interfaces_1.ParserState.Fragment;
}
else if (walker.c() !== EOF) {
/**
* 4. Otherwise, if c is not the EOF code point:
* 4.1. Set state to path state.
* 4.2. If c is not U+002F (/), then decrease pointer by one.
*/
state = interfaces_1.ParserState.Path;
if (walker.c() !== '/')
walker.pointer--;
}
break;
case interfaces_1.ParserState.Path:
if ((walker.c() === EOF || walker.c() === '/') ||
(isSpecial(url) && walker.c() === '\\') ||
(stateOverride === undefined && (walker.c() === '?' || walker.c() === '#'))) {
/**
* 1. If one of the following is true
* - c is the EOF code point or U+002F (/)
* - url is special and c is U+005C (\)
* - state override is not given and c is U+003F (?) or U+0023 (#)
* then:
*/
if (isSpecial(url) && walker.c() === '\\') {
/**
* 1.1 If url is special and c is U+005C (\), validation error.
*/
validationError("Invalid input string.");
}
if (isDoubleDotPathSegment(buffer)) {
/**
* 1.2. If buffer is a double-dot path segment, shorten url’s path,
* and then if neither c is U+002F (/), nor url is special and c is
* U+005C (\), append the empty string to url’s path.
*/
shorten(url);
if (walker.c() !== '/' && !(isSpecial(url) && walker.c() === '\\')) {
url.path.push("");
}
}
else if (isSingleDotPathSegment(buffer) && walker.c() !== '/' &&
!(isSpecial(url) && walker.c() === '\\')) {
/**
* 1.3. Otherwise, if buffer is a single-dot path segment and if
* neither c is U+002F (/), nor url is special and c is U+005C (\),
* append the empty string to url’s path.
*/
url.path.push("");
}
else if (!isSingleDotPathSegment(buffer)) {
/**
* 1.4. Otherwise, if buffer is not a single-dot path segment, then:
*/
if (url.scheme === "file" && url.path.length === 0 &&
isWindowsDriveLetter(buffer)) {
/**
* 1.4.1. If url’s scheme is "file", url’s path is empty, and
* buffer is a Windows drive letter, then:
* 1.4.1.1. If url’s host is neither the empty string nor null,
* validation error, set url’s host to the empty string.
* 1.4.1.2. Replace the second code point in buffer with U+003A (:).
* _Note:_ is a (platform-independent) Windows drive letter quirk.
*/
if (url.host !== null && url.host !== "") {
validationError("Invalid input string.");
url.host = "";
}
const bufferCodePoints = Array.from(buffer);
buffer = bufferCodePoints.slice(0, 1) + ':' + bufferCodePoints.slice(2);
}
/**
* 1.4.2. Append buffer to url’s path.
*/
url.path.push(buffer);
}
/**
* 1.5. Set buffer to the empty string.
*/
buffer = "";
/**
* 1.6. If url’s scheme is "file" and c is the EOF code point,
* U+003F (?), or U+0023 (#), then while url’s path’s size is
* greater than 1 and url’s path[0] is the empty string, validation
* error, remove the first item from url’s path.
*/
if (url.scheme === "file" && (walker.c() === EOF || walker.c() === '?' || walker.c() === '#')) {
while (url.path.length > 1 && url.path[0] === "") {
validationError("Invalid input string.");
url.path.splice(0, 1);
}
}
/**
* 1.7. If c is U+003F (?), then set url’s query to the empty string
* and state to query state.
* 1.8. If c is U+0023 (#), then set url’s fragment to the empty
* string and state to fragment state.
*/
if (walker.c() === '?') {
url.query = "";
state = interfaces_1.ParserState.Query;
}
if (walker.c() === '#') {
url.fragment = "";
state = interfaces_1.ParserState.Fragment;
}
}
else {
/**
* 2. Otherwise, run these steps:
* 2.1. If c is not a URL code point and not U+0025 (%), validation
* error.
* 2.2. If c is U+0025 (%) and remaining does not start with two
* ASCII hex digits, validation error.
* 2.3. UTF-8 percent encode c using the path percent-encode set,
* and append the result to buffer.
*/
if (!_urlCodePoints.test(walker.c()) && walker.c() !== '%') {
validationError("Character is not a URL code point or a percent encoded character.");
}
if (walker.c() === '%' && !/^[0-9a-fA-F][0-9a-fA-F]/.test(walker.remaining())) {
validationError("Percent encoded character must be followed by two hex digits.");
}
buffer += utf8PercentEncode(walker.c(), _pathPercentEncodeSet);
}
break;
case interfaces_1.ParserState.CannotBeABaseURLPath:
/**
* 1. If c is U+003F (?), then set url’s query to the empty string and
* state to query state.
* 2. Otherwise, if c is U+0023 (#), then set url’s fragment to the
* empty string and state to fragment state.
* 3. Otherwise:
* 3.1. If c is not the EOF code point, not a URL code point, and not
* U+0025 (%), validation error.
* 3.2. If c is U+0025 (%) and remaining does not start with two ASCII
* hex digits, validation error.
* 3.3. If c is not the EOF code point, UTF-8 percent encode c using
* the C0 control percent-encode set, and append the result to url’s
* path[0].
*/
if (walker.c() === '?') {
url.query = "";
state = interfaces_1.ParserState.Query;
}
else if (walker.c() === '#') {
url.fragment = "";
state = interfaces_1.ParserState.Fragment;
}
else {
if (walker.c() !== EOF && !_urlCodePoints.test(walker.c()) && walker.c() !== '%') {
validationError("Character is not a URL code point or a percent encoded character.");
}
if (walker.c() === '%' && !/^[0-9a-fA-F][0-9a-fA-F]/.test(walker.remaining())) {
validationError("Percent encoded character must be followed by two hex digits.");
}
if (walker.c() !== EOF) {
url.path[0] += utf8PercentEncode(walker.c(), _c0ControlPercentEncodeSet);
}
}
break;
case interfaces_1.ParserState.Query:
/**
* 1. If encoding is not UTF-8 and one of the following is true
* - url is not special
* - url’s scheme is "ws" or "wss"
* then set encoding to UTF-8.
*/
if (encoding !== "UTF-8" && (!isSpecial(url) ||
url.scheme === "ws" || url.scheme === "wss")) {
encoding = "UTF-8";
}
if (stateOverride === undefined && walker.c() === '#') {
/**
* 2. If state override is not given and c is U+0023 (#), then set
* url’s fragment to the empty string and state to fragment state.
*/
url.fragment = "";
state = interfaces_1.ParserState.Fragment;
}
else if (walker.c() !== EOF) {
/**
* 3. Otherwise, if c is not the EOF code point:
* 3.1. If c is not a URL code point and not U+0025 (%), validation
* error.
*/
if (!_urlCodePoints.test(walker.c()) && walker.c() !== '%') {
validationError("Character is not a URL code point or a percent encoded character.");
}
/**
* 3.2. If c is U+0025 (%) and remaining does not start with two
* ASCII hex digits, validation error.
*/
if (walker.c() === '%' && !/^[0-9a-fA-F][0-9a-fA-F]/.test(walker.remaining())) {
validationError("Percent encoded character must be followed by two hex digits.");
}
/**
* 3.3. Let bytes be the result of encoding c using encoding.
*/
if (encoding.toUpperCase() !== "UTF-8") {
throw new Error("Only UTF-8 encoding is supported.");
}
let bytes = (0, util_1.utf8Encode)(walker.c());
/**
* 3.4. If bytes starts with `&#` and ends with 0x3B (;), then:
*/
if (bytes.length >= 3 && bytes[0] === 38 && bytes[1] === 35 &&
bytes[bytes.length - 1] === 59) {
/**
* 3.4.1. Replace `&#` at the start of bytes with `%26%23`.
* 3.4.2. Replace 0x3B (;) at the end of bytes with `%3B`.
* 3.4.4. Append bytes, isomorphic decoded, to url’s query.
* _Note:_ can happen when encoding code points using a
* non-UTF-8 encoding.
*/
bytes = bytes.subarray(2, bytes.length - 1);
url.query += "%26%23" + infra_1.byteSequence.isomorphicDecode(bytes) + "%3B";
}
else {
/**
* 3.5. Otherwise, for each byte in bytes:
* 3.5.1. If one of the following is true
* - byte is less than 0x21 (!)
* - byte is greater than 0x7E (~)
* - byte is 0x22 ("), 0x23 (#), 0x3C (<), or 0x3E (>)
* - byte is 0x27 (') and url is special
* then append byte, percent encoded, to url’s query.
* 3.5.2. Otherwise, append a code point whose value is byte to
* url’s query.
*/
for (const byte of bytes) {
if (byte < 0x21 || byte > 0x7E || byte === 0x22 ||
byte === 0x23 || byte === 0x3C || byte === 0x3E ||
(byte === 0x27 && isSpecial(url))) {
url.query += percentEncode(byte);
}
else {
url.query += String.fromCharCode(byte);
}
}
}
}
break;
case interfaces_1.ParserState.Fragment:
/**
* Switching on c:
* - The EOF code point
* Do nothing.
* - U+0000 NULL
* Validation error.
* - Otherwise
* 1. If c is not a URL code point and not U+0025 (%), validation
* error.
* 2. If c is U+0025 (%) and remaining does not start with two ASCII
* hex digits, validation error.
* 3. UTF-8 percent encode c using the fragment percent-encode set and
* append the result to url’s fragment.
*/
if (walker.c() === EOF) {
//
}
else if (walker.c() === "\u0000") {
validationError("NULL character in input string.");
}
else {
if (!_urlCodePoints.test(walker.c()) && walker.c() !== '%') {
validationError("Unexpected character in fragment string.");
}
if (walker.c() === '%' && !/^[A-Za-z0-9][A-Za-z0-9]/.test(walker.remaining())) {
validationError("Unexpected character in fragment string.");
}
url.fragment += utf8PercentEncode(walker.c(), _fragmentPercentEncodeSet);
}
break;
}
if (walker.eof)
break;
else
walker.pointer++;
}
/**
* 12. Return url.
*/
return url;
}
/**
* Sets a URL's username.
*
* @param url - a URL
* @param username - username string
*/
function setTheUsername(url, username) {
/**
* 1. Set url’s username to the empty string.
* 2. For each code point in username, UTF-8 percent encode it using the
* userinfo percent-encode set, and append the result to url’s username.
*/
let result = "";
for (const codePoint of username) {
result += utf8PercentEncode(codePoint, _userInfoPercentEncodeSet);
}
url.username = result;
}
/**
* Sets a URL's password.
*
* @param url - a URL
* @param username - password string
*/
function setThePassword(url, password) {
/**
* 1. Set url’s password to the empty string.
* 2. For each code point in password, UTF-8 percent encode it using the
* userinfo percent-encode set, and append the result to url’s password.
*/
let result = "";
for (const codePoint of password) {
result += utf8PercentEncode(codePoint, _userInfoPercentEncodeSet);
}
url.password = result;
}
/**
* Determines if the string represents a single dot path.
*
* @param str - a string
*/
function isSingleDotPathSegment(str) {
return str === '.' || str.toLowerCase() === "%2e";
}
/**
* Determines if the string represents a double dot path.
*
* @param str - a string
*/
function isDoubleDotPathSegment(str) {
const lowerStr = str.toLowerCase();
return lowerStr === ".." || lowerStr === ".%2e" ||
lowerStr === "%2e." || lowerStr === "%2e%2e";
}
/**
* Shorten's URL's path.
*
* @param url - an URL
*/
function shorten(url) {
/**
* 1. Let path be url’s path.
* 2. If path is empty, then return.
* 3. If url’s scheme is "file", path’s size is 1, and path[0] is a
* normalized Windows drive letter, then return.
* 4. Remove path’s last item.
*/
const path = url.path;
if (path.length === 0)
return;
if (url.scheme === "file" && path.length === 1 &&
isNormalizedWindowsDriveLetter(path[0]))
return;
url.path.splice(url.path.length - 1, 1);
}
/**
* Determines if a string is a normalized Windows drive letter.
*
* @param str - a string
*/
function isNormalizedWindowsDriveLetter(str) {
/**
* A normalized Windows drive letter is a Windows drive letter of which the
* second code point is U+003A (:).
*/
return str.length >= 2 && infra_1.codePoint.ASCIIAlpha.test(str[0]) &&
str[1] === ':';
}
/**
* Determines if a string is a Windows drive letter.
*
* @param str - a string
*/
function isWindowsDriveLetter(str) {
/**
* A Windows drive letter is two code points, of which the first is an ASCII
* alpha and the second is either U+003A (:) or U+007C (|).
*/
return str.length >= 2 && infra_1.codePoint.ASCIIAlpha.test(str[0]) &&
(str[1] === ':' || str[1] === '|');
}
/**
* Determines if a string starts with a Windows drive letter.
*
* @param str - a string
*/
function startsWithAWindowsDriveLetter(str) {
/**
* A string starts with a Windows drive letter if all of the following are
* true:
* - its length is greater than or equal to 2
* - its first two code points are a Windows drive letter
* - its length is 2 or its third code point is U+002F (/), U+005C (\),
* U+003F (?), or U+0023 (#).
*/
return str.length >= 2 && isWindowsDriveLetter(str) &&
(str.length === 2 || (str[2] === '/' || str[2] === '\\' ||
str[2] === '?' || str[2] === '#'));
}
/**
* Parses a host string.
*
* @param input - input string
* @param isNotSpecial - `true` if the source URL is not special; otherwise
* `false`.
*/
function hostParser(input, isNotSpecial = false) {
/**
* 1. If isNotSpecial is not given, then set isNotSpecial to false.
* 2. If input starts with U+005B ([), then:
* 2.1. If input does not end with U+005D (]), validation error, return
* failure.
* 2.2. Return the result of IPv6 parsing input with its leading U+005B ([)
* and trailing U+005D (]) removed.
*/
if (input.startsWith('[')) {
if (!input.endsWith(']')) {
validationError("Expected ']' after '['.");
return null;
}
return iPv6Parser(input.substring(1, input.length - 1));
}
/**
* 3. If isNotSpecial is true, then return the result of opaque-host parsing
* input.
*/
if (isNotSpecial) {
return opaqueHostParser(input);
}
/**
* 4. Let domain be the result of running UTF-8 decode without BOM on the
* string percent decoding of input.
* _Note:_ Alternatively UTF-8 decode without BOM or fail can be used,
* coupled with an early return for failure, as domain to ASCII fails
* on U+FFFD REPLACEMENT CHARACTER.
*/
const domain = (0, util_1.utf8Decode)(stringPercentDecode(input));
/**
* 5. Let asciiDomain be the result of running domain to ASCII on domain.
* 6. If asciiDomain is failure, validation error, return failure.
* 7. If asciiDomain contains a forbidden host code point, validation error,
* return failure.
*/
const asciiDomain = domainToASCII(domain);
if (asciiDomain === null) {
validationError("Invalid domain.");
return null;
}
if (_forbiddenHostCodePoint.test(asciiDomain)) {
validationError("Invalid domain.");
return null;
}
/**
* 8. Let ipv4Host be the result of IPv4 parsing asciiDomain.
* 9. If ipv4Host is an IPv4 address or failure, return ipv4Host.
* 10. Return asciiDomain.
*/
const ipv4Host = iPv4Parser(asciiDomain);
if (ipv4Host === null || (0, util_1.isNumber)(ipv4Host))
return ipv4Host;
return asciiDomain;
}
/**
* Parses a string containing an IP v4 address.
*
* @param input - input string
* @param isNotSpecial - `true` if the source URL is not special; otherwise
* `false`.
*/
function iPv4NumberParser(input, validationErrorFlag = { value: false }) {
/**
* 1. Let R be 10.
*/
let R = 10;
if (input.startsWith("0x") || input.startsWith("0X")) {
/**
* 2. If input contains at least two code points and the first two code
* points are either "0x" or "0X", then:
* 2.1. Set validationErrorFlag.
* 2.2. Remove the first two code points from input.
* 2.3. Set R to 16.
*/
validationErrorFlag.value = true;
input = input.substr(2);
R = 16;
}
else if (input.length >= 2 && input[0] === '0') {
/**
* 3. Otherwise, if input contains at least two code points and the first
* code point is U+0030 (0), then:
* 3.1. Set validationErrorFlag.
* 3.2. Remove the first code point from input.
* 3.3. Set R to 8.
*/
validationErrorFlag.value = true;
input = input.substr(1);
R = 8;
}
/**
* 4. If input is the empty string, then return zero.
* 5. If input contains a code point that is not a radix-R digit, then
* return failure.
*/
if (input === "")
return 0;
const radixRDigits = (R === 10 ? /^[0-9]+$/ : (R === 16 ? /^[0-9A-Fa-f]+$/ : /^[0-7]+$/));
if (!radixRDigits.test(input))
return null;
/**
* 6. Return the mathematical integer value that is represented by input in
* radix-R notation, using ASCII hex digits for digits with values
* 0 through 15.
*/
return parseInt(input, R);
}
/**
* Parses a string containing an IP v4 address.
*
* @param input - input string
*/
function iPv4Parser(input) {
/**
* 1. Let validationErrorFlag be unset.
* 2. Let parts be input split on U+002E (.).
*/
const validationErrorFlag = { value: false };
const parts = input.split('.');
/**
* 3. If the last item in parts is the empty string, then:
* 3.1. Set validationErrorFlag.
* 3.2. If parts has more than one item, then remove the last item from
* parts.
*/
if (parts[parts.length - 1] === "") {
validationErrorFlag.value = true;
if (parts.length > 1)
parts.pop();
}
/**
* 4. If parts has more than four items, return input.
*/
if (parts.length > 4)
return input;
/**
* 5. Let numbers be the empty list.
* 6. For each part in parts:
* 6.1. If part is the empty string, return input.
* 6.2. Let n be the result of parsing part using validationErrorFlag.
* 6.3. If n is failure, return input.
* 6.4. Append n to numbers.
*/
const numbers = [];
for (const part of parts) {
if (part === "")
return input;
const n = iPv4NumberParser(part, validationErrorFlag);
if (n === null)
return input;
numbers.push(n);
}
/**
* 7. If validationErrorFlag is set, validation error.
* 8. If any item in numbers is greater than 255, validation error.
* 9. If any but the last item in numbers is greater than 255, return
* failure.
* 10. If the last item in numbers is greater than or equal to
* 256**(5 − the number of items in numbers), validation error, return failure.
*/
if (validationErrorFlag.value)
validationError("Invalid IP v4 address.");
for (let i = 0; i < numbers.length; i++) {
const item = numbers[i];
if (item > 255) {
validationError("Invalid IP v4 address.");
if (i < numbers.length - 1)
return null;
}
}
if (numbers[numbers.length - 1] >= Math.pow(256, 5 - numbers.length)) {
validationError("Invalid IP v4 address.");
return null;
}
/**
* 11. Let ipv4 be the last item in numbers.
* 12. Remove the last item from numbers.
*/
let ipv4 = numbers[numbers.length - 1];
numbers.pop();
/**
* 13. Let counter be zero.
* 14. For each n in numbers:
* 14.2. Increment ipv4 by n × 256**(3 − counter).
* 14.2. Increment counter by 1.
*/
let counter = 0;
for (const n of numbers) {
ipv4 += n * Math.pow(256, 3 - counter);
counter++;
}
/**
* 15. Return ipv4.
*/
return ipv4;
}
/**
* Parses a string containing an IP v6 address.
*
* @param input - input string
*/
function iPv6Parser(input) {
/**
* 1. Let address be a new IPv6 address whose IPv6 pieces are all 0.
* 2. Let pieceIndex be 0.
* 3. Let compress be null.
* 4. Let pointer be a pointer into input, initially 0 (pointing to the
* first code point).
*/
const EOF = "";
const address = [0, 0, 0, 0, 0, 0, 0, 0];
let pieceIndex = 0;
let compress = null;
const walker = new util_1.StringWalker(input);
/**
* 5. If c is U+003A (:), then:
* 5.1. If remaining does not start with U+003A (:), validation error,
* return failure.
* 5.2. Increase pointer by 2.
* 5.3. Increase pieceIndex by 1 and then set compress to pieceIndex.
*/
if (walker.c() === ':') {
if (!walker.remaining().startsWith(':')) {
validationError("Invalid IP v6 address.");
return null;
}
walker.pointer += 2;
pieceIndex += 1;
compress = pieceIndex;
}
/**
* 6. While c is not the EOF code point:
*/
while (walker.c() !== EOF) {
/**
* 6.1. If pieceIndex is 8, validation error, return failure.
*/
if (pieceIndex === 8) {
validationError("Invalid IP v6 address.");
return null;
}
/**
* 6.2. If c is U+003A (:), then:
* 6.2.1. If compress is non-null, validation error, return failure.
* 6.2.2. Increase pointer and pieceIndex by 1, set compress to pieceIndex,
* and then continue.
*/
if (walker.c() === ':') {
if (compress !== null) {
validationError("Invalid IP v6 address.");
return null;
}
walker.pointer++;
pieceIndex++;
compress = pieceIndex;
continue;
}
/**
* 6.3. Let value and length be 0.
* 6.4. While length is less than 4 and c is an ASCII hex digit, set value
* to value × 0x10 + c interpreted as hexadecimal number, and increase
* pointer and length by 1.
*/
let value = 0;
let length = 0;
while (length < 4 && infra_1.codePoint.ASCIIHexDigit.test(walker.c())) {
value = value * 0x10 + parseInt(walker.c(), 16);
walker.pointer++;
length++;
}
/**
* 6.5. If c is U+002E (.), then:
*/
if (walker.c() === '.') {
/**
* 6.5.1. If length is 0, validation error, return failure.
* 6.5.2. Decrease pointer by length.
* 6.5.3. If pieceIndex is greater than 6, validation error, return
* failure.
* 6.5.4. Let numbersSeen be 0.
*/
if (length === 0) {
validationError("Invalid IP v6 address.");
return null;
}
walker.pointer -= length;
if (pieceIndex > 6) {
validationError("Invalid IP v6 address.");
return null;
}
let numbersSeen = 0;
/**
* 6.5.5. While c is not the EOF code point:
*/
while (walker.c() !== EOF) {
/**
* 6.5.5.1. Let ipv4Piece be null.
*/
let ipv4Piece = null;
/**
* 6.5.5.2. If numbersSeen is greater than 0, then:
* 6.5.5.2.1. If c is a U+002E (.) and numbersSeen is less than 4, then
* increase pointer by 1.
* 6.5.5.2.1. Otherwise, validation error, return failure.
*/
if (numbersSeen > 0) {
if (walker.c() === '.' && numbersSeen < 4) {
walker.pointer++;
}
else {
validationError("Invalid IP v6 address.");
return null;
}
}
/**
* 6.5.5.3. If c is not an ASCII digit, validation error, return
* failure.
*/
if (!infra_1.codePoint.ASCIIDigit.test(walker.c())) {
validationError("Invalid IP v6 address.");
return null;
}
/**
* 6.5.5.4. While c is an ASCII digit:
*/
while (infra_1.codePoint.ASCIIDigit.test(walker.c())) {
/**
* 6.5.5.4.1. Let number be c interpreted as decimal number.
*/
const number = parseInt(walker.c(), 10);
/**
* 6.5.5.4.2. If ipv4Piece is null, then set ipv4Piece to number.
* Otherwise, if ipv4Piece is 0, validation error, return failure.
* Otherwise, set ipv4Piece to ipv4Piece × 10 + number.
*/
if (ipv4Piece === null) {
ipv4Piece = number;
}
else if (ipv4Piece === 0) {
validationError("Invalid IP v6 address.");
return null;
}
else {
ipv4Piece = ipv4Piece * 10 + number;
}
/**
* 6.5.5.4.3. If ipv4Piece is greater than 255, validation error, return failure.
* 6.5.5.4.4. Increase pointer by 1.
*/
if (ipv4Piece > 255) {
validationError("Invalid IP v6 address.");
return null;
}
walker.pointer++;
}
/**
* 6.5.5.5. Set address[pieceIndex] to address[pieceIndex] × 0x100 + ipv4Piece.
* 6.5.5.6. Increase numbersSeen by 1.
* 6.5.5.7. If numbersSeen is 2 or 4, then increase pieceIndex by 1.
*/
if (ipv4Piece === null) {
validationError("Invalid IP v6 address.");
return null;
}
address[pieceIndex] = address[pieceIndex] * 0x100 + ipv4Piece;
numbersSeen++;
if (numbersSeen === 2 || numbersSeen === 4)
pieceIndex++;
}
/**
* 6.5.6. If numbersSeen is not 4, validation error, return failure.
*/
if (numbersSeen !== 4) {
validationError("Invalid IP v6 address.");
return null;
}
/**
* 6.5.7. Break.
*/
break;
}
else if (walker.c() === ':') {
/**
* 6.6. Otherwise, if c is U+003A (:):
* 6.6.1. Increase pointer by 1.
* 6.6.2. If c is the EOF code point, validation error, return failure.
*/
walker.pointer++;
if (walker.c() === EOF) {
validationError("Invalid IP v6 address.");
return null;
}
}
else if (walker.c() !== EOF) {
/**
* 6.7. Otherwise, if c is not the EOF code point, validation error,
* return failure.
*/
validationError("Invalid IP v6 address.");
return null;
}
/**
* 6.8. Set address[pieceIndex] to value.
* 6.9. Increase pieceIndex by 1.
*/
address[pieceIndex] = value;
pieceIndex++;
}
/**
* 7. If compress is non-null, then:
* 7.1. Let swaps be pieceIndex − compress.
* 7.2. Set pieceIndex to 7.
* 7.3. While pieceIndex is not 0 and swaps is greater than 0, swap
* address[pieceIndex] with address[compress + swaps − 1], and then decrease
* both pieceIndex and swaps by 1.
*/
if (compress !== null) {
let swaps = pieceIndex - compress;
pieceIndex = 7;
while (pieceIndex !== 0 && swaps > 0) {
[address[pieceIndex], address[compress + swaps - 1]] =
[address[compress + swaps - 1], address[pieceIndex]];
pieceIndex--;
swaps--;
}
}
else if (compress === null && pieceIndex !== 8) {
/**
* 8. Otherwise, if compress is null and pieceIndex is not 8,
* validation error, return failure.
*/
validationError("Invalid IP v6 address.");
return null;
}
/**
* 9. Return address.
*/
return address;
}
/**
* Parses an opaque host string.
*
* @param input - a string
*/
function opaqueHostParser(input) {
/**
* 1. If input contains a forbidden host code point excluding U+0025 (%),
* validation error, return failure.
* 2. Let output be the empty string.
* 3. For each code point in input, UTF-8 percent encode it using the C0
* control percent-encode set, and append the result to output.
* 4. Return output.
*/
const forbiddenChars = /[\x00\t\f\r #/:?@\[\\\]]/;
if (forbiddenChars.test(input)) {
validationError("Invalid host string.");
return null;
}
let output = "";
for (const codePoint of input) {
output += utf8PercentEncode(codePoint, _c0ControlPercentEncodeSet);
}
return output;
}
/**
* Resolves a Blob URL from the user agent's Blob URL store.
* function is not implemented.
* See: https://w3c.github.io/FileAPI/#blob-url-resolve
*
* @param url - an url
*/
function resolveABlobURL(url) {
return null;
}
/**
* Percent encodes a byte.
*
* @param value - a byte
*/
function percentEncode(value) {
/**
* To percent encode a byte into a percent-encoded byte, return a string
* consisting of U+0025 (%), followed by two ASCII upper hex digits
* representing byte.
*/
return '%' + ('00' + value.toString(16).toUpperCase()).slice(-2);
}
/**
* Percent decodes a byte sequence input.
*
* @param input - a byte sequence
*/
function percentDecode(input) {
const isHexDigit = (byte) => {
return (byte >= 0x30 && byte <= 0x39) || (byte >= 0x41 && byte <= 0x46) ||
(byte >= 0x61 && byte <= 0x66);
};
/**
* 1. Let output be an empty byte sequence.
* 2. For each byte byte in input:
*/
const output = new Uint8Array(input.length);
let n = 0;
for (let i = 0; i < input.length; i++) {
const byte = input[i];
/**
* 2.1. If byte is not 0x25 (%), then append byte to output.
* 2.2. Otherwise, if byte is 0x25 (%) and the next two bytes after byte
* in input are not in the ranges 0x30 (0) to 0x39 (9), 0x41 (A)
* to 0x46 (F), and 0x61 (a) to 0x66 (f), all inclusive, append byte
* to output.
* 2.3. Otherwise:
* 2.3.1. Let bytePoint be the two bytes after byte in input, decoded,
* and then interpreted as hexadecimal number.
* 2.3.2. Append a byte whose value is bytePoint to output.
* 2.3.3. Skip the next two bytes in input.
*/
if (byte !== 0x25) {
output[n] = byte;
n++;
}
else if (byte === 0x25 && i >= input.length - 2) {
output[n] = byte;
n++;
}
else if (byte === 0x25 && (!isHexDigit(input[i + 1]) || !isHexDigit(input[i + 2]))) {
output[n] = byte;
n++;
}
else {
const bytePoint = parseInt((0, util_1.utf8Decode)(Uint8Array.of(input[i + 1], input[i + 2])), 16);
output[n] = bytePoint;
n++;
i += 2;
}
}
return output.subarray(0, n);
}
/**
* String percent decodes a string.
*
* @param input - a string
*/
function stringPercentDecode(input) {
/**
* 1. Let bytes be the UTF-8 encoding of input.
* 2. Return the percent decoding of bytes.
*/
return percentDecode((0, util_1.utf8Encode)(input));
}
/**
* UTF-8 percent encodes a code point, using a percent encode set.
*
* @param codePoint - a code point
* @param percentEncodeSet - a percent encode set
*/
function utf8PercentEncode(codePoint, percentEncodeSet) {
/**
* 1. If codePoint is not in percentEncodeSet, then return codePoint.
* 2. Let bytes be the result of running UTF-8 encode on codePoint.
* 3. Percent encode each byte in bytes, and then return the results
* concatenated, in the same order.
*/
if (!percentEncodeSet.test(codePoint))
return codePoint;
const bytes = (0, util_1.utf8Encode)(codePoint);
let result = "";
for (const byte of bytes) {
result += percentEncode(byte);
}
return result;
}
/**
* Determines if two hosts are considered equal.
*
* @param hostA - a host
* @param hostB - a host
*/
function hostEquals(hostA, hostB) {
return hostA === hostB;
}
/**
* Determines if two URLs are considered equal.
*
* @param urlA - a URL
* @param urlB - a URL
* @param excludeFragmentsFlag - whether to ignore fragments while comparing
*/
function urlEquals(urlA, urlB, excludeFragmentsFlag = false) {
/**
* 1. Let serializedA be the result of serializing A, with the exclude
* fragment flag set if the exclude fragments flag is set.
* 2. Let serializedB be the result of serializing B, with the exclude
* fragment flag set if the exclude fragments flag is set.
* 3. Return true if serializedA is serializedB, and false otherwise.
*/
return urlSerializer(urlA, excludeFragmentsFlag) ===
urlSerializer(urlB, excludeFragmentsFlag);
}
/**
* Parses an `application/x-www-form-urlencoded` string.
*
* @param input - a string
*/
function urlEncodedStringParser(input) {
/**
* The application/x-www-form-urlencoded string parser takes a string input,
* UTF-8 encodes it, and then returns the result of
* application/x-www-form-urlencoded parsing it.
*/
return urlEncodedParser((0, util_1.utf8Encode)(input));
}
/**
* Parses `application/x-www-form-urlencoded` bytes.
*
* @param input - a byte sequence
*/
function urlEncodedParser(input) {
/**
* 1. Let sequences be the result of splitting input on 0x26 (&).
*/
const sequences = [];
let currentSequence = [];
for (const byte of input) {
if (byte === 0x26) {
sequences.push(Uint8Array.from(currentSequence));
currentSequence = [];
}
else {
currentSequence.push(byte);
}
}
if (currentSequence.length !== 0) {
sequences.push(Uint8Array.from(currentSequence));
}
/**
* 2. Let output be an initially empty list of name-value tuples where both name and value hold a string.
*/
const output = [];
/**
* 3. For each byte sequence bytes in sequences:
*/
for (const bytes of sequences) {
/**
* 3.1. If bytes is the empty byte sequence, then continue.
*/
if (bytes.length === 0)
continue;
/**
* 3.2. If bytes contains a 0x3D (=), then let name be the bytes from the
* start of bytes up to but excluding its first 0x3D (=), and let value be
* the bytes, if any, after the first 0x3D (=) up to the end of bytes.
* If 0x3D (=) is the first byte, then name will be the empty byte
* sequence. If it is the last, then value will be the empty byte sequence.
* 3.3. Otherwise, let name have the value of bytes and let value be the
* empty byte sequence.
*/
const index = bytes.indexOf(0x3D);
const name = (index !== -1 ? bytes.slice(0, index) : bytes);
const value = (index !== -1 ? bytes.slice(index + 1) : new Uint8Array());
/**
* 3.4. Replace any 0x2B (+) in name and value with 0x20 (SP).
*/
for (let i = 0; i < name.length; i++)
if (name[i] === 0x2B)
name[i] = 0x20;
for (let i = 0; i < value.length; i++)
if (value[i] === 0x2B)
value[i] = 0x20;
/**
* 3.5. Let nameString and valueString be the result of running UTF-8
* decode without BOM on the percent decoding of name and value,
* respectively.
*/
const nameString = (0, util_1.utf8Decode)(name);
const valueString = (0, util_1.utf8Decode)(value);
/**
* 3.6. Append (nameString, valueString) to output.
*/
output.push([nameString, valueString]);
}
/**
* 4. Return output.
*/
return output;
}
/**
* Serializes `application/x-www-form-urlencoded` bytes.
*
* @param input - a byte sequence
*/
function urlEncodedByteSerializer(input) {
/**
* 1. Let output be the empty string.
* 2. For each byte in input, depending on byte:
* 0x20 (SP)
* Append U+002B (+) to output.
*
* 0x2A (*)
* 0x2D (-)
* 0x2E (.)
* 0x30 (0) to 0x39 (9)
* 0x41 (A) to 0x5A (Z)
* 0x5F (_)
* 0x61 (a) to 0x7A (z)
* Append a code point whose value is byte to output.
*
* Otherwise
* Append byte, percent encoded, to output.
* 3. Return output.
*/
let output = "";
for (const byte of input) {
if (byte === 0x20) {
output += '+';
}
else if (byte === 0x2A || byte === 0x2D || byte === 0x2E ||
(byte >= 0x30 && byte <= 0x39) || (byte >= 0x41 && byte <= 0x5A) ||
byte === 0x5F || (byte >= 0x61 && byte <= 0x7A)) {
output += String.fromCodePoint(byte);
}
else {
output += percentEncode(byte);
}
}
return output;
}
/**
* Serializes `application/x-www-form-urlencoded` tuples.
*
* @param input - input tuple of name/value pairs
* @param encodingOverride: encoding override
*/
function urlEncodedSerializer(tuples, encodingOverride) {
/**
* 1. Let encoding be UTF-8.
* 2. If encoding override is given, set encoding to the result of getting
* an output encoding from encoding override.
*/
const encoding = (encodingOverride === undefined ||
encodingOverride === "replacement" || encodingOverride === "UTF-16BE" ||
encodingOverride === "UTF-16LE" ? "UTF-8" : encodingOverride);
if (encoding.toUpperCase() !== "UTF-8") {
throw new Error("Only UTF-8 encoding is supported.");
}
/**
* 3. Let output be the empty string.
*/
let output = "";
/**
* 4. For each tuple in tuples:
*/
for (const tuple of tuples) {
/**
* 4.1. Let name be the result of serializing the result of encoding
* tuple’s name, using encoding.
*/
const name = urlEncodedByteSerializer((0, util_1.utf8Encode)(tuple[0]));
/**
* 4.2. Let value be tuple’s value.
*/
let value = tuple[1];
/**
* TODO:
* 4.3. If value is a file, then set value to value’s filename.
*/
/**
* 4.4. Set value to the result of serializing the result of encoding
* value, using encoding.
*/
value = urlEncodedByteSerializer((0, util_1.utf8Encode)(value));
/**
* 4.5. If tuple is not the first pair in tuples, then append U+0026 (&)
* to output.
*/
if (output !== "")
output += '&';
/**
* 4.6. Append name, followed by U+003D (=), followed by value, to output.
*/
output += name + '=' + value;
}
/**
* 5. Return output.
*/
return output;
}
/**
* Returns a URL's origin.
*
* @param url - a URL
*/
function origin(url) {
/**
* A URL’s origin is the origin returned by running these steps, switching
* on URL’s scheme:
* "blob"
* 1. If URL’s blob URL entry is non-null, then return URL’s blob URL
* entry’s environment’s origin.
* 2. Let url be the result of parsing URL’s path[0].
* 3. Return a new opaque origin, if url is failure, and url’s origin
* otherwise.
* "ftp"
* "http"
* "https"
* "ws"
* "wss"
* Return a tuple consisting of URL’s scheme, URL’s host, URL’s port, and
* null.
* "file"
* Unfortunate as it is, is left as an exercise to the reader. When in
* doubt, return a new opaque origin.
* Otherwise
* Return a new opaque origin.
*/
switch (url.scheme) {
case "blob":
if (url._blobURLEntry !== null) {
// TODO: return URL’s blob URL entry’s environment’s origin.
}
const parsedURL = basicURLParser(url.path[0]);
if (parsedURL === null)
return interfaces_1.OpaqueOrigin;
else
return origin(parsedURL);
case "ftp":
case "http":
case "https":
case "ws":
case "wss":
return [url.scheme, url.host === null ? "" : url.host, url.port, null];
case "file":
return interfaces_1.OpaqueOrigin;
default:
return interfaces_1.OpaqueOrigin;
}
}
/**
* Converts a domain string to ASCII.
*
* @param domain - a domain string
*/
function domainToASCII(domain, beStrict = false) {
/**
* 1. If beStrict is not given, set it to false.
* 2. Let result be the result of running Unicode ToASCII with domain_name
* set to domain, UseSTD3ASCIIRules set to beStrict, CheckHyphens set to
* false, CheckBidi set to true, CheckJoiners set to true,
* Transitional_Processing set to false, and VerifyDnsLength set to beStrict.
* 3. If result is a failure value, validation error, return failure.
* 4. Return result.
*/
// Use node.js function
const result = (0, url_1.domainToASCII)(domain);
if (result === "") {
validationError("Invalid domain name.");
return null;
}
return result;
}
/**
* Converts a domain string to Unicode.
*
* @param domain - a domain string
*/
function domainToUnicode(domain, beStrict = false) {
/**
* 1. Let result be the result of running Unicode ToUnicode with domain_name
* set to domain, CheckHyphens set to false, CheckBidi set to true,
* CheckJoiners set to true, UseSTD3ASCIIRules set to false, and
* Transitional_Processing set to false.
* 2. Signify validation errors for any returned errors, and then,
* return result.
*/
// Use node.js function
const result = (0, url_1.domainToUnicode)(domain);
if (result === "") {
validationError("Invalid domain name.");
}
return result;
}
/**
* Serializes an origin.
* function is from the HTML spec:
* https://html.spec.whatwg.org/#ascii-serialisation-of-an-origin
*
* @param origin - an origin
*/
function asciiSerializationOfAnOrigin(origin) {
/**
* 1. If origin is an opaque origin, then return "null".
* 2. Otherwise, let result be origin's scheme.
* 3. Append "://" to result.
* 4. Append origin's host, serialized, to result.
* 5. If origin's port is non-null, append a U+003A COLON character (:),
* and origin's port, serialized, to result.
* 6. Return result.
*/
if (origin[0] === "" && origin[1] === "" && origin[2] === null && origin[3] === null) {
return "null";
}
let result = origin[0] + "://" + hostSerializer(origin[1]);
if (origin[2] !== null)
result += ":" + origin[2].toString();
return result;
}
//# sourceMappingURL=URLAlgorithm.js.map