“use strict”; const punycode = require(“punycode”); const tr46 = require(“tr46”);

const specialSchemes = {

ftp: 21,
file: null,
gopher: 70,
http: 80,
https: 443,
ws: 80,
wss: 443

};

const failure = Symbol(“failure”);

function countSymbols(str) {

return punycode.ucs2.decode(str).length;

}

function at(input, idx) {

const c = input[idx];
return isNaN(c) ? undefined : String.fromCodePoint(c);

}

function isASCIIDigit© {

return c >= 0x30 && c <= 0x39;

}

function isASCIIAlpha© {

return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);

}

function isASCIIAlphanumeric© {

return isASCIIAlpha(c) || isASCIIDigit(c);

}

function isASCIIHex© {

return isASCIIDigit(c) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66);

}

function isSingleDot(buffer) {

return buffer === "." || buffer.toLowerCase() === "%2e";

}

function isDoubleDot(buffer) {

buffer = buffer.toLowerCase();
return buffer === ".." || buffer === "%2e." || buffer === ".%2e" || buffer === "%2e%2e";

}

function isWindowsDriveLetterCodePoints(cp1, cp2) {

return isASCIIAlpha(cp1) && (cp2 === 58 || cp2 === 124);

}

function isWindowsDriveLetterString(string) {

return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && (string[1] === ":" || string[1] === "|");

}

function isNormalizedWindowsDriveLetterString(string) {

return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && string[1] === ":";

}

function containsForbiddenHostCodePoint(string) {

return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|%|\/|:|\?|@|\[|\\|\]/) !== -1;

}

function containsForbiddenHostCodePointExcludingPercent(string) {

return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|\/|:|\?|@|\[|\\|\]/) !== -1;

}

function isSpecialScheme(scheme) {

return specialSchemes[scheme] !== undefined;

}

function isSpecial(url) {

return isSpecialScheme(url.scheme);

}

function defaultPort(scheme) {

return specialSchemes[scheme];

}

function percentEncode© {

let hex = c.toString(16).toUpperCase();
if (hex.length === 1) {
  hex = "0" + hex;
}
return "%" + hex;

}

function utf8PercentEncode© {

const buf = new Buffer(c);
let str = "";
for (let i = 0; i < buf.length; ++i) {
  str += percentEncode(buf[i]);
}
return str;

}

function utf8PercentDecode(str) {

const input = new Buffer(str);
const output = [];
for (let i = 0; i < input.length; ++i) {
  if (input[i] !== 37) {
    output.push(input[i]);
  } else if (input[i] === 37 && isASCIIHex(input[i + 1]) && isASCIIHex(input[i + 2])) {
    output.push(parseInt(input.slice(i + 1, i + 3).toString(), 16));
    i += 2;
  } else {
    output.push(input[i]);
  }
}
return new Buffer(output).toString();

}

function isC0ControlPercentEncode© {

return c <= 0x1F || c > 0x7E;

}

const extraPathPercentEncodeSet = new Set([32, 34, 35, 60, 62, 63, 96, 123, 125]); function isPathPercentEncode© {

return isC0ControlPercentEncode(c) || extraPathPercentEncodeSet.has(c);

}

const extraUserinfoPercentEncodeSet =

new Set([47, 58, 59, 61, 64, 91, 92, 93, 94, 124]);

function isUserinfoPercentEncode© {

return isPathPercentEncode(c) || extraUserinfoPercentEncodeSet.has(c);

}

function percentEncodeChar(c, encodeSetPredicate) {

const cStr = String.fromCodePoint(c);
if (encodeSetPredicate(c)) {
  return utf8PercentEncode(cStr);
}
return cStr;

}

function parseIPv4Number(input) {

let R = 10;
if (input.length >= 2 && input.charAt(0) === "0" && input.charAt(1).toLowerCase() === "x") {
  input = input.substring(2);
  R = 16;
} else if (input.length >= 2 && input.charAt(0) === "0") {
  input = input.substring(1);
  R = 8;
}
if (input === "") {
  return 0;
}
const regex = R === 10 ? /[^0-9]/ : (R === 16 ? /[^0-9A-Fa-f]/ : /[^0-7]/);
if (regex.test(input)) {
  return failure;
}
return parseInt(input, R);

}

