1/* globals AbortController */ 2 3'use strict' 4 5const { extractBody, mixinBody, cloneBody } = require('./body') 6const { Headers, fill: fillHeaders, HeadersList } = require('./headers') 7const { FinalizationRegistry } = require('../compat/dispatcher-weakref')() 8const util = require('../core/util') 9const { 10 isValidHTTPToken, 11 sameOrigin, 12 normalizeMethod, 13 makePolicyContainer, 14 normalizeMethodRecord 15} = require('./util') 16const { 17 forbiddenMethodsSet, 18 corsSafeListedMethodsSet, 19 referrerPolicy, 20 requestRedirect, 21 requestMode, 22 requestCredentials, 23 requestCache, 24 requestDuplex 25} = require('./constants') 26const { kEnumerableProperty } = util 27const { kHeaders, kSignal, kState, kGuard, kRealm } = require('./symbols') 28const { webidl } = require('./webidl') 29const { getGlobalOrigin } = require('./global') 30const { URLSerializer } = require('./dataURL') 31const { kHeadersList, kConstruct } = require('../core/symbols') 32const assert = require('assert') 33const { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = require('events') 34 35let TransformStream = globalThis.TransformStream 36 37const kAbortController = Symbol('abortController') 38 39const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { 40 signal.removeEventListener('abort', abort) 41}) 42 43// https://fetch.spec.whatwg.org/#request-class 44class Request { 45 // https://fetch.spec.whatwg.org/#dom-request 46 constructor (input, init = {}) { 47 if (input === kConstruct) { 48 return 49 } 50 51 webidl.argumentLengthCheck(arguments, 1, { header: 'Request constructor' }) 52 53 input = webidl.converters.RequestInfo(input) 54 init = webidl.converters.RequestInit(init) 55 56 // https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object 57 this[kRealm] = { 58 settingsObject: { 59 baseUrl: getGlobalOrigin(), 60 get origin () { 61 return this.baseUrl?.origin 62 }, 63 policyContainer: makePolicyContainer() 64 } 65 } 66 67 // 1. Let request be null. 68 let request = null 69 70 // 2. Let fallbackMode be null. 71 let fallbackMode = null 72 73 // 3. Let baseURL be this’s relevant settings object’s API base URL. 74 const baseUrl = this[kRealm].settingsObject.baseUrl 75 76 // 4. Let signal be null. 77 let signal = null 78 79 // 5. If input is a string, then: 80 if (typeof input === 'string') { 81 // 1. Let parsedURL be the result of parsing input with baseURL. 82 // 2. If parsedURL is failure, then throw a TypeError. 83 let parsedURL 84 try { 85 parsedURL = new URL(input, baseUrl) 86 } catch (err) { 87 throw new TypeError('Failed to parse URL from ' + input, { cause: err }) 88 } 89 90 // 3. If parsedURL includes credentials, then throw a TypeError. 91 if (parsedURL.username || parsedURL.password) { 92 throw new TypeError( 93 'Request cannot be constructed from a URL that includes credentials: ' + 94 input 95 ) 96 } 97 98 // 4. Set request to a new request whose URL is parsedURL. 99 request = makeRequest({ urlList: [parsedURL] }) 100 101 // 5. Set fallbackMode to "cors". 102 fallbackMode = 'cors' 103 } else { 104 // 6. Otherwise: 105 106 // 7. Assert: input is a Request object. 107 assert(input instanceof Request) 108 109 // 8. Set request to input’s request. 110 request = input[kState] 111 112 // 9. Set signal to input’s signal. 113 signal = input[kSignal] 114 } 115 116 // 7. Let origin be this’s relevant settings object’s origin. 117 const origin = this[kRealm].settingsObject.origin 118 119 // 8. Let window be "client". 120 let window = 'client' 121 122 // 9. If request’s window is an environment settings object and its origin 123 // is same origin with origin, then set window to request’s window. 124 if ( 125 request.window?.constructor?.name === 'EnvironmentSettingsObject' && 126 sameOrigin(request.window, origin) 127 ) { 128 window = request.window 129 } 130 131 // 10. If init["window"] exists and is non-null, then throw a TypeError. 132 if (init.window != null) { 133 throw new TypeError(`'window' option '${window}' must be null`) 134 } 135 136 // 11. If init["window"] exists, then set window to "no-window". 137 if ('window' in init) { 138 window = 'no-window' 139 } 140 141 // 12. Set request to a new request with the following properties: 142 request = makeRequest({ 143 // URL request’s URL. 144 // undici implementation note: this is set as the first item in request's urlList in makeRequest 145 // method request’s method. 146 method: request.method, 147 // header list A copy of request’s header list. 148 // undici implementation note: headersList is cloned in makeRequest 149 headersList: request.headersList, 150 // unsafe-request flag Set. 151 unsafeRequest: request.unsafeRequest, 152 // client This’s relevant settings object. 153 client: this[kRealm].settingsObject, 154 // window window. 155 window, 156 // priority request’s priority. 157 priority: request.priority, 158 // origin request’s origin. The propagation of the origin is only significant for navigation requests 159 // being handled by a service worker. In this scenario a request can have an origin that is different 160 // from the current client. 161 origin: request.origin, 162 // referrer request’s referrer. 163 referrer: request.referrer, 164 // referrer policy request’s referrer policy. 165 referrerPolicy: request.referrerPolicy, 166 // mode request’s mode. 167 mode: request.mode, 168 // credentials mode request’s credentials mode. 169 credentials: request.credentials, 170 // cache mode request’s cache mode. 171 cache: request.cache, 172 // redirect mode request’s redirect mode. 173 redirect: request.redirect, 174 // integrity metadata request’s integrity metadata. 175 integrity: request.integrity, 176 // keepalive request’s keepalive. 177 keepalive: request.keepalive, 178 // reload-navigation flag request’s reload-navigation flag. 179 reloadNavigation: request.reloadNavigation, 180 // history-navigation flag request’s history-navigation flag. 181 historyNavigation: request.historyNavigation, 182 // URL list A clone of request’s URL list. 183 urlList: [...request.urlList] 184 }) 185 186 const initHasKey = Object.keys(init).length !== 0 187 188 // 13. If init is not empty, then: 189 if (initHasKey) { 190 // 1. If request’s mode is "navigate", then set it to "same-origin". 191 if (request.mode === 'navigate') { 192 request.mode = 'same-origin' 193 } 194 195 // 2. Unset request’s reload-navigation flag. 196 request.reloadNavigation = false 197 198 // 3. Unset request’s history-navigation flag. 199 request.historyNavigation = false 200 201 // 4. Set request’s origin to "client". 202 request.origin = 'client' 203 204 // 5. Set request’s referrer to "client" 205 request.referrer = 'client' 206 207 // 6. Set request’s referrer policy to the empty string. 208 request.referrerPolicy = '' 209 210 // 7. Set request’s URL to request’s current URL. 211 request.url = request.urlList[request.urlList.length - 1] 212 213 // 8. Set request’s URL list to « request’s URL ». 214 request.urlList = [request.url] 215 } 216 217 // 14. If init["referrer"] exists, then: 218 if (init.referrer !== undefined) { 219 // 1. Let referrer be init["referrer"]. 220 const referrer = init.referrer 221 222 // 2. If referrer is the empty string, then set request’s referrer to "no-referrer". 223 if (referrer === '') { 224 request.referrer = 'no-referrer' 225 } else { 226 // 1. Let parsedReferrer be the result of parsing referrer with 227 // baseURL. 228 // 2. If parsedReferrer is failure, then throw a TypeError. 229 let parsedReferrer 230 try { 231 parsedReferrer = new URL(referrer, baseUrl) 232 } catch (err) { 233 throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err }) 234 } 235 236 // 3. If one of the following is true 237 // - parsedReferrer’s scheme is "about" and path is the string "client" 238 // - parsedReferrer’s origin is not same origin with origin 239 // then set request’s referrer to "client". 240 if ( 241 (parsedReferrer.protocol === 'about:' && parsedReferrer.hostname === 'client') || 242 (origin && !sameOrigin(parsedReferrer, this[kRealm].settingsObject.baseUrl)) 243 ) { 244 request.referrer = 'client' 245 } else { 246 // 4. Otherwise, set request’s referrer to parsedReferrer. 247 request.referrer = parsedReferrer 248 } 249 } 250 } 251 252 // 15. If init["referrerPolicy"] exists, then set request’s referrer policy 253 // to it. 254 if (init.referrerPolicy !== undefined) { 255 request.referrerPolicy = init.referrerPolicy 256 } 257 258 // 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise. 259 let mode 260 if (init.mode !== undefined) { 261 mode = init.mode 262 } else { 263 mode = fallbackMode 264 } 265 266 // 17. If mode is "navigate", then throw a TypeError. 267 if (mode === 'navigate') { 268 throw webidl.errors.exception({ 269 header: 'Request constructor', 270 message: 'invalid request mode navigate.' 271 }) 272 } 273 274 // 18. If mode is non-null, set request’s mode to mode. 275 if (mode != null) { 276 request.mode = mode 277 } 278 279 // 19. If init["credentials"] exists, then set request’s credentials mode 280 // to it. 281 if (init.credentials !== undefined) { 282 request.credentials = init.credentials 283 } 284 285 // 18. If init["cache"] exists, then set request’s cache mode to it. 286 if (init.cache !== undefined) { 287 request.cache = init.cache 288 } 289 290 // 21. If request’s cache mode is "only-if-cached" and request’s mode is 291 // not "same-origin", then throw a TypeError. 292 if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { 293 throw new TypeError( 294 "'only-if-cached' can be set only with 'same-origin' mode" 295 ) 296 } 297 298 // 22. If init["redirect"] exists, then set request’s redirect mode to it. 299 if (init.redirect !== undefined) { 300 request.redirect = init.redirect 301 } 302 303 // 23. If init["integrity"] exists, then set request’s integrity metadata to it. 304 if (init.integrity != null) { 305 request.integrity = String(init.integrity) 306 } 307 308 // 24. If init["keepalive"] exists, then set request’s keepalive to it. 309 if (init.keepalive !== undefined) { 310 request.keepalive = Boolean(init.keepalive) 311 } 312 313 // 25. If init["method"] exists, then: 314 if (init.method !== undefined) { 315 // 1. Let method be init["method"]. 316 let method = init.method 317 318 // 2. If method is not a method or method is a forbidden method, then 319 // throw a TypeError. 320 if (!isValidHTTPToken(method)) { 321 throw new TypeError(`'${method}' is not a valid HTTP method.`) 322 } 323 324 if (forbiddenMethodsSet.has(method.toUpperCase())) { 325 throw new TypeError(`'${method}' HTTP method is unsupported.`) 326 } 327 328 // 3. Normalize method. 329 method = normalizeMethodRecord[method] ?? normalizeMethod(method) 330 331 // 4. Set request’s method to method. 332 request.method = method 333 } 334 335 // 26. If init["signal"] exists, then set signal to it. 336 if (init.signal !== undefined) { 337 signal = init.signal 338 } 339 340 // 27. Set this’s request to request. 341 this[kState] = request 342 343 // 28. Set this’s signal to a new AbortSignal object with this’s relevant 344 // Realm. 345 // TODO: could this be simplified with AbortSignal.any 346 // (https://dom.spec.whatwg.org/#dom-abortsignal-any) 347 const ac = new AbortController() 348 this[kSignal] = ac.signal 349 this[kSignal][kRealm] = this[kRealm] 350 351 // 29. If signal is not null, then make this’s signal follow signal. 352 if (signal != null) { 353 if ( 354 !signal || 355 typeof signal.aborted !== 'boolean' || 356 typeof signal.addEventListener !== 'function' 357 ) { 358 throw new TypeError( 359 "Failed to construct 'Request': member signal is not of type AbortSignal." 360 ) 361 } 362 363 if (signal.aborted) { 364 ac.abort(signal.reason) 365 } else { 366 // Keep a strong ref to ac while request object 367 // is alive. This is needed to prevent AbortController 368 // from being prematurely garbage collected. 369 // See, https://github.com/nodejs/undici/issues/1926. 370 this[kAbortController] = ac 371 372 const acRef = new WeakRef(ac) 373 const abort = function () { 374 const ac = acRef.deref() 375 if (ac !== undefined) { 376 ac.abort(this.reason) 377 } 378 } 379 380 // Third-party AbortControllers may not work with these. 381 // See, https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619. 382 try { 383 // If the max amount of listeners is equal to the default, increase it 384 // This is only available in node >= v19.9.0 385 if (typeof getMaxListeners === 'function' && getMaxListeners(signal) === defaultMaxListeners) { 386 setMaxListeners(100, signal) 387 } else if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) { 388 setMaxListeners(100, signal) 389 } 390 } catch {} 391 392 util.addAbortListener(signal, abort) 393 requestFinalizer.register(ac, { signal, abort }) 394 } 395 } 396 397 // 30. Set this’s headers to a new Headers object with this’s relevant 398 // Realm, whose header list is request’s header list and guard is 399 // "request". 400 this[kHeaders] = new Headers(kConstruct) 401 this[kHeaders][kHeadersList] = request.headersList 402 this[kHeaders][kGuard] = 'request' 403 this[kHeaders][kRealm] = this[kRealm] 404 405 // 31. If this’s request’s mode is "no-cors", then: 406 if (mode === 'no-cors') { 407 // 1. If this’s request’s method is not a CORS-safelisted method, 408 // then throw a TypeError. 409 if (!corsSafeListedMethodsSet.has(request.method)) { 410 throw new TypeError( 411 `'${request.method} is unsupported in no-cors mode.` 412 ) 413 } 414 415 // 2. Set this’s headers’s guard to "request-no-cors". 416 this[kHeaders][kGuard] = 'request-no-cors' 417 } 418 419 // 32. If init is not empty, then: 420 if (initHasKey) { 421 /** @type {HeadersList} */ 422 const headersList = this[kHeaders][kHeadersList] 423 // 1. Let headers be a copy of this’s headers and its associated header 424 // list. 425 // 2. If init["headers"] exists, then set headers to init["headers"]. 426 const headers = init.headers !== undefined ? init.headers : new HeadersList(headersList) 427 428 // 3. Empty this’s headers’s header list. 429 headersList.clear() 430 431 // 4. If headers is a Headers object, then for each header in its header 432 // list, append header’s name/header’s value to this’s headers. 433 if (headers instanceof HeadersList) { 434 for (const [key, val] of headers) { 435 headersList.append(key, val) 436 } 437 // Note: Copy the `set-cookie` meta-data. 438 headersList.cookies = headers.cookies 439 } else { 440 // 5. Otherwise, fill this’s headers with headers. 441 fillHeaders(this[kHeaders], headers) 442 } 443 } 444 445 // 33. Let inputBody be input’s request’s body if input is a Request 446 // object; otherwise null. 447 const inputBody = input instanceof Request ? input[kState].body : null 448 449 // 34. If either init["body"] exists and is non-null or inputBody is 450 // non-null, and request’s method is `GET` or `HEAD`, then throw a 451 // TypeError. 452 if ( 453 (init.body != null || inputBody != null) && 454 (request.method === 'GET' || request.method === 'HEAD') 455 ) { 456 throw new TypeError('Request with GET/HEAD method cannot have body.') 457 } 458 459 // 35. Let initBody be null. 460 let initBody = null 461 462 // 36. If init["body"] exists and is non-null, then: 463 if (init.body != null) { 464 // 1. Let Content-Type be null. 465 // 2. Set initBody and Content-Type to the result of extracting 466 // init["body"], with keepalive set to request’s keepalive. 467 const [extractedBody, contentType] = extractBody( 468 init.body, 469 request.keepalive 470 ) 471 initBody = extractedBody 472 473 // 3, If Content-Type is non-null and this’s headers’s header list does 474 // not contain `Content-Type`, then append `Content-Type`/Content-Type to 475 // this’s headers. 476 if (contentType && !this[kHeaders][kHeadersList].contains('content-type')) { 477 this[kHeaders].append('content-type', contentType) 478 } 479 } 480 481 // 37. Let inputOrInitBody be initBody if it is non-null; otherwise 482 // inputBody. 483 const inputOrInitBody = initBody ?? inputBody 484 485 // 38. If inputOrInitBody is non-null and inputOrInitBody’s source is 486 // null, then: 487 if (inputOrInitBody != null && inputOrInitBody.source == null) { 488 // 1. If initBody is non-null and init["duplex"] does not exist, 489 // then throw a TypeError. 490 if (initBody != null && init.duplex == null) { 491 throw new TypeError('RequestInit: duplex option is required when sending a body.') 492 } 493 494 // 2. If this’s request’s mode is neither "same-origin" nor "cors", 495 // then throw a TypeError. 496 if (request.mode !== 'same-origin' && request.mode !== 'cors') { 497 throw new TypeError( 498 'If request is made from ReadableStream, mode should be "same-origin" or "cors"' 499 ) 500 } 501 502 // 3. Set this’s request’s use-CORS-preflight flag. 503 request.useCORSPreflightFlag = true 504 } 505 506 // 39. Let finalBody be inputOrInitBody. 507 let finalBody = inputOrInitBody 508 509 // 40. If initBody is null and inputBody is non-null, then: 510 if (initBody == null && inputBody != null) { 511 // 1. If input is unusable, then throw a TypeError. 512 if (util.isDisturbed(inputBody.stream) || inputBody.stream.locked) { 513 throw new TypeError( 514 'Cannot construct a Request with a Request object that has already been used.' 515 ) 516 } 517 518 // 2. Set finalBody to the result of creating a proxy for inputBody. 519 if (!TransformStream) { 520 TransformStream = require('stream/web').TransformStream 521 } 522 523 // https://streams.spec.whatwg.org/#readablestream-create-a-proxy 524 const identityTransform = new TransformStream() 525 inputBody.stream.pipeThrough(identityTransform) 526 finalBody = { 527 source: inputBody.source, 528 length: inputBody.length, 529 stream: identityTransform.readable 530 } 531 } 532 533 // 41. Set this’s request’s body to finalBody. 534 this[kState].body = finalBody 535 } 536 537 // Returns request’s HTTP method, which is "GET" by default. 538 get method () { 539 webidl.brandCheck(this, Request) 540 541 // The method getter steps are to return this’s request’s method. 542 return this[kState].method 543 } 544 545 // Returns the URL of request as a string. 546 get url () { 547 webidl.brandCheck(this, Request) 548 549 // The url getter steps are to return this’s request’s URL, serialized. 550 return URLSerializer(this[kState].url) 551 } 552 553 // Returns a Headers object consisting of the headers associated with request. 554 // Note that headers added in the network layer by the user agent will not 555 // be accounted for in this object, e.g., the "Host" header. 556 get headers () { 557 webidl.brandCheck(this, Request) 558 559 // The headers getter steps are to return this’s headers. 560 return this[kHeaders] 561 } 562 563 // Returns the kind of resource requested by request, e.g., "document" 564 // or "script". 565 get destination () { 566 webidl.brandCheck(this, Request) 567 568 // The destination getter are to return this’s request’s destination. 569 return this[kState].destination 570 } 571 572 // Returns the referrer of request. Its value can be a same-origin URL if 573 // explicitly set in init, the empty string to indicate no referrer, and 574 // "about:client" when defaulting to the global’s default. This is used 575 // during fetching to determine the value of the `Referer` header of the 576 // request being made. 577 get referrer () { 578 webidl.brandCheck(this, Request) 579 580 // 1. If this’s request’s referrer is "no-referrer", then return the 581 // empty string. 582 if (this[kState].referrer === 'no-referrer') { 583 return '' 584 } 585 586 // 2. If this’s request’s referrer is "client", then return 587 // "about:client". 588 if (this[kState].referrer === 'client') { 589 return 'about:client' 590 } 591 592 // Return this’s request’s referrer, serialized. 593 return this[kState].referrer.toString() 594 } 595 596 // Returns the referrer policy associated with request. 597 // This is used during fetching to compute the value of the request’s 598 // referrer. 599 get referrerPolicy () { 600 webidl.brandCheck(this, Request) 601 602 // The referrerPolicy getter steps are to return this’s request’s referrer policy. 603 return this[kState].referrerPolicy 604 } 605 606 // Returns the mode associated with request, which is a string indicating 607 // whether the request will use CORS, or will be restricted to same-origin 608 // URLs. 609 get mode () { 610 webidl.brandCheck(this, Request) 611 612 // The mode getter steps are to return this’s request’s mode. 613 return this[kState].mode 614 } 615 616 // Returns the credentials mode associated with request, 617 // which is a string indicating whether credentials will be sent with the 618 // request always, never, or only when sent to a same-origin URL. 619 get credentials () { 620 // The credentials getter steps are to return this’s request’s credentials mode. 621 return this[kState].credentials 622 } 623 624 // Returns the cache mode associated with request, 625 // which is a string indicating how the request will 626 // interact with the browser’s cache when fetching. 627 get cache () { 628 webidl.brandCheck(this, Request) 629 630 // The cache getter steps are to return this’s request’s cache mode. 631 return this[kState].cache 632 } 633 634 // Returns the redirect mode associated with request, 635 // which is a string indicating how redirects for the 636 // request will be handled during fetching. A request 637 // will follow redirects by default. 638 get redirect () { 639 webidl.brandCheck(this, Request) 640 641 // The redirect getter steps are to return this’s request’s redirect mode. 642 return this[kState].redirect 643 } 644 645 // Returns request’s subresource integrity metadata, which is a 646 // cryptographic hash of the resource being fetched. Its value 647 // consists of multiple hashes separated by whitespace. [SRI] 648 get integrity () { 649 webidl.brandCheck(this, Request) 650 651 // The integrity getter steps are to return this’s request’s integrity 652 // metadata. 653 return this[kState].integrity 654 } 655 656 // Returns a boolean indicating whether or not request can outlive the 657 // global in which it was created. 658 get keepalive () { 659 webidl.brandCheck(this, Request) 660 661 // The keepalive getter steps are to return this’s request’s keepalive. 662 return this[kState].keepalive 663 } 664 665 // Returns a boolean indicating whether or not request is for a reload 666 // navigation. 667 get isReloadNavigation () { 668 webidl.brandCheck(this, Request) 669 670 // The isReloadNavigation getter steps are to return true if this’s 671 // request’s reload-navigation flag is set; otherwise false. 672 return this[kState].reloadNavigation 673 } 674 675 // Returns a boolean indicating whether or not request is for a history 676 // navigation (a.k.a. back-foward navigation). 677 get isHistoryNavigation () { 678 webidl.brandCheck(this, Request) 679 680 // The isHistoryNavigation getter steps are to return true if this’s request’s 681 // history-navigation flag is set; otherwise false. 682 return this[kState].historyNavigation 683 } 684 685 // Returns the signal associated with request, which is an AbortSignal 686 // object indicating whether or not request has been aborted, and its 687 // abort event handler. 688 get signal () { 689 webidl.brandCheck(this, Request) 690 691 // The signal getter steps are to return this’s signal. 692 return this[kSignal] 693 } 694 695 get body () { 696 webidl.brandCheck(this, Request) 697 698 return this[kState].body ? this[kState].body.stream : null 699 } 700 701 get bodyUsed () { 702 webidl.brandCheck(this, Request) 703 704 return !!this[kState].body && util.isDisturbed(this[kState].body.stream) 705 } 706 707 get duplex () { 708 webidl.brandCheck(this, Request) 709 710 return 'half' 711 } 712 713 // Returns a clone of request. 714 clone () { 715 webidl.brandCheck(this, Request) 716 717 // 1. If this is unusable, then throw a TypeError. 718 if (this.bodyUsed || this.body?.locked) { 719 throw new TypeError('unusable') 720 } 721 722 // 2. Let clonedRequest be the result of cloning this’s request. 723 const clonedRequest = cloneRequest(this[kState]) 724 725 // 3. Let clonedRequestObject be the result of creating a Request object, 726 // given clonedRequest, this’s headers’s guard, and this’s relevant Realm. 727 const clonedRequestObject = new Request(kConstruct) 728 clonedRequestObject[kState] = clonedRequest 729 clonedRequestObject[kRealm] = this[kRealm] 730 clonedRequestObject[kHeaders] = new Headers(kConstruct) 731 clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList 732 clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard] 733 clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm] 734 735 // 4. Make clonedRequestObject’s signal follow this’s signal. 736 const ac = new AbortController() 737 if (this.signal.aborted) { 738 ac.abort(this.signal.reason) 739 } else { 740 util.addAbortListener( 741 this.signal, 742 () => { 743 ac.abort(this.signal.reason) 744 } 745 ) 746 } 747 clonedRequestObject[kSignal] = ac.signal 748 749 // 4. Return clonedRequestObject. 750 return clonedRequestObject 751 } 752} 753 754mixinBody(Request) 755 756function makeRequest (init) { 757 // https://fetch.spec.whatwg.org/#requests 758 const request = { 759 method: 'GET', 760 localURLsOnly: false, 761 unsafeRequest: false, 762 body: null, 763 client: null, 764 reservedClient: null, 765 replacesClientId: '', 766 window: 'client', 767 keepalive: false, 768 serviceWorkers: 'all', 769 initiator: '', 770 destination: '', 771 priority: null, 772 origin: 'client', 773 policyContainer: 'client', 774 referrer: 'client', 775 referrerPolicy: '', 776 mode: 'no-cors', 777 useCORSPreflightFlag: false, 778 credentials: 'same-origin', 779 useCredentials: false, 780 cache: 'default', 781 redirect: 'follow', 782 integrity: '', 783 cryptoGraphicsNonceMetadata: '', 784 parserMetadata: '', 785 reloadNavigation: false, 786 historyNavigation: false, 787 userActivation: false, 788 taintedOrigin: false, 789 redirectCount: 0, 790 responseTainting: 'basic', 791 preventNoCacheCacheControlHeaderModification: false, 792 done: false, 793 timingAllowFailed: false, 794 ...init, 795 headersList: init.headersList 796 ? new HeadersList(init.headersList) 797 : new HeadersList() 798 } 799 request.url = request.urlList[0] 800 return request 801} 802 803// https://fetch.spec.whatwg.org/#concept-request-clone 804function cloneRequest (request) { 805 // To clone a request request, run these steps: 806 807 // 1. Let newRequest be a copy of request, except for its body. 808 const newRequest = makeRequest({ ...request, body: null }) 809 810 // 2. If request’s body is non-null, set newRequest’s body to the 811 // result of cloning request’s body. 812 if (request.body != null) { 813 newRequest.body = cloneBody(request.body) 814 } 815 816 // 3. Return newRequest. 817 return newRequest 818} 819 820Object.defineProperties(Request.prototype, { 821 method: kEnumerableProperty, 822 url: kEnumerableProperty, 823 headers: kEnumerableProperty, 824 redirect: kEnumerableProperty, 825 clone: kEnumerableProperty, 826 signal: kEnumerableProperty, 827 duplex: kEnumerableProperty, 828 destination: kEnumerableProperty, 829 body: kEnumerableProperty, 830 bodyUsed: kEnumerableProperty, 831 isHistoryNavigation: kEnumerableProperty, 832 isReloadNavigation: kEnumerableProperty, 833 keepalive: kEnumerableProperty, 834 integrity: kEnumerableProperty, 835 cache: kEnumerableProperty, 836 credentials: kEnumerableProperty, 837 attribute: kEnumerableProperty, 838 referrerPolicy: kEnumerableProperty, 839 referrer: kEnumerableProperty, 840 mode: kEnumerableProperty, 841 [Symbol.toStringTag]: { 842 value: 'Request', 843 configurable: true 844 } 845}) 846 847webidl.converters.Request = webidl.interfaceConverter( 848 Request 849) 850 851// https://fetch.spec.whatwg.org/#requestinfo 852webidl.converters.RequestInfo = function (V) { 853 if (typeof V === 'string') { 854 return webidl.converters.USVString(V) 855 } 856 857 if (V instanceof Request) { 858 return webidl.converters.Request(V) 859 } 860 861 return webidl.converters.USVString(V) 862} 863 864webidl.converters.AbortSignal = webidl.interfaceConverter( 865 AbortSignal 866) 867 868// https://fetch.spec.whatwg.org/#requestinit 869webidl.converters.RequestInit = webidl.dictionaryConverter([ 870 { 871 key: 'method', 872 converter: webidl.converters.ByteString 873 }, 874 { 875 key: 'headers', 876 converter: webidl.converters.HeadersInit 877 }, 878 { 879 key: 'body', 880 converter: webidl.nullableConverter( 881 webidl.converters.BodyInit 882 ) 883 }, 884 { 885 key: 'referrer', 886 converter: webidl.converters.USVString 887 }, 888 { 889 key: 'referrerPolicy', 890 converter: webidl.converters.DOMString, 891 // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy 892 allowedValues: referrerPolicy 893 }, 894 { 895 key: 'mode', 896 converter: webidl.converters.DOMString, 897 // https://fetch.spec.whatwg.org/#concept-request-mode 898 allowedValues: requestMode 899 }, 900 { 901 key: 'credentials', 902 converter: webidl.converters.DOMString, 903 // https://fetch.spec.whatwg.org/#requestcredentials 904 allowedValues: requestCredentials 905 }, 906 { 907 key: 'cache', 908 converter: webidl.converters.DOMString, 909 // https://fetch.spec.whatwg.org/#requestcache 910 allowedValues: requestCache 911 }, 912 { 913 key: 'redirect', 914 converter: webidl.converters.DOMString, 915 // https://fetch.spec.whatwg.org/#requestredirect 916 allowedValues: requestRedirect 917 }, 918 { 919 key: 'integrity', 920 converter: webidl.converters.DOMString 921 }, 922 { 923 key: 'keepalive', 924 converter: webidl.converters.boolean 925 }, 926 { 927 key: 'signal', 928 converter: webidl.nullableConverter( 929 (signal) => webidl.converters.AbortSignal( 930 signal, 931 { strict: false } 932 ) 933 ) 934 }, 935 { 936 key: 'window', 937 converter: webidl.converters.any 938 }, 939 { 940 key: 'duplex', 941 converter: webidl.converters.DOMString, 942 allowedValues: requestDuplex 943 } 944]) 945 946module.exports = { Request, makeRequest } 947