JAVASCRIPT   15

url.js

Guest on 15th May 2021 10:20:42 AM

  1. 'use strict';
  2.  
  3. const util = require('util');
  4. const {
  5.   hexTable,
  6.   isHexTable
  7. } = require('internal/querystring');
  8.  
  9. const { getConstructorOf } = require('internal/util');
  10. const errors = require('internal/errors');
  11. const querystring = require('querystring');
  12.  
  13. const { platform } = process;
  14. const isWindows = platform === 'win32';
  15.  
  16. const {
  17.   domainToASCII: _domainToASCII,
  18.   domainToUnicode: _domainToUnicode,
  19.   encodeAuth,
  20.   toUSVString: _toUSVString,
  21.   parse: _parse,
  22.   setURLConstructor,
  23.   URL_FLAGS_CANNOT_BE_BASE,
  24.   URL_FLAGS_HAS_FRAGMENT,
  25.   URL_FLAGS_HAS_HOST,
  26.   URL_FLAGS_HAS_PASSWORD,
  27.   URL_FLAGS_HAS_PATH,
  28.   URL_FLAGS_HAS_QUERY,
  29.   URL_FLAGS_HAS_USERNAME,
  30.   URL_FLAGS_SPECIAL,
  31.   kFragment,
  32.   kHost,
  33.   kHostname,
  34.   kPathStart,
  35.   kPort,
  36.   kQuery,
  37.   kSchemeStart
  38. } = process.binding('url');
  39.  
  40. const context = Symbol('context');
  41. const cannotBeBase = Symbol('cannot-be-base');
  42. const cannotHaveUsernamePasswordPort =
  43.     Symbol('cannot-have-username-password-port');
  44. const special = Symbol('special');
  45. const searchParams = Symbol('query');
  46. const kFormat = Symbol('format');
  47.  
  48. // https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object
  49. const IteratorPrototype = Object.getPrototypeOf(
  50.   Object.getPrototypeOf([][Symbol.iterator]())
  51. );
  52.  
  53. const unpairedSurrogateRe =
  54.     /(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/;
  55. function toUSVString(val) {
  56.   const str = `${val}`;
  57.   // As of V8 5.5, `str.search()` (and `unpairedSurrogateRe[@@search]()`) are
  58.   // slower than `unpairedSurrogateRe.exec()`.
  59.   const match = unpairedSurrogateRe.exec(str);
  60.   if (!match)
  61.     return str;
  62.   return _toUSVString(str, match.index);
  63. }
  64.  
  65. // Refs: https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-opaque
  66. const kOpaqueOrigin = 'null';
  67.  
  68. // Refs: https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin
  69. function serializeTupleOrigin(scheme, host, port) {
  70.   return `${scheme}//${host}${port === null ? '' : `:${port}`}`;
  71. }
  72.  
  73. // This class provides the internal state of a URL object. An instance of this
  74. // class is stored in every URL object and is accessed internally by setters
  75. // and getters. It roughly corresponds to the concept of a URL record in the
  76. // URL Standard, with a few differences. It is also the object transported to
  77. // the C++ binding.
  78. // Refs: https://url.spec.whatwg.org/#concept-url
  79. class URLContext {
  80.   constructor() {
  81.     this.flags = 0;
  82.     this.scheme = ':';
  83.     this.username = '';
  84.     this.password = '';
  85.     this.host = null;
  86.     this.port = null;
  87.     this.path = [];
  88.     this.query = null;
  89.     this.fragment = null;
  90.   }
  91. }
  92.  
  93. class URLSearchParams {
  94.   // URL Standard says the default value is '', but as undefined and '' have
  95.   // the same result, undefined is used to prevent unnecessary parsing.
  96.   // Default parameter is necessary to keep URLSearchParams.length === 0 in
  97.   // accordance with Web IDL spec.
  98.   constructor(init = undefined) {
  99.     if (init === null || init === undefined) {
  100.       this[searchParams] = [];
  101.     } else if ((typeof init === 'object' && init !== null) ||
  102.                typeof init === 'function') {
  103.       const method = init[Symbol.iterator];
  104.       if (method === this[Symbol.iterator]) {
  105.         // While the spec does not have this branch, we can use it as a
  106.         // shortcut to avoid having to go through the costly generic iterator.
  107.         const childParams = init[searchParams];
  108.         this[searchParams] = childParams.slice();
  109.       } else if (method !== null && method !== undefined) {
  110.         if (typeof method !== 'function') {
  111.           throw new errors.TypeError('ERR_ARG_NOT_ITERABLE', 'Query pairs');
  112.         }
  113.  
  114.         // sequence<sequence<USVString>>
  115.         // Note: per spec we have to first exhaust the lists then process them
  116.         const pairs = [];
  117.         for (const pair of init) {
  118.           if ((typeof pair !== 'object' && typeof pair !== 'function') ||
  119.               pair === null ||
  120.               typeof pair[Symbol.iterator] !== 'function') {
  121.             throw new errors.TypeError('ERR_INVALID_TUPLE', 'Each query pair',
  122.                                        '[name, value]');
  123.           }
  124.           const convertedPair = [];
  125.           for (const element of pair)
  126.             convertedPair.push(toUSVString(element));
  127.           pairs.push(convertedPair);
  128.         }
  129.  
  130.         this[searchParams] = [];
  131.         for (const pair of pairs) {
  132.           if (pair.length !== 2) {
  133.             throw new errors.TypeError('ERR_INVALID_TUPLE', 'Each query pair',
  134.                                        '[name, value]');
  135.           }
  136.           this[searchParams].push(pair[0], pair[1]);
  137.         }
  138.       } else {
  139.         // record<USVString, USVString>
  140.         // Need to use reflection APIs for full spec compliance.
  141.         this[searchParams] = [];
  142.         const keys = Reflect.ownKeys(init);
  143.         for (var i = 0; i < keys.length; i++) {
  144.           const key = keys[i];
  145.           const desc = Reflect.getOwnPropertyDescriptor(init, key);
  146.           if (desc !== undefined && desc.enumerable) {
  147.             const typedKey = toUSVString(key);
  148.             const typedValue = toUSVString(init[key]);
  149.             this[searchParams].push(typedKey, typedValue);
  150.           }
  151.         }
  152.       }
  153.     } else {
  154.       // USVString
  155.       init = toUSVString(init);
  156.       if (init[0] === '?') init = init.slice(1);
  157.       initSearchParams(this, init);
  158.     }
  159.  
  160.     // "associated url object"
  161.     this[context] = null;
  162.   }
  163.  
  164.   [util.inspect.custom](recurseTimes, ctx) {
  165.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  166.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  167.     }
  168.  
  169.     if (typeof recurseTimes === 'number' && recurseTimes < 0)
  170.       return ctx.stylize('[Object]', 'special');
  171.  
  172.     var separator = ', ';
  173.     var innerOpts = util._extend({}, ctx);
  174.     if (recurseTimes !== null) {
  175.       innerOpts.depth = recurseTimes - 1;
  176.     }
  177.     var innerInspect = (v) => util.inspect(v, innerOpts);
  178.  
  179.     var list = this[searchParams];
  180.     var output = [];
  181.     for (var i = 0; i < list.length; i += 2)
  182.       output.push(`${innerInspect(list[i])} => ${innerInspect(list[i + 1])}`);
  183.  
  184.     var colorRe = /\u001b\[\d\d?m/g;
  185.     var length = output.reduce(
  186.       (prev, cur) => prev + cur.replace(colorRe, '').length + separator.length,
  187.       -separator.length
  188.     );
  189.     if (length > ctx.breakLength) {
  190.       return `${this.constructor.name} {\n  ${output.join(',\n  ')} }`;
  191.     } else if (output.length) {
  192.       return `${this.constructor.name} { ${output.join(separator)} }`;
  193.     } else {
  194.       return `${this.constructor.name} {}`;
  195.     }
  196.   }
  197. }
  198.  
  199. function onParseComplete(flags, protocol, username, password,
  200.                          host, port, path, query, fragment) {
  201.   var ctx = this[context];
  202.   ctx.flags = flags;
  203.   ctx.scheme = protocol;
  204.   ctx.username = (flags & URL_FLAGS_HAS_USERNAME) !== 0 ? username : '';
  205.   ctx.password = (flags & URL_FLAGS_HAS_PASSWORD) !== 0 ? password : '';
  206.   ctx.port = port;
  207.   ctx.path = (flags & URL_FLAGS_HAS_PATH) !== 0 ? path : [];
  208.   ctx.query = query;
  209.   ctx.fragment = fragment;
  210.   ctx.host = host;
  211.   if (!this[searchParams]) { // invoked from URL constructor
  212.     this[searchParams] = new URLSearchParams();
  213.     this[searchParams][context] = this;
  214.   }
  215.   initSearchParams(this[searchParams], query);
  216. }
  217.  
  218. function onParseError(flags, input) {
  219.   const error = new errors.TypeError('ERR_INVALID_URL', input);
  220.   error.input = input;
  221.   throw error;
  222. }
  223.  
  224. // Reused by URL constructor and URL#href setter.
  225. function parse(url, input, base) {
  226.   const base_context = base ? base[context] : undefined;
  227.   url[context] = new URLContext();
  228.   _parse(input.trim(), -1, base_context, undefined,
  229.          onParseComplete.bind(url), onParseError);
  230. }
  231.  
  232. function onParseProtocolComplete(flags, protocol, username, password,
  233.                                  host, port, path, query, fragment) {
  234.   const ctx = this[context];
  235.   if ((flags & URL_FLAGS_SPECIAL) !== 0) {
  236.     ctx.flags |= URL_FLAGS_SPECIAL;
  237.   } else {
  238.     ctx.flags &= ~URL_FLAGS_SPECIAL;
  239.   }
  240.   ctx.scheme = protocol;
  241.   ctx.port = port;
  242. }
  243.  
  244. function onParseHostComplete(flags, protocol, username, password,
  245.                              host, port, path, query, fragment) {
  246.   const ctx = this[context];
  247.   if ((flags & URL_FLAGS_HAS_HOST) !== 0) {
  248.     ctx.host = host;
  249.     ctx.flags |= URL_FLAGS_HAS_HOST;
  250.   } else {
  251.     ctx.host = null;
  252.     ctx.flags &= ~URL_FLAGS_HAS_HOST;
  253.   }
  254.   if (port !== null)
  255.     ctx.port = port;
  256. }
  257.  
  258. function onParseHostnameComplete(flags, protocol, username, password,
  259.                                  host, port, path, query, fragment) {
  260.   const ctx = this[context];
  261.   if ((flags & URL_FLAGS_HAS_HOST) !== 0) {
  262.     ctx.host = host;
  263.     ctx.flags |= URL_FLAGS_HAS_HOST;
  264.   } else {
  265.     ctx.host = null;
  266.     ctx.flags &= ~URL_FLAGS_HAS_HOST;
  267.   }
  268. }
  269.  
  270. function onParsePortComplete(flags, protocol, username, password,
  271.                              host, port, path, query, fragment) {
  272.   this[context].port = port;
  273. }
  274.  
  275. function onParsePathComplete(flags, protocol, username, password,
  276.                              host, port, path, query, fragment) {
  277.   const ctx = this[context];
  278.   if ((flags & URL_FLAGS_HAS_PATH) !== 0) {
  279.     ctx.path = path;
  280.     ctx.flags |= URL_FLAGS_HAS_PATH;
  281.   } else {
  282.     ctx.path = [];
  283.     ctx.flags &= ~URL_FLAGS_HAS_PATH;
  284.   }
  285.  
  286.   // The C++ binding may set host to empty string.
  287.   if ((flags & URL_FLAGS_HAS_HOST) !== 0) {
  288.     ctx.host = host;
  289.     ctx.flags |= URL_FLAGS_HAS_HOST;
  290.   }
  291. }
  292.  
  293. function onParseSearchComplete(flags, protocol, username, password,
  294.                                host, port, path, query, fragment) {
  295.   this[context].query = query;
  296. }
  297.  
  298. function onParseHashComplete(flags, protocol, username, password,
  299.                              host, port, path, query, fragment) {
  300.   this[context].fragment = fragment;
  301. }
  302.  
  303. class URL {
  304.   constructor(input, base) {
  305.     // toUSVString is not needed.
  306.     input = `${input}`;
  307.     if (base !== undefined &&
  308.         (!base[searchParams] || !base[searchParams][searchParams])) {
  309.       base = new URL(base);
  310.     }
  311.     parse(this, input, base);
  312.   }
  313.  
  314.   get [special]() {
  315.     return (this[context].flags & URL_FLAGS_SPECIAL) !== 0;
  316.   }
  317.  
  318.   get [cannotBeBase]() {
  319.     return (this[context].flags & URL_FLAGS_CANNOT_BE_BASE) !== 0;
  320.   }
  321.  
  322.   // https://url.spec.whatwg.org/#cannot-have-a-username-password-port
  323.   get [cannotHaveUsernamePasswordPort]() {
  324.     const { host, scheme } = this[context];
  325.     return ((host == null || host === '') ||
  326.             this[cannotBeBase] ||
  327.             scheme === 'file:');
  328.   }
  329.  
  330.   [util.inspect.custom](depth, opts) {
  331.     if (this == null ||
  332.         Object.getPrototypeOf(this[context]) !== URLContext.prototype) {
  333.       throw new errors.TypeError('ERR_INVALID_THIS', 'URL');
  334.     }
  335.  
  336.     if (typeof depth === 'number' && depth < 0)
  337.       return opts.stylize('[Object]', 'special');
  338.  
  339.     var ctor = getConstructorOf(this);
  340.  
  341.     var obj = Object.create({
  342.       constructor: ctor === null ? URL : ctor
  343.     });
  344.  
  345.     obj.href = this.href;
  346.     obj.origin = this.origin;
  347.     obj.protocol = this.protocol;
  348.     obj.username = this.username;
  349.     obj.password = this.password;
  350.     obj.host = this.host;
  351.     obj.hostname = this.hostname;
  352.     obj.port = this.port;
  353.     obj.pathname = this.pathname;
  354.     obj.search = this.search;
  355.     obj.searchParams = this.searchParams;
  356.     obj.hash = this.hash;
  357.  
  358.     if (opts.showHidden) {
  359.       obj.cannotBeBase = this[cannotBeBase];
  360.       obj.special = this[special];
  361.       obj[context] = this[context];
  362.     }
  363.  
  364.     return util.inspect(obj, opts);
  365.   }
  366. }
  367.  
  368. Object.defineProperties(URL.prototype, {
  369.   [kFormat]: {
  370.     enumerable: false,
  371.     configurable: false,
  372.     // eslint-disable-next-line func-name-matching
  373.     value: function format(options) {
  374.       if (options && typeof options !== 'object')
  375.         throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options', 'object');
  376.       options = util._extend({
  377.         fragment: true,
  378.         unicode: false,
  379.         search: true,
  380.         auth: true
  381.       }, options);
  382.       const ctx = this[context];
  383.       var ret = ctx.scheme;
  384.       if (ctx.host !== null) {
  385.         ret += '//';
  386.         const has_username = ctx.username !== '';
  387.         const has_password = ctx.password !== '';
  388.         if (options.auth && (has_username || has_password)) {
  389.           if (has_username)
  390.             ret += ctx.username;
  391.           if (has_password)
  392.             ret += `:${ctx.password}`;
  393.           ret += '@';
  394.         }
  395.         ret += options.unicode ?
  396.           domainToUnicode(this.host) : this.host;
  397.       } else if (ctx.scheme === 'file:') {
  398.         ret += '//';
  399.       }
  400.       if (this.pathname)
  401.         ret += this.pathname;
  402.       if (options.search && ctx.query !== null)
  403.         ret += `?${ctx.query}`;
  404.       if (options.fragment && ctx.fragment !== null)
  405.         ret += `#${ctx.fragment}`;
  406.       return ret;
  407.     }
  408.   },
  409.   [Symbol.toStringTag]: {
  410.     configurable: true,
  411.     value: 'URL'
  412.   },
  413.   toString: {
  414.     // https://heycam.github.io/webidl/#es-stringifier
  415.     writable: true,
  416.     enumerable: true,
  417.     configurable: true,
  418.     // eslint-disable-next-line func-name-matching
  419.     value: function toString() {
  420.       return this[kFormat]({});
  421.     }
  422.   },
  423.   href: {
  424.     enumerable: true,
  425.     configurable: true,
  426.     get() {
  427.       return this[kFormat]({});
  428.     },
  429.     set(input) {
  430.       // toUSVString is not needed.
  431.       input = `${input}`;
  432.       parse(this, input);
  433.     }
  434.   },
  435.   origin: {  // readonly
  436.     enumerable: true,
  437.     configurable: true,
  438.     get() {
  439.       // Refs: https://url.spec.whatwg.org/#concept-url-origin
  440.       const ctx = this[context];
  441.       switch (ctx.scheme) {
  442.         case 'blob:':
  443.           if (ctx.path.length > 0) {
  444.             try {
  445.               return (new URL(ctx.path[0])).origin;
  446.             } catch (err) {
  447.               // fall through... do nothing
  448.             }
  449.           }
  450.           return kOpaqueOrigin;
  451.         case 'ftp:':
  452.         case 'gopher:':
  453.         case 'http:':
  454.         case 'https:':
  455.         case 'ws:':
  456.         case 'wss:':
  457.           return serializeTupleOrigin(ctx.scheme, ctx.host, ctx.port);
  458.       }
  459.       return kOpaqueOrigin;
  460.     }
  461.   },
  462.   protocol: {
  463.     enumerable: true,
  464.     configurable: true,
  465.     get() {
  466.       return this[context].scheme;
  467.     },
  468.     set(scheme) {
  469.       // toUSVString is not needed.
  470.       scheme = `${scheme}`;
  471.       if (scheme.length === 0)
  472.         return;
  473.       const ctx = this[context];
  474.       if (ctx.scheme === 'file:' &&
  475.           (ctx.host === '' || ctx.host === null)) {
  476.         return;
  477.       }
  478.       _parse(scheme, kSchemeStart, null, ctx,
  479.              onParseProtocolComplete.bind(this));
  480.     }
  481.   },
  482.   username: {
  483.     enumerable: true,
  484.     configurable: true,
  485.     get() {
  486.       return this[context].username;
  487.     },
  488.     set(username) {
  489.       // toUSVString is not needed.
  490.       username = `${username}`;
  491.       if (this[cannotHaveUsernamePasswordPort])
  492.         return;
  493.       const ctx = this[context];
  494.       if (username === '') {
  495.         ctx.username = '';
  496.         ctx.flags &= ~URL_FLAGS_HAS_USERNAME;
  497.         return;
  498.       }
  499.       ctx.username = encodeAuth(username);
  500.       ctx.flags |= URL_FLAGS_HAS_USERNAME;
  501.     }
  502.   },
  503.   password: {
  504.     enumerable: true,
  505.     configurable: true,
  506.     get() {
  507.       return this[context].password;
  508.     },
  509.     set(password) {
  510.       // toUSVString is not needed.
  511.       password = `${password}`;
  512.       if (this[cannotHaveUsernamePasswordPort])
  513.         return;
  514.       const ctx = this[context];
  515.       if (password === '') {
  516.         ctx.password = '';
  517.         ctx.flags &= ~URL_FLAGS_HAS_PASSWORD;
  518.         return;
  519.       }
  520.       ctx.password = encodeAuth(password);
  521.       ctx.flags |= URL_FLAGS_HAS_PASSWORD;
  522.     }
  523.   },
  524.   host: {
  525.     enumerable: true,
  526.     configurable: true,
  527.     get() {
  528.       const ctx = this[context];
  529.       var ret = ctx.host || '';
  530.       if (ctx.port !== null)
  531.         ret += `:${ctx.port}`;
  532.       return ret;
  533.     },
  534.     set(host) {
  535.       const ctx = this[context];
  536.       // toUSVString is not needed.
  537.       host = `${host}`;
  538.       if (this[cannotBeBase]) {
  539.         // Cannot set the host if cannot-be-base is set
  540.         return;
  541.       }
  542.       _parse(host, kHost, null, ctx, onParseHostComplete.bind(this));
  543.     }
  544.   },
  545.   hostname: {
  546.     enumerable: true,
  547.     configurable: true,
  548.     get() {
  549.       return this[context].host || '';
  550.     },
  551.     set(host) {
  552.       const ctx = this[context];
  553.       // toUSVString is not needed.
  554.       host = `${host}`;
  555.       if (this[cannotBeBase]) {
  556.         // Cannot set the host if cannot-be-base is set
  557.         return;
  558.       }
  559.       _parse(host, kHostname, null, ctx, onParseHostnameComplete.bind(this));
  560.     }
  561.   },
  562.   port: {
  563.     enumerable: true,
  564.     configurable: true,
  565.     get() {
  566.       const port = this[context].port;
  567.       return port === null ? '' : String(port);
  568.     },
  569.     set(port) {
  570.       // toUSVString is not needed.
  571.       port = `${port}`;
  572.       if (this[cannotHaveUsernamePasswordPort])
  573.         return;
  574.       const ctx = this[context];
  575.       if (port === '') {
  576.         ctx.port = null;
  577.         return;
  578.       }
  579.       _parse(port, kPort, null, ctx, onParsePortComplete.bind(this));
  580.     }
  581.   },
  582.   pathname: {
  583.     enumerable: true,
  584.     configurable: true,
  585.     get() {
  586.       const ctx = this[context];
  587.       if (this[cannotBeBase])
  588.         return ctx.path[0];
  589.       if (ctx.path.length === 0)
  590.         return '';
  591.       return `/${ctx.path.join('/')}`;
  592.     },
  593.     set(path) {
  594.       // toUSVString is not needed.
  595.       path = `${path}`;
  596.       if (this[cannotBeBase])
  597.         return;
  598.       _parse(path, kPathStart, null, this[context],
  599.              onParsePathComplete.bind(this));
  600.     }
  601.   },
  602.   search: {
  603.     enumerable: true,
  604.     configurable: true,
  605.     get() {
  606.       const { query } = this[context];
  607.       if (query === null || query === '')
  608.         return '';
  609.       return `?${query}`;
  610.     },
  611.     set(search) {
  612.       const ctx = this[context];
  613.       search = toUSVString(search);
  614.       if (search === '') {
  615.         ctx.query = null;
  616.         ctx.flags &= ~URL_FLAGS_HAS_QUERY;
  617.       } else {
  618.         if (search[0] === '?') search = search.slice(1);
  619.         ctx.query = '';
  620.         ctx.flags |= URL_FLAGS_HAS_QUERY;
  621.         if (search) {
  622.           _parse(search, kQuery, null, ctx, onParseSearchComplete.bind(this));
  623.         }
  624.       }
  625.       initSearchParams(this[searchParams], search);
  626.     }
  627.   },
  628.   searchParams: {  // readonly
  629.     enumerable: true,
  630.     configurable: true,
  631.     get() {
  632.       return this[searchParams];
  633.     }
  634.   },
  635.   hash: {
  636.     enumerable: true,
  637.     configurable: true,
  638.     get() {
  639.       const { fragment } = this[context];
  640.       if (fragment === null || fragment === '')
  641.         return '';
  642.       return `#${fragment}`;
  643.     },
  644.     set(hash) {
  645.       const ctx = this[context];
  646.       // toUSVString is not needed.
  647.       hash = `${hash}`;
  648.       if (!hash) {
  649.         ctx.fragment = null;
  650.         ctx.flags &= ~URL_FLAGS_HAS_FRAGMENT;
  651.         return;
  652.       }
  653.       if (hash[0] === '#') hash = hash.slice(1);
  654.       ctx.fragment = '';
  655.       ctx.flags |= URL_FLAGS_HAS_FRAGMENT;
  656.       _parse(hash, kFragment, null, ctx, onParseHashComplete.bind(this));
  657.     }
  658.   },
  659.   toJSON: {
  660.     writable: true,
  661.     enumerable: true,
  662.     configurable: true,
  663.     // eslint-disable-next-line func-name-matching
  664.     value: function toJSON() {
  665.       return this[kFormat]({});
  666.     }
  667.   }
  668. });
  669.  
  670. function update(url, params) {
  671.   if (!url)
  672.     return;
  673.  
  674.   const ctx = url[context];
  675.   const serializedParams = params.toString();
  676.   if (serializedParams) {
  677.     ctx.query = serializedParams;
  678.     ctx.flags |= URL_FLAGS_HAS_QUERY;
  679.   } else {
  680.     ctx.query = null;
  681.     ctx.flags &= ~URL_FLAGS_HAS_QUERY;
  682.   }
  683. }
  684.  
  685. function initSearchParams(url, init) {
  686.   if (!init) {
  687.     url[searchParams] = [];
  688.     return;
  689.   }
  690.   url[searchParams] = parseParams(init);
  691. }
  692.  
  693. // application/x-www-form-urlencoded parser
  694. // Ref: https://url.spec.whatwg.org/#concept-urlencoded-parser
  695. function parseParams(qs) {
  696.   const out = [];
  697.   var pairStart = 0;
  698.   var lastPos = 0;
  699.   var seenSep = false;
  700.   var buf = '';
  701.   var encoded = false;
  702.   var encodeCheck = 0;
  703.   var i;
  704.   for (i = 0; i < qs.length; ++i) {
  705.     const code = qs.charCodeAt(i);
  706.  
  707.     // Try matching key/value pair separator
  708.     if (code === 38/*&*/) {
  709.       if (pairStart === i) {
  710.         // We saw an empty substring between pair separators
  711.         lastPos = pairStart = i + 1;
  712.         continue;
  713.       }
  714.  
  715.       if (lastPos < i)
  716.         buf += qs.slice(lastPos, i);
  717.       if (encoded)
  718.         buf = querystring.unescape(buf);
  719.       out.push(buf);
  720.  
  721.       // If `buf` is the key, add an empty value.
  722.       if (!seenSep)
  723.         out.push('');
  724.  
  725.       seenSep = false;
  726.       buf = '';
  727.       encoded = false;
  728.       encodeCheck = 0;
  729.       lastPos = pairStart = i + 1;
  730.       continue;
  731.     }
  732.  
  733.     // Try matching key/value separator (e.g. '=') if we haven't already
  734.     if (!seenSep && code === 61/*=*/) {
  735.       // Key/value separator match!
  736.       if (lastPos < i)
  737.         buf += qs.slice(lastPos, i);
  738.       if (encoded)
  739.         buf = querystring.unescape(buf);
  740.       out.push(buf);
  741.  
  742.       seenSep = true;
  743.       buf = '';
  744.       encoded = false;
  745.       encodeCheck = 0;
  746.       lastPos = i + 1;
  747.       continue;
  748.     }
  749.  
  750.     // Handle + and percent decoding.
  751.     if (code === 43/*+*/) {
  752.       if (lastPos < i)
  753.         buf += qs.slice(lastPos, i);
  754.       buf += ' ';
  755.       lastPos = i + 1;
  756.     } else if (!encoded) {
  757.       // Try to match an (valid) encoded byte (once) to minimize unnecessary
  758.       // calls to string decoding functions
  759.       if (code === 37/*%*/) {
  760.         encodeCheck = 1;
  761.       } else if (encodeCheck > 0) {
  762.         // eslint-disable-next-line no-extra-boolean-cast
  763.         if (!!isHexTable[code]) {
  764.           if (++encodeCheck === 3)
  765.             encoded = true;
  766.         } else {
  767.           encodeCheck = 0;
  768.         }
  769.       }
  770.     }
  771.   }
  772.  
  773.   // Deal with any leftover key or value data
  774.  
  775.   // There is a trailing &. No more processing is needed.
  776.   if (pairStart === i)
  777.     return out;
  778.  
  779.   if (lastPos < i)
  780.     buf += qs.slice(lastPos, i);
  781.   if (encoded)
  782.     buf = querystring.unescape(buf);
  783.   out.push(buf);
  784.  
  785.   // If `buf` is the key, add an empty value.
  786.   if (!seenSep)
  787.     out.push('');
  788.  
  789.   return out;
  790. }
  791.  
  792. // Adapted from querystring's implementation.
  793. // Ref: https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
  794. const noEscape = [
  795. //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
  796.   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F
  797.   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F
  798.   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, // 0x20 - 0x2F
  799.   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 0x30 - 0x3F
  800.   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
  801.   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 0x50 - 0x5F
  802.   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
  803.   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0  // 0x70 - 0x7F
  804. ];
  805.  
  806. // Special version of hexTable that uses `+` for U+0020 SPACE.
  807. const paramHexTable = hexTable.slice();
  808. paramHexTable[0x20] = '+';
  809.  
  810. function escapeParam(str) {
  811.   const len = str.length;
  812.   if (len === 0)
  813.     return '';
  814.  
  815.   var out = '';
  816.   var lastPos = 0;
  817.  
  818.   for (var i = 0; i < len; i++) {
  819.     var c = str.charCodeAt(i);
  820.  
  821.     // ASCII
  822.     if (c < 0x80) {
  823.       if (noEscape[c] === 1)
  824.         continue;
  825.       if (lastPos < i)
  826.         out += str.slice(lastPos, i);
  827.       lastPos = i + 1;
  828.       out += paramHexTable[c];
  829.       continue;
  830.     }
  831.  
  832.     if (lastPos < i)
  833.       out += str.slice(lastPos, i);
  834.  
  835.     // Multi-byte characters ...
  836.     if (c < 0x800) {
  837.       lastPos = i + 1;
  838.       out += paramHexTable[0xC0 | (c >> 6)] +
  839.              paramHexTable[0x80 | (c & 0x3F)];
  840.       continue;
  841.     }
  842.     if (c < 0xD800 || c >= 0xE000) {
  843.       lastPos = i + 1;
  844.       out += paramHexTable[0xE0 | (c >> 12)] +
  845.              paramHexTable[0x80 | ((c >> 6) & 0x3F)] +
  846.              paramHexTable[0x80 | (c & 0x3F)];
  847.       continue;
  848.     }
  849.     // Surrogate pair
  850.     ++i;
  851.     var c2;
  852.     if (i < len)
  853.       c2 = str.charCodeAt(i) & 0x3FF;
  854.     else {
  855.       // This branch should never happen because all URLSearchParams entries
  856.       // should already be converted to USVString. But, included for
  857.       // completion's sake anyway.
  858.       c2 = 0;
  859.     }
  860.     lastPos = i + 1;
  861.     c = 0x10000 + (((c & 0x3FF) << 10) | c2);
  862.     out += paramHexTable[0xF0 | (c >> 18)] +
  863.            paramHexTable[0x80 | ((c >> 12) & 0x3F)] +
  864.            paramHexTable[0x80 | ((c >> 6) & 0x3F)] +
  865.            paramHexTable[0x80 | (c & 0x3F)];
  866.   }
  867.   if (lastPos === 0)
  868.     return str;
  869.   if (lastPos < len)
  870.     return out + str.slice(lastPos);
  871.   return out;
  872. }
  873.  
  874. // application/x-www-form-urlencoded serializer
  875. // Ref: https://url.spec.whatwg.org/#concept-urlencoded-serializer
  876. function serializeParams(array) {
  877.   const len = array.length;
  878.   if (len === 0)
  879.     return '';
  880.  
  881.   var output = `${escapeParam(array[0])}=${escapeParam(array[1])}`;
  882.   for (var i = 2; i < len; i += 2)
  883.     output += `&${escapeParam(array[i])}=${escapeParam(array[i + 1])}`;
  884.   return output;
  885. }
  886.  
  887. // Mainly to mitigate func-name-matching ESLint rule
  888. function defineIDLClass(proto, classStr, obj) {
  889.   // https://heycam.github.io/webidl/#dfn-class-string
  890.   Object.defineProperty(proto, Symbol.toStringTag, {
  891.     writable: false,
  892.     enumerable: false,
  893.     configurable: true,
  894.     value: classStr
  895.   });
  896.  
  897.   // https://heycam.github.io/webidl/#es-operations
  898.   for (const key of Object.keys(obj)) {
  899.     Object.defineProperty(proto, key, {
  900.       writable: true,
  901.       enumerable: true,
  902.       configurable: true,
  903.       value: obj[key]
  904.     });
  905.   }
  906.   for (const key of Object.getOwnPropertySymbols(obj)) {
  907.     Object.defineProperty(proto, key, {
  908.       writable: true,
  909.       enumerable: false,
  910.       configurable: true,
  911.       value: obj[key]
  912.     });
  913.   }
  914. }
  915.  
  916. // for merge sort
  917. function merge(out, start, mid, end, lBuffer, rBuffer) {
  918.   const sizeLeft = mid - start;
  919.   const sizeRight = end - mid;
  920.   var l, r, o;
  921.  
  922.   for (l = 0; l < sizeLeft; l++)
  923.     lBuffer[l] = out[start + l];
  924.   for (r = 0; r < sizeRight; r++)
  925.     rBuffer[r] = out[mid + r];
  926.  
  927.   l = 0;
  928.   r = 0;
  929.   o = start;
  930.   while (l < sizeLeft && r < sizeRight) {
  931.     if (lBuffer[l] <= rBuffer[r]) {
  932.       out[o++] = lBuffer[l++];
  933.       out[o++] = lBuffer[l++];
  934.     } else {
  935.       out[o++] = rBuffer[r++];
  936.       out[o++] = rBuffer[r++];
  937.     }
  938.   }
  939.   while (l < sizeLeft)
  940.     out[o++] = lBuffer[l++];
  941.   while (r < sizeRight)
  942.     out[o++] = rBuffer[r++];
  943. }
  944.  
  945. defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
  946.   append(name, value) {
  947.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  948.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  949.     }
  950.     if (arguments.length < 2) {
  951.       throw new errors.TypeError('ERR_MISSING_ARGS', 'name', 'value');
  952.     }
  953.  
  954.     name = toUSVString(name);
  955.     value = toUSVString(value);
  956.     this[searchParams].push(name, value);
  957.     update(this[context], this);
  958.   },
  959.  
  960.   delete(name) {
  961.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  962.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  963.     }
  964.     if (arguments.length < 1) {
  965.       throw new errors.TypeError('ERR_MISSING_ARGS', 'name');
  966.     }
  967.  
  968.     const list = this[searchParams];
  969.     name = toUSVString(name);
  970.     for (var i = 0; i < list.length;) {
  971.       const cur = list[i];
  972.       if (cur === name) {
  973.         list.splice(i, 2);
  974.       } else {
  975.         i += 2;
  976.       }
  977.     }
  978.     update(this[context], this);
  979.   },
  980.  
  981.   get(name) {
  982.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  983.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  984.     }
  985.     if (arguments.length < 1) {
  986.       throw new errors.TypeError('ERR_MISSING_ARGS', 'name');
  987.     }
  988.  
  989.     const list = this[searchParams];
  990.     name = toUSVString(name);
  991.     for (var i = 0; i < list.length; i += 2) {
  992.       if (list[i] === name) {
  993.         return list[i + 1];
  994.       }
  995.     }
  996.     return null;
  997.   },
  998.  
  999.   getAll(name) {
  1000.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  1001.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  1002.     }
  1003.     if (arguments.length < 1) {
  1004.       throw new errors.TypeError('ERR_MISSING_ARGS', 'name');
  1005.     }
  1006.  
  1007.     const list = this[searchParams];
  1008.     const values = [];
  1009.     name = toUSVString(name);
  1010.     for (var i = 0; i < list.length; i += 2) {
  1011.       if (list[i] === name) {
  1012.         values.push(list[i + 1]);
  1013.       }
  1014.     }
  1015.     return values;
  1016.   },
  1017.  
  1018.   has(name) {
  1019.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  1020.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  1021.     }
  1022.     if (arguments.length < 1) {
  1023.       throw new errors.TypeError('ERR_MISSING_ARGS', 'name');
  1024.     }
  1025.  
  1026.     const list = this[searchParams];
  1027.     name = toUSVString(name);
  1028.     for (var i = 0; i < list.length; i += 2) {
  1029.       if (list[i] === name) {
  1030.         return true;
  1031.       }
  1032.     }
  1033.     return false;
  1034.   },
  1035.  
  1036.   set(name, value) {
  1037.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  1038.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  1039.     }
  1040.     if (arguments.length < 2) {
  1041.       throw new errors.TypeError('ERR_MISSING_ARGS', 'name', 'value');
  1042.     }
  1043.  
  1044.     const list = this[searchParams];
  1045.     name = toUSVString(name);
  1046.     value = toUSVString(value);
  1047.  
  1048.     // If there are any name-value pairs whose name is `name`, in `list`, set
  1049.     // the value of the first such name-value pair to `value` and remove the
  1050.     // others.
  1051.     var found = false;
  1052.     for (var i = 0; i < list.length;) {
  1053.       const cur = list[i];
  1054.       if (cur === name) {
  1055.         if (!found) {
  1056.           list[i + 1] = value;
  1057.           found = true;
  1058.           i += 2;
  1059.         } else {
  1060.           list.splice(i, 2);
  1061.         }
  1062.       } else {
  1063.         i += 2;
  1064.       }
  1065.     }
  1066.  
  1067.     // Otherwise, append a new name-value pair whose name is `name` and value
  1068.     // is `value`, to `list`.
  1069.     if (!found) {
  1070.       list.push(name, value);
  1071.     }
  1072.  
  1073.     update(this[context], this);
  1074.   },
  1075.  
  1076.   sort() {
  1077.     const a = this[searchParams];
  1078.     const len = a.length;
  1079.  
  1080.     if (len <= 2) {
  1081.       // Nothing needs to be done.
  1082.     } else if (len < 100) {
  1083.       // 100 is found through testing.
  1084.       // Simple stable in-place insertion sort
  1085.       // Derived from v8/src/js/array.js
  1086.       for (var i = 2; i < len; i += 2) {
  1087.         var curKey = a[i];
  1088.         var curVal = a[i + 1];
  1089.         var j;
  1090.         for (j = i - 2; j >= 0; j -= 2) {
  1091.           if (a[j] > curKey) {
  1092.             a[j + 2] = a[j];
  1093.             a[j + 3] = a[j + 1];
  1094.           } else {
  1095.             break;
  1096.           }
  1097.         }
  1098.         a[j + 2] = curKey;
  1099.         a[j + 3] = curVal;
  1100.       }
  1101.     } else {
  1102.       // Bottom-up iterative stable merge sort
  1103.       const lBuffer = new Array(len);
  1104.       const rBuffer = new Array(len);
  1105.       for (var step = 2; step < len; step *= 2) {
  1106.         for (var start = 0; start < len - 2; start += 2 * step) {
  1107.           var mid = start + step;
  1108.           var end = mid + step;
  1109.           end = end < len ? end : len;
  1110.           if (mid > end)
  1111.             continue;
  1112.           merge(a, start, mid, end, lBuffer, rBuffer);
  1113.         }
  1114.       }
  1115.     }
  1116.  
  1117.     update(this[context], this);
  1118.   },
  1119.  
  1120.   // https://heycam.github.io/webidl/#es-iterators
  1121.   // Define entries here rather than [Symbol.iterator] as the function name
  1122.   // must be set to `entries`.
  1123.   entries() {
  1124.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  1125.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  1126.     }
  1127.  
  1128.     return createSearchParamsIterator(this, 'key+value');
  1129.   },
  1130.  
  1131.   forEach(callback, thisArg = undefined) {
  1132.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  1133.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  1134.     }
  1135.     if (typeof callback !== 'function') {
  1136.       throw new errors.TypeError('ERR_INVALID_CALLBACK');
  1137.     }
  1138.  
  1139.     let list = this[searchParams];
  1140.  
  1141.     var i = 0;
  1142.     while (i < list.length) {
  1143.       const key = list[i];
  1144.       const value = list[i + 1];
  1145.       callback.call(thisArg, value, key, this);
  1146.       // in case the URL object's `search` is updated
  1147.       list = this[searchParams];
  1148.       i += 2;
  1149.     }
  1150.   },
  1151.  
  1152.   // https://heycam.github.io/webidl/#es-iterable
  1153.   keys() {
  1154.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  1155.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  1156.     }
  1157.  
  1158.     return createSearchParamsIterator(this, 'key');
  1159.   },
  1160.  
  1161.   values() {
  1162.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  1163.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  1164.     }
  1165.  
  1166.     return createSearchParamsIterator(this, 'value');
  1167.   },
  1168.  
  1169.   // https://heycam.github.io/webidl/#es-stringifier
  1170.   // https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
  1171.   toString() {
  1172.     if (!this || !this[searchParams] || this[searchParams][searchParams]) {
  1173.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams');
  1174.     }
  1175.  
  1176.     return serializeParams(this[searchParams]);
  1177.   }
  1178. });
  1179.  
  1180. // https://heycam.github.io/webidl/#es-iterable-entries
  1181. Object.defineProperty(URLSearchParams.prototype, Symbol.iterator, {
  1182.   writable: true,
  1183.   configurable: true,
  1184.   value: URLSearchParams.prototype.entries
  1185. });
  1186.  
  1187. // https://heycam.github.io/webidl/#dfn-default-iterator-object
  1188. function createSearchParamsIterator(target, kind) {
  1189.   const iterator = Object.create(URLSearchParamsIteratorPrototype);
  1190.   iterator[context] = {
  1191.     target,
  1192.     kind,
  1193.     index: 0
  1194.   };
  1195.   return iterator;
  1196. }
  1197.  
  1198. // https://heycam.github.io/webidl/#dfn-iterator-prototype-object
  1199. const URLSearchParamsIteratorPrototype = Object.create(IteratorPrototype);
  1200.  
  1201. defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParamsIterator', {
  1202.   next() {
  1203.     if (!this ||
  1204.         Object.getPrototypeOf(this) !== URLSearchParamsIteratorPrototype) {
  1205.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParamsIterator');
  1206.     }
  1207.  
  1208.     const {
  1209.       target,
  1210.       kind,
  1211.       index
  1212.     } = this[context];
  1213.     const values = target[searchParams];
  1214.     const len = values.length;
  1215.     if (index >= len) {
  1216.       return {
  1217.         value: undefined,
  1218.         done: true
  1219.       };
  1220.     }
  1221.  
  1222.     const name = values[index];
  1223.     const value = values[index + 1];
  1224.     this[context].index = index + 2;
  1225.  
  1226.     let result;
  1227.     if (kind === 'key') {
  1228.       result = name;
  1229.     } else if (kind === 'value') {
  1230.       result = value;
  1231.     } else {
  1232.       result = [name, value];
  1233.     }
  1234.  
  1235.     return {
  1236.       value: result,
  1237.       done: false
  1238.     };
  1239.   },
  1240.   [util.inspect.custom](recurseTimes, ctx) {
  1241.     if (this == null || this[context] == null || this[context].target == null)
  1242.       throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParamsIterator');
  1243.  
  1244.     if (typeof recurseTimes === 'number' && recurseTimes < 0)
  1245.       return ctx.stylize('[Object]', 'special');
  1246.  
  1247.     const innerOpts = util._extend({}, ctx);
  1248.     if (recurseTimes !== null) {
  1249.       innerOpts.depth = recurseTimes - 1;
  1250.     }
  1251.     const {
  1252.       target,
  1253.       kind,
  1254.       index
  1255.     } = this[context];
  1256.     const output = target[searchParams].slice(index).reduce((prev, cur, i) => {
  1257.       const key = i % 2 === 0;
  1258.       if (kind === 'key' && key) {
  1259.         prev.push(cur);
  1260.       } else if (kind === 'value' && !key) {
  1261.         prev.push(cur);
  1262.       } else if (kind === 'key+value' && !key) {
  1263.         prev.push([target[searchParams][index + i - 1], cur]);
  1264.       }
  1265.       return prev;
  1266.     }, []);
  1267.     const breakLn = util.inspect(output, innerOpts).includes('\n');
  1268.     const outputStrs = output.map((p) => util.inspect(p, innerOpts));
  1269.     let outputStr;
  1270.     if (breakLn) {
  1271.       outputStr = `\n  ${outputStrs.join(',\n  ')}`;
  1272.     } else {
  1273.       outputStr = ` ${outputStrs.join(', ')}`;
  1274.     }
  1275.     return `${this[Symbol.toStringTag]} {${outputStr} }`;
  1276.   }
  1277. });
  1278.  
  1279. function domainToASCII(domain) {
  1280.   if (arguments.length < 1)
  1281.     throw new errors.TypeError('ERR_MISSING_ARGS', 'domain');
  1282.  
  1283.   // toUSVString is not needed.
  1284.   return _domainToASCII(`${domain}`);
  1285. }
  1286.  
  1287. function domainToUnicode(domain) {
  1288.   if (arguments.length < 1)
  1289.     throw new errors.TypeError('ERR_MISSING_ARGS', 'domain');
  1290.  
  1291.   // toUSVString is not needed.
  1292.   return _domainToUnicode(`${domain}`);
  1293. }
  1294.  
  1295. // Utility function that converts a URL object into an ordinary
  1296. // options object as expected by the http.request and https.request
  1297. // APIs.
  1298. function urlToOptions(url) {
  1299.   var options = {
  1300.     protocol: url.protocol,
  1301.     hostname: url.hostname,
  1302.     hash: url.hash,
  1303.     search: url.search,
  1304.     pathname: url.pathname,
  1305.     path: `${url.pathname}${url.search}`,
  1306.     href: url.href
  1307.   };
  1308.   if (url.port !== '') {
  1309.     options.port = Number(url.port);
  1310.   }
  1311.   if (url.username || url.password) {
  1312.     options.auth = `${url.username}:${url.password}`;
  1313.   }
  1314.   return options;
  1315. }
  1316.  
  1317. function getPathFromURLWin32(url) {
  1318.   var hostname = url.hostname;
  1319.   var pathname = url.pathname;
  1320.   for (var n = 0; n < pathname.length; n++) {
  1321.     if (pathname[n] === '%') {
  1322.       var third = pathname.codePointAt(n + 2) | 0x20;
  1323.       if ((pathname[n + 1] === '2' && third === 102) || // 2f 2F /
  1324.           (pathname[n + 1] === '5' && third === 99)) {  // 5c 5C \
  1325.         return new errors.TypeError(
  1326.           'ERR_INVALID_FILE_URL_PATH',
  1327.           'must not include encoded \\ or / characters');
  1328.       }
  1329.     }
  1330.   }
  1331.   pathname = decodeURIComponent(pathname);
  1332.   if (hostname !== '') {
  1333.     // If hostname is set, then we have a UNC path
  1334.     // Pass the hostname through domainToUnicode just in case
  1335.     // it is an IDN using punycode encoding. We do not need to worry
  1336.     // about percent encoding because the URL parser will have
  1337.     // already taken care of that for us. Note that this only
  1338.     // causes IDNs with an appropriate `xn--` prefix to be decoded.
  1339.     return `//${domainToUnicode(hostname)}${pathname}`;
  1340.   } else {
  1341.     // Otherwise, it's a local path that requires a drive letter
  1342.     var letter = pathname.codePointAt(1) | 0x20;
  1343.     var sep = pathname[2];
  1344.     if (letter < 97 || letter > 122 ||   // a..z A..Z
  1345.         (sep !== ':')) {
  1346.       return new errors.TypeError('ERR_INVALID_FILE_URL_PATH',
  1347.                                   'must be absolute');
  1348.     }
  1349.     return pathname.slice(1);
  1350.   }
  1351. }
  1352.  
  1353. function getPathFromURLPosix(url) {
  1354.   if (url.hostname !== '') {
  1355.     return new errors.TypeError('ERR_INVALID_FILE_URL_HOST',
  1356.                                 `must be "localhost" or empty on ${platform}`);
  1357.   }
  1358.   var pathname = url.pathname;
  1359.   for (var n = 0; n < pathname.length; n++) {
  1360.     if (pathname[n] === '%') {
  1361.       var third = pathname.codePointAt(n + 2) | 0x20;
  1362.       if (pathname[n + 1] === '2' && third === 102) {
  1363.         return new errors.TypeError('ERR_INVALID_FILE_URL_PATH',
  1364.                                     'must not include encoded / characters');
  1365.       }
  1366.     }
  1367.   }
  1368.   return decodeURIComponent(pathname);
  1369. }
  1370.  
  1371. function getPathFromURL(path) {
  1372.   if (path == null || !path[searchParams] ||
  1373.       !path[searchParams][searchParams]) {
  1374.     return path;
  1375.   }
  1376.   if (path.protocol !== 'file:')
  1377.     return new errors.TypeError('ERR_INVALID_URL_SCHEME', 'file');
  1378.   return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
  1379. }
  1380.  
  1381. // We percent-encode % character when converting from file path to URL,
  1382. // as this is the only character that won't be percent encoded by
  1383. // default URL percent encoding when pathname is set.
  1384. const percentRegEx = /%/g;
  1385. function getURLFromFilePath(filepath) {
  1386.   const tmp = new URL('file://');
  1387.   if (filepath.includes('%'))
  1388.     filepath = filepath.replace(percentRegEx, '%25');
  1389.   tmp.pathname = filepath;
  1390.   return tmp;
  1391. }
  1392.  
  1393. function NativeURL(ctx) {
  1394.   this[context] = ctx;
  1395. }
  1396. NativeURL.prototype = URL.prototype;
  1397.  
  1398. function constructUrl(flags, protocol, username, password,
  1399.                       host, port, path, query, fragment) {
  1400.   var ctx = new URLContext();
  1401.   ctx.flags = flags;
  1402.   ctx.scheme = protocol;
  1403.   ctx.username = (flags & URL_FLAGS_HAS_USERNAME) !== 0 ? username : '';
  1404.   ctx.password = (flags & URL_FLAGS_HAS_PASSWORD) !== 0 ? password : '';
  1405.   ctx.port = port;
  1406.   ctx.path = (flags & URL_FLAGS_HAS_PATH) !== 0 ? path : [];
  1407.   ctx.query = query;
  1408.   ctx.fragment = fragment;
  1409.   ctx.host = host;
  1410.   const url = new NativeURL(ctx);
  1411.   url[searchParams] = new URLSearchParams();
  1412.   url[searchParams][context] = url;
  1413.   initSearchParams(url[searchParams], query);
  1414.   return url;
  1415. }
  1416. setURLConstructor(constructUrl);
  1417.  
  1418. module.exports = {
  1419.   toUSVString,
  1420.   getPathFromURL,
  1421.   getURLFromFilePath,
  1422.   URL,
  1423.   URLSearchParams,
  1424.   domainToASCII,
  1425.   domainToUnicode,
  1426.   urlToOptions,
  1427.   formatSymbol: kFormat,
  1428.   searchParamsSymbol: searchParams
  1429. };

Raw Paste


Login or Register to edit or fork this paste. It's free.