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