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