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