function parseIPv4(input) {

const parts = input.split(".");
if (parts[parts.length - 1] === "") {
  if (parts.length > 1) {
    parts.pop();
  }
}
if (parts.length > 4) {
  return input;
}
const numbers = [];
for (const part of parts) {
  if (part === "") {
    return input;
  }
  const n = parseIPv4Number(part);
  if (n === failure) {
    return input;
  }
  numbers.push(n);
}
for (let i = 0; i < numbers.length - 1; ++i) {
  if (numbers[i] > 255) {
    return failure;
  }
}
if (numbers[numbers.length - 1] >= Math.pow(256, 5 - numbers.length)) {
  return failure;
}
let ipv4 = numbers.pop();
let counter = 0;
for (const n of numbers) {
  ipv4 += n * Math.pow(256, 3 - counter);
  ++counter;
}
return ipv4;

}

function serializeIPv4(address) {

let output = "";
let n = address;
for (let i = 0; i < 4; ++i) {
  output = String(n % 256) + output;
  if (i !== 3) {
    output = "." + output;
  }
  n = Math.floor(n / 256);
}
return output;

}

function parseIPv6(input) {

const ip = [0, 0, 0, 0, 0, 0, 0, 0];
let piecePtr = 0;
let compressPtr = null;
let pointer = 0;
input = punycode.ucs2.decode(input);
if (input[pointer] === 58) {
  if (input[pointer + 1] !== 58) {
    return failure;
  }
  pointer += 2;
  ++piecePtr;
  compressPtr = piecePtr;
}
while (pointer < input.length) {
  if (piecePtr === 8) {
    return failure;
  }
  if (input[pointer] === 58) {
    if (compressPtr !== null) {
      return failure;
    }
    ++pointer;
    ++piecePtr;
    compressPtr = piecePtr;
    continue;
  }
  let value = 0;
  let length = 0;
  while (length < 4 && isASCIIHex(input[pointer])) {
    value = value * 0x10 + parseInt(at(input, pointer), 16);
    ++pointer;
    ++length;
  }
  if (input[pointer] === 46) {
    if (length === 0) {
      return failure;
    }
    pointer -= length;
    if (piecePtr > 6) {
      return failure;
    }
    let numbersSeen = 0;
    while (input[pointer] !== undefined) {
      let ipv4Piece = null;
      if (numbersSeen > 0) {
        if (input[pointer] === 46 && numbersSeen < 4) {
          ++pointer;
        } else {
          return failure;
        }
      }
      if (!isASCIIDigit(input[pointer])) {
        return failure;
      }
      while (isASCIIDigit(input[pointer])) {
        const number = parseInt(at(input, pointer));
        if (ipv4Piece === null) {
          ipv4Piece = number;
        } else if (ipv4Piece === 0) {
          return failure;
        } else {
          ipv4Piece = ipv4Piece * 10 + number;
        }
        if (ipv4Piece > 255) {
          return failure;
        }
        ++pointer;
      }
      ip[piecePtr] = ip[piecePtr] * 0x100 + ipv4Piece;
      ++numbersSeen;
      if (numbersSeen === 2 || numbersSeen === 4) {
        ++piecePtr;
      }
    }
    if (numbersSeen !== 4) {
      return failure;
    }
    break;
  } else if (input[pointer] === 58) {
    ++pointer;
    if (input[pointer] === undefined) {
      return failure;
    }
  } else if (input[pointer] !== undefined) {
    return failure;
  }
  ip[piecePtr] = value;
  ++piecePtr;
}
if (compressPtr !== null) {
  let swaps = piecePtr - compressPtr;
  piecePtr = 7;
  while (piecePtr !== 0 && swaps > 0) {
    const temp = ip[compressPtr + swaps - 1]; // piece
    ip[compressPtr + swaps - 1] = ip[piecePtr];
    ip[piecePtr] = temp;
    --piecePtr;
    --swaps;
  }
} else if (compressPtr === null && piecePtr !== 8) {
  return failure;
}
return ip;

}

