github.com/nektos/act@v0.2.63-0.20240520024548-8acde99bfa9c/pkg/runner/testdata/actions/node12/node_modules/whatwg-url/lib/url-state-machine.js (about)

     1  "use strict";
     2  const punycode = require("punycode");
     3  const tr46 = require("tr46");
     4  
     5  const specialSchemes = {
     6    ftp: 21,
     7    file: null,
     8    gopher: 70,
     9    http: 80,
    10    https: 443,
    11    ws: 80,
    12    wss: 443
    13  };
    14  
    15  const failure = Symbol("failure");
    16  
    17  function countSymbols(str) {
    18    return punycode.ucs2.decode(str).length;
    19  }
    20  
    21  function at(input, idx) {
    22    const c = input[idx];
    23    return isNaN(c) ? undefined : String.fromCodePoint(c);
    24  }
    25  
    26  function isASCIIDigit(c) {
    27    return c >= 0x30 && c <= 0x39;
    28  }
    29  
    30  function isASCIIAlpha(c) {
    31    return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);
    32  }
    33  
    34  function isASCIIAlphanumeric(c) {
    35    return isASCIIAlpha(c) || isASCIIDigit(c);
    36  }
    37  
    38  function isASCIIHex(c) {
    39    return isASCIIDigit(c) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66);
    40  }
    41  
    42  function isSingleDot(buffer) {
    43    return buffer === "." || buffer.toLowerCase() === "%2e";
    44  }
    45  
    46  function isDoubleDot(buffer) {
    47    buffer = buffer.toLowerCase();
    48    return buffer === ".." || buffer === "%2e." || buffer === ".%2e" || buffer === "%2e%2e";
    49  }
    50  
    51  function isWindowsDriveLetterCodePoints(cp1, cp2) {
    52    return isASCIIAlpha(cp1) && (cp2 === 58 || cp2 === 124);
    53  }
    54  
    55  function isWindowsDriveLetterString(string) {
    56    return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && (string[1] === ":" || string[1] === "|");
    57  }
    58  
    59  function isNormalizedWindowsDriveLetterString(string) {
    60    return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && string[1] === ":";
    61  }
    62  
    63  function containsForbiddenHostCodePoint(string) {
    64    return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|%|\/|:|\?|@|\[|\\|\]/) !== -1;
    65  }
    66  
    67  function containsForbiddenHostCodePointExcludingPercent(string) {
    68    return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|\/|:|\?|@|\[|\\|\]/) !== -1;
    69  }
    70  
    71  function isSpecialScheme(scheme) {
    72    return specialSchemes[scheme] !== undefined;
    73  }
    74  
    75  function isSpecial(url) {
    76    return isSpecialScheme(url.scheme);
    77  }
    78  
    79  function defaultPort(scheme) {
    80    return specialSchemes[scheme];
    81  }
    82  
    83  function percentEncode(c) {
    84    let hex = c.toString(16).toUpperCase();
    85    if (hex.length === 1) {
    86      hex = "0" + hex;
    87    }
    88  
    89    return "%" + hex;
    90  }
    91  
    92  function utf8PercentEncode(c) {
    93    const buf = new Buffer(c);
    94  
    95    let str = "";
    96  
    97    for (let i = 0; i < buf.length; ++i) {
    98      str += percentEncode(buf[i]);
    99    }
   100  
   101    return str;
   102  }
   103  
   104  function utf8PercentDecode(str) {
   105    const input = new Buffer(str);
   106    const output = [];
   107    for (let i = 0; i < input.length; ++i) {
   108      if (input[i] !== 37) {
   109        output.push(input[i]);
   110      } else if (input[i] === 37 && isASCIIHex(input[i + 1]) && isASCIIHex(input[i + 2])) {
   111        output.push(parseInt(input.slice(i + 1, i + 3).toString(), 16));
   112        i += 2;
   113      } else {
   114        output.push(input[i]);
   115      }
   116    }
   117    return new Buffer(output).toString();
   118  }
   119  
   120  function isC0ControlPercentEncode(c) {
   121    return c <= 0x1F || c > 0x7E;
   122  }
   123  
   124  const extraPathPercentEncodeSet = new Set([32, 34, 35, 60, 62, 63, 96, 123, 125]);
   125  function isPathPercentEncode(c) {
   126    return isC0ControlPercentEncode(c) || extraPathPercentEncodeSet.has(c);
   127  }
   128  
   129  const extraUserinfoPercentEncodeSet =
   130    new Set([47, 58, 59, 61, 64, 91, 92, 93, 94, 124]);
   131  function isUserinfoPercentEncode(c) {
   132    return isPathPercentEncode(c) || extraUserinfoPercentEncodeSet.has(c);
   133  }
   134  
   135  function percentEncodeChar(c, encodeSetPredicate) {
   136    const cStr = String.fromCodePoint(c);
   137  
   138    if (encodeSetPredicate(c)) {
   139      return utf8PercentEncode(cStr);
   140    }
   141  
   142    return cStr;
   143  }
   144  
   145  function parseIPv4Number(input) {
   146    let R = 10;
   147  
   148    if (input.length >= 2 && input.charAt(0) === "0" && input.charAt(1).toLowerCase() === "x") {
   149      input = input.substring(2);
   150      R = 16;
   151    } else if (input.length >= 2 && input.charAt(0) === "0") {
   152      input = input.substring(1);
   153      R = 8;
   154    }
   155  
   156    if (input === "") {
   157      return 0;
   158    }
   159  
   160    const regex = R === 10 ? /[^0-9]/ : (R === 16 ? /[^0-9A-Fa-f]/ : /[^0-7]/);
   161    if (regex.test(input)) {
   162      return failure;
   163    }
   164  
   165    return parseInt(input, R);
   166  }
   167  
   168  function parseIPv4(input) {
   169    const parts = input.split(".");
   170    if (parts[parts.length - 1] === "") {
   171      if (parts.length > 1) {
   172        parts.pop();
   173      }
   174    }
   175  
   176    if (parts.length > 4) {
   177      return input;
   178    }
   179  
   180    const numbers = [];
   181    for (const part of parts) {
   182      if (part === "") {
   183        return input;
   184      }
   185      const n = parseIPv4Number(part);
   186      if (n === failure) {
   187        return input;
   188      }
   189  
   190      numbers.push(n);
   191    }
   192  
   193    for (let i = 0; i < numbers.length - 1; ++i) {
   194      if (numbers[i] > 255) {
   195        return failure;
   196      }
   197    }
   198    if (numbers[numbers.length - 1] >= Math.pow(256, 5 - numbers.length)) {
   199      return failure;
   200    }
   201  
   202    let ipv4 = numbers.pop();
   203    let counter = 0;
   204  
   205    for (const n of numbers) {
   206      ipv4 += n * Math.pow(256, 3 - counter);
   207      ++counter;
   208    }
   209  
   210    return ipv4;
   211  }
   212  
   213  function serializeIPv4(address) {
   214    let output = "";
   215    let n = address;
   216  
   217    for (let i = 1; i <= 4; ++i) {
   218      output = String(n % 256) + output;
   219      if (i !== 4) {
   220        output = "." + output;
   221      }
   222      n = Math.floor(n / 256);
   223    }
   224  
   225    return output;
   226  }
   227  
   228  function parseIPv6(input) {
   229    const address = [0, 0, 0, 0, 0, 0, 0, 0];
   230    let pieceIndex = 0;
   231    let compress = null;
   232    let pointer = 0;
   233  
   234    input = punycode.ucs2.decode(input);
   235  
   236    if (input[pointer] === 58) {
   237      if (input[pointer + 1] !== 58) {
   238        return failure;
   239      }
   240  
   241      pointer += 2;
   242      ++pieceIndex;
   243      compress = pieceIndex;
   244    }
   245  
   246    while (pointer < input.length) {
   247      if (pieceIndex === 8) {
   248        return failure;
   249      }
   250  
   251      if (input[pointer] === 58) {
   252        if (compress !== null) {
   253          return failure;
   254        }
   255        ++pointer;
   256        ++pieceIndex;
   257        compress = pieceIndex;
   258        continue;
   259      }
   260  
   261      let value = 0;
   262      let length = 0;
   263  
   264      while (length < 4 && isASCIIHex(input[pointer])) {
   265        value = value * 0x10 + parseInt(at(input, pointer), 16);
   266        ++pointer;
   267        ++length;
   268      }
   269  
   270      if (input[pointer] === 46) {
   271        if (length === 0) {
   272          return failure;
   273        }
   274  
   275        pointer -= length;
   276  
   277        if (pieceIndex > 6) {
   278          return failure;
   279        }
   280  
   281        let numbersSeen = 0;
   282  
   283        while (input[pointer] !== undefined) {
   284          let ipv4Piece = null;
   285  
   286          if (numbersSeen > 0) {
   287            if (input[pointer] === 46 && numbersSeen < 4) {
   288              ++pointer;
   289            } else {
   290              return failure;
   291            }
   292          }
   293  
   294          if (!isASCIIDigit(input[pointer])) {
   295            return failure;
   296          }
   297  
   298          while (isASCIIDigit(input[pointer])) {
   299            const number = parseInt(at(input, pointer));
   300            if (ipv4Piece === null) {
   301              ipv4Piece = number;
   302            } else if (ipv4Piece === 0) {
   303              return failure;
   304            } else {
   305              ipv4Piece = ipv4Piece * 10 + number;
   306            }
   307            if (ipv4Piece > 255) {
   308              return failure;
   309            }
   310            ++pointer;
   311          }
   312  
   313          address[pieceIndex] = address[pieceIndex] * 0x100 + ipv4Piece;
   314  
   315          ++numbersSeen;
   316  
   317          if (numbersSeen === 2 || numbersSeen === 4) {
   318            ++pieceIndex;
   319          }
   320        }
   321  
   322        if (numbersSeen !== 4) {
   323          return failure;
   324        }
   325  
   326        break;
   327      } else if (input[pointer] === 58) {
   328        ++pointer;
   329        if (input[pointer] === undefined) {
   330          return failure;
   331        }
   332      } else if (input[pointer] !== undefined) {
   333        return failure;
   334      }
   335  
   336      address[pieceIndex] = value;
   337      ++pieceIndex;
   338    }
   339  
   340    if (compress !== null) {
   341      let swaps = pieceIndex - compress;
   342      pieceIndex = 7;
   343      while (pieceIndex !== 0 && swaps > 0) {
   344        const temp = address[compress + swaps - 1];
   345        address[compress + swaps - 1] = address[pieceIndex];
   346        address[pieceIndex] = temp;
   347        --pieceIndex;
   348        --swaps;
   349      }
   350    } else if (compress === null && pieceIndex !== 8) {
   351      return failure;
   352    }
   353  
   354    return address;
   355  }
   356  
   357  function serializeIPv6(address) {
   358    let output = "";
   359    const seqResult = findLongestZeroSequence(address);
   360    const compress = seqResult.idx;
   361    let ignore0 = false;
   362  
   363    for (let pieceIndex = 0; pieceIndex <= 7; ++pieceIndex) {
   364      if (ignore0 && address[pieceIndex] === 0) {
   365        continue;
   366      } else if (ignore0) {
   367        ignore0 = false;
   368      }
   369  
   370      if (compress === pieceIndex) {
   371        const separator = pieceIndex === 0 ? "::" : ":";
   372        output += separator;
   373        ignore0 = true;
   374        continue;
   375      }
   376  
   377      output += address[pieceIndex].toString(16);
   378  
   379      if (pieceIndex !== 7) {
   380        output += ":";
   381      }
   382    }
   383  
   384    return output;
   385  }
   386  
   387  function parseHost(input, isSpecialArg) {
   388    if (input[0] === "[") {
   389      if (input[input.length - 1] !== "]") {
   390        return failure;
   391      }
   392  
   393      return parseIPv6(input.substring(1, input.length - 1));
   394    }
   395  
   396    if (!isSpecialArg) {
   397      return parseOpaqueHost(input);
   398    }
   399  
   400    const domain = utf8PercentDecode(input);
   401    const asciiDomain = tr46.toASCII(domain, false, tr46.PROCESSING_OPTIONS.NONTRANSITIONAL, false);
   402    if (asciiDomain === null) {
   403      return failure;
   404    }
   405  
   406    if (containsForbiddenHostCodePoint(asciiDomain)) {
   407      return failure;
   408    }
   409  
   410    const ipv4Host = parseIPv4(asciiDomain);
   411    if (typeof ipv4Host === "number" || ipv4Host === failure) {
   412      return ipv4Host;
   413    }
   414  
   415    return asciiDomain;
   416  }
   417  
   418  function parseOpaqueHost(input) {
   419    if (containsForbiddenHostCodePointExcludingPercent(input)) {
   420      return failure;
   421    }
   422  
   423    let output = "";
   424    const decoded = punycode.ucs2.decode(input);
   425    for (let i = 0; i < decoded.length; ++i) {
   426      output += percentEncodeChar(decoded[i], isC0ControlPercentEncode);
   427    }
   428    return output;
   429  }
   430  
   431  function findLongestZeroSequence(arr) {
   432    let maxIdx = null;
   433    let maxLen = 1; // only find elements > 1
   434    let currStart = null;
   435    let currLen = 0;
   436  
   437    for (let i = 0; i < arr.length; ++i) {
   438      if (arr[i] !== 0) {
   439        if (currLen > maxLen) {
   440          maxIdx = currStart;
   441          maxLen = currLen;
   442        }
   443  
   444        currStart = null;
   445        currLen = 0;
   446      } else {
   447        if (currStart === null) {
   448          currStart = i;
   449        }
   450        ++currLen;
   451      }
   452    }
   453  
   454    // if trailing zeros
   455    if (currLen > maxLen) {
   456      maxIdx = currStart;
   457      maxLen = currLen;
   458    }
   459  
   460    return {
   461      idx: maxIdx,
   462      len: maxLen
   463    };
   464  }
   465  
   466  function serializeHost(host) {
   467    if (typeof host === "number") {
   468      return serializeIPv4(host);
   469    }
   470  
   471    // IPv6 serializer
   472    if (host instanceof Array) {
   473      return "[" + serializeIPv6(host) + "]";
   474    }
   475  
   476    return host;
   477  }
   478  
   479  function trimControlChars(url) {
   480    return url.replace(/^[\u0000-\u001F\u0020]+|[\u0000-\u001F\u0020]+$/g, "");
   481  }
   482  
   483  function trimTabAndNewline(url) {
   484    return url.replace(/\u0009|\u000A|\u000D/g, "");
   485  }
   486  
   487  function shortenPath(url) {
   488    const path = url.path;
   489    if (path.length === 0) {
   490      return;
   491    }
   492    if (url.scheme === "file" && path.length === 1 && isNormalizedWindowsDriveLetter(path[0])) {
   493      return;
   494    }
   495  
   496    path.pop();
   497  }
   498  
   499  function includesCredentials(url) {
   500    return url.username !== "" || url.password !== "";
   501  }
   502  
   503  function cannotHaveAUsernamePasswordPort(url) {
   504    return url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file";
   505  }
   506  
   507  function isNormalizedWindowsDriveLetter(string) {
   508    return /^[A-Za-z]:$/.test(string);
   509  }
   510  
   511  function URLStateMachine(input, base, encodingOverride, url, stateOverride) {
   512    this.pointer = 0;
   513    this.input = input;
   514    this.base = base || null;
   515    this.encodingOverride = encodingOverride || "utf-8";
   516    this.stateOverride = stateOverride;
   517    this.url = url;
   518    this.failure = false;
   519    this.parseError = false;
   520  
   521    if (!this.url) {
   522      this.url = {
   523        scheme: "",
   524        username: "",
   525        password: "",
   526        host: null,
   527        port: null,
   528        path: [],
   529        query: null,
   530        fragment: null,
   531  
   532        cannotBeABaseURL: false
   533      };
   534  
   535      const res = trimControlChars(this.input);
   536      if (res !== this.input) {
   537        this.parseError = true;
   538      }
   539      this.input = res;
   540    }
   541  
   542    const res = trimTabAndNewline(this.input);
   543    if (res !== this.input) {
   544      this.parseError = true;
   545    }
   546    this.input = res;
   547  
   548    this.state = stateOverride || "scheme start";
   549  
   550    this.buffer = "";
   551    this.atFlag = false;
   552    this.arrFlag = false;
   553    this.passwordTokenSeenFlag = false;
   554  
   555    this.input = punycode.ucs2.decode(this.input);
   556  
   557    for (; this.pointer <= this.input.length; ++this.pointer) {
   558      const c = this.input[this.pointer];
   559      const cStr = isNaN(c) ? undefined : String.fromCodePoint(c);
   560  
   561      // exec state machine
   562      const ret = this["parse " + this.state](c, cStr);
   563      if (!ret) {
   564        break; // terminate algorithm
   565      } else if (ret === failure) {
   566        this.failure = true;
   567        break;
   568      }
   569    }
   570  }
   571  
   572  URLStateMachine.prototype["parse scheme start"] = function parseSchemeStart(c, cStr) {
   573    if (isASCIIAlpha(c)) {
   574      this.buffer += cStr.toLowerCase();
   575      this.state = "scheme";
   576    } else if (!this.stateOverride) {
   577      this.state = "no scheme";
   578      --this.pointer;
   579    } else {
   580      this.parseError = true;
   581      return failure;
   582    }
   583  
   584    return true;
   585  };
   586  
   587  URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) {
   588    if (isASCIIAlphanumeric(c) || c === 43 || c === 45 || c === 46) {
   589      this.buffer += cStr.toLowerCase();
   590    } else if (c === 58) {
   591      if (this.stateOverride) {
   592        if (isSpecial(this.url) && !isSpecialScheme(this.buffer)) {
   593          return false;
   594        }
   595  
   596        if (!isSpecial(this.url) && isSpecialScheme(this.buffer)) {
   597          return false;
   598        }
   599  
   600        if ((includesCredentials(this.url) || this.url.port !== null) && this.buffer === "file") {
   601          return false;
   602        }
   603  
   604        if (this.url.scheme === "file" && (this.url.host === "" || this.url.host === null)) {
   605          return false;
   606        }
   607      }
   608      this.url.scheme = this.buffer;
   609      this.buffer = "";
   610      if (this.stateOverride) {
   611        return false;
   612      }
   613      if (this.url.scheme === "file") {
   614        if (this.input[this.pointer + 1] !== 47 || this.input[this.pointer + 2] !== 47) {
   615          this.parseError = true;
   616        }
   617        this.state = "file";
   618      } else if (isSpecial(this.url) && this.base !== null && this.base.scheme === this.url.scheme) {
   619        this.state = "special relative or authority";
   620      } else if (isSpecial(this.url)) {
   621        this.state = "special authority slashes";
   622      } else if (this.input[this.pointer + 1] === 47) {
   623        this.state = "path or authority";
   624        ++this.pointer;
   625      } else {
   626        this.url.cannotBeABaseURL = true;
   627        this.url.path.push("");
   628        this.state = "cannot-be-a-base-URL path";
   629      }
   630    } else if (!this.stateOverride) {
   631      this.buffer = "";
   632      this.state = "no scheme";
   633      this.pointer = -1;
   634    } else {
   635      this.parseError = true;
   636      return failure;
   637    }
   638  
   639    return true;
   640  };
   641  
   642  URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) {
   643    if (this.base === null || (this.base.cannotBeABaseURL && c !== 35)) {
   644      return failure;
   645    } else if (this.base.cannotBeABaseURL && c === 35) {
   646      this.url.scheme = this.base.scheme;
   647      this.url.path = this.base.path.slice();
   648      this.url.query = this.base.query;
   649      this.url.fragment = "";
   650      this.url.cannotBeABaseURL = true;
   651      this.state = "fragment";
   652    } else if (this.base.scheme === "file") {
   653      this.state = "file";
   654      --this.pointer;
   655    } else {
   656      this.state = "relative";
   657      --this.pointer;
   658    }
   659  
   660    return true;
   661  };
   662  
   663  URLStateMachine.prototype["parse special relative or authority"] = function parseSpecialRelativeOrAuthority(c) {
   664    if (c === 47 && this.input[this.pointer + 1] === 47) {
   665      this.state = "special authority ignore slashes";
   666      ++this.pointer;
   667    } else {
   668      this.parseError = true;
   669      this.state = "relative";
   670      --this.pointer;
   671    }
   672  
   673    return true;
   674  };
   675  
   676  URLStateMachine.prototype["parse path or authority"] = function parsePathOrAuthority(c) {
   677    if (c === 47) {
   678      this.state = "authority";
   679    } else {
   680      this.state = "path";
   681      --this.pointer;
   682    }
   683  
   684    return true;
   685  };
   686  
   687  URLStateMachine.prototype["parse relative"] = function parseRelative(c) {
   688    this.url.scheme = this.base.scheme;
   689    if (isNaN(c)) {
   690      this.url.username = this.base.username;
   691      this.url.password = this.base.password;
   692      this.url.host = this.base.host;
   693      this.url.port = this.base.port;
   694      this.url.path = this.base.path.slice();
   695      this.url.query = this.base.query;
   696    } else if (c === 47) {
   697      this.state = "relative slash";
   698    } else if (c === 63) {
   699      this.url.username = this.base.username;
   700      this.url.password = this.base.password;
   701      this.url.host = this.base.host;
   702      this.url.port = this.base.port;
   703      this.url.path = this.base.path.slice();
   704      this.url.query = "";
   705      this.state = "query";
   706    } else if (c === 35) {
   707      this.url.username = this.base.username;
   708      this.url.password = this.base.password;
   709      this.url.host = this.base.host;
   710      this.url.port = this.base.port;
   711      this.url.path = this.base.path.slice();
   712      this.url.query = this.base.query;
   713      this.url.fragment = "";
   714      this.state = "fragment";
   715    } else if (isSpecial(this.url) && c === 92) {
   716      this.parseError = true;
   717      this.state = "relative slash";
   718    } else {
   719      this.url.username = this.base.username;
   720      this.url.password = this.base.password;
   721      this.url.host = this.base.host;
   722      this.url.port = this.base.port;
   723      this.url.path = this.base.path.slice(0, this.base.path.length - 1);
   724  
   725      this.state = "path";
   726      --this.pointer;
   727    }
   728  
   729    return true;
   730  };
   731  
   732  URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) {
   733    if (isSpecial(this.url) && (c === 47 || c === 92)) {
   734      if (c === 92) {
   735        this.parseError = true;
   736      }
   737      this.state = "special authority ignore slashes";
   738    } else if (c === 47) {
   739      this.state = "authority";
   740    } else {
   741      this.url.username = this.base.username;
   742      this.url.password = this.base.password;
   743      this.url.host = this.base.host;
   744      this.url.port = this.base.port;
   745      this.state = "path";
   746      --this.pointer;
   747    }
   748  
   749    return true;
   750  };
   751  
   752  URLStateMachine.prototype["parse special authority slashes"] = function parseSpecialAuthoritySlashes(c) {
   753    if (c === 47 && this.input[this.pointer + 1] === 47) {
   754      this.state = "special authority ignore slashes";
   755      ++this.pointer;
   756    } else {
   757      this.parseError = true;
   758      this.state = "special authority ignore slashes";
   759      --this.pointer;
   760    }
   761  
   762    return true;
   763  };
   764  
   765  URLStateMachine.prototype["parse special authority ignore slashes"] = function parseSpecialAuthorityIgnoreSlashes(c) {
   766    if (c !== 47 && c !== 92) {
   767      this.state = "authority";
   768      --this.pointer;
   769    } else {
   770      this.parseError = true;
   771    }
   772  
   773    return true;
   774  };
   775  
   776  URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) {
   777    if (c === 64) {
   778      this.parseError = true;
   779      if (this.atFlag) {
   780        this.buffer = "%40" + this.buffer;
   781      }
   782      this.atFlag = true;
   783  
   784      // careful, this is based on buffer and has its own pointer (this.pointer != pointer) and inner chars
   785      const len = countSymbols(this.buffer);
   786      for (let pointer = 0; pointer < len; ++pointer) {
   787        const codePoint = this.buffer.codePointAt(pointer);
   788  
   789        if (codePoint === 58 && !this.passwordTokenSeenFlag) {
   790          this.passwordTokenSeenFlag = true;
   791          continue;
   792        }
   793        const encodedCodePoints = percentEncodeChar(codePoint, isUserinfoPercentEncode);
   794        if (this.passwordTokenSeenFlag) {
   795          this.url.password += encodedCodePoints;
   796        } else {
   797          this.url.username += encodedCodePoints;
   798        }
   799      }
   800      this.buffer = "";
   801    } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
   802               (isSpecial(this.url) && c === 92)) {
   803      if (this.atFlag && this.buffer === "") {
   804        this.parseError = true;
   805        return failure;
   806      }
   807      this.pointer -= countSymbols(this.buffer) + 1;
   808      this.buffer = "";
   809      this.state = "host";
   810    } else {
   811      this.buffer += cStr;
   812    }
   813  
   814    return true;
   815  };
   816  
   817  URLStateMachine.prototype["parse hostname"] =
   818  URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) {
   819    if (this.stateOverride && this.url.scheme === "file") {
   820      --this.pointer;
   821      this.state = "file host";
   822    } else if (c === 58 && !this.arrFlag) {
   823      if (this.buffer === "") {
   824        this.parseError = true;
   825        return failure;
   826      }
   827  
   828      const host = parseHost(this.buffer, isSpecial(this.url));
   829      if (host === failure) {
   830        return failure;
   831      }
   832  
   833      this.url.host = host;
   834      this.buffer = "";
   835      this.state = "port";
   836      if (this.stateOverride === "hostname") {
   837        return false;
   838      }
   839    } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
   840               (isSpecial(this.url) && c === 92)) {
   841      --this.pointer;
   842      if (isSpecial(this.url) && this.buffer === "") {
   843        this.parseError = true;
   844        return failure;
   845      } else if (this.stateOverride && this.buffer === "" &&
   846                 (includesCredentials(this.url) || this.url.port !== null)) {
   847        this.parseError = true;
   848        return false;
   849      }
   850  
   851      const host = parseHost(this.buffer, isSpecial(this.url));
   852      if (host === failure) {
   853        return failure;
   854      }
   855  
   856      this.url.host = host;
   857      this.buffer = "";
   858      this.state = "path start";
   859      if (this.stateOverride) {
   860        return false;
   861      }
   862    } else {
   863      if (c === 91) {
   864        this.arrFlag = true;
   865      } else if (c === 93) {
   866        this.arrFlag = false;
   867      }
   868      this.buffer += cStr;
   869    }
   870  
   871    return true;
   872  };
   873  
   874  URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) {
   875    if (isASCIIDigit(c)) {
   876      this.buffer += cStr;
   877    } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
   878               (isSpecial(this.url) && c === 92) ||
   879               this.stateOverride) {
   880      if (this.buffer !== "") {
   881        const port = parseInt(this.buffer);
   882        if (port > Math.pow(2, 16) - 1) {
   883          this.parseError = true;
   884          return failure;
   885        }
   886        this.url.port = port === defaultPort(this.url.scheme) ? null : port;
   887        this.buffer = "";
   888      }
   889      if (this.stateOverride) {
   890        return false;
   891      }
   892      this.state = "path start";
   893      --this.pointer;
   894    } else {
   895      this.parseError = true;
   896      return failure;
   897    }
   898  
   899    return true;
   900  };
   901  
   902  const fileOtherwiseCodePoints = new Set([47, 92, 63, 35]);
   903  
   904  URLStateMachine.prototype["parse file"] = function parseFile(c) {
   905    this.url.scheme = "file";
   906  
   907    if (c === 47 || c === 92) {
   908      if (c === 92) {
   909        this.parseError = true;
   910      }
   911      this.state = "file slash";
   912    } else if (this.base !== null && this.base.scheme === "file") {
   913      if (isNaN(c)) {
   914        this.url.host = this.base.host;
   915        this.url.path = this.base.path.slice();
   916        this.url.query = this.base.query;
   917      } else if (c === 63) {
   918        this.url.host = this.base.host;
   919        this.url.path = this.base.path.slice();
   920        this.url.query = "";
   921        this.state = "query";
   922      } else if (c === 35) {
   923        this.url.host = this.base.host;
   924        this.url.path = this.base.path.slice();
   925        this.url.query = this.base.query;
   926        this.url.fragment = "";
   927        this.state = "fragment";
   928      } else {
   929        if (this.input.length - this.pointer - 1 === 0 || // remaining consists of 0 code points
   930            !isWindowsDriveLetterCodePoints(c, this.input[this.pointer + 1]) ||
   931            (this.input.length - this.pointer - 1 >= 2 && // remaining has at least 2 code points
   932             !fileOtherwiseCodePoints.has(this.input[this.pointer + 2]))) {
   933          this.url.host = this.base.host;
   934          this.url.path = this.base.path.slice();
   935          shortenPath(this.url);
   936        } else {
   937          this.parseError = true;
   938        }
   939  
   940        this.state = "path";
   941        --this.pointer;
   942      }
   943    } else {
   944      this.state = "path";
   945      --this.pointer;
   946    }
   947  
   948    return true;
   949  };
   950  
   951  URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) {
   952    if (c === 47 || c === 92) {
   953      if (c === 92) {
   954        this.parseError = true;
   955      }
   956      this.state = "file host";
   957    } else {
   958      if (this.base !== null && this.base.scheme === "file") {
   959        if (isNormalizedWindowsDriveLetterString(this.base.path[0])) {
   960          this.url.path.push(this.base.path[0]);
   961        } else {
   962          this.url.host = this.base.host;
   963        }
   964      }
   965      this.state = "path";
   966      --this.pointer;
   967    }
   968  
   969    return true;
   970  };
   971  
   972  URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) {
   973    if (isNaN(c) || c === 47 || c === 92 || c === 63 || c === 35) {
   974      --this.pointer;
   975      if (!this.stateOverride && isWindowsDriveLetterString(this.buffer)) {
   976        this.parseError = true;
   977        this.state = "path";
   978      } else if (this.buffer === "") {
   979        this.url.host = "";
   980        if (this.stateOverride) {
   981          return false;
   982        }
   983        this.state = "path start";
   984      } else {
   985        let host = parseHost(this.buffer, isSpecial(this.url));
   986        if (host === failure) {
   987          return failure;
   988        }
   989        if (host === "localhost") {
   990          host = "";
   991        }
   992        this.url.host = host;
   993  
   994        if (this.stateOverride) {
   995          return false;
   996        }
   997  
   998        this.buffer = "";
   999        this.state = "path start";
  1000      }
  1001    } else {
  1002      this.buffer += cStr;
  1003    }
  1004  
  1005    return true;
  1006  };
  1007  
  1008  URLStateMachine.prototype["parse path start"] = function parsePathStart(c) {
  1009    if (isSpecial(this.url)) {
  1010      if (c === 92) {
  1011        this.parseError = true;
  1012      }
  1013      this.state = "path";
  1014  
  1015      if (c !== 47 && c !== 92) {
  1016        --this.pointer;
  1017      }
  1018    } else if (!this.stateOverride && c === 63) {
  1019      this.url.query = "";
  1020      this.state = "query";
  1021    } else if (!this.stateOverride && c === 35) {
  1022      this.url.fragment = "";
  1023      this.state = "fragment";
  1024    } else if (c !== undefined) {
  1025      this.state = "path";
  1026      if (c !== 47) {
  1027        --this.pointer;
  1028      }
  1029    }
  1030  
  1031    return true;
  1032  };
  1033  
  1034  URLStateMachine.prototype["parse path"] = function parsePath(c) {
  1035    if (isNaN(c) || c === 47 || (isSpecial(this.url) && c === 92) ||
  1036        (!this.stateOverride && (c === 63 || c === 35))) {
  1037      if (isSpecial(this.url) && c === 92) {
  1038        this.parseError = true;
  1039      }
  1040  
  1041      if (isDoubleDot(this.buffer)) {
  1042        shortenPath(this.url);
  1043        if (c !== 47 && !(isSpecial(this.url) && c === 92)) {
  1044          this.url.path.push("");
  1045        }
  1046      } else if (isSingleDot(this.buffer) && c !== 47 &&
  1047                 !(isSpecial(this.url) && c === 92)) {
  1048        this.url.path.push("");
  1049      } else if (!isSingleDot(this.buffer)) {
  1050        if (this.url.scheme === "file" && this.url.path.length === 0 && isWindowsDriveLetterString(this.buffer)) {
  1051          if (this.url.host !== "" && this.url.host !== null) {
  1052            this.parseError = true;
  1053            this.url.host = "";
  1054          }
  1055          this.buffer = this.buffer[0] + ":";
  1056        }
  1057        this.url.path.push(this.buffer);
  1058      }
  1059      this.buffer = "";
  1060      if (this.url.scheme === "file" && (c === undefined || c === 63 || c === 35)) {
  1061        while (this.url.path.length > 1 && this.url.path[0] === "") {
  1062          this.parseError = true;
  1063          this.url.path.shift();
  1064        }
  1065      }
  1066      if (c === 63) {
  1067        this.url.query = "";
  1068        this.state = "query";
  1069      }
  1070      if (c === 35) {
  1071        this.url.fragment = "";
  1072        this.state = "fragment";
  1073      }
  1074    } else {
  1075      // TODO: If c is not a URL code point and not "%", parse error.
  1076  
  1077      if (c === 37 &&
  1078        (!isASCIIHex(this.input[this.pointer + 1]) ||
  1079          !isASCIIHex(this.input[this.pointer + 2]))) {
  1080        this.parseError = true;
  1081      }
  1082  
  1083      this.buffer += percentEncodeChar(c, isPathPercentEncode);
  1084    }
  1085  
  1086    return true;
  1087  };
  1088  
  1089  URLStateMachine.prototype["parse cannot-be-a-base-URL path"] = function parseCannotBeABaseURLPath(c) {
  1090    if (c === 63) {
  1091      this.url.query = "";
  1092      this.state = "query";
  1093    } else if (c === 35) {
  1094      this.url.fragment = "";
  1095      this.state = "fragment";
  1096    } else {
  1097      // TODO: Add: not a URL code point
  1098      if (!isNaN(c) && c !== 37) {
  1099        this.parseError = true;
  1100      }
  1101  
  1102      if (c === 37 &&
  1103          (!isASCIIHex(this.input[this.pointer + 1]) ||
  1104           !isASCIIHex(this.input[this.pointer + 2]))) {
  1105        this.parseError = true;
  1106      }
  1107  
  1108      if (!isNaN(c)) {
  1109        this.url.path[0] = this.url.path[0] + percentEncodeChar(c, isC0ControlPercentEncode);
  1110      }
  1111    }
  1112  
  1113    return true;
  1114  };
  1115  
  1116  URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) {
  1117    if (isNaN(c) || (!this.stateOverride && c === 35)) {
  1118      if (!isSpecial(this.url) || this.url.scheme === "ws" || this.url.scheme === "wss") {
  1119        this.encodingOverride = "utf-8";
  1120      }
  1121  
  1122      const buffer = new Buffer(this.buffer); // TODO: Use encoding override instead
  1123      for (let i = 0; i < buffer.length; ++i) {
  1124        if (buffer[i] < 0x21 || buffer[i] > 0x7E || buffer[i] === 0x22 || buffer[i] === 0x23 ||
  1125            buffer[i] === 0x3C || buffer[i] === 0x3E) {
  1126          this.url.query += percentEncode(buffer[i]);
  1127        } else {
  1128          this.url.query += String.fromCodePoint(buffer[i]);
  1129        }
  1130      }
  1131  
  1132      this.buffer = "";
  1133      if (c === 35) {
  1134        this.url.fragment = "";
  1135        this.state = "fragment";
  1136      }
  1137    } else {
  1138      // TODO: If c is not a URL code point and not "%", parse error.
  1139      if (c === 37 &&
  1140        (!isASCIIHex(this.input[this.pointer + 1]) ||
  1141          !isASCIIHex(this.input[this.pointer + 2]))) {
  1142        this.parseError = true;
  1143      }
  1144  
  1145      this.buffer += cStr;
  1146    }
  1147  
  1148    return true;
  1149  };
  1150  
  1151  URLStateMachine.prototype["parse fragment"] = function parseFragment(c) {
  1152    if (isNaN(c)) { // do nothing
  1153    } else if (c === 0x0) {
  1154      this.parseError = true;
  1155    } else {
  1156      // TODO: If c is not a URL code point and not "%", parse error.
  1157      if (c === 37 &&
  1158        (!isASCIIHex(this.input[this.pointer + 1]) ||
  1159          !isASCIIHex(this.input[this.pointer + 2]))) {
  1160        this.parseError = true;
  1161      }
  1162  
  1163      this.url.fragment += percentEncodeChar(c, isC0ControlPercentEncode);
  1164    }
  1165  
  1166    return true;
  1167  };
  1168  
  1169  function serializeURL(url, excludeFragment) {
  1170    let output = url.scheme + ":";
  1171    if (url.host !== null) {
  1172      output += "//";
  1173  
  1174      if (url.username !== "" || url.password !== "") {
  1175        output += url.username;
  1176        if (url.password !== "") {
  1177          output += ":" + url.password;
  1178        }
  1179        output += "@";
  1180      }
  1181  
  1182      output += serializeHost(url.host);
  1183  
  1184      if (url.port !== null) {
  1185        output += ":" + url.port;
  1186      }
  1187    } else if (url.host === null && url.scheme === "file") {
  1188      output += "//";
  1189    }
  1190  
  1191    if (url.cannotBeABaseURL) {
  1192      output += url.path[0];
  1193    } else {
  1194      for (const string of url.path) {
  1195        output += "/" + string;
  1196      }
  1197    }
  1198  
  1199    if (url.query !== null) {
  1200      output += "?" + url.query;
  1201    }
  1202  
  1203    if (!excludeFragment && url.fragment !== null) {
  1204      output += "#" + url.fragment;
  1205    }
  1206  
  1207    return output;
  1208  }
  1209  
  1210  function serializeOrigin(tuple) {
  1211    let result = tuple.scheme + "://";
  1212    result += serializeHost(tuple.host);
  1213  
  1214    if (tuple.port !== null) {
  1215      result += ":" + tuple.port;
  1216    }
  1217  
  1218    return result;
  1219  }
  1220  
  1221  module.exports.serializeURL = serializeURL;
  1222  
  1223  module.exports.serializeURLOrigin = function (url) {
  1224    // https://url.spec.whatwg.org/#concept-url-origin
  1225    switch (url.scheme) {
  1226      case "blob":
  1227        try {
  1228          return module.exports.serializeURLOrigin(module.exports.parseURL(url.path[0]));
  1229        } catch (e) {
  1230          // serializing an opaque origin returns "null"
  1231          return "null";
  1232        }
  1233      case "ftp":
  1234      case "gopher":
  1235      case "http":
  1236      case "https":
  1237      case "ws":
  1238      case "wss":
  1239        return serializeOrigin({
  1240          scheme: url.scheme,
  1241          host: url.host,
  1242          port: url.port
  1243        });
  1244      case "file":
  1245        // spec says "exercise to the reader", chrome says "file://"
  1246        return "file://";
  1247      default:
  1248        // serializing an opaque origin returns "null"
  1249        return "null";
  1250    }
  1251  };
  1252  
  1253  module.exports.basicURLParse = function (input, options) {
  1254    if (options === undefined) {
  1255      options = {};
  1256    }
  1257  
  1258    const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride);
  1259    if (usm.failure) {
  1260      return "failure";
  1261    }
  1262  
  1263    return usm.url;
  1264  };
  1265  
  1266  module.exports.setTheUsername = function (url, username) {
  1267    url.username = "";
  1268    const decoded = punycode.ucs2.decode(username);
  1269    for (let i = 0; i < decoded.length; ++i) {
  1270      url.username += percentEncodeChar(decoded[i], isUserinfoPercentEncode);
  1271    }
  1272  };
  1273  
  1274  module.exports.setThePassword = function (url, password) {
  1275    url.password = "";
  1276    const decoded = punycode.ucs2.decode(password);
  1277    for (let i = 0; i < decoded.length; ++i) {
  1278      url.password += percentEncodeChar(decoded[i], isUserinfoPercentEncode);
  1279    }
  1280  };
  1281  
  1282  module.exports.serializeHost = serializeHost;
  1283  
  1284  module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort;
  1285  
  1286  module.exports.serializeInteger = function (integer) {
  1287    return String(integer);
  1288  };
  1289  
  1290  module.exports.parseURL = function (input, options) {
  1291    if (options === undefined) {
  1292      options = {};
  1293    }
  1294  
  1295    // We don't handle blobs, so this just delegates:
  1296    return module.exports.basicURLParse(input, { baseURL: options.baseURL, encodingOverride: options.encodingOverride });
  1297  };