util.js 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. 'use strict'
  2. const { redirectStatus, badPorts, referrerPolicy: referrerPolicyTokens } = require('./constants')
  3. const { getGlobalOrigin } = require('./global')
  4. const { performance } = require('perf_hooks')
  5. const { isBlobLike, toUSVString, ReadableStreamFrom } = require('../core/util')
  6. const assert = require('assert')
  7. const { isUint8Array } = require('util/types')
  8. // https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
  9. /** @type {import('crypto')|undefined} */
  10. let crypto
  11. try {
  12. crypto = require('crypto')
  13. } catch {
  14. }
  15. function responseURL (response) {
  16. // https://fetch.spec.whatwg.org/#responses
  17. // A response has an associated URL. It is a pointer to the last URL
  18. // in response’s URL list and null if response’s URL list is empty.
  19. const urlList = response.urlList
  20. const length = urlList.length
  21. return length === 0 ? null : urlList[length - 1].toString()
  22. }
  23. // https://fetch.spec.whatwg.org/#concept-response-location-url
  24. function responseLocationURL (response, requestFragment) {
  25. // 1. If response’s status is not a redirect status, then return null.
  26. if (!redirectStatus.includes(response.status)) {
  27. return null
  28. }
  29. // 2. Let location be the result of extracting header list values given
  30. // `Location` and response’s header list.
  31. let location = response.headersList.get('location')
  32. // 3. If location is a header value, then set location to the result of
  33. // parsing location with response’s URL.
  34. if (location !== null && isValidHeaderValue(location)) {
  35. location = new URL(location, responseURL(response))
  36. }
  37. // 4. If location is a URL whose fragment is null, then set location’s
  38. // fragment to requestFragment.
  39. if (location && !location.hash) {
  40. location.hash = requestFragment
  41. }
  42. // 5. Return location.
  43. return location
  44. }
  45. /** @returns {URL} */
  46. function requestCurrentURL (request) {
  47. return request.urlList[request.urlList.length - 1]
  48. }
  49. function requestBadPort (request) {
  50. // 1. Let url be request’s current URL.
  51. const url = requestCurrentURL(request)
  52. // 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port,
  53. // then return blocked.
  54. if (urlIsHttpHttpsScheme(url) && badPorts.includes(url.port)) {
  55. return 'blocked'
  56. }
  57. // 3. Return allowed.
  58. return 'allowed'
  59. }
  60. function isErrorLike (object) {
  61. return object instanceof Error || (
  62. object?.constructor?.name === 'Error' ||
  63. object?.constructor?.name === 'DOMException'
  64. )
  65. }
  66. // Check whether |statusText| is a ByteString and
  67. // matches the Reason-Phrase token production.
  68. // RFC 2616: https://tools.ietf.org/html/rfc2616
  69. // RFC 7230: https://tools.ietf.org/html/rfc7230
  70. // "reason-phrase = *( HTAB / SP / VCHAR / obs-text )"
  71. // https://github.com/chromium/chromium/blob/94.0.4604.1/third_party/blink/renderer/core/fetch/response.cc#L116
  72. function isValidReasonPhrase (statusText) {
  73. for (let i = 0; i < statusText.length; ++i) {
  74. const c = statusText.charCodeAt(i)
  75. if (
  76. !(
  77. (
  78. c === 0x09 || // HTAB
  79. (c >= 0x20 && c <= 0x7e) || // SP / VCHAR
  80. (c >= 0x80 && c <= 0xff)
  81. ) // obs-text
  82. )
  83. ) {
  84. return false
  85. }
  86. }
  87. return true
  88. }
  89. function isTokenChar (c) {
  90. return !(
  91. c >= 0x7f ||
  92. c <= 0x20 ||
  93. c === '(' ||
  94. c === ')' ||
  95. c === '<' ||
  96. c === '>' ||
  97. c === '@' ||
  98. c === ',' ||
  99. c === ';' ||
  100. c === ':' ||
  101. c === '\\' ||
  102. c === '"' ||
  103. c === '/' ||
  104. c === '[' ||
  105. c === ']' ||
  106. c === '?' ||
  107. c === '=' ||
  108. c === '{' ||
  109. c === '}'
  110. )
  111. }
  112. // See RFC 7230, Section 3.2.6.
  113. // https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/third_party/blink/renderer/platform/network/http_parsers.cc#L321
  114. function isValidHTTPToken (characters) {
  115. if (!characters || typeof characters !== 'string') {
  116. return false
  117. }
  118. for (let i = 0; i < characters.length; ++i) {
  119. const c = characters.charCodeAt(i)
  120. if (c > 0x7f || !isTokenChar(c)) {
  121. return false
  122. }
  123. }
  124. return true
  125. }
  126. // https://fetch.spec.whatwg.org/#header-name
  127. // https://github.com/chromium/chromium/blob/b3d37e6f94f87d59e44662d6078f6a12de845d17/net/http/http_util.cc#L342
  128. function isValidHeaderName (potentialValue) {
  129. if (potentialValue.length === 0) {
  130. return false
  131. }
  132. return isValidHTTPToken(potentialValue)
  133. }
  134. /**
  135. * @see https://fetch.spec.whatwg.org/#header-value
  136. * @param {string} potentialValue
  137. */
  138. function isValidHeaderValue (potentialValue) {
  139. // - Has no leading or trailing HTTP tab or space bytes.
  140. // - Contains no 0x00 (NUL) or HTTP newline bytes.
  141. if (
  142. potentialValue.startsWith('\t') ||
  143. potentialValue.startsWith(' ') ||
  144. potentialValue.endsWith('\t') ||
  145. potentialValue.endsWith(' ')
  146. ) {
  147. return false
  148. }
  149. if (
  150. potentialValue.includes('\0') ||
  151. potentialValue.includes('\r') ||
  152. potentialValue.includes('\n')
  153. ) {
  154. return false
  155. }
  156. return true
  157. }
  158. // https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
  159. function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
  160. // Given a request request and a response actualResponse, this algorithm
  161. // updates request’s referrer policy according to the Referrer-Policy
  162. // header (if any) in actualResponse.
  163. // 1. Let policy be the result of executing § 8.1 Parse a referrer policy
  164. // from a Referrer-Policy header on actualResponse.
  165. // 8.1 Parse a referrer policy from a Referrer-Policy header
  166. // 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
  167. const { headersList } = actualResponse
  168. // 2. Let policy be the empty string.
  169. // 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token.
  170. // 4. Return policy.
  171. const policyHeader = (headersList.get('referrer-policy') ?? '').split(',')
  172. // Note: As the referrer-policy can contain multiple policies
  173. // separated by comma, we need to loop through all of them
  174. // and pick the first valid one.
  175. // Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#specify_a_fallback_policy
  176. let policy = ''
  177. if (policyHeader.length > 0) {
  178. // The right-most policy takes precedence.
  179. // The left-most policy is the fallback.
  180. for (let i = policyHeader.length; i !== 0; i--) {
  181. const token = policyHeader[i - 1].trim()
  182. if (referrerPolicyTokens.includes(token)) {
  183. policy = token
  184. break
  185. }
  186. }
  187. }
  188. // 2. If policy is not the empty string, then set request’s referrer policy to policy.
  189. if (policy !== '') {
  190. request.referrerPolicy = policy
  191. }
  192. }
  193. // https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check
  194. function crossOriginResourcePolicyCheck () {
  195. // TODO
  196. return 'allowed'
  197. }
  198. // https://fetch.spec.whatwg.org/#concept-cors-check
  199. function corsCheck () {
  200. // TODO
  201. return 'success'
  202. }
  203. // https://fetch.spec.whatwg.org/#concept-tao-check
  204. function TAOCheck () {
  205. // TODO
  206. return 'success'
  207. }
  208. function appendFetchMetadata (httpRequest) {
  209. // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-dest-header
  210. // TODO
  211. // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-mode-header
  212. // 1. Assert: r’s url is a potentially trustworthy URL.
  213. // TODO
  214. // 2. Let header be a Structured Header whose value is a token.
  215. let header = null
  216. // 3. Set header’s value to r’s mode.
  217. header = httpRequest.mode
  218. // 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list.
  219. httpRequest.headersList.set('sec-fetch-mode', header)
  220. // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header
  221. // TODO
  222. // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-user-header
  223. // TODO
  224. }
  225. // https://fetch.spec.whatwg.org/#append-a-request-origin-header
  226. function appendRequestOriginHeader (request) {
  227. // 1. Let serializedOrigin be the result of byte-serializing a request origin with request.
  228. let serializedOrigin = request.origin
  229. // 2. If request’s response tainting is "cors" or request’s mode is "websocket", then append (`Origin`, serializedOrigin) to request’s header list.
  230. if (request.responseTainting === 'cors' || request.mode === 'websocket') {
  231. if (serializedOrigin) {
  232. request.headersList.append('origin', serializedOrigin)
  233. }
  234. // 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
  235. } else if (request.method !== 'GET' && request.method !== 'HEAD') {
  236. // 1. Switch on request’s referrer policy:
  237. switch (request.referrerPolicy) {
  238. case 'no-referrer':
  239. // Set serializedOrigin to `null`.
  240. serializedOrigin = null
  241. break
  242. case 'no-referrer-when-downgrade':
  243. case 'strict-origin':
  244. case 'strict-origin-when-cross-origin':
  245. // If request’s origin is a tuple origin, its scheme is "https", and request’s current URL’s scheme is not "https", then set serializedOrigin to `null`.
  246. if (request.origin && urlHasHttpsScheme(request.origin) && !urlHasHttpsScheme(requestCurrentURL(request))) {
  247. serializedOrigin = null
  248. }
  249. break
  250. case 'same-origin':
  251. // If request’s origin is not same origin with request’s current URL’s origin, then set serializedOrigin to `null`.
  252. if (!sameOrigin(request, requestCurrentURL(request))) {
  253. serializedOrigin = null
  254. }
  255. break
  256. default:
  257. // Do nothing.
  258. }
  259. if (serializedOrigin) {
  260. // 2. Append (`Origin`, serializedOrigin) to request’s header list.
  261. request.headersList.append('origin', serializedOrigin)
  262. }
  263. }
  264. }
  265. function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) {
  266. // TODO
  267. return performance.now()
  268. }
  269. // https://fetch.spec.whatwg.org/#create-an-opaque-timing-info
  270. function createOpaqueTimingInfo (timingInfo) {
  271. return {
  272. startTime: timingInfo.startTime ?? 0,
  273. redirectStartTime: 0,
  274. redirectEndTime: 0,
  275. postRedirectStartTime: timingInfo.startTime ?? 0,
  276. finalServiceWorkerStartTime: 0,
  277. finalNetworkResponseStartTime: 0,
  278. finalNetworkRequestStartTime: 0,
  279. endTime: 0,
  280. encodedBodySize: 0,
  281. decodedBodySize: 0,
  282. finalConnectionTimingInfo: null
  283. }
  284. }
  285. // https://html.spec.whatwg.org/multipage/origin.html#policy-container
  286. function makePolicyContainer () {
  287. // Note: the fetch spec doesn't make use of embedder policy or CSP list
  288. return {
  289. referrerPolicy: 'strict-origin-when-cross-origin'
  290. }
  291. }
  292. // https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container
  293. function clonePolicyContainer (policyContainer) {
  294. return {
  295. referrerPolicy: policyContainer.referrerPolicy
  296. }
  297. }
  298. // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
  299. function determineRequestsReferrer (request) {
  300. // 1. Let policy be request's referrer policy.
  301. const policy = request.referrerPolicy
  302. // Note: policy cannot (shouldn't) be null or an empty string.
  303. assert(policy)
  304. // 2. Let environment be request’s client.
  305. let referrerSource = null
  306. // 3. Switch on request’s referrer:
  307. if (request.referrer === 'client') {
  308. // Note: node isn't a browser and doesn't implement document/iframes,
  309. // so we bypass this step and replace it with our own.
  310. const globalOrigin = getGlobalOrigin()
  311. if (!globalOrigin || globalOrigin.origin === 'null') {
  312. return 'no-referrer'
  313. }
  314. // note: we need to clone it as it's mutated
  315. referrerSource = new URL(globalOrigin)
  316. } else if (request.referrer instanceof URL) {
  317. // Let referrerSource be request’s referrer.
  318. referrerSource = request.referrer
  319. }
  320. // 4. Let request’s referrerURL be the result of stripping referrerSource for
  321. // use as a referrer.
  322. let referrerURL = stripURLForReferrer(referrerSource)
  323. // 5. Let referrerOrigin be the result of stripping referrerSource for use as
  324. // a referrer, with the origin-only flag set to true.
  325. const referrerOrigin = stripURLForReferrer(referrerSource, true)
  326. // 6. If the result of serializing referrerURL is a string whose length is
  327. // greater than 4096, set referrerURL to referrerOrigin.
  328. if (referrerURL.toString().length > 4096) {
  329. referrerURL = referrerOrigin
  330. }
  331. const areSameOrigin = sameOrigin(request, referrerURL)
  332. const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerURL) &&
  333. !isURLPotentiallyTrustworthy(request.url)
  334. // 8. Execute the switch statements corresponding to the value of policy:
  335. switch (policy) {
  336. case 'origin': return referrerOrigin != null ? referrerOrigin : stripURLForReferrer(referrerSource, true)
  337. case 'unsafe-url': return referrerURL
  338. case 'same-origin':
  339. return areSameOrigin ? referrerOrigin : 'no-referrer'
  340. case 'origin-when-cross-origin':
  341. return areSameOrigin ? referrerURL : referrerOrigin
  342. case 'strict-origin-when-cross-origin': {
  343. const currentURL = requestCurrentURL(request)
  344. // 1. If the origin of referrerURL and the origin of request’s current
  345. // URL are the same, then return referrerURL.
  346. if (sameOrigin(referrerURL, currentURL)) {
  347. return referrerURL
  348. }
  349. // 2. If referrerURL is a potentially trustworthy URL and request’s
  350. // current URL is not a potentially trustworthy URL, then return no
  351. // referrer.
  352. if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
  353. return 'no-referrer'
  354. }
  355. // 3. Return referrerOrigin.
  356. return referrerOrigin
  357. }
  358. case 'strict-origin': // eslint-disable-line
  359. /**
  360. * 1. If referrerURL is a potentially trustworthy URL and
  361. * request’s current URL is not a potentially trustworthy URL,
  362. * then return no referrer.
  363. * 2. Return referrerOrigin
  364. */
  365. case 'no-referrer-when-downgrade': // eslint-disable-line
  366. /**
  367. * 1. If referrerURL is a potentially trustworthy URL and
  368. * request’s current URL is not a potentially trustworthy URL,
  369. * then return no referrer.
  370. * 2. Return referrerOrigin
  371. */
  372. default: // eslint-disable-line
  373. return isNonPotentiallyTrustWorthy ? 'no-referrer' : referrerOrigin
  374. }
  375. }
  376. /**
  377. * @see https://w3c.github.io/webappsec-referrer-policy/#strip-url
  378. * @param {URL} url
  379. * @param {boolean|undefined} originOnly
  380. */
  381. function stripURLForReferrer (url, originOnly) {
  382. // 1. Assert: url is a URL.
  383. assert(url instanceof URL)
  384. // 2. If url’s scheme is a local scheme, then return no referrer.
  385. if (url.protocol === 'file:' || url.protocol === 'about:' || url.protocol === 'blank:') {
  386. return 'no-referrer'
  387. }
  388. // 3. Set url’s username to the empty string.
  389. url.username = ''
  390. // 4. Set url’s password to the empty string.
  391. url.password = ''
  392. // 5. Set url’s fragment to null.
  393. url.hash = ''
  394. // 6. If the origin-only flag is true, then:
  395. if (originOnly) {
  396. // 1. Set url’s path to « the empty string ».
  397. url.pathname = ''
  398. // 2. Set url’s query to null.
  399. url.search = ''
  400. }
  401. // 7. Return url.
  402. return url
  403. }
  404. function isURLPotentiallyTrustworthy (url) {
  405. if (!(url instanceof URL)) {
  406. return false
  407. }
  408. // If child of about, return true
  409. if (url.href === 'about:blank' || url.href === 'about:srcdoc') {
  410. return true
  411. }
  412. // If scheme is data, return true
  413. if (url.protocol === 'data:') return true
  414. // If file, return true
  415. if (url.protocol === 'file:') return true
  416. return isOriginPotentiallyTrustworthy(url.origin)
  417. function isOriginPotentiallyTrustworthy (origin) {
  418. // If origin is explicitly null, return false
  419. if (origin == null || origin === 'null') return false
  420. const originAsURL = new URL(origin)
  421. // If secure, return true
  422. if (originAsURL.protocol === 'https:' || originAsURL.protocol === 'wss:') {
  423. return true
  424. }
  425. // If localhost or variants, return true
  426. if (/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(originAsURL.hostname) ||
  427. (originAsURL.hostname === 'localhost' || originAsURL.hostname.includes('localhost.')) ||
  428. (originAsURL.hostname.endsWith('.localhost'))) {
  429. return true
  430. }
  431. // If any other, return false
  432. return false
  433. }
  434. }
  435. /**
  436. * @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist
  437. * @param {Uint8Array} bytes
  438. * @param {string} metadataList
  439. */
  440. function bytesMatch (bytes, metadataList) {
  441. // If node is not built with OpenSSL support, we cannot check
  442. // a request's integrity, so allow it by default (the spec will
  443. // allow requests if an invalid hash is given, as precedence).
  444. /* istanbul ignore if: only if node is built with --without-ssl */
  445. if (crypto === undefined) {
  446. return true
  447. }
  448. // 1. Let parsedMetadata be the result of parsing metadataList.
  449. const parsedMetadata = parseMetadata(metadataList)
  450. // 2. If parsedMetadata is no metadata, return true.
  451. if (parsedMetadata === 'no metadata') {
  452. return true
  453. }
  454. // 3. If parsedMetadata is the empty set, return true.
  455. if (parsedMetadata.length === 0) {
  456. return true
  457. }
  458. // 4. Let metadata be the result of getting the strongest
  459. // metadata from parsedMetadata.
  460. const list = parsedMetadata.sort((c, d) => d.algo.localeCompare(c.algo))
  461. // get the strongest algorithm
  462. const strongest = list[0].algo
  463. // get all entries that use the strongest algorithm; ignore weaker
  464. const metadata = list.filter((item) => item.algo === strongest)
  465. // 5. For each item in metadata:
  466. for (const item of metadata) {
  467. // 1. Let algorithm be the alg component of item.
  468. const algorithm = item.algo
  469. // 2. Let expectedValue be the val component of item.
  470. const expectedValue = item.hash
  471. // 3. Let actualValue be the result of applying algorithm to bytes.
  472. const actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
  473. // 4. If actualValue is a case-sensitive match for expectedValue,
  474. // return true.
  475. if (actualValue === expectedValue) {
  476. return true
  477. }
  478. }
  479. // 6. Return false.
  480. return false
  481. }
  482. // https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
  483. // https://www.w3.org/TR/CSP2/#source-list-syntax
  484. // https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
  485. const parseHashWithOptions = /((?<algo>sha256|sha384|sha512)-(?<hash>[A-z0-9+/]{1}.*={0,2}))( +[\x21-\x7e]?)?/i
  486. /**
  487. * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
  488. * @param {string} metadata
  489. */
  490. function parseMetadata (metadata) {
  491. // 1. Let result be the empty set.
  492. /** @type {{ algo: string, hash: string }[]} */
  493. const result = []
  494. // 2. Let empty be equal to true.
  495. let empty = true
  496. const supportedHashes = crypto.getHashes()
  497. // 3. For each token returned by splitting metadata on spaces:
  498. for (const token of metadata.split(' ')) {
  499. // 1. Set empty to false.
  500. empty = false
  501. // 2. Parse token as a hash-with-options.
  502. const parsedToken = parseHashWithOptions.exec(token)
  503. // 3. If token does not parse, continue to the next token.
  504. if (parsedToken === null || parsedToken.groups === undefined) {
  505. // Note: Chromium blocks the request at this point, but Firefox
  506. // gives a warning that an invalid integrity was given. The
  507. // correct behavior is to ignore these, and subsequently not
  508. // check the integrity of the resource.
  509. continue
  510. }
  511. // 4. Let algorithm be the hash-algo component of token.
  512. const algorithm = parsedToken.groups.algo
  513. // 5. If algorithm is a hash function recognized by the user
  514. // agent, add the parsed token to result.
  515. if (supportedHashes.includes(algorithm.toLowerCase())) {
  516. result.push(parsedToken.groups)
  517. }
  518. }
  519. // 4. Return no metadata if empty is true, otherwise return result.
  520. if (empty === true) {
  521. return 'no metadata'
  522. }
  523. return result
  524. }
  525. // https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
  526. function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
  527. // TODO
  528. }
  529. /**
  530. * @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin}
  531. * @param {URL} A
  532. * @param {URL} B
  533. */
  534. function sameOrigin (A, B) {
  535. // 1. If A and B are the same opaque origin, then return true.
  536. if (A.origin === B.origin && A.origin === 'null') {
  537. return true
  538. }
  539. // 2. If A and B are both tuple origins and their schemes,
  540. // hosts, and port are identical, then return true.
  541. if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) {
  542. return true
  543. }
  544. // 3. Return false.
  545. return false
  546. }
  547. function createDeferredPromise () {
  548. let res
  549. let rej
  550. const promise = new Promise((resolve, reject) => {
  551. res = resolve
  552. rej = reject
  553. })
  554. return { promise, resolve: res, reject: rej }
  555. }
  556. function isAborted (fetchParams) {
  557. return fetchParams.controller.state === 'aborted'
  558. }
  559. function isCancelled (fetchParams) {
  560. return fetchParams.controller.state === 'aborted' ||
  561. fetchParams.controller.state === 'terminated'
  562. }
  563. // https://fetch.spec.whatwg.org/#concept-method-normalize
  564. function normalizeMethod (method) {
  565. return /^(DELETE|GET|HEAD|OPTIONS|POST|PUT)$/i.test(method)
  566. ? method.toUpperCase()
  567. : method
  568. }
  569. // https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
  570. function serializeJavascriptValueToJSONString (value) {
  571. // 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
  572. const result = JSON.stringify(value)
  573. // 2. If result is undefined, then throw a TypeError.
  574. if (result === undefined) {
  575. throw new TypeError('Value is not JSON serializable')
  576. }
  577. // 3. Assert: result is a string.
  578. assert(typeof result === 'string')
  579. // 4. Return result.
  580. return result
  581. }
  582. // https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
  583. const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
  584. /**
  585. * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
  586. * @param {() => unknown[]} iterator
  587. * @param {string} name name of the instance
  588. * @param {'key'|'value'|'key+value'} kind
  589. */
  590. function makeIterator (iterator, name, kind) {
  591. const object = {
  592. index: 0,
  593. kind,
  594. target: iterator
  595. }
  596. const i = {
  597. next () {
  598. // 1. Let interface be the interface for which the iterator prototype object exists.
  599. // 2. Let thisValue be the this value.
  600. // 3. Let object be ? ToObject(thisValue).
  601. // 4. If object is a platform object, then perform a security
  602. // check, passing:
  603. // 5. If object is not a default iterator object for interface,
  604. // then throw a TypeError.
  605. if (Object.getPrototypeOf(this) !== i) {
  606. throw new TypeError(
  607. `'next' called on an object that does not implement interface ${name} Iterator.`
  608. )
  609. }
  610. // 6. Let index be object’s index.
  611. // 7. Let kind be object’s kind.
  612. // 8. Let values be object’s target's value pairs to iterate over.
  613. const { index, kind, target } = object
  614. const values = target()
  615. // 9. Let len be the length of values.
  616. const len = values.length
  617. // 10. If index is greater than or equal to len, then return
  618. // CreateIterResultObject(undefined, true).
  619. if (index >= len) {
  620. return { value: undefined, done: true }
  621. }
  622. // 11. Let pair be the entry in values at index index.
  623. const pair = values[index]
  624. // 12. Set object’s index to index + 1.
  625. object.index = index + 1
  626. // 13. Return the iterator result for pair and kind.
  627. return iteratorResult(pair, kind)
  628. },
  629. // The class string of an iterator prototype object for a given interface is the
  630. // result of concatenating the identifier of the interface and the string " Iterator".
  631. [Symbol.toStringTag]: `${name} Iterator`
  632. }
  633. // The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
  634. Object.setPrototypeOf(i, esIteratorPrototype)
  635. // esIteratorPrototype needs to be the prototype of i
  636. // which is the prototype of an empty object. Yes, it's confusing.
  637. return Object.setPrototypeOf({}, i)
  638. }
  639. // https://webidl.spec.whatwg.org/#iterator-result
  640. function iteratorResult (pair, kind) {
  641. let result
  642. // 1. Let result be a value determined by the value of kind:
  643. switch (kind) {
  644. case 'key': {
  645. // 1. Let idlKey be pair’s key.
  646. // 2. Let key be the result of converting idlKey to an
  647. // ECMAScript value.
  648. // 3. result is key.
  649. result = pair[0]
  650. break
  651. }
  652. case 'value': {
  653. // 1. Let idlValue be pair’s value.
  654. // 2. Let value be the result of converting idlValue to
  655. // an ECMAScript value.
  656. // 3. result is value.
  657. result = pair[1]
  658. break
  659. }
  660. case 'key+value': {
  661. // 1. Let idlKey be pair’s key.
  662. // 2. Let idlValue be pair’s value.
  663. // 3. Let key be the result of converting idlKey to an
  664. // ECMAScript value.
  665. // 4. Let value be the result of converting idlValue to
  666. // an ECMAScript value.
  667. // 5. Let array be ! ArrayCreate(2).
  668. // 6. Call ! CreateDataProperty(array, "0", key).
  669. // 7. Call ! CreateDataProperty(array, "1", value).
  670. // 8. result is array.
  671. result = pair
  672. break
  673. }
  674. }
  675. // 2. Return CreateIterResultObject(result, false).
  676. return { value: result, done: false }
  677. }
  678. /**
  679. * @see https://fetch.spec.whatwg.org/#body-fully-read
  680. */
  681. function fullyReadBody (body, processBody, processBodyError) {
  682. // 1. If taskDestination is null, then set taskDestination to
  683. // the result of starting a new parallel queue.
  684. // 2. Let successSteps given a byte sequence bytes be to queue a
  685. // fetch task to run processBody given bytes, with taskDestination.
  686. const successSteps = (bytes) => queueMicrotask(() => processBody(bytes))
  687. // 3. Let errorSteps be to queue a fetch task to run processBodyError,
  688. // with taskDestination.
  689. const errorSteps = (error) => queueMicrotask(() => processBodyError(error))
  690. // 4. Let reader be the result of getting a reader for body’s stream.
  691. // If that threw an exception, then run errorSteps with that
  692. // exception and return.
  693. let reader
  694. try {
  695. reader = body.stream.getReader()
  696. } catch (e) {
  697. errorSteps(e)
  698. return
  699. }
  700. // 5. Read all bytes from reader, given successSteps and errorSteps.
  701. readAllBytes(reader, successSteps, errorSteps)
  702. }
  703. /** @type {ReadableStream} */
  704. let ReadableStream = globalThis.ReadableStream
  705. function isReadableStreamLike (stream) {
  706. if (!ReadableStream) {
  707. ReadableStream = require('stream/web').ReadableStream
  708. }
  709. return stream instanceof ReadableStream || (
  710. stream[Symbol.toStringTag] === 'ReadableStream' &&
  711. typeof stream.tee === 'function'
  712. )
  713. }
  714. const MAXIMUM_ARGUMENT_LENGTH = 65535
  715. /**
  716. * @see https://infra.spec.whatwg.org/#isomorphic-decode
  717. * @param {number[]|Uint8Array} input
  718. */
  719. function isomorphicDecode (input) {
  720. // 1. To isomorphic decode a byte sequence input, return a string whose code point
  721. // length is equal to input’s length and whose code points have the same values
  722. // as the values of input’s bytes, in the same order.
  723. if (input.length < MAXIMUM_ARGUMENT_LENGTH) {
  724. return String.fromCharCode(...input)
  725. }
  726. return input.reduce((previous, current) => previous + String.fromCharCode(current), '')
  727. }
  728. /**
  729. * @param {ReadableStreamController<Uint8Array>} controller
  730. */
  731. function readableStreamClose (controller) {
  732. try {
  733. controller.close()
  734. } catch (err) {
  735. // TODO: add comment explaining why this error occurs.
  736. if (!err.message.includes('Controller is already closed')) {
  737. throw err
  738. }
  739. }
  740. }
  741. /**
  742. * @see https://infra.spec.whatwg.org/#isomorphic-encode
  743. * @param {string} input
  744. */
  745. function isomorphicEncode (input) {
  746. // 1. Assert: input contains no code points greater than U+00FF.
  747. for (let i = 0; i < input.length; i++) {
  748. assert(input.charCodeAt(i) <= 0xFF)
  749. }
  750. // 2. Return a byte sequence whose length is equal to input’s code
  751. // point length and whose bytes have the same values as the
  752. // values of input’s code points, in the same order
  753. return input
  754. }
  755. /**
  756. * @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
  757. * @see https://streams.spec.whatwg.org/#read-loop
  758. * @param {ReadableStreamDefaultReader} reader
  759. * @param {(bytes: Uint8Array) => void} successSteps
  760. * @param {(error: Error) => void} failureSteps
  761. */
  762. async function readAllBytes (reader, successSteps, failureSteps) {
  763. const bytes = []
  764. let byteLength = 0
  765. while (true) {
  766. let done
  767. let chunk
  768. try {
  769. ({ done, value: chunk } = await reader.read())
  770. } catch (e) {
  771. // 1. Call failureSteps with e.
  772. failureSteps(e)
  773. return
  774. }
  775. if (done) {
  776. // 1. Call successSteps with bytes.
  777. successSteps(Buffer.concat(bytes, byteLength))
  778. return
  779. }
  780. // 1. If chunk is not a Uint8Array object, call failureSteps
  781. // with a TypeError and abort these steps.
  782. if (!isUint8Array(chunk)) {
  783. failureSteps(new TypeError('Received non-Uint8Array chunk'))
  784. return
  785. }
  786. // 2. Append the bytes represented by chunk to bytes.
  787. bytes.push(chunk)
  788. byteLength += chunk.length
  789. // 3. Read-loop given reader, bytes, successSteps, and failureSteps.
  790. }
  791. }
  792. /**
  793. * @see https://fetch.spec.whatwg.org/#is-local
  794. * @param {URL} url
  795. */
  796. function urlIsLocal (url) {
  797. assert('protocol' in url) // ensure it's a url object
  798. const protocol = url.protocol
  799. return protocol === 'about:' || protocol === 'blob:' || protocol === 'data:'
  800. }
  801. /**
  802. * @param {string|URL} url
  803. */
  804. function urlHasHttpsScheme (url) {
  805. if (typeof url === 'string') {
  806. return url.startsWith('https:')
  807. }
  808. return url.protocol === 'https:'
  809. }
  810. /**
  811. * @see https://fetch.spec.whatwg.org/#http-scheme
  812. * @param {URL} url
  813. */
  814. function urlIsHttpHttpsScheme (url) {
  815. assert('protocol' in url) // ensure it's a url object
  816. const protocol = url.protocol
  817. return protocol === 'http:' || protocol === 'https:'
  818. }
  819. /**
  820. * Fetch supports node >= 16.8.0, but Object.hasOwn was added in v16.9.0.
  821. */
  822. const hasOwn = Object.hasOwn || ((dict, key) => Object.prototype.hasOwnProperty.call(dict, key))
  823. module.exports = {
  824. isAborted,
  825. isCancelled,
  826. createDeferredPromise,
  827. ReadableStreamFrom,
  828. toUSVString,
  829. tryUpgradeRequestToAPotentiallyTrustworthyURL,
  830. coarsenedSharedCurrentTime,
  831. determineRequestsReferrer,
  832. makePolicyContainer,
  833. clonePolicyContainer,
  834. appendFetchMetadata,
  835. appendRequestOriginHeader,
  836. TAOCheck,
  837. corsCheck,
  838. crossOriginResourcePolicyCheck,
  839. createOpaqueTimingInfo,
  840. setRequestReferrerPolicyOnRedirect,
  841. isValidHTTPToken,
  842. requestBadPort,
  843. requestCurrentURL,
  844. responseURL,
  845. responseLocationURL,
  846. isBlobLike,
  847. isURLPotentiallyTrustworthy,
  848. isValidReasonPhrase,
  849. sameOrigin,
  850. normalizeMethod,
  851. serializeJavascriptValueToJSONString,
  852. makeIterator,
  853. isValidHeaderName,
  854. isValidHeaderValue,
  855. hasOwn,
  856. isErrorLike,
  857. fullyReadBody,
  858. bytesMatch,
  859. isReadableStreamLike,
  860. readableStreamClose,
  861. isomorphicEncode,
  862. isomorphicDecode,
  863. urlIsLocal,
  864. urlHasHttpsScheme,
  865. urlIsHttpHttpsScheme,
  866. readAllBytes
  867. }