function serializeIPv6(address) {

let output = "";
const seqResult = findLongestZeroSequence(address);
const compressPtr = seqResult.idx;
let ignore0 = false;
for (let i = 0; i < address.length; ++i) {
  const piece = address[i];
  if (ignore0 && piece === 0) {
    continue;
  } else if (ignore0) {
    ignore0 = false;
  }
  if (compressPtr === i) {
    const separator = i === 0 ? "::" : ":";
    output += separator;
    ignore0 = true;
    continue;
  }
  output += piece.toString(16);
  if (i !== address.length - 1) {
    output += ":";
  }
}
return output;

}

function parseHost(input, isSpecialArg) {

if (input[0] === "[") {
  if (input[input.length - 1] !== "]") {
    return failure;
  }
  return parseIPv6(input.substring(1, input.length - 1));
}
if (!isSpecialArg) {
  return parseOpaqueHost(input);
}
const domain = utf8PercentDecode(input);
const asciiDomain = tr46.toASCII(domain, false, tr46.PROCESSING_OPTIONS.NONTRANSITIONAL, false);
if (asciiDomain === null) {
  return failure;
}
if (containsForbiddenHostCodePoint(asciiDomain)) {
  return failure;
}
const ipv4Host = parseIPv4(asciiDomain);
if (typeof ipv4Host === "number" || ipv4Host === failure) {
  return ipv4Host;
}
return asciiDomain;

}

function parseOpaqueHost(input) {

if (containsForbiddenHostCodePointExcludingPercent(input)) {
  return failure;
}
let output = "";
const decoded = punycode.ucs2.decode(input);
for (let i = 0; i < decoded.length; ++i) {
  output += percentEncodeChar(decoded[i], isC0ControlPercentEncode);
}
return output;

}

function findLongestZeroSequence(arr) {

let maxIdx = null;
let maxLen = 1; // only find elements > 1
let currStart = null;
let currLen = 0;
for (let i = 0; i < arr.length; ++i) {
  if (arr[i] !== 0) {
    if (currLen > maxLen) {
      maxIdx = currStart;
      maxLen = currLen;
    }
    currStart = null;
    currLen = 0;
  } else {
    if (currStart === null) {
      currStart = i;
    }
    ++currLen;
  }
}
// if trailing zeros
if (currLen > maxLen) {
  maxIdx = currStart;
  maxLen = currLen;
}
return {
  idx: maxIdx,
  len: maxLen
};

}

function serializeHost(host) {

if (typeof host === "number") {
  return serializeIPv4(host);
}
// IPv6 serializer
if (host instanceof Array) {
  return "[" + serializeIPv6(host) + "]";
}
return host;

}

function trimControlChars(url) {

return url.replace(/^[\u0000-\u001F\u0020]+|[\u0000-\u001F\u0020]+$/g, "");

}

function trimTabAndNewline(url) {

return url.replace(/\u0009|\u000A|\u000D/g, "");

}

function shortenPath(url) {

const path = url.path;
if (path.length === 0) {
  return;
}
if (url.scheme === "file" && path.length === 1 && isNormalizedWindowsDriveLetter(path[0])) {
  return;
}
path.pop();

}

function includesCredentials(url) {

return url.username !== "" || url.password !== "";

}

function cannotHaveAUsernamePasswordPort(url) {

return url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file";

}

function isNormalizedWindowsDriveLetter(string) {

return /^[A-Za-z]:$/.test(string);

}

function URLStateMachine(input, base, encodingOverride, url, stateOverride) {

this.pointer = 0;
this.input = input;
this.base = base || null;
this.encodingOverride = encodingOverride || "utf-8";
this.stateOverride = stateOverride;
this.url = url;
this.failure = false;
this.parseError = false;
if (!this.url) {
  this.url = {
    scheme: "",
    username: "",
    password: "",
    host: null,
    port: null,
    path: [],
    query: null,
    fragment: null,
    cannotBeABaseURL: false
  };
  const res = trimControlChars(this.input);
  if (res !== this.input) {
    this.parseError = true;
  }
  this.input = res;
}
const res = trimTabAndNewline(this.input);
if (res !== this.input) {
  this.parseError = true;
}
this.input = res;
this.state = stateOverride || "scheme start";
this.buffer = "";
this.atFlag = false;
this.arrFlag = false;
this.passwordTokenSeenFlag = false;
this.input = punycode.ucs2.decode(this.input);
for (; this.pointer <= this.input.length; ++this.pointer) {
  const c = this.input[this.pointer];
  const cStr = isNaN(c) ? undefined : String.fromCodePoint(c);
  // exec state machine
  const ret = this["parse " + this.state](c, cStr);
  if (!ret) {
    break; // terminate algorithm
  } else if (ret === failure) {
    this.failure = true;
    break;
  }
}

}

