aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/node_modules/undici/lib/web/fetch/request.js
diff options
context:
space:
mode:
authorEgor Tensin <egor@tensin.name>2026-02-28 21:59:12 +0100
committerEgor Tensin <egor@tensin.name>2026-02-28 22:19:57 +0100
commit9611c248995ad3bacda1d0a93d0b5e1e6605d162 (patch)
treeeb3ae35b426e9d72e7cb34e8e9cc0453745a8e17 /node_modules/undici/lib/web/fetch/request.js
parentDEVELOPMENT.md: expand on sources (diff)
downloadcleanup-path-9611c248995ad3bacda1d0a93d0b5e1e6605d162.tar.gz
cleanup-path-9611c248995ad3bacda1d0a93d0b5e1e6605d162.zip
upgrade actions/core to version 2
Diffstat (limited to '')
-rw-r--r--node_modules/undici/lib/web/fetch/request.js (renamed from node_modules/undici/lib/fetch/request.js)351
1 files changed, 221 insertions, 130 deletions
diff --git a/node_modules/undici/lib/fetch/request.js b/node_modules/undici/lib/web/fetch/request.js
index 6fe4dff..ee3ce48 100644
--- a/node_modules/undici/lib/fetch/request.js
+++ b/node_modules/undici/lib/web/fetch/request.js
@@ -2,16 +2,15 @@
'use strict'
-const { extractBody, mixinBody, cloneBody } = require('./body')
-const { Headers, fill: fillHeaders, HeadersList } = require('./headers')
-const { FinalizationRegistry } = require('../compat/dispatcher-weakref')()
-const util = require('../core/util')
+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,
- normalizeMethod,
- makePolicyContainer,
- normalizeMethodRecord
+ environmentSettingsObject
} = require('./util')
const {
forbiddenMethodsSet,
@@ -23,16 +22,13 @@ const {
requestCache,
requestDuplex
} = require('./constants')
-const { kEnumerableProperty } = util
-const { kHeaders, kSignal, kState, kGuard, kRealm } = require('./symbols')
+const { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util
+const { kHeaders, kSignal, kState, kDispatcher } = require('./symbols')
const { webidl } = require('./webidl')
-const { getGlobalOrigin } = require('./global')
-const { URLSerializer } = require('./dataURL')
-const { kHeadersList, kConstruct } = require('../core/symbols')
-const assert = require('assert')
-const { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = require('events')
-
-let TransformStream = globalThis.TransformStream
+const { URLSerializer } = require('./data-url')
+const { kConstruct } = require('../../core/symbols')
+const assert = require('node:assert')
+const { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = require('node:events')
const kAbortController = Symbol('abortController')
@@ -40,29 +36,62 @@ const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
signal.removeEventListener('abort', abort)
})
+const dependentControllerMap = new WeakMap()
+
+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 {
// https://fetch.spec.whatwg.org/#dom-request
constructor (input, init = {}) {
+ webidl.util.markAsUncloneable(this)
if (input === kConstruct) {
return
}
- webidl.argumentLengthCheck(arguments, 1, { header: 'Request constructor' })
-
- input = webidl.converters.RequestInfo(input)
- init = webidl.converters.RequestInit(init)
+ const prefix = 'Request constructor'
+ webidl.argumentLengthCheck(arguments, 1, prefix)
- // https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
- this[kRealm] = {
- settingsObject: {
- baseUrl: getGlobalOrigin(),
- get origin () {
- return this.baseUrl?.origin
- },
- policyContainer: makePolicyContainer()
- }
- }
+ input = webidl.converters.RequestInfo(input, prefix, 'input')
+ init = webidl.converters.RequestInit(init, prefix, 'init')
// 1. Let request be null.
let request = null
@@ -71,13 +100,15 @@ class Request {
let fallbackMode = null
// 3. Let baseURL be this’s relevant settings object’s API base URL.
- const baseUrl = this[kRealm].settingsObject.baseUrl
+ 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[kDispatcher] = init.dispatcher
+
// 1. Let parsedURL be the result of parsing input with baseURL.
// 2. If parsedURL is failure, then throw a TypeError.
let parsedURL
@@ -101,6 +132,8 @@ class Request {
// 5. Set fallbackMode to "cors".
fallbackMode = 'cors'
} else {
+ this[kDispatcher] = init.dispatcher || input[kDispatcher]
+
// 6. Otherwise:
// 7. Assert: input is a Request object.
@@ -114,7 +147,7 @@ class Request {
}
// 7. Let origin be this’s relevant settings object’s origin.
- const origin = this[kRealm].settingsObject.origin
+ const origin = environmentSettingsObject.settingsObject.origin
// 8. Let window be "client".
let window = 'client'
@@ -150,7 +183,7 @@ class Request {
// unsafe-request flag Set.
unsafeRequest: request.unsafeRequest,
// client This’s relevant settings object.
- client: this[kRealm].settingsObject,
+ client: environmentSettingsObject.settingsObject,
// window window.
window,
// priority request’s priority.
@@ -239,7 +272,7 @@ class Request {
// then set request’s referrer to "client".
if (
(parsedReferrer.protocol === 'about:' && parsedReferrer.hostname === 'client') ||
- (origin && !sameOrigin(parsedReferrer, this[kRealm].settingsObject.baseUrl))
+ (origin && !sameOrigin(parsedReferrer, environmentSettingsObject.settingsObject.baseUrl))
) {
request.referrer = 'client'
} else {
@@ -315,21 +348,40 @@ class Request {
// 1. Let method be init["method"].
let method = init.method
- // 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 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(method.toUpperCase())) {
- throw new TypeError(`'${method}' HTTP method is unsupported.`)
+ 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
}
- // 3. Normalize method.
- method = normalizeMethodRecord[method] ?? normalizeMethod(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'
+ })
- // 4. Set request’s method to method.
- request.method = method
+ patchMethodWarning = true
+ }
}
// 26. If init["signal"] exists, then set signal to it.
@@ -346,7 +398,6 @@ class Request {
// (https://dom.spec.whatwg.org/#dom-abortsignal-any)
const ac = new AbortController()
this[kSignal] = ac.signal
- this[kSignal][kRealm] = this[kRealm]
// 29. If signal is not null, then make this’s signal follow signal.
if (signal != null) {
@@ -370,12 +421,7 @@ class Request {
this[kAbortController] = ac
const acRef = new WeakRef(ac)
- const abort = function () {
- const ac = acRef.deref()
- if (ac !== undefined) {
- ac.abort(this.reason)
- }
- }
+ const abort = buildAbort(acRef)
// Third-party AbortControllers may not work with these.
// See, https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619.
@@ -383,14 +429,18 @@ class Request {
// If the max amount of listeners is equal to the default, increase it
// This is only available in node >= v19.9.0
if (typeof getMaxListeners === 'function' && getMaxListeners(signal) === defaultMaxListeners) {
- setMaxListeners(100, signal)
+ setMaxListeners(1500, signal)
} else if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) {
- setMaxListeners(100, signal)
+ setMaxListeners(1500, signal)
}
} catch {}
util.addAbortListener(signal, abort)
- requestFinalizer.register(ac, { 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)
}
}
@@ -398,9 +448,8 @@ class Request {
// Realm, whose header list is request’s header list and guard is
// "request".
this[kHeaders] = new Headers(kConstruct)
- this[kHeaders][kHeadersList] = request.headersList
- this[kHeaders][kGuard] = 'request'
- this[kHeaders][kRealm] = this[kRealm]
+ setHeadersList(this[kHeaders], request.headersList)
+ setHeadersGuard(this[kHeaders], 'request')
// 31. If this’s request’s mode is "no-cors", then:
if (mode === 'no-cors') {
@@ -413,13 +462,13 @@ class Request {
}
// 2. Set this’s headers’s guard to "request-no-cors".
- this[kHeaders][kGuard] = 'request-no-cors'
+ setHeadersGuard(this[kHeaders], 'request-no-cors')
}
// 32. If init is not empty, then:
if (initHasKey) {
/** @type {HeadersList} */
- const headersList = this[kHeaders][kHeadersList]
+ const headersList = getHeadersList(this[kHeaders])
// 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"].
@@ -431,8 +480,8 @@ class Request {
// 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 [key, val] of headers) {
- headersList.append(key, val)
+ for (const { name, value } of headers.rawValues()) {
+ headersList.append(name, value, false)
}
// Note: Copy the `set-cookie` meta-data.
headersList.cookies = headers.cookies
@@ -473,7 +522,7 @@ class Request {
// 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 && !this[kHeaders][kHeadersList].contains('content-type')) {
+ if (contentType && !getHeadersList(this[kHeaders]).contains('content-type', true)) {
this[kHeaders].append('content-type', contentType)
}
}
@@ -509,17 +558,13 @@ class Request {
// 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 (util.isDisturbed(inputBody.stream) || inputBody.stream.locked) {
+ if (bodyUnusable(input)) {
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.
- if (!TransformStream) {
- TransformStream = require('stream/web').TransformStream
- }
-
// https://streams.spec.whatwg.org/#readablestream-create-a-proxy
const identityTransform = new TransformStream()
inputBody.stream.pipeThrough(identityTransform)
@@ -673,7 +718,7 @@ class Request {
}
// Returns a boolean indicating whether or not request is for a history
- // navigation (a.k.a. back-foward navigation).
+ // navigation (a.k.a. back-forward navigation).
get isHistoryNavigation () {
webidl.brandCheck(this, Request)
@@ -715,7 +760,7 @@ class Request {
webidl.brandCheck(this, Request)
// 1. If this is unusable, then throw a TypeError.
- if (this.bodyUsed || this.body?.locked) {
+ if (bodyUnusable(this)) {
throw new TypeError('unusable')
}
@@ -724,80 +769,103 @@ class Request {
// 3. Let clonedRequestObject be the result of creating a Request object,
// given clonedRequest, this’s headers’s guard, and this’s relevant Realm.
- const clonedRequestObject = new Request(kConstruct)
- clonedRequestObject[kState] = clonedRequest
- clonedRequestObject[kRealm] = this[kRealm]
- clonedRequestObject[kHeaders] = new Headers(kConstruct)
- clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList
- clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard]
- clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm]
-
// 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(
- this.signal,
- () => {
- ac.abort(this.signal.reason)
- }
+ ac.signal,
+ buildAbort(acRef)
)
}
- clonedRequestObject[kSignal] = ac.signal
// 4. Return clonedRequestObject.
- return clonedRequestObject
+ return fromInnerRequest(clonedRequest, ac.signal, getHeadersGuard(this[kHeaders]))
+ }
+
+ [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)}`
}
}
mixinBody(Request)
+// https://fetch.spec.whatwg.org/#requests
function makeRequest (init) {
- // https://fetch.spec.whatwg.org/#requests
- const request = {
- method: 'GET',
- localURLsOnly: false,
- unsafeRequest: false,
- body: null,
- client: null,
- reservedClient: null,
- replacesClientId: '',
- window: 'client',
- keepalive: false,
- serviceWorkers: 'all',
- initiator: '',
- destination: '',
- priority: null,
- origin: 'client',
- policyContainer: 'client',
- referrer: 'client',
- referrerPolicy: '',
- mode: 'no-cors',
- useCORSPreflightFlag: false,
- credentials: 'same-origin',
- useCredentials: false,
- cache: 'default',
- redirect: 'follow',
- integrity: '',
- cryptoGraphicsNonceMetadata: '',
- parserMetadata: '',
- reloadNavigation: false,
- historyNavigation: false,
- userActivation: false,
- taintedOrigin: false,
- redirectCount: 0,
- responseTainting: 'basic',
- preventNoCacheCacheControlHeaderModification: false,
- done: false,
- timingAllowFailed: false,
- ...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()
}
- request.url = request.urlList[0]
- return request
}
// https://fetch.spec.whatwg.org/#concept-request-clone
@@ -810,13 +878,30 @@ function cloneRequest (request) {
// 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(request.body)
+ newRequest.body = cloneBody(newRequest, request.body)
}
// 3. Return newRequest.
return newRequest
}
+/**
+ * @see https://fetch.spec.whatwg.org/#request-create
+ * @param {any} innerRequest
+ * @param {AbortSignal} signal
+ * @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard
+ * @returns {Request}
+ */
+function fromInnerRequest (innerRequest, signal, guard) {
+ const request = new Request(kConstruct)
+ request[kState] = innerRequest
+ request[kSignal] = signal
+ request[kHeaders] = new Headers(kConstruct)
+ setHeadersList(request[kHeaders], innerRequest.headersList)
+ setHeadersGuard(request[kHeaders], guard)
+ return request
+}
+
Object.defineProperties(Request.prototype, {
method: kEnumerableProperty,
url: kEnumerableProperty,
@@ -849,16 +934,16 @@ webidl.converters.Request = webidl.interfaceConverter(
)
// https://fetch.spec.whatwg.org/#requestinfo
-webidl.converters.RequestInfo = function (V) {
+webidl.converters.RequestInfo = function (V, prefix, argument) {
if (typeof V === 'string') {
- return webidl.converters.USVString(V)
+ return webidl.converters.USVString(V, prefix, argument)
}
if (V instanceof Request) {
- return webidl.converters.Request(V)
+ return webidl.converters.Request(V, prefix, argument)
}
- return webidl.converters.USVString(V)
+ return webidl.converters.USVString(V, prefix, argument)
}
webidl.converters.AbortSignal = webidl.interfaceConverter(
@@ -928,6 +1013,8 @@ webidl.converters.RequestInit = webidl.dictionaryConverter([
converter: webidl.nullableConverter(
(signal) => webidl.converters.AbortSignal(
signal,
+ 'RequestInit',
+ 'signal',
{ strict: false }
)
)
@@ -940,7 +1027,11 @@ webidl.converters.RequestInit = webidl.dictionaryConverter([
key: 'duplex',
converter: webidl.converters.DOMString,
allowedValues: requestDuplex
+ },
+ {
+ key: 'dispatcher', // undici specific option
+ converter: webidl.converters.any
}
])
-module.exports = { Request, makeRequest }
+module.exports = { Request, makeRequest, fromInnerRequest, cloneRequest }