request.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. /* globals AbortController */
  2. 'use strict'
  3. const { extractBody, mixinBody, cloneBody } = require('./body')
  4. const { Headers, fill: fillHeaders, HeadersList } = require('./headers')
  5. const { FinalizationRegistry } = require('../compat/dispatcher-weakref')()
  6. const util = require('../core/util')
  7. const {
  8. isValidHTTPToken,
  9. sameOrigin,
  10. normalizeMethod,
  11. makePolicyContainer
  12. } = require('./util')
  13. const {
  14. forbiddenMethods,
  15. corsSafeListedMethods,
  16. referrerPolicy,
  17. requestRedirect,
  18. requestMode,
  19. requestCredentials,
  20. requestCache,
  21. requestDuplex
  22. } = require('./constants')
  23. const { kEnumerableProperty } = util
  24. const { kHeaders, kSignal, kState, kGuard, kRealm } = require('./symbols')
  25. const { webidl } = require('./webidl')
  26. const { getGlobalOrigin } = require('./global')
  27. const { URLSerializer } = require('./dataURL')
  28. const { kHeadersList } = require('../core/symbols')
  29. const assert = require('assert')
  30. const { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = require('events')
  31. let TransformStream = globalThis.TransformStream
  32. const kInit = Symbol('init')
  33. const kAbortController = Symbol('abortController')
  34. const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
  35. signal.removeEventListener('abort', abort)
  36. })
  37. // https://fetch.spec.whatwg.org/#request-class
  38. class Request {
  39. // https://fetch.spec.whatwg.org/#dom-request
  40. constructor (input, init = {}) {
  41. if (input === kInit) {
  42. return
  43. }
  44. webidl.argumentLengthCheck(arguments, 1, { header: 'Request constructor' })
  45. input = webidl.converters.RequestInfo(input)
  46. init = webidl.converters.RequestInit(init)
  47. // https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
  48. this[kRealm] = {
  49. settingsObject: {
  50. baseUrl: getGlobalOrigin(),
  51. get origin () {
  52. return this.baseUrl?.origin
  53. },
  54. policyContainer: makePolicyContainer()
  55. }
  56. }
  57. // 1. Let request be null.
  58. let request = null
  59. // 2. Let fallbackMode be null.
  60. let fallbackMode = null
  61. // 3. Let baseURL be this’s relevant settings object’s API base URL.
  62. const baseUrl = this[kRealm].settingsObject.baseUrl
  63. // 4. Let signal be null.
  64. let signal = null
  65. // 5. If input is a string, then:
  66. if (typeof input === 'string') {
  67. // 1. Let parsedURL be the result of parsing input with baseURL.
  68. // 2. If parsedURL is failure, then throw a TypeError.
  69. let parsedURL
  70. try {
  71. parsedURL = new URL(input, baseUrl)
  72. } catch (err) {
  73. throw new TypeError('Failed to parse URL from ' + input, { cause: err })
  74. }
  75. // 3. If parsedURL includes credentials, then throw a TypeError.
  76. if (parsedURL.username || parsedURL.password) {
  77. throw new TypeError(
  78. 'Request cannot be constructed from a URL that includes credentials: ' +
  79. input
  80. )
  81. }
  82. // 4. Set request to a new request whose URL is parsedURL.
  83. request = makeRequest({ urlList: [parsedURL] })
  84. // 5. Set fallbackMode to "cors".
  85. fallbackMode = 'cors'
  86. } else {
  87. // 6. Otherwise:
  88. // 7. Assert: input is a Request object.
  89. assert(input instanceof Request)
  90. // 8. Set request to input’s request.
  91. request = input[kState]
  92. // 9. Set signal to input’s signal.
  93. signal = input[kSignal]
  94. }
  95. // 7. Let origin be this’s relevant settings object’s origin.
  96. const origin = this[kRealm].settingsObject.origin
  97. // 8. Let window be "client".
  98. let window = 'client'
  99. // 9. If request’s window is an environment settings object and its origin
  100. // is same origin with origin, then set window to request’s window.
  101. if (
  102. request.window?.constructor?.name === 'EnvironmentSettingsObject' &&
  103. sameOrigin(request.window, origin)
  104. ) {
  105. window = request.window
  106. }
  107. // 10. If init["window"] exists and is non-null, then throw a TypeError.
  108. if (init.window != null) {
  109. throw new TypeError(`'window' option '${window}' must be null`)
  110. }
  111. // 11. If init["window"] exists, then set window to "no-window".
  112. if ('window' in init) {
  113. window = 'no-window'
  114. }
  115. // 12. Set request to a new request with the following properties:
  116. request = makeRequest({
  117. // URL request’s URL.
  118. // undici implementation note: this is set as the first item in request's urlList in makeRequest
  119. // method request’s method.
  120. method: request.method,
  121. // header list A copy of request’s header list.
  122. // undici implementation note: headersList is cloned in makeRequest
  123. headersList: request.headersList,
  124. // unsafe-request flag Set.
  125. unsafeRequest: request.unsafeRequest,
  126. // client This’s relevant settings object.
  127. client: this[kRealm].settingsObject,
  128. // window window.
  129. window,
  130. // priority request’s priority.
  131. priority: request.priority,
  132. // origin request’s origin. The propagation of the origin is only significant for navigation requests
  133. // being handled by a service worker. In this scenario a request can have an origin that is different
  134. // from the current client.
  135. origin: request.origin,
  136. // referrer request’s referrer.
  137. referrer: request.referrer,
  138. // referrer policy request’s referrer policy.
  139. referrerPolicy: request.referrerPolicy,
  140. // mode request’s mode.
  141. mode: request.mode,
  142. // credentials mode request’s credentials mode.
  143. credentials: request.credentials,
  144. // cache mode request’s cache mode.
  145. cache: request.cache,
  146. // redirect mode request’s redirect mode.
  147. redirect: request.redirect,
  148. // integrity metadata request’s integrity metadata.
  149. integrity: request.integrity,
  150. // keepalive request’s keepalive.
  151. keepalive: request.keepalive,
  152. // reload-navigation flag request’s reload-navigation flag.
  153. reloadNavigation: request.reloadNavigation,
  154. // history-navigation flag request’s history-navigation flag.
  155. historyNavigation: request.historyNavigation,
  156. // URL list A clone of request’s URL list.
  157. urlList: [...request.urlList]
  158. })
  159. // 13. If init is not empty, then:
  160. if (Object.keys(init).length > 0) {
  161. // 1. If request’s mode is "navigate", then set it to "same-origin".
  162. if (request.mode === 'navigate') {
  163. request.mode = 'same-origin'
  164. }
  165. // 2. Unset request’s reload-navigation flag.
  166. request.reloadNavigation = false
  167. // 3. Unset request’s history-navigation flag.
  168. request.historyNavigation = false
  169. // 4. Set request’s origin to "client".
  170. request.origin = 'client'
  171. // 5. Set request’s referrer to "client"
  172. request.referrer = 'client'
  173. // 6. Set request’s referrer policy to the empty string.
  174. request.referrerPolicy = ''
  175. // 7. Set request’s URL to request’s current URL.
  176. request.url = request.urlList[request.urlList.length - 1]
  177. // 8. Set request’s URL list to « request’s URL ».
  178. request.urlList = [request.url]
  179. }
  180. // 14. If init["referrer"] exists, then:
  181. if (init.referrer !== undefined) {
  182. // 1. Let referrer be init["referrer"].
  183. const referrer = init.referrer
  184. // 2. If referrer is the empty string, then set request’s referrer to "no-referrer".
  185. if (referrer === '') {
  186. request.referrer = 'no-referrer'
  187. } else {
  188. // 1. Let parsedReferrer be the result of parsing referrer with
  189. // baseURL.
  190. // 2. If parsedReferrer is failure, then throw a TypeError.
  191. let parsedReferrer
  192. try {
  193. parsedReferrer = new URL(referrer, baseUrl)
  194. } catch (err) {
  195. throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err })
  196. }
  197. // 3. If one of the following is true
  198. // - parsedReferrer’s scheme is "about" and path is the string "client"
  199. // - parsedReferrer’s origin is not same origin with origin
  200. // then set request’s referrer to "client".
  201. if (
  202. (parsedReferrer.protocol === 'about:' && parsedReferrer.hostname === 'client') ||
  203. (origin && !sameOrigin(parsedReferrer, this[kRealm].settingsObject.baseUrl))
  204. ) {
  205. request.referrer = 'client'
  206. } else {
  207. // 4. Otherwise, set request’s referrer to parsedReferrer.
  208. request.referrer = parsedReferrer
  209. }
  210. }
  211. }
  212. // 15. If init["referrerPolicy"] exists, then set request’s referrer policy
  213. // to it.
  214. if (init.referrerPolicy !== undefined) {
  215. request.referrerPolicy = init.referrerPolicy
  216. }
  217. // 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise.
  218. let mode
  219. if (init.mode !== undefined) {
  220. mode = init.mode
  221. } else {
  222. mode = fallbackMode
  223. }
  224. // 17. If mode is "navigate", then throw a TypeError.
  225. if (mode === 'navigate') {
  226. throw webidl.errors.exception({
  227. header: 'Request constructor',
  228. message: 'invalid request mode navigate.'
  229. })
  230. }
  231. // 18. If mode is non-null, set request’s mode to mode.
  232. if (mode != null) {
  233. request.mode = mode
  234. }
  235. // 19. If init["credentials"] exists, then set request’s credentials mode
  236. // to it.
  237. if (init.credentials !== undefined) {
  238. request.credentials = init.credentials
  239. }
  240. // 18. If init["cache"] exists, then set request’s cache mode to it.
  241. if (init.cache !== undefined) {
  242. request.cache = init.cache
  243. }
  244. // 21. If request’s cache mode is "only-if-cached" and request’s mode is
  245. // not "same-origin", then throw a TypeError.
  246. if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
  247. throw new TypeError(
  248. "'only-if-cached' can be set only with 'same-origin' mode"
  249. )
  250. }
  251. // 22. If init["redirect"] exists, then set request’s redirect mode to it.
  252. if (init.redirect !== undefined) {
  253. request.redirect = init.redirect
  254. }
  255. // 23. If init["integrity"] exists, then set request’s integrity metadata to it.
  256. if (init.integrity !== undefined && init.integrity != null) {
  257. request.integrity = String(init.integrity)
  258. }
  259. // 24. If init["keepalive"] exists, then set request’s keepalive to it.
  260. if (init.keepalive !== undefined) {
  261. request.keepalive = Boolean(init.keepalive)
  262. }
  263. // 25. If init["method"] exists, then:
  264. if (init.method !== undefined) {
  265. // 1. Let method be init["method"].
  266. let method = init.method
  267. // 2. If method is not a method or method is a forbidden method, then
  268. // throw a TypeError.
  269. if (!isValidHTTPToken(init.method)) {
  270. throw TypeError(`'${init.method}' is not a valid HTTP method.`)
  271. }
  272. if (forbiddenMethods.indexOf(method.toUpperCase()) !== -1) {
  273. throw TypeError(`'${init.method}' HTTP method is unsupported.`)
  274. }
  275. // 3. Normalize method.
  276. method = normalizeMethod(init.method)
  277. // 4. Set request’s method to method.
  278. request.method = method
  279. }
  280. // 26. If init["signal"] exists, then set signal to it.
  281. if (init.signal !== undefined) {
  282. signal = init.signal
  283. }
  284. // 27. Set this’s request to request.
  285. this[kState] = request
  286. // 28. Set this’s signal to a new AbortSignal object with this’s relevant
  287. // Realm.
  288. // TODO: could this be simplified with AbortSignal.any
  289. // (https://dom.spec.whatwg.org/#dom-abortsignal-any)
  290. const ac = new AbortController()
  291. this[kSignal] = ac.signal
  292. this[kSignal][kRealm] = this[kRealm]
  293. // 29. If signal is not null, then make this’s signal follow signal.
  294. if (signal != null) {
  295. if (
  296. !signal ||
  297. typeof signal.aborted !== 'boolean' ||
  298. typeof signal.addEventListener !== 'function'
  299. ) {
  300. throw new TypeError(
  301. "Failed to construct 'Request': member signal is not of type AbortSignal."
  302. )
  303. }
  304. if (signal.aborted) {
  305. ac.abort(signal.reason)
  306. } else {
  307. // Keep a strong ref to ac while request object
  308. // is alive. This is needed to prevent AbortController
  309. // from being prematurely garbage collected.
  310. // See, https://github.com/nodejs/undici/issues/1926.
  311. this[kAbortController] = ac
  312. const acRef = new WeakRef(ac)
  313. const abort = function () {
  314. const ac = acRef.deref()
  315. if (ac !== undefined) {
  316. ac.abort(this.reason)
  317. }
  318. }
  319. // Third-party AbortControllers may not work with these.
  320. // See, https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619.
  321. try {
  322. // If the max amount of listeners is equal to the default, increase it
  323. // This is only available in node >= v19.9.0
  324. if (typeof getMaxListeners === 'function' && getMaxListeners(signal) === defaultMaxListeners) {
  325. setMaxListeners(100, signal)
  326. } else if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) {
  327. setMaxListeners(100, signal)
  328. }
  329. } catch {}
  330. util.addAbortListener(signal, abort)
  331. requestFinalizer.register(ac, { signal, abort })
  332. }
  333. }
  334. // 30. Set this’s headers to a new Headers object with this’s relevant
  335. // Realm, whose header list is request’s header list and guard is
  336. // "request".
  337. this[kHeaders] = new Headers()
  338. this[kHeaders][kHeadersList] = request.headersList
  339. this[kHeaders][kGuard] = 'request'
  340. this[kHeaders][kRealm] = this[kRealm]
  341. // 31. If this’s request’s mode is "no-cors", then:
  342. if (mode === 'no-cors') {
  343. // 1. If this’s request’s method is not a CORS-safelisted method,
  344. // then throw a TypeError.
  345. if (!corsSafeListedMethods.includes(request.method)) {
  346. throw new TypeError(
  347. `'${request.method} is unsupported in no-cors mode.`
  348. )
  349. }
  350. // 2. Set this’s headers’s guard to "request-no-cors".
  351. this[kHeaders][kGuard] = 'request-no-cors'
  352. }
  353. // 32. If init is not empty, then:
  354. if (Object.keys(init).length !== 0) {
  355. // 1. Let headers be a copy of this’s headers and its associated header
  356. // list.
  357. let headers = new Headers(this[kHeaders])
  358. // 2. If init["headers"] exists, then set headers to init["headers"].
  359. if (init.headers !== undefined) {
  360. headers = init.headers
  361. }
  362. // 3. Empty this’s headers’s header list.
  363. this[kHeaders][kHeadersList].clear()
  364. // 4. If headers is a Headers object, then for each header in its header
  365. // list, append header’s name/header’s value to this’s headers.
  366. if (headers.constructor.name === 'Headers') {
  367. for (const [key, val] of headers) {
  368. this[kHeaders].append(key, val)
  369. }
  370. } else {
  371. // 5. Otherwise, fill this’s headers with headers.
  372. fillHeaders(this[kHeaders], headers)
  373. }
  374. }
  375. // 33. Let inputBody be input’s request’s body if input is a Request
  376. // object; otherwise null.
  377. const inputBody = input instanceof Request ? input[kState].body : null
  378. // 34. If either init["body"] exists and is non-null or inputBody is
  379. // non-null, and request’s method is `GET` or `HEAD`, then throw a
  380. // TypeError.
  381. if (
  382. (init.body != null || inputBody != null) &&
  383. (request.method === 'GET' || request.method === 'HEAD')
  384. ) {
  385. throw new TypeError('Request with GET/HEAD method cannot have body.')
  386. }
  387. // 35. Let initBody be null.
  388. let initBody = null
  389. // 36. If init["body"] exists and is non-null, then:
  390. if (init.body != null) {
  391. // 1. Let Content-Type be null.
  392. // 2. Set initBody and Content-Type to the result of extracting
  393. // init["body"], with keepalive set to request’s keepalive.
  394. const [extractedBody, contentType] = extractBody(
  395. init.body,
  396. request.keepalive
  397. )
  398. initBody = extractedBody
  399. // 3, If Content-Type is non-null and this’s headers’s header list does
  400. // not contain `Content-Type`, then append `Content-Type`/Content-Type to
  401. // this’s headers.
  402. if (contentType && !this[kHeaders][kHeadersList].contains('content-type')) {
  403. this[kHeaders].append('content-type', contentType)
  404. }
  405. }
  406. // 37. Let inputOrInitBody be initBody if it is non-null; otherwise
  407. // inputBody.
  408. const inputOrInitBody = initBody ?? inputBody
  409. // 38. If inputOrInitBody is non-null and inputOrInitBody’s source is
  410. // null, then:
  411. if (inputOrInitBody != null && inputOrInitBody.source == null) {
  412. // 1. If initBody is non-null and init["duplex"] does not exist,
  413. // then throw a TypeError.
  414. if (initBody != null && init.duplex == null) {
  415. throw new TypeError('RequestInit: duplex option is required when sending a body.')
  416. }
  417. // 2. If this’s request’s mode is neither "same-origin" nor "cors",
  418. // then throw a TypeError.
  419. if (request.mode !== 'same-origin' && request.mode !== 'cors') {
  420. throw new TypeError(
  421. 'If request is made from ReadableStream, mode should be "same-origin" or "cors"'
  422. )
  423. }
  424. // 3. Set this’s request’s use-CORS-preflight flag.
  425. request.useCORSPreflightFlag = true
  426. }
  427. // 39. Let finalBody be inputOrInitBody.
  428. let finalBody = inputOrInitBody
  429. // 40. If initBody is null and inputBody is non-null, then:
  430. if (initBody == null && inputBody != null) {
  431. // 1. If input is unusable, then throw a TypeError.
  432. if (util.isDisturbed(inputBody.stream) || inputBody.stream.locked) {
  433. throw new TypeError(
  434. 'Cannot construct a Request with a Request object that has already been used.'
  435. )
  436. }
  437. // 2. Set finalBody to the result of creating a proxy for inputBody.
  438. if (!TransformStream) {
  439. TransformStream = require('stream/web').TransformStream
  440. }
  441. // https://streams.spec.whatwg.org/#readablestream-create-a-proxy
  442. const identityTransform = new TransformStream()
  443. inputBody.stream.pipeThrough(identityTransform)
  444. finalBody = {
  445. source: inputBody.source,
  446. length: inputBody.length,
  447. stream: identityTransform.readable
  448. }
  449. }
  450. // 41. Set this’s request’s body to finalBody.
  451. this[kState].body = finalBody
  452. }
  453. // Returns request’s HTTP method, which is "GET" by default.
  454. get method () {
  455. webidl.brandCheck(this, Request)
  456. // The method getter steps are to return this’s request’s method.
  457. return this[kState].method
  458. }
  459. // Returns the URL of request as a string.
  460. get url () {
  461. webidl.brandCheck(this, Request)
  462. // The url getter steps are to return this’s request’s URL, serialized.
  463. return URLSerializer(this[kState].url)
  464. }
  465. // Returns a Headers object consisting of the headers associated with request.
  466. // Note that headers added in the network layer by the user agent will not
  467. // be accounted for in this object, e.g., the "Host" header.
  468. get headers () {
  469. webidl.brandCheck(this, Request)
  470. // The headers getter steps are to return this’s headers.
  471. return this[kHeaders]
  472. }
  473. // Returns the kind of resource requested by request, e.g., "document"
  474. // or "script".
  475. get destination () {
  476. webidl.brandCheck(this, Request)
  477. // The destination getter are to return this’s request’s destination.
  478. return this[kState].destination
  479. }
  480. // Returns the referrer of request. Its value can be a same-origin URL if
  481. // explicitly set in init, the empty string to indicate no referrer, and
  482. // "about:client" when defaulting to the global’s default. This is used
  483. // during fetching to determine the value of the `Referer` header of the
  484. // request being made.
  485. get referrer () {
  486. webidl.brandCheck(this, Request)
  487. // 1. If this’s request’s referrer is "no-referrer", then return the
  488. // empty string.
  489. if (this[kState].referrer === 'no-referrer') {
  490. return ''
  491. }
  492. // 2. If this’s request’s referrer is "client", then return
  493. // "about:client".
  494. if (this[kState].referrer === 'client') {
  495. return 'about:client'
  496. }
  497. // Return this’s request’s referrer, serialized.
  498. return this[kState].referrer.toString()
  499. }
  500. // Returns the referrer policy associated with request.
  501. // This is used during fetching to compute the value of the request’s
  502. // referrer.
  503. get referrerPolicy () {
  504. webidl.brandCheck(this, Request)
  505. // The referrerPolicy getter steps are to return this’s request’s referrer policy.
  506. return this[kState].referrerPolicy
  507. }
  508. // Returns the mode associated with request, which is a string indicating
  509. // whether the request will use CORS, or will be restricted to same-origin
  510. // URLs.
  511. get mode () {
  512. webidl.brandCheck(this, Request)
  513. // The mode getter steps are to return this’s request’s mode.
  514. return this[kState].mode
  515. }
  516. // Returns the credentials mode associated with request,
  517. // which is a string indicating whether credentials will be sent with the
  518. // request always, never, or only when sent to a same-origin URL.
  519. get credentials () {
  520. // The credentials getter steps are to return this’s request’s credentials mode.
  521. return this[kState].credentials
  522. }
  523. // Returns the cache mode associated with request,
  524. // which is a string indicating how the request will
  525. // interact with the browser’s cache when fetching.
  526. get cache () {
  527. webidl.brandCheck(this, Request)
  528. // The cache getter steps are to return this’s request’s cache mode.
  529. return this[kState].cache
  530. }
  531. // Returns the redirect mode associated with request,
  532. // which is a string indicating how redirects for the
  533. // request will be handled during fetching. A request
  534. // will follow redirects by default.
  535. get redirect () {
  536. webidl.brandCheck(this, Request)
  537. // The redirect getter steps are to return this’s request’s redirect mode.
  538. return this[kState].redirect
  539. }
  540. // Returns request’s subresource integrity metadata, which is a
  541. // cryptographic hash of the resource being fetched. Its value
  542. // consists of multiple hashes separated by whitespace. [SRI]
  543. get integrity () {
  544. webidl.brandCheck(this, Request)
  545. // The integrity getter steps are to return this’s request’s integrity
  546. // metadata.
  547. return this[kState].integrity
  548. }
  549. // Returns a boolean indicating whether or not request can outlive the
  550. // global in which it was created.
  551. get keepalive () {
  552. webidl.brandCheck(this, Request)
  553. // The keepalive getter steps are to return this’s request’s keepalive.
  554. return this[kState].keepalive
  555. }
  556. // Returns a boolean indicating whether or not request is for a reload
  557. // navigation.
  558. get isReloadNavigation () {
  559. webidl.brandCheck(this, Request)
  560. // The isReloadNavigation getter steps are to return true if this’s
  561. // request’s reload-navigation flag is set; otherwise false.
  562. return this[kState].reloadNavigation
  563. }
  564. // Returns a boolean indicating whether or not request is for a history
  565. // navigation (a.k.a. back-foward navigation).
  566. get isHistoryNavigation () {
  567. webidl.brandCheck(this, Request)
  568. // The isHistoryNavigation getter steps are to return true if this’s request’s
  569. // history-navigation flag is set; otherwise false.
  570. return this[kState].historyNavigation
  571. }
  572. // Returns the signal associated with request, which is an AbortSignal
  573. // object indicating whether or not request has been aborted, and its
  574. // abort event handler.
  575. get signal () {
  576. webidl.brandCheck(this, Request)
  577. // The signal getter steps are to return this’s signal.
  578. return this[kSignal]
  579. }
  580. get body () {
  581. webidl.brandCheck(this, Request)
  582. return this[kState].body ? this[kState].body.stream : null
  583. }
  584. get bodyUsed () {
  585. webidl.brandCheck(this, Request)
  586. return !!this[kState].body && util.isDisturbed(this[kState].body.stream)
  587. }
  588. get duplex () {
  589. webidl.brandCheck(this, Request)
  590. return 'half'
  591. }
  592. // Returns a clone of request.
  593. clone () {
  594. webidl.brandCheck(this, Request)
  595. // 1. If this is unusable, then throw a TypeError.
  596. if (this.bodyUsed || this.body?.locked) {
  597. throw new TypeError('unusable')
  598. }
  599. // 2. Let clonedRequest be the result of cloning this’s request.
  600. const clonedRequest = cloneRequest(this[kState])
  601. // 3. Let clonedRequestObject be the result of creating a Request object,
  602. // given clonedRequest, this’s headers’s guard, and this’s relevant Realm.
  603. const clonedRequestObject = new Request(kInit)
  604. clonedRequestObject[kState] = clonedRequest
  605. clonedRequestObject[kRealm] = this[kRealm]
  606. clonedRequestObject[kHeaders] = new Headers()
  607. clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList
  608. clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard]
  609. clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm]
  610. // 4. Make clonedRequestObject’s signal follow this’s signal.
  611. const ac = new AbortController()
  612. if (this.signal.aborted) {
  613. ac.abort(this.signal.reason)
  614. } else {
  615. util.addAbortListener(
  616. this.signal,
  617. () => {
  618. ac.abort(this.signal.reason)
  619. }
  620. )
  621. }
  622. clonedRequestObject[kSignal] = ac.signal
  623. // 4. Return clonedRequestObject.
  624. return clonedRequestObject
  625. }
  626. }
  627. mixinBody(Request)
  628. function makeRequest (init) {
  629. // https://fetch.spec.whatwg.org/#requests
  630. const request = {
  631. method: 'GET',
  632. localURLsOnly: false,
  633. unsafeRequest: false,
  634. body: null,
  635. client: null,
  636. reservedClient: null,
  637. replacesClientId: '',
  638. window: 'client',
  639. keepalive: false,
  640. serviceWorkers: 'all',
  641. initiator: '',
  642. destination: '',
  643. priority: null,
  644. origin: 'client',
  645. policyContainer: 'client',
  646. referrer: 'client',
  647. referrerPolicy: '',
  648. mode: 'no-cors',
  649. useCORSPreflightFlag: false,
  650. credentials: 'same-origin',
  651. useCredentials: false,
  652. cache: 'default',
  653. redirect: 'follow',
  654. integrity: '',
  655. cryptoGraphicsNonceMetadata: '',
  656. parserMetadata: '',
  657. reloadNavigation: false,
  658. historyNavigation: false,
  659. userActivation: false,
  660. taintedOrigin: false,
  661. redirectCount: 0,
  662. responseTainting: 'basic',
  663. preventNoCacheCacheControlHeaderModification: false,
  664. done: false,
  665. timingAllowFailed: false,
  666. ...init,
  667. headersList: init.headersList
  668. ? new HeadersList(init.headersList)
  669. : new HeadersList()
  670. }
  671. request.url = request.urlList[0]
  672. return request
  673. }
  674. // https://fetch.spec.whatwg.org/#concept-request-clone
  675. function cloneRequest (request) {
  676. // To clone a request request, run these steps:
  677. // 1. Let newRequest be a copy of request, except for its body.
  678. const newRequest = makeRequest({ ...request, body: null })
  679. // 2. If request’s body is non-null, set newRequest’s body to the
  680. // result of cloning request’s body.
  681. if (request.body != null) {
  682. newRequest.body = cloneBody(request.body)
  683. }
  684. // 3. Return newRequest.
  685. return newRequest
  686. }
  687. Object.defineProperties(Request.prototype, {
  688. method: kEnumerableProperty,
  689. url: kEnumerableProperty,
  690. headers: kEnumerableProperty,
  691. redirect: kEnumerableProperty,
  692. clone: kEnumerableProperty,
  693. signal: kEnumerableProperty,
  694. duplex: kEnumerableProperty,
  695. destination: kEnumerableProperty,
  696. body: kEnumerableProperty,
  697. bodyUsed: kEnumerableProperty,
  698. isHistoryNavigation: kEnumerableProperty,
  699. isReloadNavigation: kEnumerableProperty,
  700. keepalive: kEnumerableProperty,
  701. integrity: kEnumerableProperty,
  702. cache: kEnumerableProperty,
  703. credentials: kEnumerableProperty,
  704. attribute: kEnumerableProperty,
  705. referrerPolicy: kEnumerableProperty,
  706. referrer: kEnumerableProperty,
  707. mode: kEnumerableProperty,
  708. [Symbol.toStringTag]: {
  709. value: 'Request',
  710. configurable: true
  711. }
  712. })
  713. webidl.converters.Request = webidl.interfaceConverter(
  714. Request
  715. )
  716. // https://fetch.spec.whatwg.org/#requestinfo
  717. webidl.converters.RequestInfo = function (V) {
  718. if (typeof V === 'string') {
  719. return webidl.converters.USVString(V)
  720. }
  721. if (V instanceof Request) {
  722. return webidl.converters.Request(V)
  723. }
  724. return webidl.converters.USVString(V)
  725. }
  726. webidl.converters.AbortSignal = webidl.interfaceConverter(
  727. AbortSignal
  728. )
  729. // https://fetch.spec.whatwg.org/#requestinit
  730. webidl.converters.RequestInit = webidl.dictionaryConverter([
  731. {
  732. key: 'method',
  733. converter: webidl.converters.ByteString
  734. },
  735. {
  736. key: 'headers',
  737. converter: webidl.converters.HeadersInit
  738. },
  739. {
  740. key: 'body',
  741. converter: webidl.nullableConverter(
  742. webidl.converters.BodyInit
  743. )
  744. },
  745. {
  746. key: 'referrer',
  747. converter: webidl.converters.USVString
  748. },
  749. {
  750. key: 'referrerPolicy',
  751. converter: webidl.converters.DOMString,
  752. // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy
  753. allowedValues: referrerPolicy
  754. },
  755. {
  756. key: 'mode',
  757. converter: webidl.converters.DOMString,
  758. // https://fetch.spec.whatwg.org/#concept-request-mode
  759. allowedValues: requestMode
  760. },
  761. {
  762. key: 'credentials',
  763. converter: webidl.converters.DOMString,
  764. // https://fetch.spec.whatwg.org/#requestcredentials
  765. allowedValues: requestCredentials
  766. },
  767. {
  768. key: 'cache',
  769. converter: webidl.converters.DOMString,
  770. // https://fetch.spec.whatwg.org/#requestcache
  771. allowedValues: requestCache
  772. },
  773. {
  774. key: 'redirect',
  775. converter: webidl.converters.DOMString,
  776. // https://fetch.spec.whatwg.org/#requestredirect
  777. allowedValues: requestRedirect
  778. },
  779. {
  780. key: 'integrity',
  781. converter: webidl.converters.DOMString
  782. },
  783. {
  784. key: 'keepalive',
  785. converter: webidl.converters.boolean
  786. },
  787. {
  788. key: 'signal',
  789. converter: webidl.nullableConverter(
  790. (signal) => webidl.converters.AbortSignal(
  791. signal,
  792. { strict: false }
  793. )
  794. )
  795. },
  796. {
  797. key: 'window',
  798. converter: webidl.converters.any
  799. },
  800. {
  801. key: 'duplex',
  802. converter: webidl.converters.DOMString,
  803. allowedValues: requestDuplex
  804. }
  805. ])
  806. module.exports = { Request, makeRequest }