URLStateMachine.prototype[“parse scheme start”] = function parseSchemeStart(c, cStr) {

if (isASCIIAlpha(c)) {
  this.buffer += cStr.toLowerCase();
  this.state = "scheme";
} else if (!this.stateOverride) {
  this.state = "no scheme";
  --this.pointer;
} else {
  this.parseError = true;
  return failure;
}
return true;

};

URLStateMachine.prototype[“parse scheme”] = function parseScheme(c, cStr) {

if (isASCIIAlphanumeric(c) || c === 43 || c === 45 || c === 46) {
  this.buffer += cStr.toLowerCase();
} else if (c === 58) {
  if (this.stateOverride) {
    if (isSpecial(this.url) && !isSpecialScheme(this.buffer)) {
      return false;
    }
    if (!isSpecial(this.url) && isSpecialScheme(this.buffer)) {
      return false;
    }
    if ((includesCredentials(this.url) || this.url.port !== null) && this.buffer === "file") {
      return false;
    }
    if (this.url.scheme === "file" && (this.url.host === "" || this.url.host === null)) {
      return false;
    }
  }
  this.url.scheme = this.buffer;
  this.buffer = "";
  if (this.stateOverride) {
    return false;
  }
  if (this.url.scheme === "file") {
    if (this.input[this.pointer + 1] !== 47 || this.input[this.pointer + 2] !== 47) {
      this.parseError = true;
    }
    this.state = "file";
  } else if (isSpecial(this.url) && this.base !== null && this.base.scheme === this.url.scheme) {
    this.state = "special relative or authority";
  } else if (isSpecial(this.url)) {
    this.state = "special authority slashes";
  } else if (this.input[this.pointer + 1] === 47) {
    this.state = "path or authority";
    ++this.pointer;
  } else {
    this.url.cannotBeABaseURL = true;
    this.url.path.push("");
    this.state = "cannot-be-a-base-URL path";
  }
} else if (!this.stateOverride) {
  this.buffer = "";
  this.state = "no scheme";
  this.pointer = -1;
} else {
  this.parseError = true;
  return failure;
}
return true;

};

URLStateMachine.prototype[“parse no scheme”] = function parseNoScheme© {

if (this.base === null || (this.base.cannotBeABaseURL && c !== 35)) {
  return failure;
} else if (this.base.cannotBeABaseURL && c === 35) {
  this.url.scheme = this.base.scheme;
  this.url.path = this.base.path.slice();
  this.url.query = this.base.query;
  this.url.fragment = "";
  this.url.cannotBeABaseURL = true;
  this.state = "fragment";
} else if (this.base.scheme === "file") {
  this.state = "file";
  --this.pointer;
} else {
  this.state = "relative";
  --this.pointer;
}
return true;

};

URLStateMachine.prototype[“parse special relative or authority”] = function parseSpecialRelativeOrAuthority© {

if (c === 47 && this.input[this.pointer + 1] === 47) {
  this.state = "special authority ignore slashes";
  ++this.pointer;
} else {
  this.parseError = true;
  this.state = "relative";
  --this.pointer;
}
return true;

};

URLStateMachine.prototype[“parse path or authority”] = function parsePathOrAuthority© {

if (c === 47) {
  this.state = "authority";
} else {
  this.state = "path";
  --this.pointer;
}
return true;

};

