util.js 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632
  1. 'use strict'
  2. const { Transform } = require('node:stream')
  3. const zlib = require('node:zlib')
  4. const { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = require('./constants')
  5. const { getGlobalOrigin } = require('./global')
  6. const { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require('./data-url')
  7. const { performance } = require('node:perf_hooks')
  8. const { isBlobLike, ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require('../../core/util')
  9. const assert = require('node:assert')
  10. const { isUint8Array } = require('node:util/types')
  11. const { webidl } = require('./webidl')
  12. let supportedHashes = []
  13. // https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
  14. /** @type {import('crypto')} */
  15. let crypto
  16. try {
  17. crypto = require('node:crypto')
  18. const possibleRelevantHashes = ['sha256', 'sha384', 'sha512']
  19. supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash))
  20. /* c8 ignore next 3 */
  21. } catch {
  22. }
  23. function responseURL (response) {
  24. // https://fetch.spec.whatwg.org/#responses
  25. // A response has an associated URL. It is a pointer to the last URL
  26. // in response’s URL list and null if response’s URL list is empty.
  27. const urlList = response.urlList
  28. const length = urlList.length
  29. return length === 0 ? null : urlList[length - 1].toString()
  30. }
  31. // https://fetch.spec.whatwg.org/#concept-response-location-url
  32. function responseLocationURL (response, requestFragment) {
  33. // 1. If response’s status is not a redirect status, then return null.
  34. if (!redirectStatusSet.has(response.status)) {
  35. return null
  36. }
  37. // 2. Let location be the result of extracting header list values given
  38. // `Location` and response’s header list.
  39. let location = response.headersList.get('location', true)
  40. // 3. If location is a header value, then set location to the result of
  41. // parsing location with response’s URL.
  42. if (location !== null && isValidHeaderValue(location)) {
  43. if (!isValidEncodedURL(location)) {
  44. // Some websites respond location header in UTF-8 form without encoding them as ASCII
  45. // and major browsers redirect them to correctly UTF-8 encoded addresses.
  46. // Here, we handle that behavior in the same way.
  47. location = normalizeBinaryStringToUtf8(location)
  48. }
  49. location = new URL(location, responseURL(response))
  50. }
  51. // 4. If location is a URL whose fragment is null, then set location’s
  52. // fragment to requestFragment.
  53. if (location && !location.hash) {
  54. location.hash = requestFragment
  55. }
  56. // 5. Return location.
  57. return location
  58. }
  59. /**
  60. * @see https://www.rfc-editor.org/rfc/rfc1738#section-2.2
  61. * @param {string} url
  62. * @returns {boolean}
  63. */
  64. function isValidEncodedURL (url) {
  65. for (let i = 0; i < url.length; ++i) {
  66. const code = url.charCodeAt(i)
  67. if (
  68. code > 0x7E || // Non-US-ASCII + DEL
  69. code < 0x20 // Control characters NUL - US
  70. ) {
  71. return false
  72. }
  73. }
  74. return true
  75. }
  76. /**
  77. * If string contains non-ASCII characters, assumes it's UTF-8 encoded and decodes it.
  78. * Since UTF-8 is a superset of ASCII, this will work for ASCII strings as well.
  79. * @param {string} value
  80. * @returns {string}
  81. */
  82. function normalizeBinaryStringToUtf8 (value) {
  83. return Buffer.from(value, 'binary').toString('utf8')
  84. }
  85. /** @returns {URL} */
  86. function requestCurrentURL (request) {
  87. return request.urlList[request.urlList.length - 1]
  88. }
  89. function requestBadPort (request) {
  90. // 1. Let url be request’s current URL.
  91. const url = requestCurrentURL(request)
  92. // 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port,
  93. // then return blocked.
  94. if (urlIsHttpHttpsScheme(url) && badPortsSet.has(url.port)) {
  95. return 'blocked'
  96. }
  97. // 3. Return allowed.
  98. return 'allowed'
  99. }
  100. function isErrorLike (object) {
  101. return object instanceof Error || (
  102. object?.constructor?.name === 'Error' ||
  103. object?.constructor?.name === 'DOMException'
  104. )
  105. }
  106. // Check whether |statusText| is a ByteString and
  107. // matches the Reason-Phrase token production.
  108. // RFC 2616: https://tools.ietf.org/html/rfc2616
  109. // RFC 7230: https://tools.ietf.org/html/rfc7230
  110. // "reason-phrase = *( HTAB / SP / VCHAR / obs-text )"
  111. // https://github.com/chromium/chromium/blob/94.0.4604.1/third_party/blink/renderer/core/fetch/response.cc#L116
  112. function isValidReasonPhrase (statusText) {
  113. for (let i = 0; i < statusText.length; ++i) {
  114. const c = statusText.charCodeAt(i)
  115. if (
  116. !(
  117. (
  118. c === 0x09 || // HTAB
  119. (c >= 0x20 && c <= 0x7e) || // SP / VCHAR
  120. (c >= 0x80 && c <= 0xff)
  121. ) // obs-text
  122. )
  123. ) {
  124. return false
  125. }
  126. }
  127. return true
  128. }
  129. /**
  130. * @see https://fetch.spec.whatwg.org/#header-name
  131. * @param {string} potentialValue
  132. */
  133. const isValidHeaderName = isValidHTTPToken
  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. return (
  142. potentialValue[0] === '\t' ||
  143. potentialValue[0] === ' ' ||
  144. potentialValue[potentialValue.length - 1] === '\t' ||
  145. potentialValue[potentialValue.length - 1] === ' ' ||
  146. potentialValue.includes('\n') ||
  147. potentialValue.includes('\r') ||
  148. potentialValue.includes('\0')
  149. ) === false
  150. }
  151. // https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
  152. function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
  153. // Given a request request and a response actualResponse, this algorithm
  154. // updates request’s referrer policy according to the Referrer-Policy
  155. // header (if any) in actualResponse.
  156. // 1. Let policy be the result of executing § 8.1 Parse a referrer policy
  157. // from a Referrer-Policy header on actualResponse.
  158. // 8.1 Parse a referrer policy from a Referrer-Policy header
  159. // 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
  160. const { headersList } = actualResponse
  161. // 2. Let policy be the empty string.
  162. // 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.
  163. // 4. Return policy.
  164. const policyHeader = (headersList.get('referrer-policy', true) ?? '').split(',')
  165. // Note: As the referrer-policy can contain multiple policies
  166. // separated by comma, we need to loop through all of them
  167. // and pick the first valid one.
  168. // Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#specify_a_fallback_policy
  169. let policy = ''
  170. if (policyHeader.length > 0) {
  171. // The right-most policy takes precedence.
  172. // The left-most policy is the fallback.
  173. for (let i = policyHeader.length; i !== 0; i--) {
  174. const token = policyHeader[i - 1].trim()
  175. if (referrerPolicyTokens.has(token)) {
  176. policy = token
  177. break
  178. }
  179. }
  180. }
  181. // 2. If policy is not the empty string, then set request’s referrer policy to policy.
  182. if (policy !== '') {
  183. request.referrerPolicy = policy
  184. }
  185. }
  186. // https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check
  187. function crossOriginResourcePolicyCheck () {
  188. // TODO
  189. return 'allowed'
  190. }
  191. // https://fetch.spec.whatwg.org/#concept-cors-check
  192. function corsCheck () {
  193. // TODO
  194. return 'success'
  195. }
  196. // https://fetch.spec.whatwg.org/#concept-tao-check
  197. function TAOCheck () {
  198. // TODO
  199. return 'success'
  200. }
  201. function appendFetchMetadata (httpRequest) {
  202. // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-dest-header
  203. // TODO
  204. // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-mode-header
  205. // 1. Assert: r’s url is a potentially trustworthy URL.
  206. // TODO
  207. // 2. Let header be a Structured Header whose value is a token.
  208. let header = null
  209. // 3. Set header’s value to r’s mode.
  210. header = httpRequest.mode
  211. // 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list.
  212. httpRequest.headersList.set('sec-fetch-mode', header, true)
  213. // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header
  214. // TODO
  215. // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-user-header
  216. // TODO
  217. }
  218. // https://fetch.spec.whatwg.org/#append-a-request-origin-header
  219. function appendRequestOriginHeader (request) {
  220. // 1. Let serializedOrigin be the result of byte-serializing a request origin
  221. // with request.
  222. // TODO: implement "byte-serializing a request origin"
  223. let serializedOrigin = request.origin
  224. // - "'client' is changed to an origin during fetching."
  225. // This doesn't happen in undici (in most cases) because undici, by default,
  226. // has no concept of origin.
  227. // - request.origin can also be set to request.client.origin (client being
  228. // an environment settings object), which is undefined without using
  229. // setGlobalOrigin.
  230. if (serializedOrigin === 'client' || serializedOrigin === undefined) {
  231. return
  232. }
  233. // 2. If request’s response tainting is "cors" or request’s mode is "websocket",
  234. // then append (`Origin`, serializedOrigin) to request’s header list.
  235. // 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
  236. if (request.responseTainting === 'cors' || request.mode === 'websocket') {
  237. request.headersList.append('origin', serializedOrigin, true)
  238. } else if (request.method !== 'GET' && request.method !== 'HEAD') {
  239. // 1. Switch on request’s referrer policy:
  240. switch (request.referrerPolicy) {
  241. case 'no-referrer':
  242. // Set serializedOrigin to `null`.
  243. serializedOrigin = null
  244. break
  245. case 'no-referrer-when-downgrade':
  246. case 'strict-origin':
  247. case 'strict-origin-when-cross-origin':
  248. // If request’s origin is a tuple origin, its scheme is "https", and
  249. // request’s current URL’s scheme is not "https", then set
  250. // serializedOrigin to `null`.
  251. if (request.origin && urlHasHttpsScheme(request.origin) && !urlHasHttpsScheme(requestCurrentURL(request))) {
  252. serializedOrigin = null
  253. }
  254. break
  255. case 'same-origin':
  256. // If request’s origin is not same origin with request’s current URL’s
  257. // origin, then set serializedOrigin to `null`.
  258. if (!sameOrigin(request, requestCurrentURL(request))) {
  259. serializedOrigin = null
  260. }
  261. break
  262. default:
  263. // Do nothing.
  264. }
  265. // 2. Append (`Origin`, serializedOrigin) to request’s header list.
  266. request.headersList.append('origin', serializedOrigin, true)
  267. }
  268. }
  269. // https://w3c.github.io/hr-time/#dfn-coarsen-time
  270. function coarsenTime (timestamp, crossOriginIsolatedCapability) {
  271. // TODO
  272. return timestamp
  273. }
  274. // https://fetch.spec.whatwg.org/#clamp-and-coarsen-connection-timing-info
  275. function clampAndCoarsenConnectionTimingInfo (connectionTimingInfo, defaultStartTime, crossOriginIsolatedCapability) {
  276. if (!connectionTimingInfo?.startTime || connectionTimingInfo.startTime < defaultStartTime) {
  277. return {
  278. domainLookupStartTime: defaultStartTime,
  279. domainLookupEndTime: defaultStartTime,
  280. connectionStartTime: defaultStartTime,
  281. connectionEndTime: defaultStartTime,
  282. secureConnectionStartTime: defaultStartTime,
  283. ALPNNegotiatedProtocol: connectionTimingInfo?.ALPNNegotiatedProtocol
  284. }
  285. }
  286. return {
  287. domainLookupStartTime: coarsenTime(connectionTimingInfo.domainLookupStartTime, crossOriginIsolatedCapability),
  288. domainLookupEndTime: coarsenTime(connectionTimingInfo.domainLookupEndTime, crossOriginIsolatedCapability),
  289. connectionStartTime: coarsenTime(connectionTimingInfo.connectionStartTime, crossOriginIsolatedCapability),
  290. connectionEndTime: coarsenTime(connectionTimingInfo.connectionEndTime, crossOriginIsolatedCapability),
  291. secureConnectionStartTime: coarsenTime(connectionTimingInfo.secureConnectionStartTime, crossOriginIsolatedCapability),
  292. ALPNNegotiatedProtocol: connectionTimingInfo.ALPNNegotiatedProtocol
  293. }
  294. }
  295. // https://w3c.github.io/hr-time/#dfn-coarsened-shared-current-time
  296. function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) {
  297. return coarsenTime(performance.now(), crossOriginIsolatedCapability)
  298. }
  299. // https://fetch.spec.whatwg.org/#create-an-opaque-timing-info
  300. function createOpaqueTimingInfo (timingInfo) {
  301. return {
  302. startTime: timingInfo.startTime ?? 0,
  303. redirectStartTime: 0,
  304. redirectEndTime: 0,
  305. postRedirectStartTime: timingInfo.startTime ?? 0,
  306. finalServiceWorkerStartTime: 0,
  307. finalNetworkResponseStartTime: 0,
  308. finalNetworkRequestStartTime: 0,
  309. endTime: 0,
  310. encodedBodySize: 0,
  311. decodedBodySize: 0,
  312. finalConnectionTimingInfo: null
  313. }
  314. }
  315. // https://html.spec.whatwg.org/multipage/origin.html#policy-container
  316. function makePolicyContainer () {
  317. // Note: the fetch spec doesn't make use of embedder policy or CSP list
  318. return {
  319. referrerPolicy: 'strict-origin-when-cross-origin'
  320. }
  321. }
  322. // https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container
  323. function clonePolicyContainer (policyContainer) {
  324. return {
  325. referrerPolicy: policyContainer.referrerPolicy
  326. }
  327. }
  328. // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
  329. function determineRequestsReferrer (request) {
  330. // 1. Let policy be request's referrer policy.
  331. const policy = request.referrerPolicy
  332. // Note: policy cannot (shouldn't) be null or an empty string.
  333. assert(policy)
  334. // 2. Let environment be request’s client.
  335. let referrerSource = null
  336. // 3. Switch on request’s referrer:
  337. if (request.referrer === 'client') {
  338. // Note: node isn't a browser and doesn't implement document/iframes,
  339. // so we bypass this step and replace it with our own.
  340. const globalOrigin = getGlobalOrigin()
  341. if (!globalOrigin || globalOrigin.origin === 'null') {
  342. return 'no-referrer'
  343. }
  344. // note: we need to clone it as it's mutated
  345. referrerSource = new URL(globalOrigin)
  346. } else if (request.referrer instanceof URL) {
  347. // Let referrerSource be request’s referrer.
  348. referrerSource = request.referrer
  349. }
  350. // 4. Let request’s referrerURL be the result of stripping referrerSource for
  351. // use as a referrer.
  352. let referrerURL = stripURLForReferrer(referrerSource)
  353. // 5. Let referrerOrigin be the result of stripping referrerSource for use as
  354. // a referrer, with the origin-only flag set to true.
  355. const referrerOrigin = stripURLForReferrer(referrerSource, true)
  356. // 6. If the result of serializing referrerURL is a string whose length is
  357. // greater than 4096, set referrerURL to referrerOrigin.
  358. if (referrerURL.toString().length > 4096) {
  359. referrerURL = referrerOrigin
  360. }
  361. const areSameOrigin = sameOrigin(request, referrerURL)
  362. const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerURL) &&
  363. !isURLPotentiallyTrustworthy(request.url)
  364. // 8. Execute the switch statements corresponding to the value of policy:
  365. switch (policy) {
  366. case 'origin': return referrerOrigin != null ? referrerOrigin : stripURLForReferrer(referrerSource, true)
  367. case 'unsafe-url': return referrerURL
  368. case 'same-origin':
  369. return areSameOrigin ? referrerOrigin : 'no-referrer'
  370. case 'origin-when-cross-origin':
  371. return areSameOrigin ? referrerURL : referrerOrigin
  372. case 'strict-origin-when-cross-origin': {
  373. const currentURL = requestCurrentURL(request)
  374. // 1. If the origin of referrerURL and the origin of request’s current
  375. // URL are the same, then return referrerURL.
  376. if (sameOrigin(referrerURL, currentURL)) {
  377. return referrerURL
  378. }
  379. // 2. If referrerURL is a potentially trustworthy URL and request’s
  380. // current URL is not a potentially trustworthy URL, then return no
  381. // referrer.
  382. if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
  383. return 'no-referrer'
  384. }
  385. // 3. Return referrerOrigin.
  386. return referrerOrigin
  387. }
  388. case 'strict-origin': // eslint-disable-line
  389. /**
  390. * 1. If referrerURL is a potentially trustworthy URL and
  391. * request’s current URL is not a potentially trustworthy URL,
  392. * then return no referrer.
  393. * 2. Return referrerOrigin
  394. */
  395. case 'no-referrer-when-downgrade': // eslint-disable-line
  396. /**
  397. * 1. If referrerURL is a potentially trustworthy URL and
  398. * request’s current URL is not a potentially trustworthy URL,
  399. * then return no referrer.
  400. * 2. Return referrerOrigin
  401. */
  402. default: // eslint-disable-line
  403. return isNonPotentiallyTrustWorthy ? 'no-referrer' : referrerOrigin
  404. }
  405. }
  406. /**
  407. * @see https://w3c.github.io/webappsec-referrer-policy/#strip-url
  408. * @param {URL} url
  409. * @param {boolean|undefined} originOnly
  410. */
  411. function stripURLForReferrer (url, originOnly) {
  412. // 1. Assert: url is a URL.
  413. assert(url instanceof URL)
  414. url = new URL(url)
  415. // 2. If url’s scheme is a local scheme, then return no referrer.
  416. if (url.protocol === 'file:' || url.protocol === 'about:' || url.protocol === 'blank:') {
  417. return 'no-referrer'
  418. }
  419. // 3. Set url’s username to the empty string.
  420. url.username = ''
  421. // 4. Set url’s password to the empty string.
  422. url.password = ''
  423. // 5. Set url’s fragment to null.
  424. url.hash = ''
  425. // 6. If the origin-only flag is true, then:
  426. if (originOnly) {
  427. // 1. Set url’s path to « the empty string ».
  428. url.pathname = ''
  429. // 2. Set url’s query to null.
  430. url.search = ''
  431. }
  432. // 7. Return url.
  433. return url
  434. }
  435. function isURLPotentiallyTrustworthy (url) {
  436. if (!(url instanceof URL)) {
  437. return false
  438. }
  439. // If child of about, return true
  440. if (url.href === 'about:blank' || url.href === 'about:srcdoc') {
  441. return true
  442. }
  443. // If scheme is data, return true
  444. if (url.protocol === 'data:') return true
  445. // If file, return true
  446. if (url.protocol === 'file:') return true
  447. return isOriginPotentiallyTrustworthy(url.origin)
  448. function isOriginPotentiallyTrustworthy (origin) {
  449. // If origin is explicitly null, return false
  450. if (origin == null || origin === 'null') return false
  451. const originAsURL = new URL(origin)
  452. // If secure, return true
  453. if (originAsURL.protocol === 'https:' || originAsURL.protocol === 'wss:') {
  454. return true
  455. }
  456. // If localhost or variants, return true
  457. if (/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(originAsURL.hostname) ||
  458. (originAsURL.hostname === 'localhost' || originAsURL.hostname.includes('localhost.')) ||
  459. (originAsURL.hostname.endsWith('.localhost'))) {
  460. return true
  461. }
  462. // If any other, return false
  463. return false
  464. }
  465. }
  466. /**
  467. * @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist
  468. * @param {Uint8Array} bytes
  469. * @param {string} metadataList
  470. */
  471. function bytesMatch (bytes, metadataList) {
  472. // If node is not built with OpenSSL support, we cannot check
  473. // a request's integrity, so allow it by default (the spec will
  474. // allow requests if an invalid hash is given, as precedence).
  475. /* istanbul ignore if: only if node is built with --without-ssl */
  476. if (crypto === undefined) {
  477. return true
  478. }
  479. // 1. Let parsedMetadata be the result of parsing metadataList.
  480. const parsedMetadata = parseMetadata(metadataList)
  481. // 2. If parsedMetadata is no metadata, return true.
  482. if (parsedMetadata === 'no metadata') {
  483. return true
  484. }
  485. // 3. If response is not eligible for integrity validation, return false.
  486. // TODO
  487. // 4. If parsedMetadata is the empty set, return true.
  488. if (parsedMetadata.length === 0) {
  489. return true
  490. }
  491. // 5. Let metadata be the result of getting the strongest
  492. // metadata from parsedMetadata.
  493. const strongest = getStrongestMetadata(parsedMetadata)
  494. const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest)
  495. // 6. For each item in metadata:
  496. for (const item of metadata) {
  497. // 1. Let algorithm be the alg component of item.
  498. const algorithm = item.algo
  499. // 2. Let expectedValue be the val component of item.
  500. const expectedValue = item.hash
  501. // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e
  502. // "be liberal with padding". This is annoying, and it's not even in the spec.
  503. // 3. Let actualValue be the result of applying algorithm to bytes.
  504. let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
  505. if (actualValue[actualValue.length - 1] === '=') {
  506. if (actualValue[actualValue.length - 2] === '=') {
  507. actualValue = actualValue.slice(0, -2)
  508. } else {
  509. actualValue = actualValue.slice(0, -1)
  510. }
  511. }
  512. // 4. If actualValue is a case-sensitive match for expectedValue,
  513. // return true.
  514. if (compareBase64Mixed(actualValue, expectedValue)) {
  515. return true
  516. }
  517. }
  518. // 7. Return false.
  519. return false
  520. }
  521. // https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
  522. // https://www.w3.org/TR/CSP2/#source-list-syntax
  523. // https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
  524. const parseHashWithOptions = /(?<algo>sha256|sha384|sha512)-((?<hash>[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i
  525. /**
  526. * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
  527. * @param {string} metadata
  528. */
  529. function parseMetadata (metadata) {
  530. // 1. Let result be the empty set.
  531. /** @type {{ algo: string, hash: string }[]} */
  532. const result = []
  533. // 2. Let empty be equal to true.
  534. let empty = true
  535. // 3. For each token returned by splitting metadata on spaces:
  536. for (const token of metadata.split(' ')) {
  537. // 1. Set empty to false.
  538. empty = false
  539. // 2. Parse token as a hash-with-options.
  540. const parsedToken = parseHashWithOptions.exec(token)
  541. // 3. If token does not parse, continue to the next token.
  542. if (
  543. parsedToken === null ||
  544. parsedToken.groups === undefined ||
  545. parsedToken.groups.algo === undefined
  546. ) {
  547. // Note: Chromium blocks the request at this point, but Firefox
  548. // gives a warning that an invalid integrity was given. The
  549. // correct behavior is to ignore these, and subsequently not
  550. // check the integrity of the resource.
  551. continue
  552. }
  553. // 4. Let algorithm be the hash-algo component of token.
  554. const algorithm = parsedToken.groups.algo.toLowerCase()
  555. // 5. If algorithm is a hash function recognized by the user
  556. // agent, add the parsed token to result.
  557. if (supportedHashes.includes(algorithm)) {
  558. result.push(parsedToken.groups)
  559. }
  560. }
  561. // 4. Return no metadata if empty is true, otherwise return result.
  562. if (empty === true) {
  563. return 'no metadata'
  564. }
  565. return result
  566. }
  567. /**
  568. * @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList
  569. */
  570. function getStrongestMetadata (metadataList) {
  571. // Let algorithm be the algo component of the first item in metadataList.
  572. // Can be sha256
  573. let algorithm = metadataList[0].algo
  574. // If the algorithm is sha512, then it is the strongest
  575. // and we can return immediately
  576. if (algorithm[3] === '5') {
  577. return algorithm
  578. }
  579. for (let i = 1; i < metadataList.length; ++i) {
  580. const metadata = metadataList[i]
  581. // If the algorithm is sha512, then it is the strongest
  582. // and we can break the loop immediately
  583. if (metadata.algo[3] === '5') {
  584. algorithm = 'sha512'
  585. break
  586. // If the algorithm is sha384, then a potential sha256 or sha384 is ignored
  587. } else if (algorithm[3] === '3') {
  588. continue
  589. // algorithm is sha256, check if algorithm is sha384 and if so, set it as
  590. // the strongest
  591. } else if (metadata.algo[3] === '3') {
  592. algorithm = 'sha384'
  593. }
  594. }
  595. return algorithm
  596. }
  597. function filterMetadataListByAlgorithm (metadataList, algorithm) {
  598. if (metadataList.length === 1) {
  599. return metadataList
  600. }
  601. let pos = 0
  602. for (let i = 0; i < metadataList.length; ++i) {
  603. if (metadataList[i].algo === algorithm) {
  604. metadataList[pos++] = metadataList[i]
  605. }
  606. }
  607. metadataList.length = pos
  608. return metadataList
  609. }
  610. /**
  611. * Compares two base64 strings, allowing for base64url
  612. * in the second string.
  613. *
  614. * @param {string} actualValue always base64
  615. * @param {string} expectedValue base64 or base64url
  616. * @returns {boolean}
  617. */
  618. function compareBase64Mixed (actualValue, expectedValue) {
  619. if (actualValue.length !== expectedValue.length) {
  620. return false
  621. }
  622. for (let i = 0; i < actualValue.length; ++i) {
  623. if (actualValue[i] !== expectedValue[i]) {
  624. if (
  625. (actualValue[i] === '+' && expectedValue[i] === '-') ||
  626. (actualValue[i] === '/' && expectedValue[i] === '_')
  627. ) {
  628. continue
  629. }
  630. return false
  631. }
  632. }
  633. return true
  634. }
  635. // https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
  636. function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
  637. // TODO
  638. }
  639. /**
  640. * @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin}
  641. * @param {URL} A
  642. * @param {URL} B
  643. */
  644. function sameOrigin (A, B) {
  645. // 1. If A and B are the same opaque origin, then return true.
  646. if (A.origin === B.origin && A.origin === 'null') {
  647. return true
  648. }
  649. // 2. If A and B are both tuple origins and their schemes,
  650. // hosts, and port are identical, then return true.
  651. if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) {
  652. return true
  653. }
  654. // 3. Return false.
  655. return false
  656. }
  657. function createDeferredPromise () {
  658. let res
  659. let rej
  660. const promise = new Promise((resolve, reject) => {
  661. res = resolve
  662. rej = reject
  663. })
  664. return { promise, resolve: res, reject: rej }
  665. }
  666. function isAborted (fetchParams) {
  667. return fetchParams.controller.state === 'aborted'
  668. }
  669. function isCancelled (fetchParams) {
  670. return fetchParams.controller.state === 'aborted' ||
  671. fetchParams.controller.state === 'terminated'
  672. }
  673. /**
  674. * @see https://fetch.spec.whatwg.org/#concept-method-normalize
  675. * @param {string} method
  676. */
  677. function normalizeMethod (method) {
  678. return normalizedMethodRecordsBase[method.toLowerCase()] ?? method
  679. }
  680. // https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
  681. function serializeJavascriptValueToJSONString (value) {
  682. // 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
  683. const result = JSON.stringify(value)
  684. // 2. If result is undefined, then throw a TypeError.
  685. if (result === undefined) {
  686. throw new TypeError('Value is not JSON serializable')
  687. }
  688. // 3. Assert: result is a string.
  689. assert(typeof result === 'string')
  690. // 4. Return result.
  691. return result
  692. }
  693. // https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
  694. const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
  695. /**
  696. * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
  697. * @param {string} name name of the instance
  698. * @param {symbol} kInternalIterator
  699. * @param {string | number} [keyIndex]
  700. * @param {string | number} [valueIndex]
  701. */
  702. function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) {
  703. class FastIterableIterator {
  704. /** @type {any} */
  705. #target
  706. /** @type {'key' | 'value' | 'key+value'} */
  707. #kind
  708. /** @type {number} */
  709. #index
  710. /**
  711. * @see https://webidl.spec.whatwg.org/#dfn-default-iterator-object
  712. * @param {unknown} target
  713. * @param {'key' | 'value' | 'key+value'} kind
  714. */
  715. constructor (target, kind) {
  716. this.#target = target
  717. this.#kind = kind
  718. this.#index = 0
  719. }
  720. next () {
  721. // 1. Let interface be the interface for which the iterator prototype object exists.
  722. // 2. Let thisValue be the this value.
  723. // 3. Let object be ? ToObject(thisValue).
  724. // 4. If object is a platform object, then perform a security
  725. // check, passing:
  726. // 5. If object is not a default iterator object for interface,
  727. // then throw a TypeError.
  728. if (typeof this !== 'object' || this === null || !(#target in this)) {
  729. throw new TypeError(
  730. `'next' called on an object that does not implement interface ${name} Iterator.`
  731. )
  732. }
  733. // 6. Let index be object’s index.
  734. // 7. Let kind be object’s kind.
  735. // 8. Let values be object’s target's value pairs to iterate over.
  736. const index = this.#index
  737. const values = this.#target[kInternalIterator]
  738. // 9. Let len be the length of values.
  739. const len = values.length
  740. // 10. If index is greater than or equal to len, then return
  741. // CreateIterResultObject(undefined, true).
  742. if (index >= len) {
  743. return {
  744. value: undefined,
  745. done: true
  746. }
  747. }
  748. // 11. Let pair be the entry in values at index index.
  749. const { [keyIndex]: key, [valueIndex]: value } = values[index]
  750. // 12. Set object’s index to index + 1.
  751. this.#index = index + 1
  752. // 13. Return the iterator result for pair and kind.
  753. // https://webidl.spec.whatwg.org/#iterator-result
  754. // 1. Let result be a value determined by the value of kind:
  755. let result
  756. switch (this.#kind) {
  757. case 'key':
  758. // 1. Let idlKey be pair’s key.
  759. // 2. Let key be the result of converting idlKey to an
  760. // ECMAScript value.
  761. // 3. result is key.
  762. result = key
  763. break
  764. case 'value':
  765. // 1. Let idlValue be pair’s value.
  766. // 2. Let value be the result of converting idlValue to
  767. // an ECMAScript value.
  768. // 3. result is value.
  769. result = value
  770. break
  771. case 'key+value':
  772. // 1. Let idlKey be pair’s key.
  773. // 2. Let idlValue be pair’s value.
  774. // 3. Let key be the result of converting idlKey to an
  775. // ECMAScript value.
  776. // 4. Let value be the result of converting idlValue to
  777. // an ECMAScript value.
  778. // 5. Let array be ! ArrayCreate(2).
  779. // 6. Call ! CreateDataProperty(array, "0", key).
  780. // 7. Call ! CreateDataProperty(array, "1", value).
  781. // 8. result is array.
  782. result = [key, value]
  783. break
  784. }
  785. // 2. Return CreateIterResultObject(result, false).
  786. return {
  787. value: result,
  788. done: false
  789. }
  790. }
  791. }
  792. // https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
  793. // @ts-ignore
  794. delete FastIterableIterator.prototype.constructor
  795. Object.setPrototypeOf(FastIterableIterator.prototype, esIteratorPrototype)
  796. Object.defineProperties(FastIterableIterator.prototype, {
  797. [Symbol.toStringTag]: {
  798. writable: false,
  799. enumerable: false,
  800. configurable: true,
  801. value: `${name} Iterator`
  802. },
  803. next: { writable: true, enumerable: true, configurable: true }
  804. })
  805. /**
  806. * @param {unknown} target
  807. * @param {'key' | 'value' | 'key+value'} kind
  808. * @returns {IterableIterator<any>}
  809. */
  810. return function (target, kind) {
  811. return new FastIterableIterator(target, kind)
  812. }
  813. }
  814. /**
  815. * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
  816. * @param {string} name name of the instance
  817. * @param {any} object class
  818. * @param {symbol} kInternalIterator
  819. * @param {string | number} [keyIndex]
  820. * @param {string | number} [valueIndex]
  821. */
  822. function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueIndex = 1) {
  823. const makeIterator = createIterator(name, kInternalIterator, keyIndex, valueIndex)
  824. const properties = {
  825. keys: {
  826. writable: true,
  827. enumerable: true,
  828. configurable: true,
  829. value: function keys () {
  830. webidl.brandCheck(this, object)
  831. return makeIterator(this, 'key')
  832. }
  833. },
  834. values: {
  835. writable: true,
  836. enumerable: true,
  837. configurable: true,
  838. value: function values () {
  839. webidl.brandCheck(this, object)
  840. return makeIterator(this, 'value')
  841. }
  842. },
  843. entries: {
  844. writable: true,
  845. enumerable: true,
  846. configurable: true,
  847. value: function entries () {
  848. webidl.brandCheck(this, object)
  849. return makeIterator(this, 'key+value')
  850. }
  851. },
  852. forEach: {
  853. writable: true,
  854. enumerable: true,
  855. configurable: true,
  856. value: function forEach (callbackfn, thisArg = globalThis) {
  857. webidl.brandCheck(this, object)
  858. webidl.argumentLengthCheck(arguments, 1, `${name}.forEach`)
  859. if (typeof callbackfn !== 'function') {
  860. throw new TypeError(
  861. `Failed to execute 'forEach' on '${name}': parameter 1 is not of type 'Function'.`
  862. )
  863. }
  864. for (const { 0: key, 1: value } of makeIterator(this, 'key+value')) {
  865. callbackfn.call(thisArg, value, key, this)
  866. }
  867. }
  868. }
  869. }
  870. return Object.defineProperties(object.prototype, {
  871. ...properties,
  872. [Symbol.iterator]: {
  873. writable: true,
  874. enumerable: false,
  875. configurable: true,
  876. value: properties.entries.value
  877. }
  878. })
  879. }
  880. /**
  881. * @see https://fetch.spec.whatwg.org/#body-fully-read
  882. */
  883. async function fullyReadBody (body, processBody, processBodyError) {
  884. // 1. If taskDestination is null, then set taskDestination to
  885. // the result of starting a new parallel queue.
  886. // 2. Let successSteps given a byte sequence bytes be to queue a
  887. // fetch task to run processBody given bytes, with taskDestination.
  888. const successSteps = processBody
  889. // 3. Let errorSteps be to queue a fetch task to run processBodyError,
  890. // with taskDestination.
  891. const errorSteps = processBodyError
  892. // 4. Let reader be the result of getting a reader for body’s stream.
  893. // If that threw an exception, then run errorSteps with that
  894. // exception and return.
  895. let reader
  896. try {
  897. reader = body.stream.getReader()
  898. } catch (e) {
  899. errorSteps(e)
  900. return
  901. }
  902. // 5. Read all bytes from reader, given successSteps and errorSteps.
  903. try {
  904. successSteps(await readAllBytes(reader))
  905. } catch (e) {
  906. errorSteps(e)
  907. }
  908. }
  909. function isReadableStreamLike (stream) {
  910. return stream instanceof ReadableStream || (
  911. stream[Symbol.toStringTag] === 'ReadableStream' &&
  912. typeof stream.tee === 'function'
  913. )
  914. }
  915. /**
  916. * @param {ReadableStreamController<Uint8Array>} controller
  917. */
  918. function readableStreamClose (controller) {
  919. try {
  920. controller.close()
  921. controller.byobRequest?.respond(0)
  922. } catch (err) {
  923. // TODO: add comment explaining why this error occurs.
  924. if (!err.message.includes('Controller is already closed') && !err.message.includes('ReadableStream is already closed')) {
  925. throw err
  926. }
  927. }
  928. }
  929. const invalidIsomorphicEncodeValueRegex = /[^\x00-\xFF]/ // eslint-disable-line
  930. /**
  931. * @see https://infra.spec.whatwg.org/#isomorphic-encode
  932. * @param {string} input
  933. */
  934. function isomorphicEncode (input) {
  935. // 1. Assert: input contains no code points greater than U+00FF.
  936. assert(!invalidIsomorphicEncodeValueRegex.test(input))
  937. // 2. Return a byte sequence whose length is equal to input’s code
  938. // point length and whose bytes have the same values as the
  939. // values of input’s code points, in the same order
  940. return input
  941. }
  942. /**
  943. * @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
  944. * @see https://streams.spec.whatwg.org/#read-loop
  945. * @param {ReadableStreamDefaultReader} reader
  946. */
  947. async function readAllBytes (reader) {
  948. const bytes = []
  949. let byteLength = 0
  950. while (true) {
  951. const { done, value: chunk } = await reader.read()
  952. if (done) {
  953. // 1. Call successSteps with bytes.
  954. return Buffer.concat(bytes, byteLength)
  955. }
  956. // 1. If chunk is not a Uint8Array object, call failureSteps
  957. // with a TypeError and abort these steps.
  958. if (!isUint8Array(chunk)) {
  959. throw new TypeError('Received non-Uint8Array chunk')
  960. }
  961. // 2. Append the bytes represented by chunk to bytes.
  962. bytes.push(chunk)
  963. byteLength += chunk.length
  964. // 3. Read-loop given reader, bytes, successSteps, and failureSteps.
  965. }
  966. }
  967. /**
  968. * @see https://fetch.spec.whatwg.org/#is-local
  969. * @param {URL} url
  970. */
  971. function urlIsLocal (url) {
  972. assert('protocol' in url) // ensure it's a url object
  973. const protocol = url.protocol
  974. return protocol === 'about:' || protocol === 'blob:' || protocol === 'data:'
  975. }
  976. /**
  977. * @param {string|URL} url
  978. * @returns {boolean}
  979. */
  980. function urlHasHttpsScheme (url) {
  981. return (
  982. (
  983. typeof url === 'string' &&
  984. url[5] === ':' &&
  985. url[0] === 'h' &&
  986. url[1] === 't' &&
  987. url[2] === 't' &&
  988. url[3] === 'p' &&
  989. url[4] === 's'
  990. ) ||
  991. url.protocol === 'https:'
  992. )
  993. }
  994. /**
  995. * @see https://fetch.spec.whatwg.org/#http-scheme
  996. * @param {URL} url
  997. */
  998. function urlIsHttpHttpsScheme (url) {
  999. assert('protocol' in url) // ensure it's a url object
  1000. const protocol = url.protocol
  1001. return protocol === 'http:' || protocol === 'https:'
  1002. }
  1003. /**
  1004. * @see https://fetch.spec.whatwg.org/#simple-range-header-value
  1005. * @param {string} value
  1006. * @param {boolean} allowWhitespace
  1007. */
  1008. function simpleRangeHeaderValue (value, allowWhitespace) {
  1009. // 1. Let data be the isomorphic decoding of value.
  1010. // Note: isomorphic decoding takes a sequence of bytes (ie. a Uint8Array) and turns it into a string,
  1011. // nothing more. We obviously don't need to do that if value is a string already.
  1012. const data = value
  1013. // 2. If data does not start with "bytes", then return failure.
  1014. if (!data.startsWith('bytes')) {
  1015. return 'failure'
  1016. }
  1017. // 3. Let position be a position variable for data, initially pointing at the 5th code point of data.
  1018. const position = { position: 5 }
  1019. // 4. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space,
  1020. // from data given position.
  1021. if (allowWhitespace) {
  1022. collectASequenceOfCodePoints(
  1023. (char) => char === '\t' || char === ' ',
  1024. data,
  1025. position
  1026. )
  1027. }
  1028. // 5. If the code point at position within data is not U+003D (=), then return failure.
  1029. if (data.charCodeAt(position.position) !== 0x3D) {
  1030. return 'failure'
  1031. }
  1032. // 6. Advance position by 1.
  1033. position.position++
  1034. // 7. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from
  1035. // data given position.
  1036. if (allowWhitespace) {
  1037. collectASequenceOfCodePoints(
  1038. (char) => char === '\t' || char === ' ',
  1039. data,
  1040. position
  1041. )
  1042. }
  1043. // 8. Let rangeStart be the result of collecting a sequence of code points that are ASCII digits,
  1044. // from data given position.
  1045. const rangeStart = collectASequenceOfCodePoints(
  1046. (char) => {
  1047. const code = char.charCodeAt(0)
  1048. return code >= 0x30 && code <= 0x39
  1049. },
  1050. data,
  1051. position
  1052. )
  1053. // 9. Let rangeStartValue be rangeStart, interpreted as decimal number, if rangeStart is not the
  1054. // empty string; otherwise null.
  1055. const rangeStartValue = rangeStart.length ? Number(rangeStart) : null
  1056. // 10. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space,
  1057. // from data given position.
  1058. if (allowWhitespace) {
  1059. collectASequenceOfCodePoints(
  1060. (char) => char === '\t' || char === ' ',
  1061. data,
  1062. position
  1063. )
  1064. }
  1065. // 11. If the code point at position within data is not U+002D (-), then return failure.
  1066. if (data.charCodeAt(position.position) !== 0x2D) {
  1067. return 'failure'
  1068. }
  1069. // 12. Advance position by 1.
  1070. position.position++
  1071. // 13. If allowWhitespace is true, collect a sequence of code points that are HTTP tab
  1072. // or space, from data given position.
  1073. // Note from Khafra: its the same step as in #8 again lol
  1074. if (allowWhitespace) {
  1075. collectASequenceOfCodePoints(
  1076. (char) => char === '\t' || char === ' ',
  1077. data,
  1078. position
  1079. )
  1080. }
  1081. // 14. Let rangeEnd be the result of collecting a sequence of code points that are
  1082. // ASCII digits, from data given position.
  1083. // Note from Khafra: you wouldn't guess it, but this is also the same step as #8
  1084. const rangeEnd = collectASequenceOfCodePoints(
  1085. (char) => {
  1086. const code = char.charCodeAt(0)
  1087. return code >= 0x30 && code <= 0x39
  1088. },
  1089. data,
  1090. position
  1091. )
  1092. // 15. Let rangeEndValue be rangeEnd, interpreted as decimal number, if rangeEnd
  1093. // is not the empty string; otherwise null.
  1094. // Note from Khafra: THE SAME STEP, AGAIN!!!
  1095. // Note: why interpret as a decimal if we only collect ascii digits?
  1096. const rangeEndValue = rangeEnd.length ? Number(rangeEnd) : null
  1097. // 16. If position is not past the end of data, then return failure.
  1098. if (position.position < data.length) {
  1099. return 'failure'
  1100. }
  1101. // 17. If rangeEndValue and rangeStartValue are null, then return failure.
  1102. if (rangeEndValue === null && rangeStartValue === null) {
  1103. return 'failure'
  1104. }
  1105. // 18. If rangeStartValue and rangeEndValue are numbers, and rangeStartValue is
  1106. // greater than rangeEndValue, then return failure.
  1107. // Note: ... when can they not be numbers?
  1108. if (rangeStartValue > rangeEndValue) {
  1109. return 'failure'
  1110. }
  1111. // 19. Return (rangeStartValue, rangeEndValue).
  1112. return { rangeStartValue, rangeEndValue }
  1113. }
  1114. /**
  1115. * @see https://fetch.spec.whatwg.org/#build-a-content-range
  1116. * @param {number} rangeStart
  1117. * @param {number} rangeEnd
  1118. * @param {number} fullLength
  1119. */
  1120. function buildContentRange (rangeStart, rangeEnd, fullLength) {
  1121. // 1. Let contentRange be `bytes `.
  1122. let contentRange = 'bytes '
  1123. // 2. Append rangeStart, serialized and isomorphic encoded, to contentRange.
  1124. contentRange += isomorphicEncode(`${rangeStart}`)
  1125. // 3. Append 0x2D (-) to contentRange.
  1126. contentRange += '-'
  1127. // 4. Append rangeEnd, serialized and isomorphic encoded to contentRange.
  1128. contentRange += isomorphicEncode(`${rangeEnd}`)
  1129. // 5. Append 0x2F (/) to contentRange.
  1130. contentRange += '/'
  1131. // 6. Append fullLength, serialized and isomorphic encoded to contentRange.
  1132. contentRange += isomorphicEncode(`${fullLength}`)
  1133. // 7. Return contentRange.
  1134. return contentRange
  1135. }
  1136. // A Stream, which pipes the response to zlib.createInflate() or
  1137. // zlib.createInflateRaw() depending on the first byte of the Buffer.
  1138. // If the lower byte of the first byte is 0x08, then the stream is
  1139. // interpreted as a zlib stream, otherwise it's interpreted as a
  1140. // raw deflate stream.
  1141. class InflateStream extends Transform {
  1142. #zlibOptions
  1143. /** @param {zlib.ZlibOptions} [zlibOptions] */
  1144. constructor (zlibOptions) {
  1145. super()
  1146. this.#zlibOptions = zlibOptions
  1147. }
  1148. _transform (chunk, encoding, callback) {
  1149. if (!this._inflateStream) {
  1150. if (chunk.length === 0) {
  1151. callback()
  1152. return
  1153. }
  1154. this._inflateStream = (chunk[0] & 0x0F) === 0x08
  1155. ? zlib.createInflate(this.#zlibOptions)
  1156. : zlib.createInflateRaw(this.#zlibOptions)
  1157. this._inflateStream.on('data', this.push.bind(this))
  1158. this._inflateStream.on('end', () => this.push(null))
  1159. this._inflateStream.on('error', (err) => this.destroy(err))
  1160. }
  1161. this._inflateStream.write(chunk, encoding, callback)
  1162. }
  1163. _final (callback) {
  1164. if (this._inflateStream) {
  1165. this._inflateStream.end()
  1166. this._inflateStream = null
  1167. }
  1168. callback()
  1169. }
  1170. }
  1171. /**
  1172. * @param {zlib.ZlibOptions} [zlibOptions]
  1173. * @returns {InflateStream}
  1174. */
  1175. function createInflate (zlibOptions) {
  1176. return new InflateStream(zlibOptions)
  1177. }
  1178. /**
  1179. * @see https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
  1180. * @param {import('./headers').HeadersList} headers
  1181. */
  1182. function extractMimeType (headers) {
  1183. // 1. Let charset be null.
  1184. let charset = null
  1185. // 2. Let essence be null.
  1186. let essence = null
  1187. // 3. Let mimeType be null.
  1188. let mimeType = null
  1189. // 4. Let values be the result of getting, decoding, and splitting `Content-Type` from headers.
  1190. const values = getDecodeSplit('content-type', headers)
  1191. // 5. If values is null, then return failure.
  1192. if (values === null) {
  1193. return 'failure'
  1194. }
  1195. // 6. For each value of values:
  1196. for (const value of values) {
  1197. // 6.1. Let temporaryMimeType be the result of parsing value.
  1198. const temporaryMimeType = parseMIMEType(value)
  1199. // 6.2. If temporaryMimeType is failure or its essence is "*/*", then continue.
  1200. if (temporaryMimeType === 'failure' || temporaryMimeType.essence === '*/*') {
  1201. continue
  1202. }
  1203. // 6.3. Set mimeType to temporaryMimeType.
  1204. mimeType = temporaryMimeType
  1205. // 6.4. If mimeType’s essence is not essence, then:
  1206. if (mimeType.essence !== essence) {
  1207. // 6.4.1. Set charset to null.
  1208. charset = null
  1209. // 6.4.2. If mimeType’s parameters["charset"] exists, then set charset to
  1210. // mimeType’s parameters["charset"].
  1211. if (mimeType.parameters.has('charset')) {
  1212. charset = mimeType.parameters.get('charset')
  1213. }
  1214. // 6.4.3. Set essence to mimeType’s essence.
  1215. essence = mimeType.essence
  1216. } else if (!mimeType.parameters.has('charset') && charset !== null) {
  1217. // 6.5. Otherwise, if mimeType’s parameters["charset"] does not exist, and
  1218. // charset is non-null, set mimeType’s parameters["charset"] to charset.
  1219. mimeType.parameters.set('charset', charset)
  1220. }
  1221. }
  1222. // 7. If mimeType is null, then return failure.
  1223. if (mimeType == null) {
  1224. return 'failure'
  1225. }
  1226. // 8. Return mimeType.
  1227. return mimeType
  1228. }
  1229. /**
  1230. * @see https://fetch.spec.whatwg.org/#header-value-get-decode-and-split
  1231. * @param {string|null} value
  1232. */
  1233. function gettingDecodingSplitting (value) {
  1234. // 1. Let input be the result of isomorphic decoding value.
  1235. const input = value
  1236. // 2. Let position be a position variable for input, initially pointing at the start of input.
  1237. const position = { position: 0 }
  1238. // 3. Let values be a list of strings, initially empty.
  1239. const values = []
  1240. // 4. Let temporaryValue be the empty string.
  1241. let temporaryValue = ''
  1242. // 5. While position is not past the end of input:
  1243. while (position.position < input.length) {
  1244. // 5.1. Append the result of collecting a sequence of code points that are not U+0022 (")
  1245. // or U+002C (,) from input, given position, to temporaryValue.
  1246. temporaryValue += collectASequenceOfCodePoints(
  1247. (char) => char !== '"' && char !== ',',
  1248. input,
  1249. position
  1250. )
  1251. // 5.2. If position is not past the end of input, then:
  1252. if (position.position < input.length) {
  1253. // 5.2.1. If the code point at position within input is U+0022 ("), then:
  1254. if (input.charCodeAt(position.position) === 0x22) {
  1255. // 5.2.1.1. Append the result of collecting an HTTP quoted string from input, given position, to temporaryValue.
  1256. temporaryValue += collectAnHTTPQuotedString(
  1257. input,
  1258. position
  1259. )
  1260. // 5.2.1.2. If position is not past the end of input, then continue.
  1261. if (position.position < input.length) {
  1262. continue
  1263. }
  1264. } else {
  1265. // 5.2.2. Otherwise:
  1266. // 5.2.2.1. Assert: the code point at position within input is U+002C (,).
  1267. assert(input.charCodeAt(position.position) === 0x2C)
  1268. // 5.2.2.2. Advance position by 1.
  1269. position.position++
  1270. }
  1271. }
  1272. // 5.3. Remove all HTTP tab or space from the start and end of temporaryValue.
  1273. temporaryValue = removeChars(temporaryValue, true, true, (char) => char === 0x9 || char === 0x20)
  1274. // 5.4. Append temporaryValue to values.
  1275. values.push(temporaryValue)
  1276. // 5.6. Set temporaryValue to the empty string.
  1277. temporaryValue = ''
  1278. }
  1279. // 6. Return values.
  1280. return values
  1281. }
  1282. /**
  1283. * @see https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split
  1284. * @param {string} name lowercase header name
  1285. * @param {import('./headers').HeadersList} list
  1286. */
  1287. function getDecodeSplit (name, list) {
  1288. // 1. Let value be the result of getting name from list.
  1289. const value = list.get(name, true)
  1290. // 2. If value is null, then return null.
  1291. if (value === null) {
  1292. return null
  1293. }
  1294. // 3. Return the result of getting, decoding, and splitting value.
  1295. return gettingDecodingSplitting(value)
  1296. }
  1297. const textDecoder = new TextDecoder()
  1298. /**
  1299. * @see https://encoding.spec.whatwg.org/#utf-8-decode
  1300. * @param {Buffer} buffer
  1301. */
  1302. function utf8DecodeBytes (buffer) {
  1303. if (buffer.length === 0) {
  1304. return ''
  1305. }
  1306. // 1. Let buffer be the result of peeking three bytes from
  1307. // ioQueue, converted to a byte sequence.
  1308. // 2. If buffer is 0xEF 0xBB 0xBF, then read three
  1309. // bytes from ioQueue. (Do nothing with those bytes.)
  1310. if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
  1311. buffer = buffer.subarray(3)
  1312. }
  1313. // 3. Process a queue with an instance of UTF-8’s
  1314. // decoder, ioQueue, output, and "replacement".
  1315. const output = textDecoder.decode(buffer)
  1316. // 4. Return output.
  1317. return output
  1318. }
  1319. class EnvironmentSettingsObjectBase {
  1320. get baseUrl () {
  1321. return getGlobalOrigin()
  1322. }
  1323. get origin () {
  1324. return this.baseUrl?.origin
  1325. }
  1326. policyContainer = makePolicyContainer()
  1327. }
  1328. class EnvironmentSettingsObject {
  1329. settingsObject = new EnvironmentSettingsObjectBase()
  1330. }
  1331. const environmentSettingsObject = new EnvironmentSettingsObject()
  1332. module.exports = {
  1333. isAborted,
  1334. isCancelled,
  1335. isValidEncodedURL,
  1336. createDeferredPromise,
  1337. ReadableStreamFrom,
  1338. tryUpgradeRequestToAPotentiallyTrustworthyURL,
  1339. clampAndCoarsenConnectionTimingInfo,
  1340. coarsenedSharedCurrentTime,
  1341. determineRequestsReferrer,
  1342. makePolicyContainer,
  1343. clonePolicyContainer,
  1344. appendFetchMetadata,
  1345. appendRequestOriginHeader,
  1346. TAOCheck,
  1347. corsCheck,
  1348. crossOriginResourcePolicyCheck,
  1349. createOpaqueTimingInfo,
  1350. setRequestReferrerPolicyOnRedirect,
  1351. isValidHTTPToken,
  1352. requestBadPort,
  1353. requestCurrentURL,
  1354. responseURL,
  1355. responseLocationURL,
  1356. isBlobLike,
  1357. isURLPotentiallyTrustworthy,
  1358. isValidReasonPhrase,
  1359. sameOrigin,
  1360. normalizeMethod,
  1361. serializeJavascriptValueToJSONString,
  1362. iteratorMixin,
  1363. createIterator,
  1364. isValidHeaderName,
  1365. isValidHeaderValue,
  1366. isErrorLike,
  1367. fullyReadBody,
  1368. bytesMatch,
  1369. isReadableStreamLike,
  1370. readableStreamClose,
  1371. isomorphicEncode,
  1372. urlIsLocal,
  1373. urlHasHttpsScheme,
  1374. urlIsHttpHttpsScheme,
  1375. readAllBytes,
  1376. simpleRangeHeaderValue,
  1377. buildContentRange,
  1378. parseMetadata,
  1379. createInflate,
  1380. extractMimeType,
  1381. getDecodeSplit,
  1382. utf8DecodeBytes,
  1383. environmentSettingsObject
  1384. }