• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// https://github.com/Ethan-Arrowood/undici-fetch
2
3'use strict'
4
5const {
6  Response,
7  makeNetworkError,
8  makeAppropriateNetworkError,
9  filterResponse,
10  makeResponse
11} = require('./response')
12const { Headers } = require('./headers')
13const { Request, makeRequest } = require('./request')
14const zlib = require('zlib')
15const {
16  bytesMatch,
17  makePolicyContainer,
18  clonePolicyContainer,
19  requestBadPort,
20  TAOCheck,
21  appendRequestOriginHeader,
22  responseLocationURL,
23  requestCurrentURL,
24  setRequestReferrerPolicyOnRedirect,
25  tryUpgradeRequestToAPotentiallyTrustworthyURL,
26  createOpaqueTimingInfo,
27  appendFetchMetadata,
28  corsCheck,
29  crossOriginResourcePolicyCheck,
30  determineRequestsReferrer,
31  coarsenedSharedCurrentTime,
32  createDeferredPromise,
33  isBlobLike,
34  sameOrigin,
35  isCancelled,
36  isAborted,
37  isErrorLike,
38  fullyReadBody,
39  readableStreamClose,
40  isomorphicEncode,
41  urlIsLocal,
42  urlIsHttpHttpsScheme,
43  urlHasHttpsScheme
44} = require('./util')
45const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
46const assert = require('assert')
47const { safelyExtractBody } = require('./body')
48const {
49  redirectStatusSet,
50  nullBodyStatus,
51  safeMethodsSet,
52  requestBodyHeader,
53  subresourceSet,
54  DOMException
55} = require('./constants')
56const { kHeadersList } = require('../core/symbols')
57const EE = require('events')
58const { Readable, pipeline } = require('stream')
59const { addAbortListener, isErrored, isReadable, nodeMajor, nodeMinor } = require('../core/util')
60const { dataURLProcessor, serializeAMimeType } = require('./dataURL')
61const { TransformStream } = require('stream/web')
62const { getGlobalDispatcher } = require('../global')
63const { webidl } = require('./webidl')
64const { STATUS_CODES } = require('http')
65const GET_OR_HEAD = ['GET', 'HEAD']
66
67/** @type {import('buffer').resolveObjectURL} */
68let resolveObjectURL
69let ReadableStream = globalThis.ReadableStream
70
71class Fetch extends EE {
72  constructor (dispatcher) {
73    super()
74
75    this.dispatcher = dispatcher
76    this.connection = null
77    this.dump = false
78    this.state = 'ongoing'
79    // 2 terminated listeners get added per request,
80    // but only 1 gets removed. If there are 20 redirects,
81    // 21 listeners will be added.
82    // See https://github.com/nodejs/undici/issues/1711
83    // TODO (fix): Find and fix root cause for leaked listener.
84    this.setMaxListeners(21)
85  }
86
87  terminate (reason) {
88    if (this.state !== 'ongoing') {
89      return
90    }
91
92    this.state = 'terminated'
93    this.connection?.destroy(reason)
94    this.emit('terminated', reason)
95  }
96
97  // https://fetch.spec.whatwg.org/#fetch-controller-abort
98  abort (error) {
99    if (this.state !== 'ongoing') {
100      return
101    }
102
103    // 1. Set controller’s state to "aborted".
104    this.state = 'aborted'
105
106    // 2. Let fallbackError be an "AbortError" DOMException.
107    // 3. Set error to fallbackError if it is not given.
108    if (!error) {
109      error = new DOMException('The operation was aborted.', 'AbortError')
110    }
111
112    // 4. Let serializedError be StructuredSerialize(error).
113    //    If that threw an exception, catch it, and let
114    //    serializedError be StructuredSerialize(fallbackError).
115
116    // 5. Set controller’s serialized abort reason to serializedError.
117    this.serializedAbortReason = error
118
119    this.connection?.destroy(error)
120    this.emit('terminated', error)
121  }
122}
123
124// https://fetch.spec.whatwg.org/#fetch-method
125function fetch (input, init = {}) {
126  webidl.argumentLengthCheck(arguments, 1, { header: 'globalThis.fetch' })
127
128  // 1. Let p be a new promise.
129  const p = createDeferredPromise()
130
131  // 2. Let requestObject be the result of invoking the initial value of
132  // Request as constructor with input and init as arguments. If this throws
133  // an exception, reject p with it and return p.
134  let requestObject
135
136  try {
137    requestObject = new Request(input, init)
138  } catch (e) {
139    p.reject(e)
140    return p.promise
141  }
142
143  // 3. Let request be requestObject’s request.
144  const request = requestObject[kState]
145
146  // 4. If requestObject’s signal’s aborted flag is set, then:
147  if (requestObject.signal.aborted) {
148    // 1. Abort the fetch() call with p, request, null, and
149    //    requestObject’s signal’s abort reason.
150    abortFetch(p, request, null, requestObject.signal.reason)
151
152    // 2. Return p.
153    return p.promise
154  }
155
156  // 5. Let globalObject be request’s client’s global object.
157  const globalObject = request.client.globalObject
158
159  // 6. If globalObject is a ServiceWorkerGlobalScope object, then set
160  // request’s service-workers mode to "none".
161  if (globalObject?.constructor?.name === 'ServiceWorkerGlobalScope') {
162    request.serviceWorkers = 'none'
163  }
164
165  // 7. Let responseObject be null.
166  let responseObject = null
167
168  // 8. Let relevantRealm be this’s relevant Realm.
169  const relevantRealm = null
170
171  // 9. Let locallyAborted be false.
172  let locallyAborted = false
173
174  // 10. Let controller be null.
175  let controller = null
176
177  // 11. Add the following abort steps to requestObject’s signal:
178  addAbortListener(
179    requestObject.signal,
180    () => {
181      // 1. Set locallyAborted to true.
182      locallyAborted = true
183
184      // 2. Assert: controller is non-null.
185      assert(controller != null)
186
187      // 3. Abort controller with requestObject’s signal’s abort reason.
188      controller.abort(requestObject.signal.reason)
189
190      // 4. Abort the fetch() call with p, request, responseObject,
191      //    and requestObject’s signal’s abort reason.
192      abortFetch(p, request, responseObject, requestObject.signal.reason)
193    }
194  )
195
196  // 12. Let handleFetchDone given response response be to finalize and
197  // report timing with response, globalObject, and "fetch".
198  const handleFetchDone = (response) =>
199    finalizeAndReportTiming(response, 'fetch')
200
201  // 13. Set controller to the result of calling fetch given request,
202  // with processResponseEndOfBody set to handleFetchDone, and processResponse
203  // given response being these substeps:
204
205  const processResponse = (response) => {
206    // 1. If locallyAborted is true, terminate these substeps.
207    if (locallyAborted) {
208      return Promise.resolve()
209    }
210
211    // 2. If response’s aborted flag is set, then:
212    if (response.aborted) {
213      // 1. Let deserializedError be the result of deserialize a serialized
214      //    abort reason given controller’s serialized abort reason and
215      //    relevantRealm.
216
217      // 2. Abort the fetch() call with p, request, responseObject, and
218      //    deserializedError.
219
220      abortFetch(p, request, responseObject, controller.serializedAbortReason)
221      return Promise.resolve()
222    }
223
224    // 3. If response is a network error, then reject p with a TypeError
225    // and terminate these substeps.
226    if (response.type === 'error') {
227      p.reject(
228        Object.assign(new TypeError('fetch failed'), { cause: response.error })
229      )
230      return Promise.resolve()
231    }
232
233    // 4. Set responseObject to the result of creating a Response object,
234    // given response, "immutable", and relevantRealm.
235    responseObject = new Response()
236    responseObject[kState] = response
237    responseObject[kRealm] = relevantRealm
238    responseObject[kHeaders][kHeadersList] = response.headersList
239    responseObject[kHeaders][kGuard] = 'immutable'
240    responseObject[kHeaders][kRealm] = relevantRealm
241
242    // 5. Resolve p with responseObject.
243    p.resolve(responseObject)
244  }
245
246  controller = fetching({
247    request,
248    processResponseEndOfBody: handleFetchDone,
249    processResponse,
250    dispatcher: init.dispatcher ?? getGlobalDispatcher() // undici
251  })
252
253  // 14. Return p.
254  return p.promise
255}
256
257// https://fetch.spec.whatwg.org/#finalize-and-report-timing
258function finalizeAndReportTiming (response, initiatorType = 'other') {
259  // 1. If response is an aborted network error, then return.
260  if (response.type === 'error' && response.aborted) {
261    return
262  }
263
264  // 2. If response’s URL list is null or empty, then return.
265  if (!response.urlList?.length) {
266    return
267  }
268
269  // 3. Let originalURL be response’s URL list[0].
270  const originalURL = response.urlList[0]
271
272  // 4. Let timingInfo be response’s timing info.
273  let timingInfo = response.timingInfo
274
275  // 5. Let cacheState be response’s cache state.
276  let cacheState = response.cacheState
277
278  // 6. If originalURL’s scheme is not an HTTP(S) scheme, then return.
279  if (!urlIsHttpHttpsScheme(originalURL)) {
280    return
281  }
282
283  // 7. If timingInfo is null, then return.
284  if (timingInfo === null) {
285    return
286  }
287
288  // 8. If response’s timing allow passed flag is not set, then:
289  if (!response.timingAllowPassed) {
290    //  1. Set timingInfo to a the result of creating an opaque timing info for timingInfo.
291    timingInfo = createOpaqueTimingInfo({
292      startTime: timingInfo.startTime
293    })
294
295    //  2. Set cacheState to the empty string.
296    cacheState = ''
297  }
298
299  // 9. Set timingInfo’s end time to the coarsened shared current time
300  // given global’s relevant settings object’s cross-origin isolated
301  // capability.
302  // TODO: given global’s relevant settings object’s cross-origin isolated
303  // capability?
304  timingInfo.endTime = coarsenedSharedCurrentTime()
305
306  // 10. Set response’s timing info to timingInfo.
307  response.timingInfo = timingInfo
308
309  // 11. Mark resource timing for timingInfo, originalURL, initiatorType,
310  // global, and cacheState.
311  markResourceTiming(
312    timingInfo,
313    originalURL,
314    initiatorType,
315    globalThis,
316    cacheState
317  )
318}
319
320// https://w3c.github.io/resource-timing/#dfn-mark-resource-timing
321function markResourceTiming (timingInfo, originalURL, initiatorType, globalThis, cacheState) {
322  if (nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 2)) {
323    performance.markResourceTiming(timingInfo, originalURL.href, initiatorType, globalThis, cacheState)
324  }
325}
326
327// https://fetch.spec.whatwg.org/#abort-fetch
328function abortFetch (p, request, responseObject, error) {
329  // Note: AbortSignal.reason was added in node v17.2.0
330  // which would give us an undefined error to reject with.
331  // Remove this once node v16 is no longer supported.
332  if (!error) {
333    error = new DOMException('The operation was aborted.', 'AbortError')
334  }
335
336  // 1. Reject promise with error.
337  p.reject(error)
338
339  // 2. If request’s body is not null and is readable, then cancel request’s
340  // body with error.
341  if (request.body != null && isReadable(request.body?.stream)) {
342    request.body.stream.cancel(error).catch((err) => {
343      if (err.code === 'ERR_INVALID_STATE') {
344        // Node bug?
345        return
346      }
347      throw err
348    })
349  }
350
351  // 3. If responseObject is null, then return.
352  if (responseObject == null) {
353    return
354  }
355
356  // 4. Let response be responseObject’s response.
357  const response = responseObject[kState]
358
359  // 5. If response’s body is not null and is readable, then error response’s
360  // body with error.
361  if (response.body != null && isReadable(response.body?.stream)) {
362    response.body.stream.cancel(error).catch((err) => {
363      if (err.code === 'ERR_INVALID_STATE') {
364        // Node bug?
365        return
366      }
367      throw err
368    })
369  }
370}
371
372// https://fetch.spec.whatwg.org/#fetching
373function fetching ({
374  request,
375  processRequestBodyChunkLength,
376  processRequestEndOfBody,
377  processResponse,
378  processResponseEndOfBody,
379  processResponseConsumeBody,
380  useParallelQueue = false,
381  dispatcher // undici
382}) {
383  // 1. Let taskDestination be null.
384  let taskDestination = null
385
386  // 2. Let crossOriginIsolatedCapability be false.
387  let crossOriginIsolatedCapability = false
388
389  // 3. If request’s client is non-null, then:
390  if (request.client != null) {
391    // 1. Set taskDestination to request’s client’s global object.
392    taskDestination = request.client.globalObject
393
394    // 2. Set crossOriginIsolatedCapability to request’s client’s cross-origin
395    // isolated capability.
396    crossOriginIsolatedCapability =
397      request.client.crossOriginIsolatedCapability
398  }
399
400  // 4. If useParallelQueue is true, then set taskDestination to the result of
401  // starting a new parallel queue.
402  // TODO
403
404  // 5. Let timingInfo be a new fetch timing info whose start time and
405  // post-redirect start time are the coarsened shared current time given
406  // crossOriginIsolatedCapability.
407  const currenTime = coarsenedSharedCurrentTime(crossOriginIsolatedCapability)
408  const timingInfo = createOpaqueTimingInfo({
409    startTime: currenTime
410  })
411
412  // 6. Let fetchParams be a new fetch params whose
413  // request is request,
414  // timing info is timingInfo,
415  // process request body chunk length is processRequestBodyChunkLength,
416  // process request end-of-body is processRequestEndOfBody,
417  // process response is processResponse,
418  // process response consume body is processResponseConsumeBody,
419  // process response end-of-body is processResponseEndOfBody,
420  // task destination is taskDestination,
421  // and cross-origin isolated capability is crossOriginIsolatedCapability.
422  const fetchParams = {
423    controller: new Fetch(dispatcher),
424    request,
425    timingInfo,
426    processRequestBodyChunkLength,
427    processRequestEndOfBody,
428    processResponse,
429    processResponseConsumeBody,
430    processResponseEndOfBody,
431    taskDestination,
432    crossOriginIsolatedCapability
433  }
434
435  // 7. If request’s body is a byte sequence, then set request’s body to
436  //    request’s body as a body.
437  // NOTE: Since fetching is only called from fetch, body should already be
438  // extracted.
439  assert(!request.body || request.body.stream)
440
441  // 8. If request’s window is "client", then set request’s window to request’s
442  // client, if request’s client’s global object is a Window object; otherwise
443  // "no-window".
444  if (request.window === 'client') {
445    // TODO: What if request.client is null?
446    request.window =
447      request.client?.globalObject?.constructor?.name === 'Window'
448        ? request.client
449        : 'no-window'
450  }
451
452  // 9. If request’s origin is "client", then set request’s origin to request’s
453  // client’s origin.
454  if (request.origin === 'client') {
455    // TODO: What if request.client is null?
456    request.origin = request.client?.origin
457  }
458
459  // 10. If all of the following conditions are true:
460  // TODO
461
462  // 11. If request’s policy container is "client", then:
463  if (request.policyContainer === 'client') {
464    // 1. If request’s client is non-null, then set request’s policy
465    // container to a clone of request’s client’s policy container. [HTML]
466    if (request.client != null) {
467      request.policyContainer = clonePolicyContainer(
468        request.client.policyContainer
469      )
470    } else {
471      // 2. Otherwise, set request’s policy container to a new policy
472      // container.
473      request.policyContainer = makePolicyContainer()
474    }
475  }
476
477  // 12. If request’s header list does not contain `Accept`, then:
478  if (!request.headersList.contains('accept')) {
479    // 1. Let value be `*/*`.
480    const value = '*/*'
481
482    // 2. A user agent should set value to the first matching statement, if
483    // any, switching on request’s destination:
484    // "document"
485    // "frame"
486    // "iframe"
487    // `text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`
488    // "image"
489    // `image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5`
490    // "style"
491    // `text/css,*/*;q=0.1`
492    // TODO
493
494    // 3. Append `Accept`/value to request’s header list.
495    request.headersList.append('accept', value)
496  }
497
498  // 13. If request’s header list does not contain `Accept-Language`, then
499  // user agents should append `Accept-Language`/an appropriate value to
500  // request’s header list.
501  if (!request.headersList.contains('accept-language')) {
502    request.headersList.append('accept-language', '*')
503  }
504
505  // 14. If request’s priority is null, then use request’s initiator and
506  // destination appropriately in setting request’s priority to a
507  // user-agent-defined object.
508  if (request.priority === null) {
509    // TODO
510  }
511
512  // 15. If request is a subresource request, then:
513  if (subresourceSet.has(request.destination)) {
514    // TODO
515  }
516
517  // 16. Run main fetch given fetchParams.
518  mainFetch(fetchParams)
519    .catch(err => {
520      fetchParams.controller.terminate(err)
521    })
522
523  // 17. Return fetchParam's controller
524  return fetchParams.controller
525}
526
527// https://fetch.spec.whatwg.org/#concept-main-fetch
528async function mainFetch (fetchParams, recursive = false) {
529  // 1. Let request be fetchParams’s request.
530  const request = fetchParams.request
531
532  // 2. Let response be null.
533  let response = null
534
535  // 3. If request’s local-URLs-only flag is set and request’s current URL is
536  // not local, then set response to a network error.
537  if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) {
538    response = makeNetworkError('local URLs only')
539  }
540
541  // 4. Run report Content Security Policy violations for request.
542  // TODO
543
544  // 5. Upgrade request to a potentially trustworthy URL, if appropriate.
545  tryUpgradeRequestToAPotentiallyTrustworthyURL(request)
546
547  // 6. If should request be blocked due to a bad port, should fetching request
548  // be blocked as mixed content, or should request be blocked by Content
549  // Security Policy returns blocked, then set response to a network error.
550  if (requestBadPort(request) === 'blocked') {
551    response = makeNetworkError('bad port')
552  }
553  // TODO: should fetching request be blocked as mixed content?
554  // TODO: should request be blocked by Content Security Policy?
555
556  // 7. If request’s referrer policy is the empty string, then set request’s
557  // referrer policy to request’s policy container’s referrer policy.
558  if (request.referrerPolicy === '') {
559    request.referrerPolicy = request.policyContainer.referrerPolicy
560  }
561
562  // 8. If request’s referrer is not "no-referrer", then set request’s
563  // referrer to the result of invoking determine request’s referrer.
564  if (request.referrer !== 'no-referrer') {
565    request.referrer = determineRequestsReferrer(request)
566  }
567
568  // 9. Set request’s current URL’s scheme to "https" if all of the following
569  // conditions are true:
570  // - request’s current URL’s scheme is "http"
571  // - request’s current URL’s host is a domain
572  // - Matching request’s current URL’s host per Known HSTS Host Domain Name
573  //   Matching results in either a superdomain match with an asserted
574  //   includeSubDomains directive or a congruent match (with or without an
575  //   asserted includeSubDomains directive). [HSTS]
576  // TODO
577
578  // 10. If recursive is false, then run the remaining steps in parallel.
579  // TODO
580
581  // 11. If response is null, then set response to the result of running
582  // the steps corresponding to the first matching statement:
583  if (response === null) {
584    response = await (async () => {
585      const currentURL = requestCurrentURL(request)
586
587      if (
588        // - request’s current URL’s origin is same origin with request’s origin,
589        //   and request’s response tainting is "basic"
590        (sameOrigin(currentURL, request.url) && request.responseTainting === 'basic') ||
591        // request’s current URL’s scheme is "data"
592        (currentURL.protocol === 'data:') ||
593        // - request’s mode is "navigate" or "websocket"
594        (request.mode === 'navigate' || request.mode === 'websocket')
595      ) {
596        // 1. Set request’s response tainting to "basic".
597        request.responseTainting = 'basic'
598
599        // 2. Return the result of running scheme fetch given fetchParams.
600        return await schemeFetch(fetchParams)
601      }
602
603      // request’s mode is "same-origin"
604      if (request.mode === 'same-origin') {
605        // 1. Return a network error.
606        return makeNetworkError('request mode cannot be "same-origin"')
607      }
608
609      // request’s mode is "no-cors"
610      if (request.mode === 'no-cors') {
611        // 1. If request’s redirect mode is not "follow", then return a network
612        // error.
613        if (request.redirect !== 'follow') {
614          return makeNetworkError(
615            'redirect mode cannot be "follow" for "no-cors" request'
616          )
617        }
618
619        // 2. Set request’s response tainting to "opaque".
620        request.responseTainting = 'opaque'
621
622        // 3. Return the result of running scheme fetch given fetchParams.
623        return await schemeFetch(fetchParams)
624      }
625
626      // request’s current URL’s scheme is not an HTTP(S) scheme
627      if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) {
628        // Return a network error.
629        return makeNetworkError('URL scheme must be a HTTP(S) scheme')
630      }
631
632      // - request’s use-CORS-preflight flag is set
633      // - request’s unsafe-request flag is set and either request’s method is
634      //   not a CORS-safelisted method or CORS-unsafe request-header names with
635      //   request’s header list is not empty
636      //    1. Set request’s response tainting to "cors".
637      //    2. Let corsWithPreflightResponse be the result of running HTTP fetch
638      //    given fetchParams and true.
639      //    3. If corsWithPreflightResponse is a network error, then clear cache
640      //    entries using request.
641      //    4. Return corsWithPreflightResponse.
642      // TODO
643
644      // Otherwise
645      //    1. Set request’s response tainting to "cors".
646      request.responseTainting = 'cors'
647
648      //    2. Return the result of running HTTP fetch given fetchParams.
649      return await httpFetch(fetchParams)
650    })()
651  }
652
653  // 12. If recursive is true, then return response.
654  if (recursive) {
655    return response
656  }
657
658  // 13. If response is not a network error and response is not a filtered
659  // response, then:
660  if (response.status !== 0 && !response.internalResponse) {
661    // If request’s response tainting is "cors", then:
662    if (request.responseTainting === 'cors') {
663      // 1. Let headerNames be the result of extracting header list values
664      // given `Access-Control-Expose-Headers` and response’s header list.
665      // TODO
666      // 2. If request’s credentials mode is not "include" and headerNames
667      // contains `*`, then set response’s CORS-exposed header-name list to
668      // all unique header names in response’s header list.
669      // TODO
670      // 3. Otherwise, if headerNames is not null or failure, then set
671      // response’s CORS-exposed header-name list to headerNames.
672      // TODO
673    }
674
675    // Set response to the following filtered response with response as its
676    // internal response, depending on request’s response tainting:
677    if (request.responseTainting === 'basic') {
678      response = filterResponse(response, 'basic')
679    } else if (request.responseTainting === 'cors') {
680      response = filterResponse(response, 'cors')
681    } else if (request.responseTainting === 'opaque') {
682      response = filterResponse(response, 'opaque')
683    } else {
684      assert(false)
685    }
686  }
687
688  // 14. Let internalResponse be response, if response is a network error,
689  // and response’s internal response otherwise.
690  let internalResponse =
691    response.status === 0 ? response : response.internalResponse
692
693  // 15. If internalResponse’s URL list is empty, then set it to a clone of
694  // request’s URL list.
695  if (internalResponse.urlList.length === 0) {
696    internalResponse.urlList.push(...request.urlList)
697  }
698
699  // 16. If request’s timing allow failed flag is unset, then set
700  // internalResponse’s timing allow passed flag.
701  if (!request.timingAllowFailed) {
702    response.timingAllowPassed = true
703  }
704
705  // 17. If response is not a network error and any of the following returns
706  // blocked
707  // - should internalResponse to request be blocked as mixed content
708  // - should internalResponse to request be blocked by Content Security Policy
709  // - should internalResponse to request be blocked due to its MIME type
710  // - should internalResponse to request be blocked due to nosniff
711  // TODO
712
713  // 18. If response’s type is "opaque", internalResponse’s status is 206,
714  // internalResponse’s range-requested flag is set, and request’s header
715  // list does not contain `Range`, then set response and internalResponse
716  // to a network error.
717  if (
718    response.type === 'opaque' &&
719    internalResponse.status === 206 &&
720    internalResponse.rangeRequested &&
721    !request.headers.contains('range')
722  ) {
723    response = internalResponse = makeNetworkError()
724  }
725
726  // 19. If response is not a network error and either request’s method is
727  // `HEAD` or `CONNECT`, or internalResponse’s status is a null body status,
728  // set internalResponse’s body to null and disregard any enqueuing toward
729  // it (if any).
730  if (
731    response.status !== 0 &&
732    (request.method === 'HEAD' ||
733      request.method === 'CONNECT' ||
734      nullBodyStatus.includes(internalResponse.status))
735  ) {
736    internalResponse.body = null
737    fetchParams.controller.dump = true
738  }
739
740  // 20. If request’s integrity metadata is not the empty string, then:
741  if (request.integrity) {
742    // 1. Let processBodyError be this step: run fetch finale given fetchParams
743    // and a network error.
744    const processBodyError = (reason) =>
745      fetchFinale(fetchParams, makeNetworkError(reason))
746
747    // 2. If request’s response tainting is "opaque", or response’s body is null,
748    // then run processBodyError and abort these steps.
749    if (request.responseTainting === 'opaque' || response.body == null) {
750      processBodyError(response.error)
751      return
752    }
753
754    // 3. Let processBody given bytes be these steps:
755    const processBody = (bytes) => {
756      // 1. If bytes do not match request’s integrity metadata,
757      // then run processBodyError and abort these steps. [SRI]
758      if (!bytesMatch(bytes, request.integrity)) {
759        processBodyError('integrity mismatch')
760        return
761      }
762
763      // 2. Set response’s body to bytes as a body.
764      response.body = safelyExtractBody(bytes)[0]
765
766      // 3. Run fetch finale given fetchParams and response.
767      fetchFinale(fetchParams, response)
768    }
769
770    // 4. Fully read response’s body given processBody and processBodyError.
771    await fullyReadBody(response.body, processBody, processBodyError)
772  } else {
773    // 21. Otherwise, run fetch finale given fetchParams and response.
774    fetchFinale(fetchParams, response)
775  }
776}
777
778// https://fetch.spec.whatwg.org/#concept-scheme-fetch
779// given a fetch params fetchParams
780function schemeFetch (fetchParams) {
781  // Note: since the connection is destroyed on redirect, which sets fetchParams to a
782  // cancelled state, we do not want this condition to trigger *unless* there have been
783  // no redirects. See https://github.com/nodejs/undici/issues/1776
784  // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
785  if (isCancelled(fetchParams) && fetchParams.request.redirectCount === 0) {
786    return Promise.resolve(makeAppropriateNetworkError(fetchParams))
787  }
788
789  // 2. Let request be fetchParams’s request.
790  const { request } = fetchParams
791
792  const { protocol: scheme } = requestCurrentURL(request)
793
794  // 3. Switch on request’s current URL’s scheme and run the associated steps:
795  switch (scheme) {
796    case 'about:': {
797      // If request’s current URL’s path is the string "blank", then return a new response
798      // whose status message is `OK`, header list is « (`Content-Type`, `text/html;charset=utf-8`) »,
799      // and body is the empty byte sequence as a body.
800
801      // Otherwise, return a network error.
802      return Promise.resolve(makeNetworkError('about scheme is not supported'))
803    }
804    case 'blob:': {
805      if (!resolveObjectURL) {
806        resolveObjectURL = require('buffer').resolveObjectURL
807      }
808
809      // 1. Let blobURLEntry be request’s current URL’s blob URL entry.
810      const blobURLEntry = requestCurrentURL(request)
811
812      // https://github.com/web-platform-tests/wpt/blob/7b0ebaccc62b566a1965396e5be7bb2bc06f841f/FileAPI/url/resources/fetch-tests.js#L52-L56
813      // Buffer.resolveObjectURL does not ignore URL queries.
814      if (blobURLEntry.search.length !== 0) {
815        return Promise.resolve(makeNetworkError('NetworkError when attempting to fetch resource.'))
816      }
817
818      const blobURLEntryObject = resolveObjectURL(blobURLEntry.toString())
819
820      // 2. If request’s method is not `GET`, blobURLEntry is null, or blobURLEntry’s
821      //    object is not a Blob object, then return a network error.
822      if (request.method !== 'GET' || !isBlobLike(blobURLEntryObject)) {
823        return Promise.resolve(makeNetworkError('invalid method'))
824      }
825
826      // 3. Let bodyWithType be the result of safely extracting blobURLEntry’s object.
827      const bodyWithType = safelyExtractBody(blobURLEntryObject)
828
829      // 4. Let body be bodyWithType’s body.
830      const body = bodyWithType[0]
831
832      // 5. Let length be body’s length, serialized and isomorphic encoded.
833      const length = isomorphicEncode(`${body.length}`)
834
835      // 6. Let type be bodyWithType’s type if it is non-null; otherwise the empty byte sequence.
836      const type = bodyWithType[1] ?? ''
837
838      // 7. Return a new response whose status message is `OK`, header list is
839      //    « (`Content-Length`, length), (`Content-Type`, type) », and body is body.
840      const response = makeResponse({
841        statusText: 'OK',
842        headersList: [
843          ['content-length', { name: 'Content-Length', value: length }],
844          ['content-type', { name: 'Content-Type', value: type }]
845        ]
846      })
847
848      response.body = body
849
850      return Promise.resolve(response)
851    }
852    case 'data:': {
853      // 1. Let dataURLStruct be the result of running the
854      //    data: URL processor on request’s current URL.
855      const currentURL = requestCurrentURL(request)
856      const dataURLStruct = dataURLProcessor(currentURL)
857
858      // 2. If dataURLStruct is failure, then return a
859      //    network error.
860      if (dataURLStruct === 'failure') {
861        return Promise.resolve(makeNetworkError('failed to fetch the data URL'))
862      }
863
864      // 3. Let mimeType be dataURLStruct’s MIME type, serialized.
865      const mimeType = serializeAMimeType(dataURLStruct.mimeType)
866
867      // 4. Return a response whose status message is `OK`,
868      //    header list is « (`Content-Type`, mimeType) »,
869      //    and body is dataURLStruct’s body as a body.
870      return Promise.resolve(makeResponse({
871        statusText: 'OK',
872        headersList: [
873          ['content-type', { name: 'Content-Type', value: mimeType }]
874        ],
875        body: safelyExtractBody(dataURLStruct.body)[0]
876      }))
877    }
878    case 'file:': {
879      // For now, unfortunate as it is, file URLs are left as an exercise for the reader.
880      // When in doubt, return a network error.
881      return Promise.resolve(makeNetworkError('not implemented... yet...'))
882    }
883    case 'http:':
884    case 'https:': {
885      // Return the result of running HTTP fetch given fetchParams.
886
887      return httpFetch(fetchParams)
888        .catch((err) => makeNetworkError(err))
889    }
890    default: {
891      return Promise.resolve(makeNetworkError('unknown scheme'))
892    }
893  }
894}
895
896// https://fetch.spec.whatwg.org/#finalize-response
897function finalizeResponse (fetchParams, response) {
898  // 1. Set fetchParams’s request’s done flag.
899  fetchParams.request.done = true
900
901  // 2, If fetchParams’s process response done is not null, then queue a fetch
902  // task to run fetchParams’s process response done given response, with
903  // fetchParams’s task destination.
904  if (fetchParams.processResponseDone != null) {
905    queueMicrotask(() => fetchParams.processResponseDone(response))
906  }
907}
908
909// https://fetch.spec.whatwg.org/#fetch-finale
910function fetchFinale (fetchParams, response) {
911  // 1. If response is a network error, then:
912  if (response.type === 'error') {
913    // 1. Set response’s URL list to « fetchParams’s request’s URL list[0] ».
914    response.urlList = [fetchParams.request.urlList[0]]
915
916    // 2. Set response’s timing info to the result of creating an opaque timing
917    // info for fetchParams’s timing info.
918    response.timingInfo = createOpaqueTimingInfo({
919      startTime: fetchParams.timingInfo.startTime
920    })
921  }
922
923  // 2. Let processResponseEndOfBody be the following steps:
924  const processResponseEndOfBody = () => {
925    // 1. Set fetchParams’s request’s done flag.
926    fetchParams.request.done = true
927
928    // If fetchParams’s process response end-of-body is not null,
929    // then queue a fetch task to run fetchParams’s process response
930    // end-of-body given response with fetchParams’s task destination.
931    if (fetchParams.processResponseEndOfBody != null) {
932      queueMicrotask(() => fetchParams.processResponseEndOfBody(response))
933    }
934  }
935
936  // 3. If fetchParams’s process response is non-null, then queue a fetch task
937  // to run fetchParams’s process response given response, with fetchParams’s
938  // task destination.
939  if (fetchParams.processResponse != null) {
940    queueMicrotask(() => fetchParams.processResponse(response))
941  }
942
943  // 4. If response’s body is null, then run processResponseEndOfBody.
944  if (response.body == null) {
945    processResponseEndOfBody()
946  } else {
947  // 5. Otherwise:
948
949    // 1. Let transformStream be a new a TransformStream.
950
951    // 2. Let identityTransformAlgorithm be an algorithm which, given chunk,
952    // enqueues chunk in transformStream.
953    const identityTransformAlgorithm = (chunk, controller) => {
954      controller.enqueue(chunk)
955    }
956
957    // 3. Set up transformStream with transformAlgorithm set to identityTransformAlgorithm
958    // and flushAlgorithm set to processResponseEndOfBody.
959    const transformStream = new TransformStream({
960      start () {},
961      transform: identityTransformAlgorithm,
962      flush: processResponseEndOfBody
963    }, {
964      size () {
965        return 1
966      }
967    }, {
968      size () {
969        return 1
970      }
971    })
972
973    // 4. Set response’s body to the result of piping response’s body through transformStream.
974    response.body = { stream: response.body.stream.pipeThrough(transformStream) }
975  }
976
977  // 6. If fetchParams’s process response consume body is non-null, then:
978  if (fetchParams.processResponseConsumeBody != null) {
979    // 1. Let processBody given nullOrBytes be this step: run fetchParams’s
980    // process response consume body given response and nullOrBytes.
981    const processBody = (nullOrBytes) => fetchParams.processResponseConsumeBody(response, nullOrBytes)
982
983    // 2. Let processBodyError be this step: run fetchParams’s process
984    // response consume body given response and failure.
985    const processBodyError = (failure) => fetchParams.processResponseConsumeBody(response, failure)
986
987    // 3. If response’s body is null, then queue a fetch task to run processBody
988    // given null, with fetchParams’s task destination.
989    if (response.body == null) {
990      queueMicrotask(() => processBody(null))
991    } else {
992      // 4. Otherwise, fully read response’s body given processBody, processBodyError,
993      // and fetchParams’s task destination.
994      return fullyReadBody(response.body, processBody, processBodyError)
995    }
996    return Promise.resolve()
997  }
998}
999
1000// https://fetch.spec.whatwg.org/#http-fetch
1001async function httpFetch (fetchParams) {
1002  // 1. Let request be fetchParams’s request.
1003  const request = fetchParams.request
1004
1005  // 2. Let response be null.
1006  let response = null
1007
1008  // 3. Let actualResponse be null.
1009  let actualResponse = null
1010
1011  // 4. Let timingInfo be fetchParams’s timing info.
1012  const timingInfo = fetchParams.timingInfo
1013
1014  // 5. If request’s service-workers mode is "all", then:
1015  if (request.serviceWorkers === 'all') {
1016    // TODO
1017  }
1018
1019  // 6. If response is null, then:
1020  if (response === null) {
1021    // 1. If makeCORSPreflight is true and one of these conditions is true:
1022    // TODO
1023
1024    // 2. If request’s redirect mode is "follow", then set request’s
1025    // service-workers mode to "none".
1026    if (request.redirect === 'follow') {
1027      request.serviceWorkers = 'none'
1028    }
1029
1030    // 3. Set response and actualResponse to the result of running
1031    // HTTP-network-or-cache fetch given fetchParams.
1032    actualResponse = response = await httpNetworkOrCacheFetch(fetchParams)
1033
1034    // 4. If request’s response tainting is "cors" and a CORS check
1035    // for request and response returns failure, then return a network error.
1036    if (
1037      request.responseTainting === 'cors' &&
1038      corsCheck(request, response) === 'failure'
1039    ) {
1040      return makeNetworkError('cors failure')
1041    }
1042
1043    // 5. If the TAO check for request and response returns failure, then set
1044    // request’s timing allow failed flag.
1045    if (TAOCheck(request, response) === 'failure') {
1046      request.timingAllowFailed = true
1047    }
1048  }
1049
1050  // 7. If either request’s response tainting or response’s type
1051  // is "opaque", and the cross-origin resource policy check with
1052  // request’s origin, request’s client, request’s destination,
1053  // and actualResponse returns blocked, then return a network error.
1054  if (
1055    (request.responseTainting === 'opaque' || response.type === 'opaque') &&
1056    crossOriginResourcePolicyCheck(
1057      request.origin,
1058      request.client,
1059      request.destination,
1060      actualResponse
1061    ) === 'blocked'
1062  ) {
1063    return makeNetworkError('blocked')
1064  }
1065
1066  // 8. If actualResponse’s status is a redirect status, then:
1067  if (redirectStatusSet.has(actualResponse.status)) {
1068    // 1. If actualResponse’s status is not 303, request’s body is not null,
1069    // and the connection uses HTTP/2, then user agents may, and are even
1070    // encouraged to, transmit an RST_STREAM frame.
1071    // See, https://github.com/whatwg/fetch/issues/1288
1072    if (request.redirect !== 'manual') {
1073      fetchParams.controller.connection.destroy()
1074    }
1075
1076    // 2. Switch on request’s redirect mode:
1077    if (request.redirect === 'error') {
1078      // Set response to a network error.
1079      response = makeNetworkError('unexpected redirect')
1080    } else if (request.redirect === 'manual') {
1081      // Set response to an opaque-redirect filtered response whose internal
1082      // response is actualResponse.
1083      // NOTE(spec): On the web this would return an `opaqueredirect` response,
1084      // but that doesn't make sense server side.
1085      // See https://github.com/nodejs/undici/issues/1193.
1086      response = actualResponse
1087    } else if (request.redirect === 'follow') {
1088      // Set response to the result of running HTTP-redirect fetch given
1089      // fetchParams and response.
1090      response = await httpRedirectFetch(fetchParams, response)
1091    } else {
1092      assert(false)
1093    }
1094  }
1095
1096  // 9. Set response’s timing info to timingInfo.
1097  response.timingInfo = timingInfo
1098
1099  // 10. Return response.
1100  return response
1101}
1102
1103// https://fetch.spec.whatwg.org/#http-redirect-fetch
1104function httpRedirectFetch (fetchParams, response) {
1105  // 1. Let request be fetchParams’s request.
1106  const request = fetchParams.request
1107
1108  // 2. Let actualResponse be response, if response is not a filtered response,
1109  // and response’s internal response otherwise.
1110  const actualResponse = response.internalResponse
1111    ? response.internalResponse
1112    : response
1113
1114  // 3. Let locationURL be actualResponse’s location URL given request’s current
1115  // URL’s fragment.
1116  let locationURL
1117
1118  try {
1119    locationURL = responseLocationURL(
1120      actualResponse,
1121      requestCurrentURL(request).hash
1122    )
1123
1124    // 4. If locationURL is null, then return response.
1125    if (locationURL == null) {
1126      return response
1127    }
1128  } catch (err) {
1129    // 5. If locationURL is failure, then return a network error.
1130    return Promise.resolve(makeNetworkError(err))
1131  }
1132
1133  // 6. If locationURL’s scheme is not an HTTP(S) scheme, then return a network
1134  // error.
1135  if (!urlIsHttpHttpsScheme(locationURL)) {
1136    return Promise.resolve(makeNetworkError('URL scheme must be a HTTP(S) scheme'))
1137  }
1138
1139  // 7. If request’s redirect count is 20, then return a network error.
1140  if (request.redirectCount === 20) {
1141    return Promise.resolve(makeNetworkError('redirect count exceeded'))
1142  }
1143
1144  // 8. Increase request’s redirect count by 1.
1145  request.redirectCount += 1
1146
1147  // 9. If request’s mode is "cors", locationURL includes credentials, and
1148  // request’s origin is not same origin with locationURL’s origin, then return
1149  //  a network error.
1150  if (
1151    request.mode === 'cors' &&
1152    (locationURL.username || locationURL.password) &&
1153    !sameOrigin(request, locationURL)
1154  ) {
1155    return Promise.resolve(makeNetworkError('cross origin not allowed for request mode "cors"'))
1156  }
1157
1158  // 10. If request’s response tainting is "cors" and locationURL includes
1159  // credentials, then return a network error.
1160  if (
1161    request.responseTainting === 'cors' &&
1162    (locationURL.username || locationURL.password)
1163  ) {
1164    return Promise.resolve(makeNetworkError(
1165      'URL cannot contain credentials for request mode "cors"'
1166    ))
1167  }
1168
1169  // 11. If actualResponse’s status is not 303, request’s body is non-null,
1170  // and request’s body’s source is null, then return a network error.
1171  if (
1172    actualResponse.status !== 303 &&
1173    request.body != null &&
1174    request.body.source == null
1175  ) {
1176    return Promise.resolve(makeNetworkError())
1177  }
1178
1179  // 12. If one of the following is true
1180  // - actualResponse’s status is 301 or 302 and request’s method is `POST`
1181  // - actualResponse’s status is 303 and request’s method is not `GET` or `HEAD`
1182  if (
1183    ([301, 302].includes(actualResponse.status) && request.method === 'POST') ||
1184    (actualResponse.status === 303 &&
1185      !GET_OR_HEAD.includes(request.method))
1186  ) {
1187    // then:
1188    // 1. Set request’s method to `GET` and request’s body to null.
1189    request.method = 'GET'
1190    request.body = null
1191
1192    // 2. For each headerName of request-body-header name, delete headerName from
1193    // request’s header list.
1194    for (const headerName of requestBodyHeader) {
1195      request.headersList.delete(headerName)
1196    }
1197  }
1198
1199  // 13. If request’s current URL’s origin is not same origin with locationURL’s
1200  //     origin, then for each headerName of CORS non-wildcard request-header name,
1201  //     delete headerName from request’s header list.
1202  if (!sameOrigin(requestCurrentURL(request), locationURL)) {
1203    // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name
1204    request.headersList.delete('authorization')
1205
1206    // https://fetch.spec.whatwg.org/#authentication-entries
1207    request.headersList.delete('proxy-authorization', true)
1208
1209    // "Cookie" and "Host" are forbidden request-headers, which undici doesn't implement.
1210    request.headersList.delete('cookie')
1211    request.headersList.delete('host')
1212  }
1213
1214  // 14. If request’s body is non-null, then set request’s body to the first return
1215  // value of safely extracting request’s body’s source.
1216  if (request.body != null) {
1217    assert(request.body.source != null)
1218    request.body = safelyExtractBody(request.body.source)[0]
1219  }
1220
1221  // 15. Let timingInfo be fetchParams’s timing info.
1222  const timingInfo = fetchParams.timingInfo
1223
1224  // 16. Set timingInfo’s redirect end time and post-redirect start time to the
1225  // coarsened shared current time given fetchParams’s cross-origin isolated
1226  // capability.
1227  timingInfo.redirectEndTime = timingInfo.postRedirectStartTime =
1228    coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability)
1229
1230  // 17. If timingInfo’s redirect start time is 0, then set timingInfo’s
1231  //  redirect start time to timingInfo’s start time.
1232  if (timingInfo.redirectStartTime === 0) {
1233    timingInfo.redirectStartTime = timingInfo.startTime
1234  }
1235
1236  // 18. Append locationURL to request’s URL list.
1237  request.urlList.push(locationURL)
1238
1239  // 19. Invoke set request’s referrer policy on redirect on request and
1240  // actualResponse.
1241  setRequestReferrerPolicyOnRedirect(request, actualResponse)
1242
1243  // 20. Return the result of running main fetch given fetchParams and true.
1244  return mainFetch(fetchParams, true)
1245}
1246
1247// https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
1248async function httpNetworkOrCacheFetch (
1249  fetchParams,
1250  isAuthenticationFetch = false,
1251  isNewConnectionFetch = false
1252) {
1253  // 1. Let request be fetchParams’s request.
1254  const request = fetchParams.request
1255
1256  // 2. Let httpFetchParams be null.
1257  let httpFetchParams = null
1258
1259  // 3. Let httpRequest be null.
1260  let httpRequest = null
1261
1262  // 4. Let response be null.
1263  let response = null
1264
1265  // 5. Let storedResponse be null.
1266  // TODO: cache
1267
1268  // 6. Let httpCache be null.
1269  const httpCache = null
1270
1271  // 7. Let the revalidatingFlag be unset.
1272  const revalidatingFlag = false
1273
1274  // 8. Run these steps, but abort when the ongoing fetch is terminated:
1275
1276  //    1. If request’s window is "no-window" and request’s redirect mode is
1277  //    "error", then set httpFetchParams to fetchParams and httpRequest to
1278  //    request.
1279  if (request.window === 'no-window' && request.redirect === 'error') {
1280    httpFetchParams = fetchParams
1281    httpRequest = request
1282  } else {
1283    // Otherwise:
1284
1285    // 1. Set httpRequest to a clone of request.
1286    httpRequest = makeRequest(request)
1287
1288    // 2. Set httpFetchParams to a copy of fetchParams.
1289    httpFetchParams = { ...fetchParams }
1290
1291    // 3. Set httpFetchParams’s request to httpRequest.
1292    httpFetchParams.request = httpRequest
1293  }
1294
1295  //    3. Let includeCredentials be true if one of
1296  const includeCredentials =
1297    request.credentials === 'include' ||
1298    (request.credentials === 'same-origin' &&
1299      request.responseTainting === 'basic')
1300
1301  //    4. Let contentLength be httpRequest’s body’s length, if httpRequest’s
1302  //    body is non-null; otherwise null.
1303  const contentLength = httpRequest.body ? httpRequest.body.length : null
1304
1305  //    5. Let contentLengthHeaderValue be null.
1306  let contentLengthHeaderValue = null
1307
1308  //    6. If httpRequest’s body is null and httpRequest’s method is `POST` or
1309  //    `PUT`, then set contentLengthHeaderValue to `0`.
1310  if (
1311    httpRequest.body == null &&
1312    ['POST', 'PUT'].includes(httpRequest.method)
1313  ) {
1314    contentLengthHeaderValue = '0'
1315  }
1316
1317  //    7. If contentLength is non-null, then set contentLengthHeaderValue to
1318  //    contentLength, serialized and isomorphic encoded.
1319  if (contentLength != null) {
1320    contentLengthHeaderValue = isomorphicEncode(`${contentLength}`)
1321  }
1322
1323  //    8. If contentLengthHeaderValue is non-null, then append
1324  //    `Content-Length`/contentLengthHeaderValue to httpRequest’s header
1325  //    list.
1326  if (contentLengthHeaderValue != null) {
1327    httpRequest.headersList.append('content-length', contentLengthHeaderValue)
1328  }
1329
1330  //    9. If contentLengthHeaderValue is non-null, then append (`Content-Length`,
1331  //    contentLengthHeaderValue) to httpRequest’s header list.
1332
1333  //    10. If contentLength is non-null and httpRequest’s keepalive is true,
1334  //    then:
1335  if (contentLength != null && httpRequest.keepalive) {
1336    // NOTE: keepalive is a noop outside of browser context.
1337  }
1338
1339  //    11. If httpRequest’s referrer is a URL, then append
1340  //    `Referer`/httpRequest’s referrer, serialized and isomorphic encoded,
1341  //     to httpRequest’s header list.
1342  if (httpRequest.referrer instanceof URL) {
1343    httpRequest.headersList.append('referer', isomorphicEncode(httpRequest.referrer.href))
1344  }
1345
1346  //    12. Append a request `Origin` header for httpRequest.
1347  appendRequestOriginHeader(httpRequest)
1348
1349  //    13. Append the Fetch metadata headers for httpRequest. [FETCH-METADATA]
1350  appendFetchMetadata(httpRequest)
1351
1352  //    14. If httpRequest’s header list does not contain `User-Agent`, then
1353  //    user agents should append `User-Agent`/default `User-Agent` value to
1354  //    httpRequest’s header list.
1355  if (!httpRequest.headersList.contains('user-agent')) {
1356    httpRequest.headersList.append('user-agent', typeof esbuildDetection === 'undefined' ? 'undici' : 'node')
1357  }
1358
1359  //    15. If httpRequest’s cache mode is "default" and httpRequest’s header
1360  //    list contains `If-Modified-Since`, `If-None-Match`,
1361  //    `If-Unmodified-Since`, `If-Match`, or `If-Range`, then set
1362  //    httpRequest’s cache mode to "no-store".
1363  if (
1364    httpRequest.cache === 'default' &&
1365    (httpRequest.headersList.contains('if-modified-since') ||
1366      httpRequest.headersList.contains('if-none-match') ||
1367      httpRequest.headersList.contains('if-unmodified-since') ||
1368      httpRequest.headersList.contains('if-match') ||
1369      httpRequest.headersList.contains('if-range'))
1370  ) {
1371    httpRequest.cache = 'no-store'
1372  }
1373
1374  //    16. If httpRequest’s cache mode is "no-cache", httpRequest’s prevent
1375  //    no-cache cache-control header modification flag is unset, and
1376  //    httpRequest’s header list does not contain `Cache-Control`, then append
1377  //    `Cache-Control`/`max-age=0` to httpRequest’s header list.
1378  if (
1379    httpRequest.cache === 'no-cache' &&
1380    !httpRequest.preventNoCacheCacheControlHeaderModification &&
1381    !httpRequest.headersList.contains('cache-control')
1382  ) {
1383    httpRequest.headersList.append('cache-control', 'max-age=0')
1384  }
1385
1386  //    17. If httpRequest’s cache mode is "no-store" or "reload", then:
1387  if (httpRequest.cache === 'no-store' || httpRequest.cache === 'reload') {
1388    // 1. If httpRequest’s header list does not contain `Pragma`, then append
1389    // `Pragma`/`no-cache` to httpRequest’s header list.
1390    if (!httpRequest.headersList.contains('pragma')) {
1391      httpRequest.headersList.append('pragma', 'no-cache')
1392    }
1393
1394    // 2. If httpRequest’s header list does not contain `Cache-Control`,
1395    // then append `Cache-Control`/`no-cache` to httpRequest’s header list.
1396    if (!httpRequest.headersList.contains('cache-control')) {
1397      httpRequest.headersList.append('cache-control', 'no-cache')
1398    }
1399  }
1400
1401  //    18. If httpRequest’s header list contains `Range`, then append
1402  //    `Accept-Encoding`/`identity` to httpRequest’s header list.
1403  if (httpRequest.headersList.contains('range')) {
1404    httpRequest.headersList.append('accept-encoding', 'identity')
1405  }
1406
1407  //    19. Modify httpRequest’s header list per HTTP. Do not append a given
1408  //    header if httpRequest’s header list contains that header’s name.
1409  //    TODO: https://github.com/whatwg/fetch/issues/1285#issuecomment-896560129
1410  if (!httpRequest.headersList.contains('accept-encoding')) {
1411    if (urlHasHttpsScheme(requestCurrentURL(httpRequest))) {
1412      httpRequest.headersList.append('accept-encoding', 'br, gzip, deflate')
1413    } else {
1414      httpRequest.headersList.append('accept-encoding', 'gzip, deflate')
1415    }
1416  }
1417
1418  httpRequest.headersList.delete('host')
1419
1420  //    20. If includeCredentials is true, then:
1421  if (includeCredentials) {
1422    // 1. If the user agent is not configured to block cookies for httpRequest
1423    // (see section 7 of [COOKIES]), then:
1424    // TODO: credentials
1425    // 2. If httpRequest’s header list does not contain `Authorization`, then:
1426    // TODO: credentials
1427  }
1428
1429  //    21. If there’s a proxy-authentication entry, use it as appropriate.
1430  //    TODO: proxy-authentication
1431
1432  //    22. Set httpCache to the result of determining the HTTP cache
1433  //    partition, given httpRequest.
1434  //    TODO: cache
1435
1436  //    23. If httpCache is null, then set httpRequest’s cache mode to
1437  //    "no-store".
1438  if (httpCache == null) {
1439    httpRequest.cache = 'no-store'
1440  }
1441
1442  //    24. If httpRequest’s cache mode is neither "no-store" nor "reload",
1443  //    then:
1444  if (httpRequest.mode !== 'no-store' && httpRequest.mode !== 'reload') {
1445    // TODO: cache
1446  }
1447
1448  // 9. If aborted, then return the appropriate network error for fetchParams.
1449  // TODO
1450
1451  // 10. If response is null, then:
1452  if (response == null) {
1453    // 1. If httpRequest’s cache mode is "only-if-cached", then return a
1454    // network error.
1455    if (httpRequest.mode === 'only-if-cached') {
1456      return makeNetworkError('only if cached')
1457    }
1458
1459    // 2. Let forwardResponse be the result of running HTTP-network fetch
1460    // given httpFetchParams, includeCredentials, and isNewConnectionFetch.
1461    const forwardResponse = await httpNetworkFetch(
1462      httpFetchParams,
1463      includeCredentials,
1464      isNewConnectionFetch
1465    )
1466
1467    // 3. If httpRequest’s method is unsafe and forwardResponse’s status is
1468    // in the range 200 to 399, inclusive, invalidate appropriate stored
1469    // responses in httpCache, as per the "Invalidation" chapter of HTTP
1470    // Caching, and set storedResponse to null. [HTTP-CACHING]
1471    if (
1472      !safeMethodsSet.has(httpRequest.method) &&
1473      forwardResponse.status >= 200 &&
1474      forwardResponse.status <= 399
1475    ) {
1476      // TODO: cache
1477    }
1478
1479    // 4. If the revalidatingFlag is set and forwardResponse’s status is 304,
1480    // then:
1481    if (revalidatingFlag && forwardResponse.status === 304) {
1482      // TODO: cache
1483    }
1484
1485    // 5. If response is null, then:
1486    if (response == null) {
1487      // 1. Set response to forwardResponse.
1488      response = forwardResponse
1489
1490      // 2. Store httpRequest and forwardResponse in httpCache, as per the
1491      // "Storing Responses in Caches" chapter of HTTP Caching. [HTTP-CACHING]
1492      // TODO: cache
1493    }
1494  }
1495
1496  // 11. Set response’s URL list to a clone of httpRequest’s URL list.
1497  response.urlList = [...httpRequest.urlList]
1498
1499  // 12. If httpRequest’s header list contains `Range`, then set response’s
1500  // range-requested flag.
1501  if (httpRequest.headersList.contains('range')) {
1502    response.rangeRequested = true
1503  }
1504
1505  // 13. Set response’s request-includes-credentials to includeCredentials.
1506  response.requestIncludesCredentials = includeCredentials
1507
1508  // 14. If response’s status is 401, httpRequest’s response tainting is not
1509  // "cors", includeCredentials is true, and request’s window is an environment
1510  // settings object, then:
1511  // TODO
1512
1513  // 15. If response’s status is 407, then:
1514  if (response.status === 407) {
1515    // 1. If request’s window is "no-window", then return a network error.
1516    if (request.window === 'no-window') {
1517      return makeNetworkError()
1518    }
1519
1520    // 2. ???
1521
1522    // 3. If fetchParams is canceled, then return the appropriate network error for fetchParams.
1523    if (isCancelled(fetchParams)) {
1524      return makeAppropriateNetworkError(fetchParams)
1525    }
1526
1527    // 4. Prompt the end user as appropriate in request’s window and store
1528    // the result as a proxy-authentication entry. [HTTP-AUTH]
1529    // TODO: Invoke some kind of callback?
1530
1531    // 5. Set response to the result of running HTTP-network-or-cache fetch given
1532    // fetchParams.
1533    // TODO
1534    return makeNetworkError('proxy authentication required')
1535  }
1536
1537  // 16. If all of the following are true
1538  if (
1539    // response’s status is 421
1540    response.status === 421 &&
1541    // isNewConnectionFetch is false
1542    !isNewConnectionFetch &&
1543    // request’s body is null, or request’s body is non-null and request’s body’s source is non-null
1544    (request.body == null || request.body.source != null)
1545  ) {
1546    // then:
1547
1548    // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
1549    if (isCancelled(fetchParams)) {
1550      return makeAppropriateNetworkError(fetchParams)
1551    }
1552
1553    // 2. Set response to the result of running HTTP-network-or-cache
1554    // fetch given fetchParams, isAuthenticationFetch, and true.
1555
1556    // TODO (spec): The spec doesn't specify this but we need to cancel
1557    // the active response before we can start a new one.
1558    // https://github.com/whatwg/fetch/issues/1293
1559    fetchParams.controller.connection.destroy()
1560
1561    response = await httpNetworkOrCacheFetch(
1562      fetchParams,
1563      isAuthenticationFetch,
1564      true
1565    )
1566  }
1567
1568  // 17. If isAuthenticationFetch is true, then create an authentication entry
1569  if (isAuthenticationFetch) {
1570    // TODO
1571  }
1572
1573  // 18. Return response.
1574  return response
1575}
1576
1577// https://fetch.spec.whatwg.org/#http-network-fetch
1578async function httpNetworkFetch (
1579  fetchParams,
1580  includeCredentials = false,
1581  forceNewConnection = false
1582) {
1583  assert(!fetchParams.controller.connection || fetchParams.controller.connection.destroyed)
1584
1585  fetchParams.controller.connection = {
1586    abort: null,
1587    destroyed: false,
1588    destroy (err) {
1589      if (!this.destroyed) {
1590        this.destroyed = true
1591        this.abort?.(err ?? new DOMException('The operation was aborted.', 'AbortError'))
1592      }
1593    }
1594  }
1595
1596  // 1. Let request be fetchParams’s request.
1597  const request = fetchParams.request
1598
1599  // 2. Let response be null.
1600  let response = null
1601
1602  // 3. Let timingInfo be fetchParams’s timing info.
1603  const timingInfo = fetchParams.timingInfo
1604
1605  // 4. Let httpCache be the result of determining the HTTP cache partition,
1606  // given request.
1607  // TODO: cache
1608  const httpCache = null
1609
1610  // 5. If httpCache is null, then set request’s cache mode to "no-store".
1611  if (httpCache == null) {
1612    request.cache = 'no-store'
1613  }
1614
1615  // 6. Let networkPartitionKey be the result of determining the network
1616  // partition key given request.
1617  // TODO
1618
1619  // 7. Let newConnection be "yes" if forceNewConnection is true; otherwise
1620  // "no".
1621  const newConnection = forceNewConnection ? 'yes' : 'no' // eslint-disable-line no-unused-vars
1622
1623  // 8. Switch on request’s mode:
1624  if (request.mode === 'websocket') {
1625    // Let connection be the result of obtaining a WebSocket connection,
1626    // given request’s current URL.
1627    // TODO
1628  } else {
1629    // Let connection be the result of obtaining a connection, given
1630    // networkPartitionKey, request’s current URL’s origin,
1631    // includeCredentials, and forceNewConnection.
1632    // TODO
1633  }
1634
1635  // 9. Run these steps, but abort when the ongoing fetch is terminated:
1636
1637  //    1. If connection is failure, then return a network error.
1638
1639  //    2. Set timingInfo’s final connection timing info to the result of
1640  //    calling clamp and coarsen connection timing info with connection’s
1641  //    timing info, timingInfo’s post-redirect start time, and fetchParams’s
1642  //    cross-origin isolated capability.
1643
1644  //    3. If connection is not an HTTP/2 connection, request’s body is non-null,
1645  //    and request’s body’s source is null, then append (`Transfer-Encoding`,
1646  //    `chunked`) to request’s header list.
1647
1648  //    4. Set timingInfo’s final network-request start time to the coarsened
1649  //    shared current time given fetchParams’s cross-origin isolated
1650  //    capability.
1651
1652  //    5. Set response to the result of making an HTTP request over connection
1653  //    using request with the following caveats:
1654
1655  //        - Follow the relevant requirements from HTTP. [HTTP] [HTTP-SEMANTICS]
1656  //        [HTTP-COND] [HTTP-CACHING] [HTTP-AUTH]
1657
1658  //        - If request’s body is non-null, and request’s body’s source is null,
1659  //        then the user agent may have a buffer of up to 64 kibibytes and store
1660  //        a part of request’s body in that buffer. If the user agent reads from
1661  //        request’s body beyond that buffer’s size and the user agent needs to
1662  //        resend request, then instead return a network error.
1663
1664  //        - Set timingInfo’s final network-response start time to the coarsened
1665  //        shared current time given fetchParams’s cross-origin isolated capability,
1666  //        immediately after the user agent’s HTTP parser receives the first byte
1667  //        of the response (e.g., frame header bytes for HTTP/2 or response status
1668  //        line for HTTP/1.x).
1669
1670  //        - Wait until all the headers are transmitted.
1671
1672  //        - Any responses whose status is in the range 100 to 199, inclusive,
1673  //        and is not 101, are to be ignored, except for the purposes of setting
1674  //        timingInfo’s final network-response start time above.
1675
1676  //    - If request’s header list contains `Transfer-Encoding`/`chunked` and
1677  //    response is transferred via HTTP/1.0 or older, then return a network
1678  //    error.
1679
1680  //    - If the HTTP request results in a TLS client certificate dialog, then:
1681
1682  //        1. If request’s window is an environment settings object, make the
1683  //        dialog available in request’s window.
1684
1685  //        2. Otherwise, return a network error.
1686
1687  // To transmit request’s body body, run these steps:
1688  let requestBody = null
1689  // 1. If body is null and fetchParams’s process request end-of-body is
1690  // non-null, then queue a fetch task given fetchParams’s process request
1691  // end-of-body and fetchParams’s task destination.
1692  if (request.body == null && fetchParams.processRequestEndOfBody) {
1693    queueMicrotask(() => fetchParams.processRequestEndOfBody())
1694  } else if (request.body != null) {
1695    // 2. Otherwise, if body is non-null:
1696
1697    //    1. Let processBodyChunk given bytes be these steps:
1698    const processBodyChunk = async function * (bytes) {
1699      // 1. If the ongoing fetch is terminated, then abort these steps.
1700      if (isCancelled(fetchParams)) {
1701        return
1702      }
1703
1704      // 2. Run this step in parallel: transmit bytes.
1705      yield bytes
1706
1707      // 3. If fetchParams’s process request body is non-null, then run
1708      // fetchParams’s process request body given bytes’s length.
1709      fetchParams.processRequestBodyChunkLength?.(bytes.byteLength)
1710    }
1711
1712    // 2. Let processEndOfBody be these steps:
1713    const processEndOfBody = () => {
1714      // 1. If fetchParams is canceled, then abort these steps.
1715      if (isCancelled(fetchParams)) {
1716        return
1717      }
1718
1719      // 2. If fetchParams’s process request end-of-body is non-null,
1720      // then run fetchParams’s process request end-of-body.
1721      if (fetchParams.processRequestEndOfBody) {
1722        fetchParams.processRequestEndOfBody()
1723      }
1724    }
1725
1726    // 3. Let processBodyError given e be these steps:
1727    const processBodyError = (e) => {
1728      // 1. If fetchParams is canceled, then abort these steps.
1729      if (isCancelled(fetchParams)) {
1730        return
1731      }
1732
1733      // 2. If e is an "AbortError" DOMException, then abort fetchParams’s controller.
1734      if (e.name === 'AbortError') {
1735        fetchParams.controller.abort()
1736      } else {
1737        fetchParams.controller.terminate(e)
1738      }
1739    }
1740
1741    // 4. Incrementally read request’s body given processBodyChunk, processEndOfBody,
1742    // processBodyError, and fetchParams’s task destination.
1743    requestBody = (async function * () {
1744      try {
1745        for await (const bytes of request.body.stream) {
1746          yield * processBodyChunk(bytes)
1747        }
1748        processEndOfBody()
1749      } catch (err) {
1750        processBodyError(err)
1751      }
1752    })()
1753  }
1754
1755  try {
1756    // socket is only provided for websockets
1757    const { body, status, statusText, headersList, socket } = await dispatch({ body: requestBody })
1758
1759    if (socket) {
1760      response = makeResponse({ status, statusText, headersList, socket })
1761    } else {
1762      const iterator = body[Symbol.asyncIterator]()
1763      fetchParams.controller.next = () => iterator.next()
1764
1765      response = makeResponse({ status, statusText, headersList })
1766    }
1767  } catch (err) {
1768    // 10. If aborted, then:
1769    if (err.name === 'AbortError') {
1770      // 1. If connection uses HTTP/2, then transmit an RST_STREAM frame.
1771      fetchParams.controller.connection.destroy()
1772
1773      // 2. Return the appropriate network error for fetchParams.
1774      return makeAppropriateNetworkError(fetchParams, err)
1775    }
1776
1777    return makeNetworkError(err)
1778  }
1779
1780  // 11. Let pullAlgorithm be an action that resumes the ongoing fetch
1781  // if it is suspended.
1782  const pullAlgorithm = () => {
1783    fetchParams.controller.resume()
1784  }
1785
1786  // 12. Let cancelAlgorithm be an algorithm that aborts fetchParams’s
1787  // controller with reason, given reason.
1788  const cancelAlgorithm = (reason) => {
1789    fetchParams.controller.abort(reason)
1790  }
1791
1792  // 13. Let highWaterMark be a non-negative, non-NaN number, chosen by
1793  // the user agent.
1794  // TODO
1795
1796  // 14. Let sizeAlgorithm be an algorithm that accepts a chunk object
1797  // and returns a non-negative, non-NaN, non-infinite number, chosen by the user agent.
1798  // TODO
1799
1800  // 15. Let stream be a new ReadableStream.
1801  // 16. Set up stream with pullAlgorithm set to pullAlgorithm,
1802  // cancelAlgorithm set to cancelAlgorithm, highWaterMark set to
1803  // highWaterMark, and sizeAlgorithm set to sizeAlgorithm.
1804  if (!ReadableStream) {
1805    ReadableStream = require('stream/web').ReadableStream
1806  }
1807
1808  const stream = new ReadableStream(
1809    {
1810      async start (controller) {
1811        fetchParams.controller.controller = controller
1812      },
1813      async pull (controller) {
1814        await pullAlgorithm(controller)
1815      },
1816      async cancel (reason) {
1817        await cancelAlgorithm(reason)
1818      }
1819    },
1820    {
1821      highWaterMark: 0,
1822      size () {
1823        return 1
1824      }
1825    }
1826  )
1827
1828  // 17. Run these steps, but abort when the ongoing fetch is terminated:
1829
1830  //    1. Set response’s body to a new body whose stream is stream.
1831  response.body = { stream }
1832
1833  //    2. If response is not a network error and request’s cache mode is
1834  //    not "no-store", then update response in httpCache for request.
1835  //    TODO
1836
1837  //    3. If includeCredentials is true and the user agent is not configured
1838  //    to block cookies for request (see section 7 of [COOKIES]), then run the
1839  //    "set-cookie-string" parsing algorithm (see section 5.2 of [COOKIES]) on
1840  //    the value of each header whose name is a byte-case-insensitive match for
1841  //    `Set-Cookie` in response’s header list, if any, and request’s current URL.
1842  //    TODO
1843
1844  // 18. If aborted, then:
1845  // TODO
1846
1847  // 19. Run these steps in parallel:
1848
1849  //    1. Run these steps, but abort when fetchParams is canceled:
1850  fetchParams.controller.on('terminated', onAborted)
1851  fetchParams.controller.resume = async () => {
1852    // 1. While true
1853    while (true) {
1854      // 1-3. See onData...
1855
1856      // 4. Set bytes to the result of handling content codings given
1857      // codings and bytes.
1858      let bytes
1859      let isFailure
1860      try {
1861        const { done, value } = await fetchParams.controller.next()
1862
1863        if (isAborted(fetchParams)) {
1864          break
1865        }
1866
1867        bytes = done ? undefined : value
1868      } catch (err) {
1869        if (fetchParams.controller.ended && !timingInfo.encodedBodySize) {
1870          // zlib doesn't like empty streams.
1871          bytes = undefined
1872        } else {
1873          bytes = err
1874
1875          // err may be propagated from the result of calling readablestream.cancel,
1876          // which might not be an error. https://github.com/nodejs/undici/issues/2009
1877          isFailure = true
1878        }
1879      }
1880
1881      if (bytes === undefined) {
1882        // 2. Otherwise, if the bytes transmission for response’s message
1883        // body is done normally and stream is readable, then close
1884        // stream, finalize response for fetchParams and response, and
1885        // abort these in-parallel steps.
1886        readableStreamClose(fetchParams.controller.controller)
1887
1888        finalizeResponse(fetchParams, response)
1889
1890        return
1891      }
1892
1893      // 5. Increase timingInfo’s decoded body size by bytes’s length.
1894      timingInfo.decodedBodySize += bytes?.byteLength ?? 0
1895
1896      // 6. If bytes is failure, then terminate fetchParams’s controller.
1897      if (isFailure) {
1898        fetchParams.controller.terminate(bytes)
1899        return
1900      }
1901
1902      // 7. Enqueue a Uint8Array wrapping an ArrayBuffer containing bytes
1903      // into stream.
1904      fetchParams.controller.controller.enqueue(new Uint8Array(bytes))
1905
1906      // 8. If stream is errored, then terminate the ongoing fetch.
1907      if (isErrored(stream)) {
1908        fetchParams.controller.terminate()
1909        return
1910      }
1911
1912      // 9. If stream doesn’t need more data ask the user agent to suspend
1913      // the ongoing fetch.
1914      if (!fetchParams.controller.controller.desiredSize) {
1915        return
1916      }
1917    }
1918  }
1919
1920  //    2. If aborted, then:
1921  function onAborted (reason) {
1922    // 2. If fetchParams is aborted, then:
1923    if (isAborted(fetchParams)) {
1924      // 1. Set response’s aborted flag.
1925      response.aborted = true
1926
1927      // 2. If stream is readable, then error stream with the result of
1928      //    deserialize a serialized abort reason given fetchParams’s
1929      //    controller’s serialized abort reason and an
1930      //    implementation-defined realm.
1931      if (isReadable(stream)) {
1932        fetchParams.controller.controller.error(
1933          fetchParams.controller.serializedAbortReason
1934        )
1935      }
1936    } else {
1937      // 3. Otherwise, if stream is readable, error stream with a TypeError.
1938      if (isReadable(stream)) {
1939        fetchParams.controller.controller.error(new TypeError('terminated', {
1940          cause: isErrorLike(reason) ? reason : undefined
1941        }))
1942      }
1943    }
1944
1945    // 4. If connection uses HTTP/2, then transmit an RST_STREAM frame.
1946    // 5. Otherwise, the user agent should close connection unless it would be bad for performance to do so.
1947    fetchParams.controller.connection.destroy()
1948  }
1949
1950  // 20. Return response.
1951  return response
1952
1953  async function dispatch ({ body }) {
1954    const url = requestCurrentURL(request)
1955    /** @type {import('../..').Agent} */
1956    const agent = fetchParams.controller.dispatcher
1957
1958    return new Promise((resolve, reject) => agent.dispatch(
1959      {
1960        path: url.pathname + url.search,
1961        origin: url.origin,
1962        method: request.method,
1963        body: fetchParams.controller.dispatcher.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
1964        headers: request.headersList.entries,
1965        maxRedirections: 0,
1966        upgrade: request.mode === 'websocket' ? 'websocket' : undefined
1967      },
1968      {
1969        body: null,
1970        abort: null,
1971
1972        onConnect (abort) {
1973          // TODO (fix): Do we need connection here?
1974          const { connection } = fetchParams.controller
1975
1976          if (connection.destroyed) {
1977            abort(new DOMException('The operation was aborted.', 'AbortError'))
1978          } else {
1979            fetchParams.controller.on('terminated', abort)
1980            this.abort = connection.abort = abort
1981          }
1982        },
1983
1984        onHeaders (status, headersList, resume, statusText) {
1985          if (status < 200) {
1986            return
1987          }
1988
1989          let codings = []
1990          let location = ''
1991
1992          const headers = new Headers()
1993
1994          // For H2, the headers are a plain JS object
1995          // We distinguish between them and iterate accordingly
1996          if (Array.isArray(headersList)) {
1997            for (let n = 0; n < headersList.length; n += 2) {
1998              const key = headersList[n + 0].toString('latin1')
1999              const val = headersList[n + 1].toString('latin1')
2000              if (key.toLowerCase() === 'content-encoding') {
2001                // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
2002                // "All content-coding values are case-insensitive..."
2003                codings = val.toLowerCase().split(',').map((x) => x.trim())
2004              } else if (key.toLowerCase() === 'location') {
2005                location = val
2006              }
2007
2008              headers[kHeadersList].append(key, val)
2009            }
2010          } else {
2011            const keys = Object.keys(headersList)
2012            for (const key of keys) {
2013              const val = headersList[key]
2014              if (key.toLowerCase() === 'content-encoding') {
2015                // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
2016                // "All content-coding values are case-insensitive..."
2017                codings = val.toLowerCase().split(',').map((x) => x.trim()).reverse()
2018              } else if (key.toLowerCase() === 'location') {
2019                location = val
2020              }
2021
2022              headers[kHeadersList].append(key, val)
2023            }
2024          }
2025
2026          this.body = new Readable({ read: resume })
2027
2028          const decoders = []
2029
2030          const willFollow = request.redirect === 'follow' &&
2031            location &&
2032            redirectStatusSet.has(status)
2033
2034          // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
2035          if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
2036            for (const coding of codings) {
2037              // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
2038              if (coding === 'x-gzip' || coding === 'gzip') {
2039                decoders.push(zlib.createGunzip({
2040                  // Be less strict when decoding compressed responses, since sometimes
2041                  // servers send slightly invalid responses that are still accepted
2042                  // by common browsers.
2043                  // Always using Z_SYNC_FLUSH is what cURL does.
2044                  flush: zlib.constants.Z_SYNC_FLUSH,
2045                  finishFlush: zlib.constants.Z_SYNC_FLUSH
2046                }))
2047              } else if (coding === 'deflate') {
2048                decoders.push(zlib.createInflate())
2049              } else if (coding === 'br') {
2050                decoders.push(zlib.createBrotliDecompress())
2051              } else {
2052                decoders.length = 0
2053                break
2054              }
2055            }
2056          }
2057
2058          resolve({
2059            status,
2060            statusText,
2061            headersList: headers[kHeadersList],
2062            body: decoders.length
2063              ? pipeline(this.body, ...decoders, () => { })
2064              : this.body.on('error', () => {})
2065          })
2066
2067          return true
2068        },
2069
2070        onData (chunk) {
2071          if (fetchParams.controller.dump) {
2072            return
2073          }
2074
2075          // 1. If one or more bytes have been transmitted from response’s
2076          // message body, then:
2077
2078          //  1. Let bytes be the transmitted bytes.
2079          const bytes = chunk
2080
2081          //  2. Let codings be the result of extracting header list values
2082          //  given `Content-Encoding` and response’s header list.
2083          //  See pullAlgorithm.
2084
2085          //  3. Increase timingInfo’s encoded body size by bytes’s length.
2086          timingInfo.encodedBodySize += bytes.byteLength
2087
2088          //  4. See pullAlgorithm...
2089
2090          return this.body.push(bytes)
2091        },
2092
2093        onComplete () {
2094          if (this.abort) {
2095            fetchParams.controller.off('terminated', this.abort)
2096          }
2097
2098          fetchParams.controller.ended = true
2099
2100          this.body.push(null)
2101        },
2102
2103        onError (error) {
2104          if (this.abort) {
2105            fetchParams.controller.off('terminated', this.abort)
2106          }
2107
2108          this.body?.destroy(error)
2109
2110          fetchParams.controller.terminate(error)
2111
2112          reject(error)
2113        },
2114
2115        onUpgrade (status, headersList, socket) {
2116          if (status !== 101) {
2117            return
2118          }
2119
2120          const headers = new Headers()
2121
2122          for (let n = 0; n < headersList.length; n += 2) {
2123            const key = headersList[n + 0].toString('latin1')
2124            const val = headersList[n + 1].toString('latin1')
2125
2126            headers[kHeadersList].append(key, val)
2127          }
2128
2129          resolve({
2130            status,
2131            statusText: STATUS_CODES[status],
2132            headersList: headers[kHeadersList],
2133            socket
2134          })
2135
2136          return true
2137        }
2138      }
2139    ))
2140  }
2141}
2142
2143module.exports = {
2144  fetch,
2145  Fetch,
2146  fetching,
2147  finalizeAndReportTiming
2148}
2149