URLStateMachine.prototype[“parse relative”] = function parseRelative© {

this.url.scheme = this.base.scheme;
if (isNaN(c)) {
  this.url.username = this.base.username;
  this.url.password = this.base.password;
  this.url.host = this.base.host;
  this.url.port = this.base.port;
  this.url.path = this.base.path.slice();
  this.url.query = this.base.query;
} else if (c === 47) {
  this.state = "relative slash";
} else if (c === 63) {
  this.url.username = this.base.username;
  this.url.password = this.base.password;
  this.url.host = this.base.host;
  this.url.port = this.base.port;
  this.url.path = this.base.path.slice();
  this.url.query = "";
  this.state = "query";
} else if (c === 35) {
  this.url.username = this.base.username;
  this.url.password = this.base.password;
  this.url.host = this.base.host;
  this.url.port = this.base.port;
  this.url.path = this.base.path.slice();
  this.url.query = this.base.query;
  this.url.fragment = "";
  this.state = "fragment";
} else if (isSpecial(this.url) && c === 92) {
  this.parseError = true;
  this.state = "relative slash";
} else {
  this.url.username = this.base.username;
  this.url.password = this.base.password;
  this.url.host = this.base.host;
  this.url.port = this.base.port;
  this.url.path = this.base.path.slice(0, this.base.path.length - 1);
  this.state = "path";
  --this.pointer;
}
return true;

};

URLStateMachine.prototype[“parse relative slash”] = function parseRelativeSlash© {

if (isSpecial(this.url) && (c === 47 || c === 92)) {
  if (c === 92) {
    this.parseError = true;
  }
  this.state = "special authority ignore slashes";
} else if (c === 47) {
  this.state = "authority";
} else {
  this.url.username = this.base.username;
  this.url.password = this.base.password;
  this.url.host = this.base.host;
  this.url.port = this.base.port;
  this.state = "path";
  --this.pointer;
}
return true;

};

URLStateMachine.prototype[“parse special authority slashes”] = function parseSpecialAuthoritySlashes© {

if (c === 47 && this.input[this.pointer + 1] === 47) {
  this.state = "special authority ignore slashes";
  ++this.pointer;
} else {
  this.parseError = true;
  this.state = "special authority ignore slashes";
  --this.pointer;
}
return true;

};

URLStateMachine.prototype[“parse special authority ignore slashes”] = function parseSpecialAuthorityIgnoreSlashes© {

if (c !== 47 && c !== 92) {
  this.state = "authority";
  --this.pointer;
} else {
  this.parseError = true;
}
return true;

};

URLStateMachine.prototype[“parse authority”] = function parseAuthority(c, cStr) {

if (c === 64) {
  this.parseError = true;
  if (this.atFlag) {
    this.buffer = "%40" + this.buffer;
  }
  this.atFlag = true;
  // careful, this is based on buffer and has its own pointer (this.pointer != pointer) and inner chars
  const len = countSymbols(this.buffer);
  for (let pointer = 0; pointer < len; ++pointer) {
    const codePoint = this.buffer.codePointAt(pointer);
    if (codePoint === 58 && !this.passwordTokenSeenFlag) {
      this.passwordTokenSeenFlag = true;
      continue;
    }
    const encodedCodePoints = percentEncodeChar(codePoint, isUserinfoPercentEncode);
    if (this.passwordTokenSeenFlag) {
      this.url.password += encodedCodePoints;
    } else {
      this.url.username += encodedCodePoints;
    }
  }
  this.buffer = "";
} else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
           (isSpecial(this.url) && c === 92)) {
  if (this.atFlag && this.buffer === "") {
    this.parseError = true;
    return failure;
  }
  this.pointer -= countSymbols(this.buffer) + 1;
  this.buffer = "";
  this.state = "host";
} else {
  this.buffer += cStr;
}
return true;

};

