/* globals AbortController */ 'use strict'; const { extractBody, mixinBody, cloneBody, bodyUnusable } = require('./body'); const { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList, } = require('./headers'); const { FinalizationRegistry } = require('./dispatcher-weakref')(); const util = require('../../core/util'); const nodeUtil = require('node:util'); const { isValidHTTPToken, sameOrigin, environmentSettingsObject, } = require('./util'); const { forbiddenMethodsSet, corsSafeListedMethodsSet, referrerPolicy, requestRedirect, requestMode, requestCredentials, requestCache, requestDuplex, } = require('./constants'); const { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords, } = util; const { webidl } = require('./webidl'); const { URLSerializer } = require('./data-url'); const { kConstruct } = require('../../core/symbols'); const assert = require('node:assert'); const { getMaxListeners, setMaxListeners, defaultMaxListeners, } = require('node:events'); const kAbortController = Symbol('abortController'); const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { signal.removeEventListener('abort', abort); }); const dependentControllerMap = new WeakMap(); let abortSignalHasEventHandlerLeakWarning; try { abortSignalHasEventHandlerLeakWarning = getMaxListeners(new AbortController().signal) > 0; } catch { abortSignalHasEventHandlerLeakWarning = false; } function buildAbort(acRef) { return abort; function abort() { const ac = acRef.deref(); if (ac !== undefined) { // Currently, there is a problem with FinalizationRegistry. // https://github.com/nodejs/node/issues/49344 // https://github.com/nodejs/node/issues/47748 // In the case of abort, the first step is to unregister from it. // If the controller can refer to it, it is still registered. // It will be removed in the future. requestFinalizer.unregister(abort); // Unsubscribe a listener. // FinalizationRegistry will no longer be called, so this must be done. this.removeEventListener('abort', abort); ac.abort(this.reason); const controllerList = dependentControllerMap.get(ac.signal); if (controllerList !== undefined) { if (controllerList.size !== 0) { for (const ref of controllerList) { const ctrl = ref.deref(); if (ctrl !== undefined) { ctrl.abort(this.reason); } } controllerList.clear(); } dependentControllerMap.delete(ac.signal); } } } } let patchMethodWarning = false; // https://fetch.spec.whatwg.org/#request-class class Request { /** @type {AbortSignal} */ #signal; /** @type {import('../../dispatcher/dispatcher')} */ #dispatcher; /** @type {Headers} */ #headers; #state; // https://fetch.spec.whatwg.org/#dom-request constructor(input, init = undefined) { webidl.util.markAsUncloneable(this); if (input === kConstruct) { return; } const prefix = 'Request constructor'; webidl.argumentLengthCheck(arguments, 1, prefix); input = webidl.converters.RequestInfo(input, prefix, 'input'); init = webidl.converters.RequestInit(init, prefix, 'init'); // 1. Let request be null. let request = null; // 2. Let fallbackMode be null. let fallbackMode = null; // 3. Let baseURL be this’s relevant settings object’s API base URL. const baseUrl = environmentSettingsObject.settingsObject.baseUrl; // 4. Let signal be null. let signal = null; // 5. If input is a string, then: if (typeof input === 'string') { this.#dispatcher = init.dispatcher; // 1. Let parsedURL be the result of parsing input with baseURL. // 2. If parsedURL is failure, then throw a TypeError. let parsedURL; try { parsedURL = new URL(input, baseUrl); } catch (err) { throw new TypeError('Failed to parse URL from ' + input, { cause: err, }); } // 3. If parsedURL includes credentials, then throw a TypeError. if (parsedURL.username || parsedURL.password) { throw new TypeError( 'Request cannot be constructed from a URL that includes credentials: ' + input ); } // 4. Set request to a new request whose URL is parsedURL. request = makeRequest({ urlList: [parsedURL] }); // 5. Set fallbackMode to "cors". fallbackMode = 'cors'; } else { // 6. Otherwise: // 7. Assert: input is a Request object. assert(webidl.is.Request(input)); // 8. Set request to input’s request. request = input.#state; // 9. Set signal to input’s signal. signal = input.#signal; this.#dispatcher = init.dispatcher || input.#dispatcher; } // 7. Let origin be this’s relevant settings object’s origin. const origin = environmentSettingsObject.settingsObject.origin; // 8. Let window be "client". let window = 'client'; // 9. If request’s window is an environment settings object and its origin // is same origin with origin, then set window to request’s window. if ( request.window?.constructor?.name === 'EnvironmentSettingsObject' && sameOrigin(request.window, origin) ) { window = request.window; } // 10. If init["window"] exists and is non-null, then throw a TypeError. if (init.window != null) { throw new TypeError(`'window' option '${window}' must be null`); } // 11. If init["window"] exists, then set window to "no-window". if ('window' in init) { window = 'no-window'; } // 12. Set request to a new request with the following properties: request = makeRequest({ // URL request’s URL. // undici implementation note: this is set as the first item in request's urlList in makeRequest // method request’s method. method: request.method, // header list A copy of request’s header list. // undici implementation note: headersList is cloned in makeRequest headersList: request.headersList, // unsafe-request flag Set. unsafeRequest: request.unsafeRequest, // client This’s relevant settings object. client: environmentSettingsObject.settingsObject, // window window. window, // priority request’s priority. priority: request.priority, // origin request’s origin. The propagation of the origin is only significant for navigation requests // being handled by a service worker. In this scenario a request can have an origin that is different // from the current client. origin: request.origin, // referrer request’s referrer. referrer: request.referrer, // referrer policy request’s referrer policy. referrerPolicy: request.referrerPolicy, // mode request’s mode. mode: request.mode, // credentials mode request’s credentials mode. credentials: request.credentials, // cache mode request’s cache mode. cache: request.cache, // redirect mode request’s redirect mode. redirect: request.redirect, // integrity metadata request’s integrity metadata. integrity: request.integrity, // keepalive request’s keepalive. keepalive: request.keepalive, // reload-navigation flag request’s reload-navigation flag. reloadNavigation: request.reloadNavigation, // history-navigation flag request’s history-navigation flag. historyNavigation: request.historyNavigation, // URL list A clone of request’s URL list. urlList: [...request.urlList], }); const initHasKey = Object.keys(init).length !== 0; // 13. If init is not empty, then: if (initHasKey) { // 1. If request’s mode is "navigate", then set it to "same-origin". if (request.mode === 'navigate') { request.mode = 'same-origin'; } // 2. Unset request’s reload-navigation flag. request.reloadNavigation = false; // 3. Unset request’s history-navigation flag. request.historyNavigation = false; // 4. Set request’s origin to "client". request.origin = 'client'; // 5. Set request’s referrer to "client" request.referrer = 'client'; // 6. Set request’s referrer policy to the empty string. request.referrerPolicy = ''; // 7. Set request’s URL to request’s current URL. request.url = request.urlList[request.urlList.length - 1]; // 8. Set request’s URL list to « request’s URL ». request.urlList = [request.url]; } // 14. If init["referrer"] exists, then: if (init.referrer !== undefined) { // 1. Let referrer be init["referrer"]. const referrer = init.referrer; // 2. If referrer is the empty string, then set request’s referrer to "no-referrer". if (referrer === '') { request.referrer = 'no-referrer'; } else { // 1. Let parsedReferrer be the result of parsing referrer with // baseURL. // 2. If parsedReferrer is failure, then throw a TypeError. let parsedReferrer; try { parsedReferrer = new URL(referrer, baseUrl); } catch (err) { throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err, }); } // 3. If one of the following is true // - parsedReferrer’s scheme is "about" and path is the string "client" // - parsedReferrer’s origin is not same origin with origin // then set request’s referrer to "client". if ( (parsedReferrer.protocol === 'about:' && parsedReferrer.hostname === 'client') || (origin && !sameOrigin( parsedReferrer, environmentSettingsObject.settingsObject.baseUrl )) ) { request.referrer = 'client'; } else { // 4. Otherwise, set request’s referrer to parsedReferrer. request.referrer = parsedReferrer; } } } // 15. If init["referrerPolicy"] exists, then set request’s referrer policy // to it. if (init.referrerPolicy !== undefined) { request.referrerPolicy = init.referrerPolicy; } // 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise. let mode; if (init.mode !== undefined) { mode = init.mode; } else { mode = fallbackMode; } // 17. If mode is "navigate", then throw a TypeError. if (mode === 'navigate') { throw webidl.errors.exception({ header: 'Request constructor', message: 'invalid request mode navigate.', }); } // 18. If mode is non-null, set request’s mode to mode. if (mode != null) { request.mode = mode; } // 19. If init["credentials"] exists, then set request’s credentials mode // to it. if (init.credentials !== undefined) { request.credentials = init.credentials; } // 18. If init["cache"] exists, then set request’s cache mode to it. if (init.cache !== undefined) { request.cache = init.cache; } // 21. If request’s cache mode is "only-if-cached" and request’s mode is // not "same-origin", then throw a TypeError. if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { throw new TypeError( "'only-if-cached' can be set only with 'same-origin' mode" ); } // 22. If init["redirect"] exists, then set request’s redirect mode to it. if (init.redirect !== undefined) { request.redirect = init.redirect; } // 23. If init["integrity"] exists, then set request’s integrity metadata to it. if (init.integrity != null) { request.integrity = String(init.integrity); } // 24. If init["keepalive"] exists, then set request’s keepalive to it. if (init.keepalive !== undefined) { request.keepalive = Boolean(init.keepalive); } // 25. If init["method"] exists, then: if (init.method !== undefined) { // 1. Let method be init["method"]. let method = init.method; const mayBeNormalized = normalizedMethodRecords[method]; if (mayBeNormalized !== undefined) { // Note: Bypass validation DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH and these lowercase ones request.method = mayBeNormalized; } else { // 2. If method is not a method or method is a forbidden method, then // throw a TypeError. if (!isValidHTTPToken(method)) { throw new TypeError(`'${method}' is not a valid HTTP method.`); } const upperCase = method.toUpperCase(); if (forbiddenMethodsSet.has(upperCase)) { throw new TypeError(`'${method}' HTTP method is unsupported.`); } // 3. Normalize method. // https://fetch.spec.whatwg.org/#concept-method-normalize // Note: must be in uppercase method = normalizedMethodRecordsBase[upperCase] ?? method; // 4. Set request’s method to method. request.method = method; } if (!patchMethodWarning && request.method === 'patch') { process.emitWarning( 'Using `patch` is highly likely to result in a `405 Method Not Allowed`. `PATCH` is much more likely to succeed.', { code: 'UNDICI-FETCH-patch', } ); patchMethodWarning = true; } } // 26. If init["signal"] exists, then set signal to it. if (init.signal !== undefined) { signal = init.signal; } // 27. Set this’s request to request. this.#state = request; // 28. Set this’s signal to a new AbortSignal object with this’s relevant // Realm. // TODO: could this be simplified with AbortSignal.any // (https://dom.spec.whatwg.org/#dom-abortsignal-any) const ac = new AbortController(); this.#signal = ac.signal; // 29. If signal is not null, then make this’s signal follow signal. if (signal != null) { if (signal.aborted) { ac.abort(signal.reason); } else { // Keep a strong ref to ac while request object // is alive. This is needed to prevent AbortController // from being prematurely garbage collected. // See, https://github.com/nodejs/undici/issues/1926. this[kAbortController] = ac; const acRef = new WeakRef(ac); const abort = buildAbort(acRef); // If the max amount of listeners is equal to the default, increase it if ( abortSignalHasEventHandlerLeakWarning && getMaxListeners(signal) === defaultMaxListeners ) { setMaxListeners(1500, signal); } util.addAbortListener(signal, abort); // The third argument must be a registry key to be unregistered. // Without it, you cannot unregister. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry // abort is used as the unregister key. (because it is unique) requestFinalizer.register(ac, { signal, abort }, abort); } } // 30. Set this’s headers to a new Headers object with this’s relevant // Realm, whose header list is request’s header list and guard is // "request". this.#headers = new Headers(kConstruct); setHeadersList(this.#headers, request.headersList); setHeadersGuard(this.#headers, 'request'); // 31. If this’s request’s mode is "no-cors", then: if (mode === 'no-cors') { // 1. If this’s request’s method is not a CORS-safelisted method, // then throw a TypeError. if (!corsSafeListedMethodsSet.has(request.method)) { throw new TypeError( `'${request.method} is unsupported in no-cors mode.` ); } // 2. Set this’s headers’s guard to "request-no-cors". setHeadersGuard(this.#headers, 'request-no-cors'); } // 32. If init is not empty, then: if (initHasKey) { /** @type {HeadersList} */ const headersList = getHeadersList(this.#headers); // 1. Let headers be a copy of this’s headers and its associated header // list. // 2. If init["headers"] exists, then set headers to init["headers"]. const headers = init.headers !== undefined ? init.headers : new HeadersList(headersList); // 3. Empty this’s headers’s header list. headersList.clear(); // 4. If headers is a Headers object, then for each header in its header // list, append header’s name/header’s value to this’s headers. if (headers instanceof HeadersList) { for (const { name, value } of headers.rawValues()) { headersList.append(name, value, false); } // Note: Copy the `set-cookie` meta-data. headersList.cookies = headers.cookies; } else { // 5. Otherwise, fill this’s headers with headers. fillHeaders(this.#headers, headers); } } // 33. Let inputBody be input’s request’s body if input is a Request // object; otherwise null. const inputBody = webidl.is.Request(input) ? input.#state.body : null; // 34. If either init["body"] exists and is non-null or inputBody is // non-null, and request’s method is `GET` or `HEAD`, then throw a // TypeError. if ( (init.body != null || inputBody != null) && (request.method === 'GET' || request.method === 'HEAD') ) { throw new TypeError('Request with GET/HEAD method cannot have body.'); } // 35. Let initBody be null. let initBody = null; // 36. If init["body"] exists and is non-null, then: if (init.body != null) { // 1. Let Content-Type be null. // 2. Set initBody and Content-Type to the result of extracting // init["body"], with keepalive set to request’s keepalive. const [extractedBody, contentType] = extractBody( init.body, request.keepalive ); initBody = extractedBody; // 3, If Content-Type is non-null and this’s headers’s header list does // not contain `Content-Type`, then append `Content-Type`/Content-Type to // this’s headers. if ( contentType && !getHeadersList(this.#headers).contains('content-type', true) ) { this.#headers.append('content-type', contentType, true); } } // 37. Let inputOrInitBody be initBody if it is non-null; otherwise // inputBody. const inputOrInitBody = initBody ?? inputBody; // 38. If inputOrInitBody is non-null and inputOrInitBody’s source is // null, then: if (inputOrInitBody != null && inputOrInitBody.source == null) { // 1. If initBody is non-null and init["duplex"] does not exist, // then throw a TypeError. if (initBody != null && init.duplex == null) { throw new TypeError( 'RequestInit: duplex option is required when sending a body.' ); } // 2. If this’s request’s mode is neither "same-origin" nor "cors", // then throw a TypeError. if (request.mode !== 'same-origin' && request.mode !== 'cors') { throw new TypeError( 'If request is made from ReadableStream, mode should be "same-origin" or "cors"' ); } // 3. Set this’s request’s use-CORS-preflight flag. request.useCORSPreflightFlag = true; } // 39. Let finalBody be inputOrInitBody. let finalBody = inputOrInitBody; // 40. If initBody is null and inputBody is non-null, then: if (initBody == null && inputBody != null) { // 1. If input is unusable, then throw a TypeError. if (bodyUnusable(input.#state)) { throw new TypeError( 'Cannot construct a Request with a Request object that has already been used.' ); } // 2. Set finalBody to the result of creating a proxy for inputBody. // https://streams.spec.whatwg.org/#readablestream-create-a-proxy const identityTransform = new TransformStream(); inputBody.stream.pipeThrough(identityTransform); finalBody = { source: inputBody.source, length: inputBody.length, stream: identityTransform.readable, }; } // 41. Set this’s request’s body to finalBody. this.#state.body = finalBody; } // Returns request’s HTTP method, which is "GET" by default. get method() { webidl.brandCheck(this, Request); // The method getter steps are to return this’s request’s method. return this.#state.method; } // Returns the URL of request as a string. get url() { webidl.brandCheck(this, Request); // The url getter steps are to return this’s request’s URL, serialized. return URLSerializer(this.#state.url); } // Returns a Headers object consisting of the headers associated with request. // Note that headers added in the network layer by the user agent will not // be accounted for in this object, e.g., the "Host" header. get headers() { webidl.brandCheck(this, Request); // The headers getter steps are to return this’s headers. return this.#headers; } // Returns the kind of resource requested by request, e.g., "document" // or "script". get destination() { webidl.brandCheck(this, Request); // The destination getter are to return this’s request’s destination. return this.#state.destination; } // Returns the referrer of request. Its value can be a same-origin URL if // explicitly set in init, the empty string to indicate no referrer, and // "about:client" when defaulting to the global’s default. This is used // during fetching to determine the value of the `Referer` header of the // request being made. get referrer() { webidl.brandCheck(this, Request); // 1. If this’s request’s referrer is "no-referrer", then return the // empty string. if (this.#state.referrer === 'no-referrer') { return ''; } // 2. If this’s request’s referrer is "client", then return // "about:client". if (this.#state.referrer === 'client') { return 'about:client'; } // Return this’s request’s referrer, serialized. return this.#state.referrer.toString(); } // Returns the referrer policy associated with request. // This is used during fetching to compute the value of the request’s // referrer. get referrerPolicy() { webidl.brandCheck(this, Request); // The referrerPolicy getter steps are to return this’s request’s referrer policy. return this.#state.referrerPolicy; } // Returns the mode associated with request, which is a string indicating // whether the request will use CORS, or will be restricted to same-origin // URLs. get mode() { webidl.brandCheck(this, Request); // The mode getter steps are to return this’s request’s mode. return this.#state.mode; } // Returns the credentials mode associated with request, // which is a string indicating whether credentials will be sent with the // request always, never, or only when sent to a same-origin URL. get credentials() { webidl.brandCheck(this, Request); // The credentials getter steps are to return this’s request’s credentials mode. return this.#state.credentials; } // Returns the cache mode associated with request, // which is a string indicating how the request will // interact with the browser’s cache when fetching. get cache() { webidl.brandCheck(this, Request); // The cache getter steps are to return this’s request’s cache mode. return this.#state.cache; } // Returns the redirect mode associated with request, // which is a string indicating how redirects for the // request will be handled during fetching. A request // will follow redirects by default. get redirect() { webidl.brandCheck(this, Request); // The redirect getter steps are to return this’s request’s redirect mode. return this.#state.redirect; } // Returns request’s subresource integrity metadata, which is a // cryptographic hash of the resource being fetched. Its value // consists of multiple hashes separated by whitespace. [SRI] get integrity() { webidl.brandCheck(this, Request); // The integrity getter steps are to return this’s request’s integrity // metadata. return this.#state.integrity; } // Returns a boolean indicating whether or not request can outlive the // global in which it was created. get keepalive() { webidl.brandCheck(this, Request); // The keepalive getter steps are to return this’s request’s keepalive. return this.#state.keepalive; } // Returns a boolean indicating whether or not request is for a reload // navigation. get isReloadNavigation() { webidl.brandCheck(this, Request); // The isReloadNavigation getter steps are to return true if this’s // request’s reload-navigation flag is set; otherwise false. return this.#state.reloadNavigation; } // Returns a boolean indicating whether or not request is for a history // navigation (a.k.a. back-forward navigation). get isHistoryNavigation() { webidl.brandCheck(this, Request); // The isHistoryNavigation getter steps are to return true if this’s request’s // history-navigation flag is set; otherwise false. return this.#state.historyNavigation; } // Returns the signal associated with request, which is an AbortSignal // object indicating whether or not request has been aborted, and its // abort event handler. get signal() { webidl.brandCheck(this, Request); // The signal getter steps are to return this’s signal. return this.#signal; } get body() { webidl.brandCheck(this, Request); return this.#state.body ? this.#state.body.stream : null; } get bodyUsed() { webidl.brandCheck(this, Request); return !!this.#state.body && util.isDisturbed(this.#state.body.stream); } get duplex() { webidl.brandCheck(this, Request); return 'half'; } // Returns a clone of request. clone() { webidl.brandCheck(this, Request); // 1. If this is unusable, then throw a TypeError. if (bodyUnusable(this.#state)) { throw new TypeError('unusable'); } // 2. Let clonedRequest be the result of cloning this’s request. const clonedRequest = cloneRequest(this.#state); // 3. Let clonedRequestObject be the result of creating a Request object, // given clonedRequest, this’s headers’s guard, and this’s relevant Realm. // 4. Make clonedRequestObject’s signal follow this’s signal. const ac = new AbortController(); if (this.signal.aborted) { ac.abort(this.signal.reason); } else { let list = dependentControllerMap.get(this.signal); if (list === undefined) { list = new Set(); dependentControllerMap.set(this.signal, list); } const acRef = new WeakRef(ac); list.add(acRef); util.addAbortListener(ac.signal, buildAbort(acRef)); } // 4. Return clonedRequestObject. return fromInnerRequest( clonedRequest, this.#dispatcher, ac.signal, getHeadersGuard(this.#headers) ); } [nodeUtil.inspect.custom](depth, options) { if (options.depth === null) { options.depth = 2; } options.colors ??= true; const properties = { method: this.method, url: this.url, headers: this.headers, destination: this.destination, referrer: this.referrer, referrerPolicy: this.referrerPolicy, mode: this.mode, credentials: this.credentials, cache: this.cache, redirect: this.redirect, integrity: this.integrity, keepalive: this.keepalive, isReloadNavigation: this.isReloadNavigation, isHistoryNavigation: this.isHistoryNavigation, signal: this.signal, }; return `Request ${nodeUtil.formatWithOptions(options, properties)}`; } /** * @param {Request} request * @param {AbortSignal} newSignal */ static setRequestSignal(request, newSignal) { request.#signal = newSignal; return request; } /** * @param {Request} request */ static getRequestDispatcher(request) { return request.#dispatcher; } /** * @param {Request} request * @param {import('../../dispatcher/dispatcher')} newDispatcher */ static setRequestDispatcher(request, newDispatcher) { request.#dispatcher = newDispatcher; } /** * @param {Request} request * @param {Headers} newHeaders */ static setRequestHeaders(request, newHeaders) { request.#headers = newHeaders; } /** * @param {Request} request */ static getRequestState(request) { return request.#state; } /** * @param {Request} request * @param {any} newState */ static setRequestState(request, newState) { request.#state = newState; } } const { setRequestSignal, getRequestDispatcher, setRequestDispatcher, setRequestHeaders, getRequestState, setRequestState, } = Request; Reflect.deleteProperty(Request, 'setRequestSignal'); Reflect.deleteProperty(Request, 'getRequestDispatcher'); Reflect.deleteProperty(Request, 'setRequestDispatcher'); Reflect.deleteProperty(Request, 'setRequestHeaders'); Reflect.deleteProperty(Request, 'getRequestState'); Reflect.deleteProperty(Request, 'setRequestState'); mixinBody(Request, getRequestState); // https://fetch.spec.whatwg.org/#requests function makeRequest(init) { return { method: init.method ?? 'GET', localURLsOnly: init.localURLsOnly ?? false, unsafeRequest: init.unsafeRequest ?? false, body: init.body ?? null, client: init.client ?? null, reservedClient: init.reservedClient ?? null, replacesClientId: init.replacesClientId ?? '', window: init.window ?? 'client', keepalive: init.keepalive ?? false, serviceWorkers: init.serviceWorkers ?? 'all', initiator: init.initiator ?? '', destination: init.destination ?? '', priority: init.priority ?? null, origin: init.origin ?? 'client', policyContainer: init.policyContainer ?? 'client', referrer: init.referrer ?? 'client', referrerPolicy: init.referrerPolicy ?? '', mode: init.mode ?? 'no-cors', useCORSPreflightFlag: init.useCORSPreflightFlag ?? false, credentials: init.credentials ?? 'same-origin', useCredentials: init.useCredentials ?? false, cache: init.cache ?? 'default', redirect: init.redirect ?? 'follow', integrity: init.integrity ?? '', cryptoGraphicsNonceMetadata: init.cryptoGraphicsNonceMetadata ?? '', parserMetadata: init.parserMetadata ?? '', reloadNavigation: init.reloadNavigation ?? false, historyNavigation: init.historyNavigation ?? false, userActivation: init.userActivation ?? false, taintedOrigin: init.taintedOrigin ?? false, redirectCount: init.redirectCount ?? 0, responseTainting: init.responseTainting ?? 'basic', preventNoCacheCacheControlHeaderModification: init.preventNoCacheCacheControlHeaderModification ?? false, done: init.done ?? false, timingAllowFailed: init.timingAllowFailed ?? false, urlList: init.urlList, url: init.urlList[0], headersList: init.headersList ? new HeadersList(init.headersList) : new HeadersList(), }; } // https://fetch.spec.whatwg.org/#concept-request-clone function cloneRequest(request) { // To clone a request request, run these steps: // 1. Let newRequest be a copy of request, except for its body. const newRequest = makeRequest({ ...request, body: null }); // 2. If request’s body is non-null, set newRequest’s body to the // result of cloning request’s body. if (request.body != null) { newRequest.body = cloneBody(newRequest, request.body); } // 3. Return newRequest. return newRequest; } /** * @see https://fetch.spec.whatwg.org/#request-create * @param {any} innerRequest * @param {import('../../dispatcher/agent')} dispatcher * @param {AbortSignal} signal * @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard * @returns {Request} */ function fromInnerRequest(innerRequest, dispatcher, signal, guard) { const request = new Request(kConstruct); setRequestState(request, innerRequest); setRequestDispatcher(request, dispatcher); setRequestSignal(request, signal); const headers = new Headers(kConstruct); setRequestHeaders(request, headers); setHeadersList(headers, innerRequest.headersList); setHeadersGuard(headers, guard); return request; } Object.defineProperties(Request.prototype, { method: kEnumerableProperty, url: kEnumerableProperty, headers: kEnumerableProperty, redirect: kEnumerableProperty, clone: kEnumerableProperty, signal: kEnumerableProperty, duplex: kEnumerableProperty, destination: kEnumerableProperty, body: kEnumerableProperty, bodyUsed: kEnumerableProperty, isHistoryNavigation: kEnumerableProperty, isReloadNavigation: kEnumerableProperty, keepalive: kEnumerableProperty, integrity: kEnumerableProperty, cache: kEnumerableProperty, credentials: kEnumerableProperty, attribute: kEnumerableProperty, referrerPolicy: kEnumerableProperty, referrer: kEnumerableProperty, mode: kEnumerableProperty, [Symbol.toStringTag]: { value: 'Request', configurable: true, }, }); webidl.is.Request = webidl.util.MakeTypeAssertion(Request); // https://fetch.spec.whatwg.org/#requestinfo webidl.converters.RequestInfo = function (V, prefix, argument) { if (typeof V === 'string') { return webidl.converters.USVString(V); } if (webidl.is.Request(V)) { return V; } return webidl.converters.USVString(V); }; // https://fetch.spec.whatwg.org/#requestinit webidl.converters.RequestInit = webidl.dictionaryConverter([ { key: 'method', converter: webidl.converters.ByteString, }, { key: 'headers', converter: webidl.converters.HeadersInit, }, { key: 'body', converter: webidl.nullableConverter(webidl.converters.BodyInit), }, { key: 'referrer', converter: webidl.converters.USVString, }, { key: 'referrerPolicy', converter: webidl.converters.DOMString, // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy allowedValues: referrerPolicy, }, { key: 'mode', converter: webidl.converters.DOMString, // https://fetch.spec.whatwg.org/#concept-request-mode allowedValues: requestMode, }, { key: 'credentials', converter: webidl.converters.DOMString, // https://fetch.spec.whatwg.org/#requestcredentials allowedValues: requestCredentials, }, { key: 'cache', converter: webidl.converters.DOMString, // https://fetch.spec.whatwg.org/#requestcache allowedValues: requestCache, }, { key: 'redirect', converter: webidl.converters.DOMString, // https://fetch.spec.whatwg.org/#requestredirect allowedValues: requestRedirect, }, { key: 'integrity', converter: webidl.converters.DOMString, }, { key: 'keepalive', converter: webidl.converters.boolean, }, { key: 'signal', converter: webidl.nullableConverter((signal) => webidl.converters.AbortSignal(signal, 'RequestInit', 'signal') ), }, { key: 'window', converter: webidl.converters.any, }, { key: 'duplex', converter: webidl.converters.DOMString, allowedValues: requestDuplex, }, { key: 'dispatcher', // undici specific option converter: webidl.converters.any, }, ]); module.exports = { Request, makeRequest, fromInnerRequest, cloneRequest, getRequestDispatcher, getRequestState, };