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