URLStateMachine.prototype[“parse hostname”] = URLStateMachine.prototype[“parse host”] = function parseHostName(c, cStr) {

if (this.stateOverride && this.url.scheme === "file") {
  --this.pointer;
  this.state = "file host";
} else if (c === 58 && !this.arrFlag) {
  if (this.buffer === "") {
    this.parseError = true;
    return failure;
  }
  const host = parseHost(this.buffer, isSpecial(this.url));
  if (host === failure) {
    return failure;
  }
  this.url.host = host;
  this.buffer = "";
  this.state = "port";
  if (this.stateOverride === "hostname") {
    return false;
  }
} else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
           (isSpecial(this.url) && c === 92)) {
  --this.pointer;
  if (isSpecial(this.url) && this.buffer === "") {
    this.parseError = true;
    return failure;
  } else if (this.stateOverride && this.buffer === "" &&
             (includesCredentials(this.url) || this.url.port !== null)) {
    this.parseError = true;
    return false;
  }
  const host = parseHost(this.buffer, isSpecial(this.url));
  if (host === failure) {
    return failure;
  }
  this.url.host = host;
  this.buffer = "";
  this.state = "path start";
  if (this.stateOverride) {
    return false;
  }
} else {
  if (c === 91) {
    this.arrFlag = true;
  } else if (c === 93) {
    this.arrFlag = false;
  }
  this.buffer += cStr;
}
return true;

};

URLStateMachine.prototype[“parse port”] = function parsePort(c, cStr) {

if (isASCIIDigit(c)) {
  this.buffer += cStr;
} else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
           (isSpecial(this.url) && c === 92) ||
           this.stateOverride) {
  if (this.buffer !== "") {
    const port = parseInt(this.buffer);
    if (port > Math.pow(2, 16) - 1) {
      this.parseError = true;
      return failure;
    }
    this.url.port = port === defaultPort(this.url.scheme) ? null : port;
    this.buffer = "";
  }
  if (this.stateOverride) {
    return false;
  }
  this.state = "path start";
  --this.pointer;
} else {
  this.parseError = true;
  return failure;
}
return true;

};

const fileOtherwiseCodePoints = new Set([47, 92, 63, 35]);

URLStateMachine.prototype[“parse file”] = function parseFile© {

this.url.scheme = "file";
if (c === 47 || c === 92) {
  if (c === 92) {
    this.parseError = true;
  }
  this.state = "file slash";
} else if (this.base !== null && this.base.scheme === "file") {
  if (isNaN(c)) {
    this.url.host = this.base.host;
    this.url.path = this.base.path.slice();
    this.url.query = this.base.query;
  } else if (c === 63) {
    this.url.host = this.base.host;
    this.url.path = this.base.path.slice();
    this.url.query = "";
    this.state = "query";
  } else if (c === 35) {
    this.url.host = this.base.host;
    this.url.path = this.base.path.slice();
    this.url.query = this.base.query;
    this.url.fragment = "";
    this.state = "fragment";
  } else {
    if (this.input.length - this.pointer - 1 === 0 || // remaining consists of 0 code points
        !isWindowsDriveLetterCodePoints(c, this.input[this.pointer + 1]) ||
        (this.input.length - this.pointer - 1 >= 2 && // remaining has at least 2 code points
         !fileOtherwiseCodePoints.has(this.input[this.pointer + 2]))) {
      this.url.host = this.base.host;
      this.url.path = this.base.path.slice();
      shortenPath(this.url);
    } else {
      this.parseError = true;
    }
    this.state = "path";
    --this.pointer;
  }
} else {
  this.state = "path";
  --this.pointer;
}
return true;

};

URLStateMachine.prototype[“parse file slash”] = function parseFileSlash© {

if (c === 47 || c === 92) {
  if (c === 92) {
    this.parseError = true;
  }
  this.state = "file host";
} else {
  if (this.base !== null && this.base.scheme === "file") {
    if (isNormalizedWindowsDriveLetterString(this.base.path[0])) {
      this.url.path.push(this.base.path[0]);
    } else {
      this.url.host = this.base.host;
    }
  }
  this.state = "path";
  --this.pointer;
}
return true;

};

URLStateMachine.prototype[“parse file host”] = function parseFileHost(c, cStr) {

if (isNaN(c) || c === 47 || c === 92 || c === 63 || c === 35) {
  --this.pointer;
  if (!this.stateOverride && isWindowsDriveLetterString(this.buffer)) {
    this.parseError = true;
    this.state = "path";
  } else if (this.buffer === "") {
    this.url.host = "";
    if (this.stateOverride) {
      return false;
    }
    this.state = "path start";
  } else {
    let host = parseHost(this.buffer, isSpecial(this.url));
    if (host === failure) {
      return failure;
    }
    if (host === "localhost") {
      host = "";
    }
    this.url.host = host;
    if (this.stateOverride) {
      return false;
    }
    this.buffer = "";
    this.state = "path start";
  }
} else {
  this.buffer += cStr;
}
return true;

};

