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