request.js 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115
  1. /* globals AbortController */
  2. 'use strict'
  3. const { extractBody, mixinBody, cloneBody, bodyUnusable } = require('./body')
  4. const { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = require('./headers')
  5. const util = require('../../core/util')
  6. const nodeUtil = require('node:util')
  7. const {
  8. isValidHTTPToken,
  9. sameOrigin,
  10. environmentSettingsObject
  11. } = require('./util')
  12. const {
  13. forbiddenMethodsSet,
  14. corsSafeListedMethodsSet,
  15. referrerPolicy,
  16. requestRedirect,
  17. requestMode,
  18. requestCredentials,
  19. requestCache,
  20. requestDuplex
  21. } = require('./constants')
  22. const { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util
  23. const { webidl } = require('../webidl')
  24. const { URLSerializer } = require('./data-url')
  25. const { kConstruct } = require('../../core/symbols')
  26. const assert = require('node:assert')
  27. const { getMaxListeners, setMaxListeners, defaultMaxListeners } = require('node:events')
  28. const kAbortController = Symbol('abortController')
  29. const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
  30. signal.removeEventListener('abort', abort)
  31. })
  32. const dependentControllerMap = new WeakMap()
  33. let abortSignalHasEventHandlerLeakWarning
  34. try {
  35. abortSignalHasEventHandlerLeakWarning = getMaxListeners(new AbortController().signal) > 0
  36. } catch {
  37. abortSignalHasEventHandlerLeakWarning = false
  38. }
  39. function buildAbort (acRef) {
  40. return abort
  41. function abort () {
  42. const ac = acRef.deref()
  43. if (ac !== undefined) {
  44. // Currently, there is a problem with FinalizationRegistry.
  45. // https://github.com/nodejs/node/issues/49344
  46. // https://github.com/nodejs/node/issues/47748
  47. // In the case of abort, the first step is to unregister from it.
  48. // If the controller can refer to it, it is still registered.
  49. // It will be removed in the future.
  50. requestFinalizer.unregister(abort)
  51. // Unsubscribe a listener.
  52. // FinalizationRegistry will no longer be called, so this must be done.
  53. this.removeEventListener('abort', abort)
  54. ac.abort(this.reason)
  55. const controllerList = dependentControllerMap.get(ac.signal)
  56. if (controllerList !== undefined) {
  57. if (controllerList.size !== 0) {
  58. for (const ref of controllerList) {
  59. const ctrl = ref.deref()
  60. if (ctrl !== undefined) {
  61. ctrl.abort(this.reason)
  62. }
  63. }
  64. controllerList.clear()
  65. }
  66. dependentControllerMap.delete(ac.signal)
  67. }
  68. }
  69. }
  70. }
  71. let patchMethodWarning = false
  72. // https://fetch.spec.whatwg.org/#request-class
  73. class Request {
  74. /** @type {AbortSignal} */
  75. #signal
  76. /** @type {import('../../dispatcher/dispatcher')} */
  77. #dispatcher
  78. /** @type {Headers} */
  79. #headers
  80. #state
  81. // https://fetch.spec.whatwg.org/#dom-request
  82. constructor (input, init = undefined) {
  83. webidl.util.markAsUncloneable(this)
  84. if (input === kConstruct) {
  85. return
  86. }
  87. const prefix = 'Request constructor'
  88. webidl.argumentLengthCheck(arguments, 1, prefix)
  89. input = webidl.converters.RequestInfo(input)
  90. init = webidl.converters.RequestInit(init)
  91. // 1. Let request be null.
  92. let request = null
  93. // 2. Let fallbackMode be null.
  94. let fallbackMode = null
  95. // 3. Let baseURL be this’s relevant settings object’s API base URL.
  96. const baseUrl = environmentSettingsObject.settingsObject.baseUrl
  97. // 4. Let signal be null.
  98. let signal = null
  99. // 5. If input is a string, then:
  100. if (typeof input === 'string') {
  101. this.#dispatcher = init.dispatcher
  102. // 1. Let parsedURL be the result of parsing input with baseURL.
  103. // 2. If parsedURL is failure, then throw a TypeError.
  104. let parsedURL
  105. try {
  106. parsedURL = new URL(input, baseUrl)
  107. } catch (err) {
  108. throw new TypeError('Failed to parse URL from ' + input, { cause: err })
  109. }
  110. // 3. If parsedURL includes credentials, then throw a TypeError.
  111. if (parsedURL.username || parsedURL.password) {
  112. throw new TypeError(
  113. 'Request cannot be constructed from a URL that includes credentials: ' +
  114. input
  115. )
  116. }
  117. // 4. Set request to a new request whose URL is parsedURL.
  118. request = makeRequest({ urlList: [parsedURL] })
  119. // 5. Set fallbackMode to "cors".
  120. fallbackMode = 'cors'
  121. } else {
  122. // 6. Otherwise:
  123. // 7. Assert: input is a Request object.
  124. assert(webidl.is.Request(input))
  125. // 8. Set request to input’s request.
  126. request = input.#state
  127. // 9. Set signal to input’s signal.
  128. signal = input.#signal
  129. this.#dispatcher = init.dispatcher || input.#dispatcher
  130. }
  131. // 7. Let origin be this’s relevant settings object’s origin.
  132. const origin = environmentSettingsObject.settingsObject.origin
  133. // 8. Let window be "client".
  134. let window = 'client'
  135. // 9. If request’s window is an environment settings object and its origin
  136. // is same origin with origin, then set window to request’s window.
  137. if (
  138. request.window?.constructor?.name === 'EnvironmentSettingsObject' &&
  139. sameOrigin(request.window, origin)
  140. ) {
  141. window = request.window
  142. }
  143. // 10. If init["window"] exists and is non-null, then throw a TypeError.
  144. if (init.window != null) {
  145. throw new TypeError(`'window' option '${window}' must be null`)
  146. }
  147. // 11. If init["window"] exists, then set window to "no-window".
  148. if ('window' in init) {
  149. window = 'no-window'
  150. }
  151. // 12. Set request to a new request with the following properties:
  152. request = makeRequest({
  153. // URL request’s URL.
  154. // undici implementation note: this is set as the first item in request's urlList in makeRequest
  155. // method request’s method.
  156. method: request.method,
  157. // header list A copy of request’s header list.
  158. // undici implementation note: headersList is cloned in makeRequest
  159. headersList: request.headersList,
  160. // unsafe-request flag Set.
  161. unsafeRequest: request.unsafeRequest,
  162. // client This’s relevant settings object.
  163. client: environmentSettingsObject.settingsObject,
  164. // window window.
  165. window,
  166. // priority request’s priority.
  167. priority: request.priority,
  168. // origin request’s origin. The propagation of the origin is only significant for navigation requests
  169. // being handled by a service worker. In this scenario a request can have an origin that is different
  170. // from the current client.
  171. origin: request.origin,
  172. // referrer request’s referrer.
  173. referrer: request.referrer,
  174. // referrer policy request’s referrer policy.
  175. referrerPolicy: request.referrerPolicy,
  176. // mode request’s mode.
  177. mode: request.mode,
  178. // credentials mode request’s credentials mode.
  179. credentials: request.credentials,
  180. // cache mode request’s cache mode.
  181. cache: request.cache,
  182. // redirect mode request’s redirect mode.
  183. redirect: request.redirect,
  184. // integrity metadata request’s integrity metadata.
  185. integrity: request.integrity,
  186. // keepalive request’s keepalive.
  187. keepalive: request.keepalive,
  188. // reload-navigation flag request’s reload-navigation flag.
  189. reloadNavigation: request.reloadNavigation,
  190. // history-navigation flag request’s history-navigation flag.
  191. historyNavigation: request.historyNavigation,
  192. // URL list A clone of request’s URL list.
  193. urlList: [...request.urlList]
  194. })
  195. const initHasKey = Object.keys(init).length !== 0
  196. // 13. If init is not empty, then:
  197. if (initHasKey) {
  198. // 1. If request’s mode is "navigate", then set it to "same-origin".
  199. if (request.mode === 'navigate') {
  200. request.mode = 'same-origin'
  201. }
  202. // 2. Unset request’s reload-navigation flag.
  203. request.reloadNavigation = false
  204. // 3. Unset request’s history-navigation flag.
  205. request.historyNavigation = false
  206. // 4. Set request’s origin to "client".
  207. request.origin = 'client'
  208. // 5. Set request’s referrer to "client"
  209. request.referrer = 'client'
  210. // 6. Set request’s referrer policy to the empty string.
  211. request.referrerPolicy = ''
  212. // 7. Set request’s URL to request’s current URL.
  213. request.url = request.urlList[request.urlList.length - 1]
  214. // 8. Set request’s URL list to « request’s URL ».
  215. request.urlList = [request.url]
  216. }
  217. // 14. If init["referrer"] exists, then:
  218. if (init.referrer !== undefined) {
  219. // 1. Let referrer be init["referrer"].
  220. const referrer = init.referrer
  221. // 2. If referrer is the empty string, then set request’s referrer to "no-referrer".
  222. if (referrer === '') {
  223. request.referrer = 'no-referrer'
  224. } else {
  225. // 1. Let parsedReferrer be the result of parsing referrer with
  226. // baseURL.
  227. // 2. If parsedReferrer is failure, then throw a TypeError.
  228. let parsedReferrer
  229. try {
  230. parsedReferrer = new URL(referrer, baseUrl)
  231. } catch (err) {
  232. throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err })
  233. }
  234. // 3. If one of the following is true
  235. // - parsedReferrer’s scheme is "about" and path is the string "client"
  236. // - parsedReferrer’s origin is not same origin with origin
  237. // then set request’s referrer to "client".
  238. if (
  239. (parsedReferrer.protocol === 'about:' && parsedReferrer.hostname === 'client') ||
  240. (origin && !sameOrigin(parsedReferrer, environmentSettingsObject.settingsObject.baseUrl))
  241. ) {
  242. request.referrer = 'client'
  243. } else {
  244. // 4. Otherwise, set request’s referrer to parsedReferrer.
  245. request.referrer = parsedReferrer
  246. }
  247. }
  248. }
  249. // 15. If init["referrerPolicy"] exists, then set request’s referrer policy
  250. // to it.
  251. if (init.referrerPolicy !== undefined) {
  252. request.referrerPolicy = init.referrerPolicy
  253. }
  254. // 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise.
  255. let mode
  256. if (init.mode !== undefined) {
  257. mode = init.mode
  258. } else {
  259. mode = fallbackMode
  260. }
  261. // 17. If mode is "navigate", then throw a TypeError.
  262. if (mode === 'navigate') {
  263. throw webidl.errors.exception({
  264. header: 'Request constructor',
  265. message: 'invalid request mode navigate.'
  266. })
  267. }
  268. // 18. If mode is non-null, set request’s mode to mode.
  269. if (mode != null) {
  270. request.mode = mode
  271. }
  272. // 19. If init["credentials"] exists, then set request’s credentials mode
  273. // to it.
  274. if (init.credentials !== undefined) {
  275. request.credentials = init.credentials
  276. }
  277. // 18. If init["cache"] exists, then set request’s cache mode to it.
  278. if (init.cache !== undefined) {
  279. request.cache = init.cache
  280. }
  281. // 21. If request’s cache mode is "only-if-cached" and request’s mode is
  282. // not "same-origin", then throw a TypeError.
  283. if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
  284. throw new TypeError(
  285. "'only-if-cached' can be set only with 'same-origin' mode"
  286. )
  287. }
  288. // 22. If init["redirect"] exists, then set request’s redirect mode to it.
  289. if (init.redirect !== undefined) {
  290. request.redirect = init.redirect
  291. }
  292. // 23. If init["integrity"] exists, then set request’s integrity metadata to it.
  293. if (init.integrity != null) {
  294. request.integrity = String(init.integrity)
  295. }
  296. // 24. If init["keepalive"] exists, then set request’s keepalive to it.
  297. if (init.keepalive !== undefined) {
  298. request.keepalive = Boolean(init.keepalive)
  299. }
  300. // 25. If init["method"] exists, then:
  301. if (init.method !== undefined) {
  302. // 1. Let method be init["method"].
  303. let method = init.method
  304. const mayBeNormalized = normalizedMethodRecords[method]
  305. if (mayBeNormalized !== undefined) {
  306. // Note: Bypass validation DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH and these lowercase ones
  307. request.method = mayBeNormalized
  308. } else {
  309. // 2. If method is not a method or method is a forbidden method, then
  310. // throw a TypeError.
  311. if (!isValidHTTPToken(method)) {
  312. throw new TypeError(`'${method}' is not a valid HTTP method.`)
  313. }
  314. const upperCase = method.toUpperCase()
  315. if (forbiddenMethodsSet.has(upperCase)) {
  316. throw new TypeError(`'${method}' HTTP method is unsupported.`)
  317. }
  318. // 3. Normalize method.
  319. // https://fetch.spec.whatwg.org/#concept-method-normalize
  320. // Note: must be in uppercase
  321. method = normalizedMethodRecordsBase[upperCase] ?? method
  322. // 4. Set request’s method to method.
  323. request.method = method
  324. }
  325. if (!patchMethodWarning && request.method === 'patch') {
  326. process.emitWarning('Using `patch` is highly likely to result in a `405 Method Not Allowed`. `PATCH` is much more likely to succeed.', {
  327. code: 'UNDICI-FETCH-patch'
  328. })
  329. patchMethodWarning = true
  330. }
  331. }
  332. // 26. If init["signal"] exists, then set signal to it.
  333. if (init.signal !== undefined) {
  334. signal = init.signal
  335. }
  336. // 27. Set this’s request to request.
  337. this.#state = request
  338. // 28. Set this’s signal to a new AbortSignal object with this’s relevant
  339. // Realm.
  340. // TODO: could this be simplified with AbortSignal.any
  341. // (https://dom.spec.whatwg.org/#dom-abortsignal-any)
  342. const ac = new AbortController()
  343. this.#signal = ac.signal
  344. // 29. If signal is not null, then make this’s signal follow signal.
  345. if (signal != null) {
  346. if (signal.aborted) {
  347. ac.abort(signal.reason)
  348. } else {
  349. // Keep a strong ref to ac while request object
  350. // is alive. This is needed to prevent AbortController
  351. // from being prematurely garbage collected.
  352. // See, https://github.com/nodejs/undici/issues/1926.
  353. this[kAbortController] = ac
  354. const acRef = new WeakRef(ac)
  355. const abort = buildAbort(acRef)
  356. // If the max amount of listeners is equal to the default, increase it
  357. if (abortSignalHasEventHandlerLeakWarning && getMaxListeners(signal) === defaultMaxListeners) {
  358. setMaxListeners(1500, signal)
  359. }
  360. util.addAbortListener(signal, abort)
  361. // The third argument must be a registry key to be unregistered.
  362. // Without it, you cannot unregister.
  363. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry
  364. // abort is used as the unregister key. (because it is unique)
  365. requestFinalizer.register(ac, { signal, abort }, abort)
  366. }
  367. }
  368. // 30. Set this’s headers to a new Headers object with this’s relevant
  369. // Realm, whose header list is request’s header list and guard is
  370. // "request".
  371. this.#headers = new Headers(kConstruct)
  372. setHeadersList(this.#headers, request.headersList)
  373. setHeadersGuard(this.#headers, 'request')
  374. // 31. If this’s request’s mode is "no-cors", then:
  375. if (mode === 'no-cors') {
  376. // 1. If this’s request’s method is not a CORS-safelisted method,
  377. // then throw a TypeError.
  378. if (!corsSafeListedMethodsSet.has(request.method)) {
  379. throw new TypeError(
  380. `'${request.method} is unsupported in no-cors mode.`
  381. )
  382. }
  383. // 2. Set this’s headers’s guard to "request-no-cors".
  384. setHeadersGuard(this.#headers, 'request-no-cors')
  385. }
  386. // 32. If init is not empty, then:
  387. if (initHasKey) {
  388. /** @type {HeadersList} */
  389. const headersList = getHeadersList(this.#headers)
  390. // 1. Let headers be a copy of this’s headers and its associated header
  391. // list.
  392. // 2. If init["headers"] exists, then set headers to init["headers"].
  393. const headers = init.headers !== undefined ? init.headers : new HeadersList(headersList)
  394. // 3. Empty this’s headers’s header list.
  395. headersList.clear()
  396. // 4. If headers is a Headers object, then for each header in its header
  397. // list, append header’s name/header’s value to this’s headers.
  398. if (headers instanceof HeadersList) {
  399. for (const { name, value } of headers.rawValues()) {
  400. headersList.append(name, value, false)
  401. }
  402. // Note: Copy the `set-cookie` meta-data.
  403. headersList.cookies = headers.cookies
  404. } else {
  405. // 5. Otherwise, fill this’s headers with headers.
  406. fillHeaders(this.#headers, headers)
  407. }
  408. }
  409. // 33. Let inputBody be input’s request’s body if input is a Request
  410. // object; otherwise null.
  411. const inputBody = webidl.is.Request(input) ? input.#state.body : null
  412. // 34. If either init["body"] exists and is non-null or inputBody is
  413. // non-null, and request’s method is `GET` or `HEAD`, then throw a
  414. // TypeError.
  415. if (
  416. (init.body != null || inputBody != null) &&
  417. (request.method === 'GET' || request.method === 'HEAD')
  418. ) {
  419. throw new TypeError('Request with GET/HEAD method cannot have body.')
  420. }
  421. // 35. Let initBody be null.
  422. let initBody = null
  423. // 36. If init["body"] exists and is non-null, then:
  424. if (init.body != null) {
  425. // 1. Let Content-Type be null.
  426. // 2. Set initBody and Content-Type to the result of extracting
  427. // init["body"], with keepalive set to request’s keepalive.
  428. const [extractedBody, contentType] = extractBody(
  429. init.body,
  430. request.keepalive
  431. )
  432. initBody = extractedBody
  433. // 3, If Content-Type is non-null and this’s headers’s header list does
  434. // not contain `Content-Type`, then append `Content-Type`/Content-Type to
  435. // this’s headers.
  436. if (contentType && !getHeadersList(this.#headers).contains('content-type', true)) {
  437. this.#headers.append('content-type', contentType, true)
  438. }
  439. }
  440. // 37. Let inputOrInitBody be initBody if it is non-null; otherwise
  441. // inputBody.
  442. const inputOrInitBody = initBody ?? inputBody
  443. // 38. If inputOrInitBody is non-null and inputOrInitBody’s source is
  444. // null, then:
  445. if (inputOrInitBody != null && inputOrInitBody.source == null) {
  446. // 1. If initBody is non-null and init["duplex"] does not exist,
  447. // then throw a TypeError.
  448. if (initBody != null && init.duplex == null) {
  449. throw new TypeError('RequestInit: duplex option is required when sending a body.')
  450. }
  451. // 2. If this’s request’s mode is neither "same-origin" nor "cors",
  452. // then throw a TypeError.
  453. if (request.mode !== 'same-origin' && request.mode !== 'cors') {
  454. throw new TypeError(
  455. 'If request is made from ReadableStream, mode should be "same-origin" or "cors"'
  456. )
  457. }
  458. // 3. Set this’s request’s use-CORS-preflight flag.
  459. request.useCORSPreflightFlag = true
  460. }
  461. // 39. Let finalBody be inputOrInitBody.
  462. let finalBody = inputOrInitBody
  463. // 40. If initBody is null and inputBody is non-null, then:
  464. if (initBody == null && inputBody != null) {
  465. // 1. If input is unusable, then throw a TypeError.
  466. if (bodyUnusable(input.#state)) {
  467. throw new TypeError(
  468. 'Cannot construct a Request with a Request object that has already been used.'
  469. )
  470. }
  471. // 2. Set finalBody to the result of creating a proxy for inputBody.
  472. // https://streams.spec.whatwg.org/#readablestream-create-a-proxy
  473. const identityTransform = new TransformStream()
  474. inputBody.stream.pipeThrough(identityTransform)
  475. finalBody = {
  476. source: inputBody.source,
  477. length: inputBody.length,
  478. stream: identityTransform.readable
  479. }
  480. }
  481. // 41. Set this’s request’s body to finalBody.
  482. this.#state.body = finalBody
  483. }
  484. // Returns request’s HTTP method, which is "GET" by default.
  485. get method () {
  486. webidl.brandCheck(this, Request)
  487. // The method getter steps are to return this’s request’s method.
  488. return this.#state.method
  489. }
  490. // Returns the URL of request as a string.
  491. get url () {
  492. webidl.brandCheck(this, Request)
  493. // The url getter steps are to return this’s request’s URL, serialized.
  494. return URLSerializer(this.#state.url)
  495. }
  496. // Returns a Headers object consisting of the headers associated with request.
  497. // Note that headers added in the network layer by the user agent will not
  498. // be accounted for in this object, e.g., the "Host" header.
  499. get headers () {
  500. webidl.brandCheck(this, Request)
  501. // The headers getter steps are to return this’s headers.
  502. return this.#headers
  503. }
  504. // Returns the kind of resource requested by request, e.g., "document"
  505. // or "script".
  506. get destination () {
  507. webidl.brandCheck(this, Request)
  508. // The destination getter are to return this’s request’s destination.
  509. return this.#state.destination
  510. }
  511. // Returns the referrer of request. Its value can be a same-origin URL if
  512. // explicitly set in init, the empty string to indicate no referrer, and
  513. // "about:client" when defaulting to the global’s default. This is used
  514. // during fetching to determine the value of the `Referer` header of the
  515. // request being made.
  516. get referrer () {
  517. webidl.brandCheck(this, Request)
  518. // 1. If this’s request’s referrer is "no-referrer", then return the
  519. // empty string.
  520. if (this.#state.referrer === 'no-referrer') {
  521. return ''
  522. }
  523. // 2. If this’s request’s referrer is "client", then return
  524. // "about:client".
  525. if (this.#state.referrer === 'client') {
  526. return 'about:client'
  527. }
  528. // Return this’s request’s referrer, serialized.
  529. return this.#state.referrer.toString()
  530. }
  531. // Returns the referrer policy associated with request.
  532. // This is used during fetching to compute the value of the request’s
  533. // referrer.
  534. get referrerPolicy () {
  535. webidl.brandCheck(this, Request)
  536. // The referrerPolicy getter steps are to return this’s request’s referrer policy.
  537. return this.#state.referrerPolicy
  538. }
  539. // Returns the mode associated with request, which is a string indicating
  540. // whether the request will use CORS, or will be restricted to same-origin
  541. // URLs.
  542. get mode () {
  543. webidl.brandCheck(this, Request)
  544. // The mode getter steps are to return this’s request’s mode.
  545. return this.#state.mode
  546. }
  547. // Returns the credentials mode associated with request,
  548. // which is a string indicating whether credentials will be sent with the
  549. // request always, never, or only when sent to a same-origin URL.
  550. get credentials () {
  551. webidl.brandCheck(this, Request)
  552. // The credentials getter steps are to return this’s request’s credentials mode.
  553. return this.#state.credentials
  554. }
  555. // Returns the cache mode associated with request,
  556. // which is a string indicating how the request will
  557. // interact with the browser’s cache when fetching.
  558. get cache () {
  559. webidl.brandCheck(this, Request)
  560. // The cache getter steps are to return this’s request’s cache mode.
  561. return this.#state.cache
  562. }
  563. // Returns the redirect mode associated with request,
  564. // which is a string indicating how redirects for the
  565. // request will be handled during fetching. A request
  566. // will follow redirects by default.
  567. get redirect () {
  568. webidl.brandCheck(this, Request)
  569. // The redirect getter steps are to return this’s request’s redirect mode.
  570. return this.#state.redirect
  571. }
  572. // Returns request’s subresource integrity metadata, which is a
  573. // cryptographic hash of the resource being fetched. Its value
  574. // consists of multiple hashes separated by whitespace. [SRI]
  575. get integrity () {
  576. webidl.brandCheck(this, Request)
  577. // The integrity getter steps are to return this’s request’s integrity
  578. // metadata.
  579. return this.#state.integrity
  580. }
  581. // Returns a boolean indicating whether or not request can outlive the
  582. // global in which it was created.
  583. get keepalive () {
  584. webidl.brandCheck(this, Request)
  585. // The keepalive getter steps are to return this’s request’s keepalive.
  586. return this.#state.keepalive
  587. }
  588. // Returns a boolean indicating whether or not request is for a reload
  589. // navigation.
  590. get isReloadNavigation () {
  591. webidl.brandCheck(this, Request)
  592. // The isReloadNavigation getter steps are to return true if this’s
  593. // request’s reload-navigation flag is set; otherwise false.
  594. return this.#state.reloadNavigation
  595. }
  596. // Returns a boolean indicating whether or not request is for a history
  597. // navigation (a.k.a. back-forward navigation).
  598. get isHistoryNavigation () {
  599. webidl.brandCheck(this, Request)
  600. // The isHistoryNavigation getter steps are to return true if this’s request’s
  601. // history-navigation flag is set; otherwise false.
  602. return this.#state.historyNavigation
  603. }
  604. // Returns the signal associated with request, which is an AbortSignal
  605. // object indicating whether or not request has been aborted, and its
  606. // abort event handler.
  607. get signal () {
  608. webidl.brandCheck(this, Request)
  609. // The signal getter steps are to return this’s signal.
  610. return this.#signal
  611. }
  612. get body () {
  613. webidl.brandCheck(this, Request)
  614. return this.#state.body ? this.#state.body.stream : null
  615. }
  616. get bodyUsed () {
  617. webidl.brandCheck(this, Request)
  618. return !!this.#state.body && util.isDisturbed(this.#state.body.stream)
  619. }
  620. get duplex () {
  621. webidl.brandCheck(this, Request)
  622. return 'half'
  623. }
  624. // Returns a clone of request.
  625. clone () {
  626. webidl.brandCheck(this, Request)
  627. // 1. If this is unusable, then throw a TypeError.
  628. if (bodyUnusable(this.#state)) {
  629. throw new TypeError('unusable')
  630. }
  631. // 2. Let clonedRequest be the result of cloning this’s request.
  632. const clonedRequest = cloneRequest(this.#state)
  633. // 3. Let clonedRequestObject be the result of creating a Request object,
  634. // given clonedRequest, this’s headers’s guard, and this’s relevant Realm.
  635. // 4. Make clonedRequestObject’s signal follow this’s signal.
  636. const ac = new AbortController()
  637. if (this.signal.aborted) {
  638. ac.abort(this.signal.reason)
  639. } else {
  640. let list = dependentControllerMap.get(this.signal)
  641. if (list === undefined) {
  642. list = new Set()
  643. dependentControllerMap.set(this.signal, list)
  644. }
  645. const acRef = new WeakRef(ac)
  646. list.add(acRef)
  647. util.addAbortListener(
  648. ac.signal,
  649. buildAbort(acRef)
  650. )
  651. }
  652. // 4. Return clonedRequestObject.
  653. return fromInnerRequest(clonedRequest, this.#dispatcher, ac.signal, getHeadersGuard(this.#headers))
  654. }
  655. [nodeUtil.inspect.custom] (depth, options) {
  656. if (options.depth === null) {
  657. options.depth = 2
  658. }
  659. options.colors ??= true
  660. const properties = {
  661. method: this.method,
  662. url: this.url,
  663. headers: this.headers,
  664. destination: this.destination,
  665. referrer: this.referrer,
  666. referrerPolicy: this.referrerPolicy,
  667. mode: this.mode,
  668. credentials: this.credentials,
  669. cache: this.cache,
  670. redirect: this.redirect,
  671. integrity: this.integrity,
  672. keepalive: this.keepalive,
  673. isReloadNavigation: this.isReloadNavigation,
  674. isHistoryNavigation: this.isHistoryNavigation,
  675. signal: this.signal
  676. }
  677. return `Request ${nodeUtil.formatWithOptions(options, properties)}`
  678. }
  679. /**
  680. * @param {Request} request
  681. * @param {AbortSignal} newSignal
  682. */
  683. static setRequestSignal (request, newSignal) {
  684. request.#signal = newSignal
  685. return request
  686. }
  687. /**
  688. * @param {Request} request
  689. */
  690. static getRequestDispatcher (request) {
  691. return request.#dispatcher
  692. }
  693. /**
  694. * @param {Request} request
  695. * @param {import('../../dispatcher/dispatcher')} newDispatcher
  696. */
  697. static setRequestDispatcher (request, newDispatcher) {
  698. request.#dispatcher = newDispatcher
  699. }
  700. /**
  701. * @param {Request} request
  702. * @param {Headers} newHeaders
  703. */
  704. static setRequestHeaders (request, newHeaders) {
  705. request.#headers = newHeaders
  706. }
  707. /**
  708. * @param {Request} request
  709. */
  710. static getRequestState (request) {
  711. return request.#state
  712. }
  713. /**
  714. * @param {Request} request
  715. * @param {any} newState
  716. */
  717. static setRequestState (request, newState) {
  718. request.#state = newState
  719. }
  720. }
  721. const { setRequestSignal, getRequestDispatcher, setRequestDispatcher, setRequestHeaders, getRequestState, setRequestState } = Request
  722. Reflect.deleteProperty(Request, 'setRequestSignal')
  723. Reflect.deleteProperty(Request, 'getRequestDispatcher')
  724. Reflect.deleteProperty(Request, 'setRequestDispatcher')
  725. Reflect.deleteProperty(Request, 'setRequestHeaders')
  726. Reflect.deleteProperty(Request, 'getRequestState')
  727. Reflect.deleteProperty(Request, 'setRequestState')
  728. mixinBody(Request, getRequestState)
  729. // https://fetch.spec.whatwg.org/#requests
  730. function makeRequest (init) {
  731. return {
  732. method: init.method ?? 'GET',
  733. localURLsOnly: init.localURLsOnly ?? false,
  734. unsafeRequest: init.unsafeRequest ?? false,
  735. body: init.body ?? null,
  736. client: init.client ?? null,
  737. reservedClient: init.reservedClient ?? null,
  738. replacesClientId: init.replacesClientId ?? '',
  739. window: init.window ?? 'client',
  740. keepalive: init.keepalive ?? false,
  741. serviceWorkers: init.serviceWorkers ?? 'all',
  742. initiator: init.initiator ?? '',
  743. destination: init.destination ?? '',
  744. priority: init.priority ?? null,
  745. origin: init.origin ?? 'client',
  746. policyContainer: init.policyContainer ?? 'client',
  747. referrer: init.referrer ?? 'client',
  748. referrerPolicy: init.referrerPolicy ?? '',
  749. mode: init.mode ?? 'no-cors',
  750. useCORSPreflightFlag: init.useCORSPreflightFlag ?? false,
  751. credentials: init.credentials ?? 'same-origin',
  752. useCredentials: init.useCredentials ?? false,
  753. cache: init.cache ?? 'default',
  754. redirect: init.redirect ?? 'follow',
  755. integrity: init.integrity ?? '',
  756. cryptoGraphicsNonceMetadata: init.cryptoGraphicsNonceMetadata ?? '',
  757. parserMetadata: init.parserMetadata ?? '',
  758. reloadNavigation: init.reloadNavigation ?? false,
  759. historyNavigation: init.historyNavigation ?? false,
  760. userActivation: init.userActivation ?? false,
  761. taintedOrigin: init.taintedOrigin ?? false,
  762. redirectCount: init.redirectCount ?? 0,
  763. responseTainting: init.responseTainting ?? 'basic',
  764. preventNoCacheCacheControlHeaderModification: init.preventNoCacheCacheControlHeaderModification ?? false,
  765. done: init.done ?? false,
  766. timingAllowFailed: init.timingAllowFailed ?? false,
  767. useURLCredentials: init.useURLCredentials ?? undefined,
  768. traversableForUserPrompts: init.traversableForUserPrompts ?? 'client',
  769. urlList: init.urlList,
  770. url: init.urlList[0],
  771. headersList: init.headersList
  772. ? new HeadersList(init.headersList)
  773. : new HeadersList()
  774. }
  775. }
  776. // https://fetch.spec.whatwg.org/#concept-request-clone
  777. function cloneRequest (request) {
  778. // To clone a request request, run these steps:
  779. // 1. Let newRequest be a copy of request, except for its body.
  780. const newRequest = makeRequest({ ...request, body: null })
  781. // 2. If request’s body is non-null, set newRequest’s body to the
  782. // result of cloning request’s body.
  783. if (request.body != null) {
  784. newRequest.body = cloneBody(request.body)
  785. }
  786. // 3. Return newRequest.
  787. return newRequest
  788. }
  789. /**
  790. * @see https://fetch.spec.whatwg.org/#request-create
  791. * @param {any} innerRequest
  792. * @param {import('../../dispatcher/agent')} dispatcher
  793. * @param {AbortSignal} signal
  794. * @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard
  795. * @returns {Request}
  796. */
  797. function fromInnerRequest (innerRequest, dispatcher, signal, guard) {
  798. const request = new Request(kConstruct)
  799. setRequestState(request, innerRequest)
  800. setRequestDispatcher(request, dispatcher)
  801. setRequestSignal(request, signal)
  802. const headers = new Headers(kConstruct)
  803. setRequestHeaders(request, headers)
  804. setHeadersList(headers, innerRequest.headersList)
  805. setHeadersGuard(headers, guard)
  806. return request
  807. }
  808. Object.defineProperties(Request.prototype, {
  809. method: kEnumerableProperty,
  810. url: kEnumerableProperty,
  811. headers: kEnumerableProperty,
  812. redirect: kEnumerableProperty,
  813. clone: kEnumerableProperty,
  814. signal: kEnumerableProperty,
  815. duplex: kEnumerableProperty,
  816. destination: kEnumerableProperty,
  817. body: kEnumerableProperty,
  818. bodyUsed: kEnumerableProperty,
  819. isHistoryNavigation: kEnumerableProperty,
  820. isReloadNavigation: kEnumerableProperty,
  821. keepalive: kEnumerableProperty,
  822. integrity: kEnumerableProperty,
  823. cache: kEnumerableProperty,
  824. credentials: kEnumerableProperty,
  825. attribute: kEnumerableProperty,
  826. referrerPolicy: kEnumerableProperty,
  827. referrer: kEnumerableProperty,
  828. mode: kEnumerableProperty,
  829. [Symbol.toStringTag]: {
  830. value: 'Request',
  831. configurable: true
  832. }
  833. })
  834. webidl.is.Request = webidl.util.MakeTypeAssertion(Request)
  835. /**
  836. * @param {*} V
  837. * @returns {import('../../../types/fetch').Request|string}
  838. *
  839. * @see https://fetch.spec.whatwg.org/#requestinfo
  840. */
  841. webidl.converters.RequestInfo = function (V) {
  842. if (typeof V === 'string') {
  843. return webidl.converters.USVString(V)
  844. }
  845. if (webidl.is.Request(V)) {
  846. return V
  847. }
  848. return webidl.converters.USVString(V)
  849. }
  850. /**
  851. * @param {*} V
  852. * @returns {import('../../../types/fetch').RequestInit}
  853. * @see https://fetch.spec.whatwg.org/#requestinit
  854. */
  855. webidl.converters.RequestInit = webidl.dictionaryConverter([
  856. {
  857. key: 'method',
  858. converter: webidl.converters.ByteString
  859. },
  860. {
  861. key: 'headers',
  862. converter: webidl.converters.HeadersInit
  863. },
  864. {
  865. key: 'body',
  866. converter: webidl.nullableConverter(
  867. webidl.converters.BodyInit
  868. )
  869. },
  870. {
  871. key: 'referrer',
  872. converter: webidl.converters.USVString
  873. },
  874. {
  875. key: 'referrerPolicy',
  876. converter: webidl.converters.DOMString,
  877. // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy
  878. allowedValues: referrerPolicy
  879. },
  880. {
  881. key: 'mode',
  882. converter: webidl.converters.DOMString,
  883. // https://fetch.spec.whatwg.org/#concept-request-mode
  884. allowedValues: requestMode
  885. },
  886. {
  887. key: 'credentials',
  888. converter: webidl.converters.DOMString,
  889. // https://fetch.spec.whatwg.org/#requestcredentials
  890. allowedValues: requestCredentials
  891. },
  892. {
  893. key: 'cache',
  894. converter: webidl.converters.DOMString,
  895. // https://fetch.spec.whatwg.org/#requestcache
  896. allowedValues: requestCache
  897. },
  898. {
  899. key: 'redirect',
  900. converter: webidl.converters.DOMString,
  901. // https://fetch.spec.whatwg.org/#requestredirect
  902. allowedValues: requestRedirect
  903. },
  904. {
  905. key: 'integrity',
  906. converter: webidl.converters.DOMString
  907. },
  908. {
  909. key: 'keepalive',
  910. converter: webidl.converters.boolean
  911. },
  912. {
  913. key: 'signal',
  914. converter: webidl.nullableConverter(
  915. (signal) => webidl.converters.AbortSignal(
  916. signal,
  917. 'RequestInit',
  918. 'signal'
  919. )
  920. )
  921. },
  922. {
  923. key: 'window',
  924. converter: webidl.converters.any
  925. },
  926. {
  927. key: 'duplex',
  928. converter: webidl.converters.DOMString,
  929. allowedValues: requestDuplex
  930. },
  931. {
  932. key: 'dispatcher', // undici specific option
  933. converter: webidl.converters.any
  934. },
  935. {
  936. key: 'priority',
  937. converter: webidl.converters.DOMString,
  938. allowedValues: ['high', 'low', 'auto'],
  939. defaultValue: () => 'auto'
  940. }
  941. ])
  942. module.exports = {
  943. Request,
  944. makeRequest,
  945. fromInnerRequest,
  946. cloneRequest,
  947. getRequestDispatcher,
  948. getRequestState
  949. }