URLStateMachine.prototype[“parse path start”] = function parsePathStart© {

if (isSpecial(this.url)) {
  if (c === 92) {
    this.parseError = true;
  }
  this.state = "path";
  if (c !== 47 && c !== 92) {
    --this.pointer;
  }
} else if (!this.stateOverride && c === 63) {
  this.url.query = "";
  this.state = "query";
} else if (!this.stateOverride && c === 35) {
  this.url.fragment = "";
  this.state = "fragment";
} else if (c !== undefined) {
  this.state = "path";
  if (c !== 47) {
    --this.pointer;
  }
}
return true;

};

URLStateMachine.prototype[“parse path”] = function parsePath© {

if (isNaN(c) || c === 47 || (isSpecial(this.url) && c === 92) ||
    (!this.stateOverride && (c === 63 || c === 35))) {
  if (isSpecial(this.url) && c === 92) {
    this.parseError = true;
  }
  if (isDoubleDot(this.buffer)) {
    shortenPath(this.url);
    if (c !== 47 && !(isSpecial(this.url) && c === 92)) {
      this.url.path.push("");
    }
  } else if (isSingleDot(this.buffer) && c !== 47 &&
             !(isSpecial(this.url) && c === 92)) {
    this.url.path.push("");
  } else if (!isSingleDot(this.buffer)) {
    if (this.url.scheme === "file" && this.url.path.length === 0 && isWindowsDriveLetterString(this.buffer)) {
      if (this.url.host !== "" && this.url.host !== null) {
        this.parseError = true;
        this.url.host = "";
      }
      this.buffer = this.buffer[0] + ":";
    }
    this.url.path.push(this.buffer);
  }
  this.buffer = "";
  if (this.url.scheme === "file" && (c === undefined || c === 63 || c === 35)) {
    while (this.url.path.length > 1 && this.url.path[0] === "") {
      this.parseError = true;
      this.url.path.shift();
    }
  }
  if (c === 63) {
    this.url.query = "";
    this.state = "query";
  }
  if (c === 35) {
    this.url.fragment = "";
    this.state = "fragment";
  }
} else {
  // TODO: If c is not a URL code point and not "%", parse error.
  if (c === 37 &&
    (!isASCIIHex(this.input[this.pointer + 1]) ||
      !isASCIIHex(this.input[this.pointer + 2]))) {
    this.parseError = true;
  }
  this.buffer += percentEncodeChar(c, isPathPercentEncode);
}
return true;

};

URLStateMachine.prototype[“parse cannot-be-a-base-URL path”] = function parseCannotBeABaseURLPath© {

if (c === 63) {
  this.url.query = "";
  this.state = "query";
} else if (c === 35) {
  this.url.fragment = "";
  this.state = "fragment";
} else {
  // TODO: Add: not a URL code point
  if (!isNaN(c) && c !== 37) {
    this.parseError = true;
  }
  if (c === 37 &&
      (!isASCIIHex(this.input[this.pointer + 1]) ||
       !isASCIIHex(this.input[this.pointer + 2]))) {
    this.parseError = true;
  }
  if (!isNaN(c)) {
    this.url.path[0] = this.url.path[0] + percentEncodeChar(c, isC0ControlPercentEncode);
  }
}
return true;

};

