util.js 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520
  1. 'use strict'
  2. const { Transform } = require('node:stream')
  3. const zlib = require('node:zlib')
  4. const { redirectStatusSet, referrerPolicyTokens, badPortsSet } = require('./constants')
  5. const { getGlobalOrigin } = require('./global')
  6. const { collectAnHTTPQuotedString, parseMIMEType } = require('./data-url')
  7. const { performance } = require('node:perf_hooks')
  8. const { 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. const { isomorphicEncode, collectASequenceOfCodePoints, removeChars } = require('../infra')
  13. function responseURL (response) {
  14. // https://fetch.spec.whatwg.org/#responses
  15. // A response has an associated URL. It is a pointer to the last URL
  16. // in response’s URL list and null if response’s URL list is empty.
  17. const urlList = response.urlList
  18. const length = urlList.length
  19. return length === 0 ? null : urlList[length - 1].toString()
  20. }
  21. // https://fetch.spec.whatwg.org/#concept-response-location-url
  22. function responseLocationURL (response, requestFragment) {
  23. // 1. If response’s status is not a redirect status, then return null.
  24. if (!redirectStatusSet.has(response.status)) {
  25. return null
  26. }
  27. // 2. Let location be the result of extracting header list values given
  28. // `Location` and response’s header list.
  29. let location = response.headersList.get('location', true)
  30. // 3. If location is a header value, then set location to the result of
  31. // parsing location with response’s URL.
  32. if (location !== null && isValidHeaderValue(location)) {
  33. if (!isValidEncodedURL(location)) {
  34. // Some websites respond location header in UTF-8 form without encoding them as ASCII
  35. // and major browsers redirect them to correctly UTF-8 encoded addresses.
  36. // Here, we handle that behavior in the same way.
  37. location = normalizeBinaryStringToUtf8(location)
  38. }
  39. location = new URL(location, responseURL(response))
  40. }
  41. // 4. If location is a URL whose fragment is null, then set location’s
  42. // fragment to requestFragment.
  43. if (location && !location.hash) {
  44. location.hash = requestFragment
  45. }
  46. // 5. Return location.
  47. return location
  48. }
  49. /**
  50. * @see https://www.rfc-editor.org/rfc/rfc1738#section-2.2
  51. * @param {string} url
  52. * @returns {boolean}
  53. */
  54. function isValidEncodedURL (url) {
  55. for (let i = 0; i < url.length; ++i) {
  56. const code = url.charCodeAt(i)
  57. if (
  58. code > 0x7E || // Non-US-ASCII + DEL
  59. code < 0x20 // Control characters NUL - US
  60. ) {
  61. return false
  62. }
  63. }
  64. return true
  65. }
  66. /**
  67. * If string contains non-ASCII characters, assumes it's UTF-8 encoded and decodes it.
  68. * Since UTF-8 is a superset of ASCII, this will work for ASCII strings as well.
  69. * @param {string} value
  70. * @returns {string}
  71. */
  72. function normalizeBinaryStringToUtf8 (value) {
  73. return Buffer.from(value, 'binary').toString('utf8')
  74. }
  75. /** @returns {URL} */
  76. function requestCurrentURL (request) {
  77. return request.urlList[request.urlList.length - 1]
  78. }
  79. function requestBadPort (request) {
  80. // 1. Let url be request’s current URL.
  81. const url = requestCurrentURL(request)
  82. // 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port,
  83. // then return blocked.
  84. if (urlIsHttpHttpsScheme(url) && badPortsSet.has(url.port)) {
  85. return 'blocked'
  86. }
  87. // 3. Return allowed.
  88. return 'allowed'
  89. }
  90. function isErrorLike (object) {
  91. return object instanceof Error || (
  92. object?.constructor?.name === 'Error' ||
  93. object?.constructor?.name === 'DOMException'
  94. )
  95. }
  96. // Check whether |statusText| is a ByteString and
  97. // matches the Reason-Phrase token production.
  98. // RFC 2616: https://tools.ietf.org/html/rfc2616
  99. // RFC 7230: https://tools.ietf.org/html/rfc7230
  100. // "reason-phrase = *( HTAB / SP / VCHAR / obs-text )"
  101. // https://github.com/chromium/chromium/blob/94.0.4604.1/third_party/blink/renderer/core/fetch/response.cc#L116
  102. function isValidReasonPhrase (statusText) {
  103. for (let i = 0; i < statusText.length; ++i) {
  104. const c = statusText.charCodeAt(i)
  105. if (
  106. !(
  107. (
  108. c === 0x09 || // HTAB
  109. (c >= 0x20 && c <= 0x7e) || // SP / VCHAR
  110. (c >= 0x80 && c <= 0xff)
  111. ) // obs-text
  112. )
  113. ) {
  114. return false
  115. }
  116. }
  117. return true
  118. }
  119. /**
  120. * @see https://fetch.spec.whatwg.org/#header-name
  121. * @param {string} potentialValue
  122. */
  123. const isValidHeaderName = isValidHTTPToken
  124. /**
  125. * @see https://fetch.spec.whatwg.org/#header-value
  126. * @param {string} potentialValue
  127. */
  128. function isValidHeaderValue (potentialValue) {
  129. // - Has no leading or trailing HTTP tab or space bytes.
  130. // - Contains no 0x00 (NUL) or HTTP newline bytes.
  131. return (
  132. potentialValue[0] === '\t' ||
  133. potentialValue[0] === ' ' ||
  134. potentialValue[potentialValue.length - 1] === '\t' ||
  135. potentialValue[potentialValue.length - 1] === ' ' ||
  136. potentialValue.includes('\n') ||
  137. potentialValue.includes('\r') ||
  138. potentialValue.includes('\0')
  139. ) === false
  140. }
  141. /**
  142. * Parse a referrer policy from a Referrer-Policy header
  143. * @see https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header
  144. */
  145. function parseReferrerPolicy (actualResponse) {
  146. // 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
  147. const policyHeader = (actualResponse.headersList.get('referrer-policy', true) ?? '').split(',')
  148. // 2. Let policy be the empty string.
  149. let policy = ''
  150. // 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.
  151. // Note: As the referrer-policy can contain multiple policies
  152. // separated by comma, we need to loop through all of them
  153. // and pick the first valid one.
  154. // Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#specify_a_fallback_policy
  155. if (policyHeader.length) {
  156. // The right-most policy takes precedence.
  157. // The left-most policy is the fallback.
  158. for (let i = policyHeader.length; i !== 0; i--) {
  159. const token = policyHeader[i - 1].trim()
  160. if (referrerPolicyTokens.has(token)) {
  161. policy = token
  162. break
  163. }
  164. }
  165. }
  166. // 4. Return policy.
  167. return policy
  168. }
  169. /**
  170. * Given a request request and a response actualResponse, this algorithm
  171. * updates request’s referrer policy according to the Referrer-Policy
  172. * header (if any) in actualResponse.
  173. * @see https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
  174. * @param {import('./request').Request} request
  175. * @param {import('./response').Response} actualResponse
  176. */
  177. function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
  178. // 1. Let policy be the result of executing § 8.1 Parse a referrer policy
  179. // from a Referrer-Policy header on actualResponse.
  180. const policy = parseReferrerPolicy(actualResponse)
  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. /**
  329. * Determine request’s Referrer
  330. *
  331. * @see https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
  332. */
  333. function determineRequestsReferrer (request) {
  334. // Given a request request, we can determine the correct referrer information
  335. // to send by examining its referrer policy as detailed in the following
  336. // steps, which return either no referrer or a URL:
  337. // 1. Let policy be request's referrer policy.
  338. const policy = request.referrerPolicy
  339. // Note: policy cannot (shouldn't) be null or an empty string.
  340. assert(policy)
  341. // 2. Let environment be request’s client.
  342. let referrerSource = null
  343. // 3. Switch on request’s referrer:
  344. // "client"
  345. if (request.referrer === 'client') {
  346. // Note: node isn't a browser and doesn't implement document/iframes,
  347. // so we bypass this step and replace it with our own.
  348. const globalOrigin = getGlobalOrigin()
  349. if (!globalOrigin || globalOrigin.origin === 'null') {
  350. return 'no-referrer'
  351. }
  352. // Note: we need to clone it as it's mutated
  353. referrerSource = new URL(globalOrigin)
  354. // a URL
  355. } else if (webidl.is.URL(request.referrer)) {
  356. // Let referrerSource be request’s referrer.
  357. referrerSource = request.referrer
  358. }
  359. // 4. Let request’s referrerURL be the result of stripping referrerSource for
  360. // use as a referrer.
  361. let referrerURL = stripURLForReferrer(referrerSource)
  362. // 5. Let referrerOrigin be the result of stripping referrerSource for use as
  363. // a referrer, with the origin-only flag set to true.
  364. const referrerOrigin = stripURLForReferrer(referrerSource, true)
  365. // 6. If the result of serializing referrerURL is a string whose length is
  366. // greater than 4096, set referrerURL to referrerOrigin.
  367. if (referrerURL.toString().length > 4096) {
  368. referrerURL = referrerOrigin
  369. }
  370. // 7. The user agent MAY alter referrerURL or referrerOrigin at this point
  371. // to enforce arbitrary policy considerations in the interests of minimizing
  372. // data leakage. For example, the user agent could strip the URL down to an
  373. // origin, modify its host, replace it with an empty string, etc.
  374. // 8. Execute the switch statements corresponding to the value of policy:
  375. switch (policy) {
  376. case 'no-referrer':
  377. // Return no referrer
  378. return 'no-referrer'
  379. case 'origin':
  380. // Return referrerOrigin
  381. if (referrerOrigin != null) {
  382. return referrerOrigin
  383. }
  384. return stripURLForReferrer(referrerSource, true)
  385. case 'unsafe-url':
  386. // Return referrerURL.
  387. return referrerURL
  388. case 'strict-origin': {
  389. const currentURL = requestCurrentURL(request)
  390. // 1. If referrerURL is a potentially trustworthy URL and request’s
  391. // current URL is not a potentially trustworthy URL, then return no
  392. // referrer.
  393. if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
  394. return 'no-referrer'
  395. }
  396. // 2. Return referrerOrigin
  397. return referrerOrigin
  398. }
  399. case 'strict-origin-when-cross-origin': {
  400. const currentURL = requestCurrentURL(request)
  401. // 1. If the origin of referrerURL and the origin of request’s current
  402. // URL are the same, then return referrerURL.
  403. if (sameOrigin(referrerURL, currentURL)) {
  404. return referrerURL
  405. }
  406. // 2. If referrerURL is a potentially trustworthy URL and request’s
  407. // current URL is not a potentially trustworthy URL, then return no
  408. // referrer.
  409. if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
  410. return 'no-referrer'
  411. }
  412. // 3. Return referrerOrigin.
  413. return referrerOrigin
  414. }
  415. case 'same-origin':
  416. // 1. If the origin of referrerURL and the origin of request’s current
  417. // URL are the same, then return referrerURL.
  418. if (sameOrigin(request, referrerURL)) {
  419. return referrerURL
  420. }
  421. // 2. Return no referrer.
  422. return 'no-referrer'
  423. case 'origin-when-cross-origin':
  424. // 1. If the origin of referrerURL and the origin of request’s current
  425. // URL are the same, then return referrerURL.
  426. if (sameOrigin(request, referrerURL)) {
  427. return referrerURL
  428. }
  429. // 2. Return referrerOrigin.
  430. return referrerOrigin
  431. case 'no-referrer-when-downgrade': {
  432. const currentURL = requestCurrentURL(request)
  433. // 1. If referrerURL is a potentially trustworthy URL and request’s
  434. // current URL is not a potentially trustworthy URL, then return no
  435. // referrer.
  436. if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
  437. return 'no-referrer'
  438. }
  439. // 2. Return referrerURL.
  440. return referrerURL
  441. }
  442. }
  443. }
  444. /**
  445. * Certain portions of URLs must not be included when sending a URL as the
  446. * value of a `Referer` header: a URLs fragment, username, and password
  447. * components must be stripped from the URL before it’s sent out. This
  448. * algorithm accepts a origin-only flag, which defaults to false. If set to
  449. * true, the algorithm will additionally remove the URL’s path and query
  450. * components, leaving only the scheme, host, and port.
  451. *
  452. * @see https://w3c.github.io/webappsec-referrer-policy/#strip-url
  453. * @param {URL} url
  454. * @param {boolean} [originOnly=false]
  455. */
  456. function stripURLForReferrer (url, originOnly = false) {
  457. // 1. Assert: url is a URL.
  458. assert(webidl.is.URL(url))
  459. // Note: Create a new URL instance to avoid mutating the original URL.
  460. url = new URL(url)
  461. // 2. If url’s scheme is a local scheme, then return no referrer.
  462. if (urlIsLocal(url)) {
  463. return 'no-referrer'
  464. }
  465. // 3. Set url’s username to the empty string.
  466. url.username = ''
  467. // 4. Set url’s password to the empty string.
  468. url.password = ''
  469. // 5. Set url’s fragment to null.
  470. url.hash = ''
  471. // 6. If the origin-only flag is true, then:
  472. if (originOnly === true) {
  473. // 1. Set url’s path to « the empty string ».
  474. url.pathname = ''
  475. // 2. Set url’s query to null.
  476. url.search = ''
  477. }
  478. // 7. Return url.
  479. return url
  480. }
  481. const isPotentialleTrustworthyIPv4 = RegExp.prototype.test
  482. .bind(/^127\.(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){2}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)$/)
  483. const isPotentiallyTrustworthyIPv6 = RegExp.prototype.test
  484. .bind(/^(?:(?:0{1,4}:){7}|(?:0{1,4}:){1,6}:|::)0{0,3}1$/)
  485. /**
  486. * Check if host matches one of the CIDR notations 127.0.0.0/8 or ::1/128.
  487. *
  488. * @param {string} origin
  489. * @returns {boolean}
  490. */
  491. function isOriginIPPotentiallyTrustworthy (origin) {
  492. // IPv6
  493. if (origin.includes(':')) {
  494. // Remove brackets from IPv6 addresses
  495. if (origin[0] === '[' && origin[origin.length - 1] === ']') {
  496. origin = origin.slice(1, -1)
  497. }
  498. return isPotentiallyTrustworthyIPv6(origin)
  499. }
  500. // IPv4
  501. return isPotentialleTrustworthyIPv4(origin)
  502. }
  503. /**
  504. * A potentially trustworthy origin is one which a user agent can generally
  505. * trust as delivering data securely.
  506. *
  507. * Return value `true` means `Potentially Trustworthy`.
  508. * Return value `false` means `Not Trustworthy`.
  509. *
  510. * @see https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
  511. * @param {string} origin
  512. * @returns {boolean}
  513. */
  514. function isOriginPotentiallyTrustworthy (origin) {
  515. // 1. If origin is an opaque origin, return "Not Trustworthy".
  516. if (origin == null || origin === 'null') {
  517. return false
  518. }
  519. // 2. Assert: origin is a tuple origin.
  520. origin = new URL(origin)
  521. // 3. If origin’s scheme is either "https" or "wss",
  522. // return "Potentially Trustworthy".
  523. if (origin.protocol === 'https:' || origin.protocol === 'wss:') {
  524. return true
  525. }
  526. // 4. If origin’s host matches one of the CIDR notations 127.0.0.0/8 or
  527. // ::1/128 [RFC4632], return "Potentially Trustworthy".
  528. if (isOriginIPPotentiallyTrustworthy(origin.hostname)) {
  529. return true
  530. }
  531. // 5. If the user agent conforms to the name resolution rules in
  532. // [let-localhost-be-localhost] and one of the following is true:
  533. // origin’s host is "localhost" or "localhost."
  534. if (origin.hostname === 'localhost' || origin.hostname === 'localhost.') {
  535. return true
  536. }
  537. // origin’s host ends with ".localhost" or ".localhost."
  538. if (origin.hostname.endsWith('.localhost') || origin.hostname.endsWith('.localhost.')) {
  539. return true
  540. }
  541. // 6. If origin’s scheme is "file", return "Potentially Trustworthy".
  542. if (origin.protocol === 'file:') {
  543. return true
  544. }
  545. // 7. If origin’s scheme component is one which the user agent considers to
  546. // be authenticated, return "Potentially Trustworthy".
  547. // 8. If origin has been configured as a trustworthy origin, return
  548. // "Potentially Trustworthy".
  549. // 9. Return "Not Trustworthy".
  550. return false
  551. }
  552. /**
  553. * A potentially trustworthy URL is one which either inherits context from its
  554. * creator (about:blank, about:srcdoc, data) or one whose origin is a
  555. * potentially trustworthy origin.
  556. *
  557. * Return value `true` means `Potentially Trustworthy`.
  558. * Return value `false` means `Not Trustworthy`.
  559. *
  560. * @see https://www.w3.org/TR/secure-contexts/#is-url-trustworthy
  561. * @param {URL} url
  562. * @returns {boolean}
  563. */
  564. function isURLPotentiallyTrustworthy (url) {
  565. // Given a URL record (url), the following algorithm returns "Potentially
  566. // Trustworthy" or "Not Trustworthy" as appropriate:
  567. if (!webidl.is.URL(url)) {
  568. return false
  569. }
  570. // 1. If url is "about:blank" or "about:srcdoc",
  571. // return "Potentially Trustworthy".
  572. if (url.href === 'about:blank' || url.href === 'about:srcdoc') {
  573. return true
  574. }
  575. // 2. If url’s scheme is "data", return "Potentially Trustworthy".
  576. if (url.protocol === 'data:') return true
  577. // Note: The origin of blob: URLs is the origin of the context in which they
  578. // were created. Therefore, blobs created in a trustworthy origin will
  579. // themselves be potentially trustworthy.
  580. if (url.protocol === 'blob:') return true
  581. // 3. Return the result of executing § 3.1 Is origin potentially trustworthy?
  582. // on url’s origin.
  583. return isOriginPotentiallyTrustworthy(url.origin)
  584. }
  585. // https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
  586. function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
  587. // TODO
  588. }
  589. /**
  590. * @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin}
  591. * @param {URL} A
  592. * @param {URL} B
  593. */
  594. function sameOrigin (A, B) {
  595. // 1. If A and B are the same opaque origin, then return true.
  596. if (A.origin === B.origin && A.origin === 'null') {
  597. return true
  598. }
  599. // 2. If A and B are both tuple origins and their schemes,
  600. // hosts, and port are identical, then return true.
  601. if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) {
  602. return true
  603. }
  604. // 3. Return false.
  605. return false
  606. }
  607. function isAborted (fetchParams) {
  608. return fetchParams.controller.state === 'aborted'
  609. }
  610. function isCancelled (fetchParams) {
  611. return fetchParams.controller.state === 'aborted' ||
  612. fetchParams.controller.state === 'terminated'
  613. }
  614. /**
  615. * @see https://fetch.spec.whatwg.org/#concept-method-normalize
  616. * @param {string} method
  617. */
  618. function normalizeMethod (method) {
  619. return normalizedMethodRecordsBase[method.toLowerCase()] ?? method
  620. }
  621. // https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
  622. const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
  623. /**
  624. * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
  625. * @param {string} name name of the instance
  626. * @param {((target: any) => any)} kInternalIterator
  627. * @param {string | number} [keyIndex]
  628. * @param {string | number} [valueIndex]
  629. */
  630. function createIterator (name, kInternalIterator, keyIndex = 0, valueIndex = 1) {
  631. class FastIterableIterator {
  632. /** @type {any} */
  633. #target
  634. /** @type {'key' | 'value' | 'key+value'} */
  635. #kind
  636. /** @type {number} */
  637. #index
  638. /**
  639. * @see https://webidl.spec.whatwg.org/#dfn-default-iterator-object
  640. * @param {unknown} target
  641. * @param {'key' | 'value' | 'key+value'} kind
  642. */
  643. constructor (target, kind) {
  644. this.#target = target
  645. this.#kind = kind
  646. this.#index = 0
  647. }
  648. next () {
  649. // 1. Let interface be the interface for which the iterator prototype object exists.
  650. // 2. Let thisValue be the this value.
  651. // 3. Let object be ? ToObject(thisValue).
  652. // 4. If object is a platform object, then perform a security
  653. // check, passing:
  654. // 5. If object is not a default iterator object for interface,
  655. // then throw a TypeError.
  656. if (typeof this !== 'object' || this === null || !(#target in this)) {
  657. throw new TypeError(
  658. `'next' called on an object that does not implement interface ${name} Iterator.`
  659. )
  660. }
  661. // 6. Let index be object’s index.
  662. // 7. Let kind be object’s kind.
  663. // 8. Let values be object’s target's value pairs to iterate over.
  664. const index = this.#index
  665. const values = kInternalIterator(this.#target)
  666. // 9. Let len be the length of values.
  667. const len = values.length
  668. // 10. If index is greater than or equal to len, then return
  669. // CreateIterResultObject(undefined, true).
  670. if (index >= len) {
  671. return {
  672. value: undefined,
  673. done: true
  674. }
  675. }
  676. // 11. Let pair be the entry in values at index index.
  677. const { [keyIndex]: key, [valueIndex]: value } = values[index]
  678. // 12. Set object’s index to index + 1.
  679. this.#index = index + 1
  680. // 13. Return the iterator result for pair and kind.
  681. // https://webidl.spec.whatwg.org/#iterator-result
  682. // 1. Let result be a value determined by the value of kind:
  683. let result
  684. switch (this.#kind) {
  685. case 'key':
  686. // 1. Let idlKey be pair’s key.
  687. // 2. Let key be the result of converting idlKey to an
  688. // ECMAScript value.
  689. // 3. result is key.
  690. result = key
  691. break
  692. case 'value':
  693. // 1. Let idlValue be pair’s value.
  694. // 2. Let value be the result of converting idlValue to
  695. // an ECMAScript value.
  696. // 3. result is value.
  697. result = value
  698. break
  699. case 'key+value':
  700. // 1. Let idlKey be pair’s key.
  701. // 2. Let idlValue be pair’s value.
  702. // 3. Let key be the result of converting idlKey to an
  703. // ECMAScript value.
  704. // 4. Let value be the result of converting idlValue to
  705. // an ECMAScript value.
  706. // 5. Let array be ! ArrayCreate(2).
  707. // 6. Call ! CreateDataProperty(array, "0", key).
  708. // 7. Call ! CreateDataProperty(array, "1", value).
  709. // 8. result is array.
  710. result = [key, value]
  711. break
  712. }
  713. // 2. Return CreateIterResultObject(result, false).
  714. return {
  715. value: result,
  716. done: false
  717. }
  718. }
  719. }
  720. // https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
  721. // @ts-ignore
  722. delete FastIterableIterator.prototype.constructor
  723. Object.setPrototypeOf(FastIterableIterator.prototype, esIteratorPrototype)
  724. Object.defineProperties(FastIterableIterator.prototype, {
  725. [Symbol.toStringTag]: {
  726. writable: false,
  727. enumerable: false,
  728. configurable: true,
  729. value: `${name} Iterator`
  730. },
  731. next: { writable: true, enumerable: true, configurable: true }
  732. })
  733. /**
  734. * @param {unknown} target
  735. * @param {'key' | 'value' | 'key+value'} kind
  736. * @returns {IterableIterator<any>}
  737. */
  738. return function (target, kind) {
  739. return new FastIterableIterator(target, kind)
  740. }
  741. }
  742. /**
  743. * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
  744. * @param {string} name name of the instance
  745. * @param {any} object class
  746. * @param {(target: any) => any} kInternalIterator
  747. * @param {string | number} [keyIndex]
  748. * @param {string | number} [valueIndex]
  749. */
  750. function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueIndex = 1) {
  751. const makeIterator = createIterator(name, kInternalIterator, keyIndex, valueIndex)
  752. const properties = {
  753. keys: {
  754. writable: true,
  755. enumerable: true,
  756. configurable: true,
  757. value: function keys () {
  758. webidl.brandCheck(this, object)
  759. return makeIterator(this, 'key')
  760. }
  761. },
  762. values: {
  763. writable: true,
  764. enumerable: true,
  765. configurable: true,
  766. value: function values () {
  767. webidl.brandCheck(this, object)
  768. return makeIterator(this, 'value')
  769. }
  770. },
  771. entries: {
  772. writable: true,
  773. enumerable: true,
  774. configurable: true,
  775. value: function entries () {
  776. webidl.brandCheck(this, object)
  777. return makeIterator(this, 'key+value')
  778. }
  779. },
  780. forEach: {
  781. writable: true,
  782. enumerable: true,
  783. configurable: true,
  784. value: function forEach (callbackfn, thisArg = globalThis) {
  785. webidl.brandCheck(this, object)
  786. webidl.argumentLengthCheck(arguments, 1, `${name}.forEach`)
  787. if (typeof callbackfn !== 'function') {
  788. throw new TypeError(
  789. `Failed to execute 'forEach' on '${name}': parameter 1 is not of type 'Function'.`
  790. )
  791. }
  792. for (const { 0: key, 1: value } of makeIterator(this, 'key+value')) {
  793. callbackfn.call(thisArg, value, key, this)
  794. }
  795. }
  796. }
  797. }
  798. return Object.defineProperties(object.prototype, {
  799. ...properties,
  800. [Symbol.iterator]: {
  801. writable: true,
  802. enumerable: false,
  803. configurable: true,
  804. value: properties.entries.value
  805. }
  806. })
  807. }
  808. /**
  809. * @param {import('./body').ExtractBodyResult} body
  810. * @param {(bytes: Uint8Array) => void} processBody
  811. * @param {(error: Error) => void} processBodyError
  812. * @returns {void}
  813. *
  814. * @see https://fetch.spec.whatwg.org/#body-fully-read
  815. */
  816. function fullyReadBody (body, processBody, processBodyError) {
  817. // 1. If taskDestination is null, then set taskDestination to
  818. // the result of starting a new parallel queue.
  819. // 2. Let successSteps given a byte sequence bytes be to queue a
  820. // fetch task to run processBody given bytes, with taskDestination.
  821. const successSteps = processBody
  822. // 3. Let errorSteps be to queue a fetch task to run processBodyError,
  823. // with taskDestination.
  824. const errorSteps = processBodyError
  825. try {
  826. // 4. Let reader be the result of getting a reader for body’s stream.
  827. // If that threw an exception, then run errorSteps with that
  828. // exception and return.
  829. const reader = body.stream.getReader()
  830. // 5. Read all bytes from reader, given successSteps and errorSteps.
  831. readAllBytes(reader, successSteps, errorSteps)
  832. } catch (e) {
  833. errorSteps(e)
  834. }
  835. }
  836. /**
  837. * @param {ReadableStreamController<Uint8Array>} controller
  838. */
  839. function readableStreamClose (controller) {
  840. try {
  841. controller.close()
  842. controller.byobRequest?.respond(0)
  843. } catch (err) {
  844. // TODO: add comment explaining why this error occurs.
  845. if (!err.message.includes('Controller is already closed') && !err.message.includes('ReadableStream is already closed')) {
  846. throw err
  847. }
  848. }
  849. }
  850. /**
  851. * @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
  852. * @see https://streams.spec.whatwg.org/#read-loop
  853. * @param {ReadableStream<Uint8Array<ArrayBuffer>>} reader
  854. * @param {(bytes: Uint8Array) => void} successSteps
  855. * @param {(error: Error) => void} failureSteps
  856. * @returns {Promise<void>}
  857. */
  858. async function readAllBytes (reader, successSteps, failureSteps) {
  859. try {
  860. const bytes = []
  861. let byteLength = 0
  862. do {
  863. const { done, value: chunk } = await reader.read()
  864. if (done) {
  865. // 1. Call successSteps with bytes.
  866. successSteps(Buffer.concat(bytes, byteLength))
  867. return
  868. }
  869. // 1. If chunk is not a Uint8Array object, call failureSteps
  870. // with a TypeError and abort these steps.
  871. if (!isUint8Array(chunk)) {
  872. failureSteps(new TypeError('Received non-Uint8Array chunk'))
  873. return
  874. }
  875. // 2. Append the bytes represented by chunk to bytes.
  876. bytes.push(chunk)
  877. byteLength += chunk.length
  878. // 3. Read-loop given reader, bytes, successSteps, and failureSteps.
  879. } while (true)
  880. } catch (e) {
  881. // 1. Call failureSteps with e.
  882. failureSteps(e)
  883. }
  884. }
  885. /**
  886. * @see https://fetch.spec.whatwg.org/#is-local
  887. * @param {URL} url
  888. * @returns {boolean}
  889. */
  890. function urlIsLocal (url) {
  891. assert('protocol' in url) // ensure it's a url object
  892. const protocol = url.protocol
  893. // A URL is local if its scheme is a local scheme.
  894. // A local scheme is "about", "blob", or "data".
  895. return protocol === 'about:' || protocol === 'blob:' || protocol === 'data:'
  896. }
  897. /**
  898. * @param {string|URL} url
  899. * @returns {boolean}
  900. */
  901. function urlHasHttpsScheme (url) {
  902. return (
  903. (
  904. typeof url === 'string' &&
  905. url[5] === ':' &&
  906. url[0] === 'h' &&
  907. url[1] === 't' &&
  908. url[2] === 't' &&
  909. url[3] === 'p' &&
  910. url[4] === 's'
  911. ) ||
  912. url.protocol === 'https:'
  913. )
  914. }
  915. /**
  916. * @see https://fetch.spec.whatwg.org/#http-scheme
  917. * @param {URL} url
  918. */
  919. function urlIsHttpHttpsScheme (url) {
  920. assert('protocol' in url) // ensure it's a url object
  921. const protocol = url.protocol
  922. return protocol === 'http:' || protocol === 'https:'
  923. }
  924. /**
  925. * @typedef {Object} RangeHeaderValue
  926. * @property {number|null} rangeStartValue
  927. * @property {number|null} rangeEndValue
  928. */
  929. /**
  930. * @see https://fetch.spec.whatwg.org/#simple-range-header-value
  931. * @param {string} value
  932. * @param {boolean} allowWhitespace
  933. * @return {RangeHeaderValue|'failure'}
  934. */
  935. function simpleRangeHeaderValue (value, allowWhitespace) {
  936. // 1. Let data be the isomorphic decoding of value.
  937. // Note: isomorphic decoding takes a sequence of bytes (ie. a Uint8Array) and turns it into a string,
  938. // nothing more. We obviously don't need to do that if value is a string already.
  939. const data = value
  940. // 2. If data does not start with "bytes", then return failure.
  941. if (!data.startsWith('bytes')) {
  942. return 'failure'
  943. }
  944. // 3. Let position be a position variable for data, initially pointing at the 5th code point of data.
  945. const position = { position: 5 }
  946. // 4. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space,
  947. // from data given position.
  948. if (allowWhitespace) {
  949. collectASequenceOfCodePoints(
  950. (char) => char === '\t' || char === ' ',
  951. data,
  952. position
  953. )
  954. }
  955. // 5. If the code point at position within data is not U+003D (=), then return failure.
  956. if (data.charCodeAt(position.position) !== 0x3D) {
  957. return 'failure'
  958. }
  959. // 6. Advance position by 1.
  960. position.position++
  961. // 7. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from
  962. // data given position.
  963. if (allowWhitespace) {
  964. collectASequenceOfCodePoints(
  965. (char) => char === '\t' || char === ' ',
  966. data,
  967. position
  968. )
  969. }
  970. // 8. Let rangeStart be the result of collecting a sequence of code points that are ASCII digits,
  971. // from data given position.
  972. const rangeStart = collectASequenceOfCodePoints(
  973. (char) => {
  974. const code = char.charCodeAt(0)
  975. return code >= 0x30 && code <= 0x39
  976. },
  977. data,
  978. position
  979. )
  980. // 9. Let rangeStartValue be rangeStart, interpreted as decimal number, if rangeStart is not the
  981. // empty string; otherwise null.
  982. const rangeStartValue = rangeStart.length ? Number(rangeStart) : null
  983. // 10. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space,
  984. // from data given position.
  985. if (allowWhitespace) {
  986. collectASequenceOfCodePoints(
  987. (char) => char === '\t' || char === ' ',
  988. data,
  989. position
  990. )
  991. }
  992. // 11. If the code point at position within data is not U+002D (-), then return failure.
  993. if (data.charCodeAt(position.position) !== 0x2D) {
  994. return 'failure'
  995. }
  996. // 12. Advance position by 1.
  997. position.position++
  998. // 13. If allowWhitespace is true, collect a sequence of code points that are HTTP tab
  999. // or space, from data given position.
  1000. // Note from Khafra: its the same step as in #8 again lol
  1001. if (allowWhitespace) {
  1002. collectASequenceOfCodePoints(
  1003. (char) => char === '\t' || char === ' ',
  1004. data,
  1005. position
  1006. )
  1007. }
  1008. // 14. Let rangeEnd be the result of collecting a sequence of code points that are
  1009. // ASCII digits, from data given position.
  1010. // Note from Khafra: you wouldn't guess it, but this is also the same step as #8
  1011. const rangeEnd = collectASequenceOfCodePoints(
  1012. (char) => {
  1013. const code = char.charCodeAt(0)
  1014. return code >= 0x30 && code <= 0x39
  1015. },
  1016. data,
  1017. position
  1018. )
  1019. // 15. Let rangeEndValue be rangeEnd, interpreted as decimal number, if rangeEnd
  1020. // is not the empty string; otherwise null.
  1021. // Note from Khafra: THE SAME STEP, AGAIN!!!
  1022. // Note: why interpret as a decimal if we only collect ascii digits?
  1023. const rangeEndValue = rangeEnd.length ? Number(rangeEnd) : null
  1024. // 16. If position is not past the end of data, then return failure.
  1025. if (position.position < data.length) {
  1026. return 'failure'
  1027. }
  1028. // 17. If rangeEndValue and rangeStartValue are null, then return failure.
  1029. if (rangeEndValue === null && rangeStartValue === null) {
  1030. return 'failure'
  1031. }
  1032. // 18. If rangeStartValue and rangeEndValue are numbers, and rangeStartValue is
  1033. // greater than rangeEndValue, then return failure.
  1034. // Note: ... when can they not be numbers?
  1035. if (rangeStartValue > rangeEndValue) {
  1036. return 'failure'
  1037. }
  1038. // 19. Return (rangeStartValue, rangeEndValue).
  1039. return { rangeStartValue, rangeEndValue }
  1040. }
  1041. /**
  1042. * @see https://fetch.spec.whatwg.org/#build-a-content-range
  1043. * @param {number} rangeStart
  1044. * @param {number} rangeEnd
  1045. * @param {number} fullLength
  1046. */
  1047. function buildContentRange (rangeStart, rangeEnd, fullLength) {
  1048. // 1. Let contentRange be `bytes `.
  1049. let contentRange = 'bytes '
  1050. // 2. Append rangeStart, serialized and isomorphic encoded, to contentRange.
  1051. contentRange += isomorphicEncode(`${rangeStart}`)
  1052. // 3. Append 0x2D (-) to contentRange.
  1053. contentRange += '-'
  1054. // 4. Append rangeEnd, serialized and isomorphic encoded to contentRange.
  1055. contentRange += isomorphicEncode(`${rangeEnd}`)
  1056. // 5. Append 0x2F (/) to contentRange.
  1057. contentRange += '/'
  1058. // 6. Append fullLength, serialized and isomorphic encoded to contentRange.
  1059. contentRange += isomorphicEncode(`${fullLength}`)
  1060. // 7. Return contentRange.
  1061. return contentRange
  1062. }
  1063. // A Stream, which pipes the response to zlib.createInflate() or
  1064. // zlib.createInflateRaw() depending on the first byte of the Buffer.
  1065. // If the lower byte of the first byte is 0x08, then the stream is
  1066. // interpreted as a zlib stream, otherwise it's interpreted as a
  1067. // raw deflate stream.
  1068. class InflateStream extends Transform {
  1069. #zlibOptions
  1070. /** @param {zlib.ZlibOptions} [zlibOptions] */
  1071. constructor (zlibOptions) {
  1072. super()
  1073. this.#zlibOptions = zlibOptions
  1074. }
  1075. _transform (chunk, encoding, callback) {
  1076. if (!this._inflateStream) {
  1077. if (chunk.length === 0) {
  1078. callback()
  1079. return
  1080. }
  1081. this._inflateStream = (chunk[0] & 0x0F) === 0x08
  1082. ? zlib.createInflate(this.#zlibOptions)
  1083. : zlib.createInflateRaw(this.#zlibOptions)
  1084. this._inflateStream.on('data', this.push.bind(this))
  1085. this._inflateStream.on('end', () => this.push(null))
  1086. this._inflateStream.on('error', (err) => this.destroy(err))
  1087. }
  1088. this._inflateStream.write(chunk, encoding, callback)
  1089. }
  1090. _final (callback) {
  1091. if (this._inflateStream) {
  1092. this._inflateStream.end()
  1093. this._inflateStream = null
  1094. }
  1095. callback()
  1096. }
  1097. }
  1098. /**
  1099. * @param {zlib.ZlibOptions} [zlibOptions]
  1100. * @returns {InflateStream}
  1101. */
  1102. function createInflate (zlibOptions) {
  1103. return new InflateStream(zlibOptions)
  1104. }
  1105. /**
  1106. * @see https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
  1107. * @param {import('./headers').HeadersList} headers
  1108. */
  1109. function extractMimeType (headers) {
  1110. // 1. Let charset be null.
  1111. let charset = null
  1112. // 2. Let essence be null.
  1113. let essence = null
  1114. // 3. Let mimeType be null.
  1115. let mimeType = null
  1116. // 4. Let values be the result of getting, decoding, and splitting `Content-Type` from headers.
  1117. const values = getDecodeSplit('content-type', headers)
  1118. // 5. If values is null, then return failure.
  1119. if (values === null) {
  1120. return 'failure'
  1121. }
  1122. // 6. For each value of values:
  1123. for (const value of values) {
  1124. // 6.1. Let temporaryMimeType be the result of parsing value.
  1125. const temporaryMimeType = parseMIMEType(value)
  1126. // 6.2. If temporaryMimeType is failure or its essence is "*/*", then continue.
  1127. if (temporaryMimeType === 'failure' || temporaryMimeType.essence === '*/*') {
  1128. continue
  1129. }
  1130. // 6.3. Set mimeType to temporaryMimeType.
  1131. mimeType = temporaryMimeType
  1132. // 6.4. If mimeType’s essence is not essence, then:
  1133. if (mimeType.essence !== essence) {
  1134. // 6.4.1. Set charset to null.
  1135. charset = null
  1136. // 6.4.2. If mimeType’s parameters["charset"] exists, then set charset to
  1137. // mimeType’s parameters["charset"].
  1138. if (mimeType.parameters.has('charset')) {
  1139. charset = mimeType.parameters.get('charset')
  1140. }
  1141. // 6.4.3. Set essence to mimeType’s essence.
  1142. essence = mimeType.essence
  1143. } else if (!mimeType.parameters.has('charset') && charset !== null) {
  1144. // 6.5. Otherwise, if mimeType’s parameters["charset"] does not exist, and
  1145. // charset is non-null, set mimeType’s parameters["charset"] to charset.
  1146. mimeType.parameters.set('charset', charset)
  1147. }
  1148. }
  1149. // 7. If mimeType is null, then return failure.
  1150. if (mimeType == null) {
  1151. return 'failure'
  1152. }
  1153. // 8. Return mimeType.
  1154. return mimeType
  1155. }
  1156. /**
  1157. * @see https://fetch.spec.whatwg.org/#header-value-get-decode-and-split
  1158. * @param {string|null} value
  1159. */
  1160. function gettingDecodingSplitting (value) {
  1161. // 1. Let input be the result of isomorphic decoding value.
  1162. const input = value
  1163. // 2. Let position be a position variable for input, initially pointing at the start of input.
  1164. const position = { position: 0 }
  1165. // 3. Let values be a list of strings, initially empty.
  1166. const values = []
  1167. // 4. Let temporaryValue be the empty string.
  1168. let temporaryValue = ''
  1169. // 5. While position is not past the end of input:
  1170. while (position.position < input.length) {
  1171. // 5.1. Append the result of collecting a sequence of code points that are not U+0022 (")
  1172. // or U+002C (,) from input, given position, to temporaryValue.
  1173. temporaryValue += collectASequenceOfCodePoints(
  1174. (char) => char !== '"' && char !== ',',
  1175. input,
  1176. position
  1177. )
  1178. // 5.2. If position is not past the end of input, then:
  1179. if (position.position < input.length) {
  1180. // 5.2.1. If the code point at position within input is U+0022 ("), then:
  1181. if (input.charCodeAt(position.position) === 0x22) {
  1182. // 5.2.1.1. Append the result of collecting an HTTP quoted string from input, given position, to temporaryValue.
  1183. temporaryValue += collectAnHTTPQuotedString(
  1184. input,
  1185. position
  1186. )
  1187. // 5.2.1.2. If position is not past the end of input, then continue.
  1188. if (position.position < input.length) {
  1189. continue
  1190. }
  1191. } else {
  1192. // 5.2.2. Otherwise:
  1193. // 5.2.2.1. Assert: the code point at position within input is U+002C (,).
  1194. assert(input.charCodeAt(position.position) === 0x2C)
  1195. // 5.2.2.2. Advance position by 1.
  1196. position.position++
  1197. }
  1198. }
  1199. // 5.3. Remove all HTTP tab or space from the start and end of temporaryValue.
  1200. temporaryValue = removeChars(temporaryValue, true, true, (char) => char === 0x9 || char === 0x20)
  1201. // 5.4. Append temporaryValue to values.
  1202. values.push(temporaryValue)
  1203. // 5.6. Set temporaryValue to the empty string.
  1204. temporaryValue = ''
  1205. }
  1206. // 6. Return values.
  1207. return values
  1208. }
  1209. /**
  1210. * @see https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split
  1211. * @param {string} name lowercase header name
  1212. * @param {import('./headers').HeadersList} list
  1213. */
  1214. function getDecodeSplit (name, list) {
  1215. // 1. Let value be the result of getting name from list.
  1216. const value = list.get(name, true)
  1217. // 2. If value is null, then return null.
  1218. if (value === null) {
  1219. return null
  1220. }
  1221. // 3. Return the result of getting, decoding, and splitting value.
  1222. return gettingDecodingSplitting(value)
  1223. }
  1224. function hasAuthenticationEntry (request) {
  1225. return false
  1226. }
  1227. /**
  1228. * @see https://url.spec.whatwg.org/#include-credentials
  1229. * @param {URL} url
  1230. */
  1231. function includesCredentials (url) {
  1232. // A URL includes credentials if its username or password is not the empty string.
  1233. return !!(url.username || url.password)
  1234. }
  1235. /**
  1236. * @see https://html.spec.whatwg.org/multipage/document-sequences.html#traversable-navigable
  1237. * @param {object|string} navigable
  1238. */
  1239. function isTraversableNavigable (navigable) {
  1240. // TODO
  1241. return true
  1242. }
  1243. class EnvironmentSettingsObjectBase {
  1244. get baseUrl () {
  1245. return getGlobalOrigin()
  1246. }
  1247. get origin () {
  1248. return this.baseUrl?.origin
  1249. }
  1250. policyContainer = makePolicyContainer()
  1251. }
  1252. class EnvironmentSettingsObject {
  1253. settingsObject = new EnvironmentSettingsObjectBase()
  1254. }
  1255. const environmentSettingsObject = new EnvironmentSettingsObject()
  1256. module.exports = {
  1257. isAborted,
  1258. isCancelled,
  1259. isValidEncodedURL,
  1260. ReadableStreamFrom,
  1261. tryUpgradeRequestToAPotentiallyTrustworthyURL,
  1262. clampAndCoarsenConnectionTimingInfo,
  1263. coarsenedSharedCurrentTime,
  1264. determineRequestsReferrer,
  1265. makePolicyContainer,
  1266. clonePolicyContainer,
  1267. appendFetchMetadata,
  1268. appendRequestOriginHeader,
  1269. TAOCheck,
  1270. corsCheck,
  1271. crossOriginResourcePolicyCheck,
  1272. createOpaqueTimingInfo,
  1273. setRequestReferrerPolicyOnRedirect,
  1274. isValidHTTPToken,
  1275. requestBadPort,
  1276. requestCurrentURL,
  1277. responseURL,
  1278. responseLocationURL,
  1279. isURLPotentiallyTrustworthy,
  1280. isValidReasonPhrase,
  1281. sameOrigin,
  1282. normalizeMethod,
  1283. iteratorMixin,
  1284. createIterator,
  1285. isValidHeaderName,
  1286. isValidHeaderValue,
  1287. isErrorLike,
  1288. fullyReadBody,
  1289. readableStreamClose,
  1290. urlIsLocal,
  1291. urlHasHttpsScheme,
  1292. urlIsHttpHttpsScheme,
  1293. readAllBytes,
  1294. simpleRangeHeaderValue,
  1295. buildContentRange,
  1296. createInflate,
  1297. extractMimeType,
  1298. getDecodeSplit,
  1299. environmentSettingsObject,
  1300. isOriginIPPotentiallyTrustworthy,
  1301. hasAuthenticationEntry,
  1302. includesCredentials,
  1303. isTraversableNavigable
  1304. }