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