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