URLStateMachine.prototype[“parse query”] = function parseQuery(c, cStr) {

if (isNaN(c) || (!this.stateOverride && c === 35)) {
  if (!isSpecial(this.url) || this.url.scheme === "ws" || this.url.scheme === "wss") {
    this.encodingOverride = "utf-8";
  }
  const buffer = new Buffer(this.buffer); // TODO: Use encoding override instead
  for (let i = 0; i < buffer.length; ++i) {
    if (buffer[i] < 0x21 || buffer[i] > 0x7E || buffer[i] === 0x22 || buffer[i] === 0x23 ||
        buffer[i] === 0x3C || buffer[i] === 0x3E) {
      this.url.query += percentEncode(buffer[i]);
    } else {
      this.url.query += String.fromCodePoint(buffer[i]);
    }
  }
  this.buffer = "";
  if (c === 35) {
    this.url.fragment = "";
    this.state = "fragment";
  }
} else {
  // TODO: If c is not a URL code point and not "%", parse error.
  if (c === 37 &&
    (!isASCIIHex(this.input[this.pointer + 1]) ||
      !isASCIIHex(this.input[this.pointer + 2]))) {
    this.parseError = true;
  }
  this.buffer += cStr;
}
return true;

};

URLStateMachine.prototype[“parse fragment”] = function parseFragment© {

if (isNaN(c)) { // do nothing
} else if (c === 0x0) {
  this.parseError = true;
} else {
  // TODO: If c is not a URL code point and not "%", parse error.
  if (c === 37 &&
    (!isASCIIHex(this.input[this.pointer + 1]) ||
      !isASCIIHex(this.input[this.pointer + 2]))) {
    this.parseError = true;
  }
  this.url.fragment += percentEncodeChar(c, isC0ControlPercentEncode);
}
return true;

};

function serializeURL(url, excludeFragment) {

let output = url.scheme + ":";
if (url.host !== null) {
  output += "//";
  if (url.username !== "" || url.password !== "") {
    output += url.username;
    if (url.password !== "") {
      output += ":" + url.password;
    }
    output += "@";
  }
  output += serializeHost(url.host);
  if (url.port !== null) {
    output += ":" + url.port;
  }
} else if (url.host === null && url.scheme === "file") {
  output += "//";
}
if (url.cannotBeABaseURL) {
  output += url.path[0];
} else {
  for (const string of url.path) {
    output += "/" + string;
  }
}
if (url.query !== null) {
  output += "?" + url.query;
}
if (!excludeFragment && url.fragment !== null) {
  output += "#" + url.fragment;
}
return output;

}

function serializeOrigin(tuple) {

if (tuple.scheme === undefined || tuple.host === undefined || tuple.port === undefined) {
  return "null";
}
let result = tuple.scheme + "://";
result += tr46.toUnicode(tuple.host, false).domain;
if (tuple.port !== null) {
  result += ":" + tuple.port;
}
return result;

}

module.exports.serializeURL = serializeURL;

module.exports.serializeURLToUnicodeOrigin = function (url) {

switch (url.scheme) {
  case "blob":
    try {
      return module.exports.serializeURLToUnicodeOrigin(module.exports.parseURL(url.path[0]));
    } catch (e) {
      // serializing an opaque identifier returns "null"
      return "null";
    }
  case "ftp":
  case "gopher":
  case "http":
  case "https":
  case "ws":
  case "wss":
    return serializeOrigin({
      scheme: url.scheme,
      host: serializeHost(url.host),
      port: url.port
    });
  case "file":
    // spec says "exercise to the reader", chrome says "file://"
    return "file://";
  default:
    // serializing an opaque identifier returns "null"
    return "null";
}

};

module.exports.basicURLParse = function (input, options) {

if (options === undefined) {
  options = {};
}
const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride);
if (usm.failure) {
  return "failure";
}
return usm.url;

};

module.exports.setTheUsername = function (url, username) {

url.username = "";
const decoded = punycode.ucs2.decode(username);
for (let i = 0; i < decoded.length; ++i) {
  url.username += percentEncodeChar(decoded[i], isUserinfoPercentEncode);
}

};

module.exports.setThePassword = function (url, password) {

url.password = "";
const decoded = punycode.ucs2.decode(password);
for (let i = 0; i < decoded.length; ++i) {
  url.password += percentEncodeChar(decoded[i], isUserinfoPercentEncode);
}

};

module.exports.serializeHost = serializeHost;

module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort;

module.exports.serializeInteger = function (integer) {

return String(integer);

};

module.exports.parseURL = function (input, options) {

if (options === undefined) {
  options = {};
}
// We don't handle blobs, so this just delegates:
return module.exports.basicURLParse(input, { baseURL: options.baseURL, encodingOverride: options.encodingOverride });

};