source: node_modules/undici/lib/fetch/index.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 73.7 KB
Line 
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}
Note: See TracBrowser for help on using the repository browser.