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