@fedify/fedify 2.4.0-pr.936.41 → 2.5.0-dev.2271

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +50 -39
  2. package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
  3. package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
  4. package/dist/assert_exists-cffSpJ7X.mjs +28 -0
  5. package/dist/assert_false-CPdRnc3E.mjs +23 -0
  6. package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
  7. package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
  8. package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
  9. package/dist/assert_rejects-CTptdYck.mjs +39 -0
  10. package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
  11. package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
  12. package/dist/assertion_error-B-cx0fFA.mjs +31 -0
  13. package/dist/audience-CHQ-FlLD.mjs +95 -0
  14. package/dist/{builder-DonuxRl_.mjs → builder-Bda6LK7A.mjs} +169 -14
  15. package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
  16. package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
  17. package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
  18. package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
  19. package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
  20. package/dist/compat/mod.cjs +1 -1
  21. package/dist/compat/mod.d.cts +1 -1
  22. package/dist/compat/mod.d.ts +1 -1
  23. package/dist/compat/mod.js +1 -1
  24. package/dist/compat/outgoing-jsonld.test.mjs +71 -3
  25. package/dist/compat/public-audience.test.mjs +14 -5
  26. package/dist/compat/secured-document.test.d.mts +2 -0
  27. package/dist/compat/secured-document.test.mjs +58 -0
  28. package/dist/compat/transformers.test.mjs +5 -5
  29. package/dist/compound-proof-B-9mXxFW.mjs +989 -0
  30. package/dist/{context-Zii1f5xS.d.ts → context-7NyiqIcg.d.ts} +1600 -25
  31. package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
  32. package/dist/{context-lxX6I7as.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
  33. package/dist/{deno-BHzKn9Ig.mjs → deno-oR5aJKpU.mjs} +1 -1
  34. package/dist/dist-DJQM4u7i.mjs +189 -0
  35. package/dist/docloader-DBoYbvYW.mjs +87 -0
  36. package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
  37. package/dist/federation/audience.test.d.mts +2 -0
  38. package/dist/federation/audience.test.mjs +130 -0
  39. package/dist/federation/builder.test.mjs +54 -5
  40. package/dist/federation/circuit-breaker.test.mjs +838 -7
  41. package/dist/federation/collection.test.mjs +3 -3
  42. package/dist/federation/compound-fanout.test.d.mts +2 -0
  43. package/dist/federation/compound-fanout.test.mjs +228 -0
  44. package/dist/federation/handler-compound-proof.test.d.mts +2 -0
  45. package/dist/federation/handler-compound-proof.test.mjs +384 -0
  46. package/dist/federation/handler.test.mjs +385 -15
  47. package/dist/federation/hashlink-media.test.d.mts +2 -0
  48. package/dist/federation/hashlink-media.test.mjs +422 -0
  49. package/dist/federation/idempotency.test.mjs +6 -6
  50. package/dist/federation/inbox-report.test.d.mts +2 -0
  51. package/dist/federation/inbox-report.test.mjs +475 -0
  52. package/dist/federation/inbox.test.mjs +3 -3
  53. package/dist/federation/keycache.test.mjs +215 -14
  54. package/dist/federation/kv.test.mjs +3 -3
  55. package/dist/federation/metrics.test.mjs +5 -5
  56. package/dist/federation/middleware.test.mjs +967 -35
  57. package/dist/federation/mod.cjs +18 -261
  58. package/dist/federation/mod.d.cts +5 -5
  59. package/dist/federation/mod.d.ts +5 -5
  60. package/dist/federation/mod.js +279 -1
  61. package/dist/federation/mq.test.mjs +123 -47
  62. package/dist/federation/negotiation.test.mjs +4 -4
  63. package/dist/federation/outgoing-proof.test.d.mts +2 -0
  64. package/dist/federation/outgoing-proof.test.mjs +931 -0
  65. package/dist/federation/portable-actor.test.d.mts +2 -0
  66. package/dist/federation/portable-actor.test.mjs +496 -0
  67. package/dist/federation/portable-chain.test.d.mts +2 -0
  68. package/dist/federation/portable-chain.test.mjs +370 -0
  69. package/dist/federation/portable-collection.test.d.mts +2 -0
  70. package/dist/federation/portable-collection.test.mjs +832 -0
  71. package/dist/federation/portable-delivery.test.d.mts +2 -0
  72. package/dist/federation/portable-delivery.test.mjs +746 -0
  73. package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
  74. package/dist/federation/portable-gateway-keys.test.mjs +686 -0
  75. package/dist/federation/portable-inbox.test.d.mts +2 -0
  76. package/dist/federation/portable-inbox.test.mjs +1535 -0
  77. package/dist/federation/portable-parse-uri.test.d.mts +2 -0
  78. package/dist/federation/portable-parse-uri.test.mjs +348 -0
  79. package/dist/federation/portable-route-activity.test.d.mts +2 -0
  80. package/dist/federation/portable-route-activity.test.mjs +259 -0
  81. package/dist/federation/portable.test.d.mts +2 -0
  82. package/dist/federation/portable.test.mjs +1072 -0
  83. package/dist/federation/retry.test.mjs +4 -3
  84. package/dist/federation/router.test.mjs +9 -159
  85. package/dist/federation/send.test.mjs +245 -27
  86. package/dist/federation/tasks/codec.test.mjs +4 -4
  87. package/dist/federation/tasks/enqueue.test.mjs +42 -4
  88. package/dist/federation/tasks/tasks.test.mjs +7 -6
  89. package/dist/federation/temporal.test.mjs +4 -4
  90. package/dist/federation/webfinger.test.mjs +275 -12
  91. package/dist/{middleware-FrSMAYEx.cjs → federation-CkZiAcWY.cjs} +8640 -3722
  92. package/dist/http-B5JT115n.cjs +10630 -0
  93. package/dist/http-DydWCB7R.js +6527 -0
  94. package/dist/{http-CPVMUgsW.mjs → http-siciWkmx.mjs} +397 -168
  95. package/dist/key-BspPiXqn.mjs +951 -0
  96. package/dist/key-C1nv7_jD.d.ts +1125 -0
  97. package/dist/key-DYxuyd-s.d.cts +1125 -0
  98. package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
  99. package/dist/keycache-Da8A8mZa.mjs +200 -0
  100. package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
  101. package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
  102. package/dist/kv-DBRWVx3Q.mjs +104 -0
  103. package/dist/{kv-cache-KyGqJqSp.mjs → kv-cache-BF1hnW65.mjs} +1 -1
  104. package/dist/{kv-cache-5bnX3-Jq.js → kv-cache-CTQr1tbn.js} +54 -16
  105. package/dist/{kv-cache-DQ29C4XL.cjs → kv-cache-DCac1nEU.cjs} +55 -19
  106. package/dist/{ld-Cj9F_5AP.mjs → ld-CzaP-hJl.mjs} +44 -12
  107. package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
  108. package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
  109. package/dist/{metrics-DMk-nTxI.mjs → metrics-CYg9yxBL.mjs} +3 -2
  110. package/dist/metrics-DVKYY6K1.d.cts +36 -0
  111. package/dist/metrics-DoRtI0qU.d.ts +36 -0
  112. package/dist/{middleware-Y-kpsp6S.js → middleware-CsDDXKuL.js} +5493 -1103
  113. package/dist/{middleware-CvwTUgnP.mjs → middleware-D7_LO-Vs.mjs} +3157 -272
  114. package/dist/{middleware-CEctxBN6.mjs → middleware-DWjrfpcz.mjs} +1 -1
  115. package/dist/{mod-gOlPDuT8.d.cts → mod-BojP2Bw3.d.cts} +1 -1
  116. package/dist/mod-CavhAgLw.d.cts +320 -0
  117. package/dist/mod-CtpF2BU5.d.ts +320 -0
  118. package/dist/{mod-BtEaIEcD.d.cts → mod-DEZXwV6J.d.ts} +10 -4
  119. package/dist/{mod-UMmveiO0.d.ts → mod-Dw-nPxQh.d.cts} +10 -4
  120. package/dist/{mod-DLc2Ot6f.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
  121. package/dist/mod.cjs +40 -39
  122. package/dist/mod.d.cts +10 -11
  123. package/dist/mod.d.ts +10 -11
  124. package/dist/mod.js +9 -9
  125. package/dist/{mq-llHKivST.mjs → mq-Dm1Wmtm-.mjs} +6 -0
  126. package/dist/nodeinfo/client.test.mjs +139 -4
  127. package/dist/nodeinfo/handler.test.mjs +5 -5
  128. package/dist/nodeinfo/mod.cjs +1 -1
  129. package/dist/nodeinfo/mod.d.cts +1 -1
  130. package/dist/nodeinfo/mod.d.ts +1 -1
  131. package/dist/nodeinfo/mod.js +1 -1
  132. package/dist/nodeinfo/types.test.mjs +4 -4
  133. package/dist/otel/exporter.test.mjs +3 -3
  134. package/dist/otel/mod.cjs +0 -1
  135. package/dist/otel/mod.d.cts +1 -1
  136. package/dist/otel/mod.d.ts +1 -1
  137. package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
  138. package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
  139. package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
  140. package/dist/owner-DYgfTFHy.mjs +267 -0
  141. package/dist/portable-collection-BpsVsRov.js +670 -0
  142. package/dist/portable-collection-C74P1vtC.mjs +418 -0
  143. package/dist/portable-collection-DZd2bm7M.cjs +686 -0
  144. package/dist/portable-key-BIMLrAP4.mjs +549 -0
  145. package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
  146. package/dist/proof-Dxy6ByqI.mjs +1299 -0
  147. package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
  148. package/dist/router-u5O5KCQr.mjs +156 -0
  149. package/dist/runtime/mod.cjs +1 -1
  150. package/dist/runtime/mod.js +1 -1
  151. package/dist/secured-document-CxzZZ9nL.mjs +35 -0
  152. package/dist/send-C4dDpAxm.mjs +495 -0
  153. package/dist/sig/accept.test.mjs +1 -1
  154. package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
  155. package/dist/sig/compound-proof-discovery.test.mjs +331 -0
  156. package/dist/sig/compound-proof-producer.test.d.mts +2 -0
  157. package/dist/sig/compound-proof-producer.test.mjs +107 -0
  158. package/dist/sig/compound-proof-verification.test.d.mts +2 -0
  159. package/dist/sig/compound-proof-verification.test.mjs +833 -0
  160. package/dist/sig/compound-proof.test.d.mts +2 -0
  161. package/dist/sig/compound-proof.test.mjs +411 -0
  162. package/dist/sig/http.test.mjs +702 -31
  163. package/dist/sig/key-owner-evidence.test.d.mts +2 -0
  164. package/dist/sig/key-owner-evidence.test.mjs +100 -0
  165. package/dist/sig/key.test.mjs +220 -8
  166. package/dist/sig/ld.test.mjs +65 -7
  167. package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
  168. package/dist/sig/mitra-receive-interop.test.mjs +260 -0
  169. package/dist/sig/mitra-send-interop.test.d.mts +2 -0
  170. package/dist/sig/mitra-send-interop.test.mjs +461 -0
  171. package/dist/sig/mod.cjs +18 -16
  172. package/dist/sig/mod.d.cts +4 -4
  173. package/dist/sig/mod.d.ts +4 -4
  174. package/dist/sig/mod.js +3 -3
  175. package/dist/sig/owner.test.mjs +108 -9
  176. package/dist/sig/portable-collection.test.d.mts +2 -0
  177. package/dist/sig/portable-collection.test.mjs +697 -0
  178. package/dist/sig/portable-interop.test.d.mts +2 -0
  179. package/dist/sig/portable-interop.test.mjs +156 -0
  180. package/dist/sig/portable-key-id.test.d.mts +2 -0
  181. package/dist/sig/portable-key-id.test.mjs +67 -0
  182. package/dist/sig/portable-key.test.d.mts +2 -0
  183. package/dist/sig/portable-key.test.mjs +1439 -0
  184. package/dist/sig/proof.test.mjs +1753 -16
  185. package/dist/sig/tootik-request-interop.test.d.mts +2 -0
  186. package/dist/sig/tootik-request-interop.test.mjs +190 -0
  187. package/dist/sig/tootik-send-interop.test.d.mts +2 -0
  188. package/dist/sig/tootik-send-interop.test.mjs +331 -0
  189. package/dist/sig/verification.test.d.mts +2 -0
  190. package/dist/sig/verification.test.mjs +310 -0
  191. package/dist/std__assert-DvMDRPes.mjs +8 -0
  192. package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
  193. package/dist/{temporal-fsXO5IvD.mjs → temporal-D0KJI4NF.mjs} +3 -3
  194. package/dist/testing/context.test.d.mts +2 -0
  195. package/dist/testing/context.test.mjs +19 -0
  196. package/dist/testing/kv.test.d.mts +2 -0
  197. package/dist/testing/kv.test.mjs +101 -0
  198. package/dist/testing/mod.d.mts +1617 -48
  199. package/dist/testing/mod.mjs +6 -3
  200. package/dist/testing-t8xDeJZ5.mjs +8 -0
  201. package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
  202. package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
  203. package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
  204. package/dist/utils/docloader.test.mjs +530 -8
  205. package/dist/utils/kv-cache.test.mjs +2 -2
  206. package/dist/utils/mod.cjs +1 -1
  207. package/dist/utils/mod.d.cts +1 -1
  208. package/dist/utils/mod.d.ts +1 -1
  209. package/dist/utils/mod.js +1 -1
  210. package/dist/verification-C3954hQh.mjs +5 -0
  211. package/dist/verification-Dpfcw8ml.mjs +105 -0
  212. package/dist/vocab/cjs.test.mjs +1 -1
  213. package/package.json +8 -8
  214. package/dist/assert_rejects-EgstcQy5.mjs +0 -82
  215. package/dist/docloader-e1khNUKy.mjs +0 -49
  216. package/dist/http-CsmQ-BQ4.js +0 -2483
  217. package/dist/http-VyDTd4G3.d.cts +0 -553
  218. package/dist/http-hlIjmewS.cjs +0 -2694
  219. package/dist/http-lf8Hsd91.d.ts +0 -553
  220. package/dist/key-DfPsX8hv.mjs +0 -453
  221. package/dist/keycache-_DHqz5z_.mjs +0 -97
  222. package/dist/metrics-CoUJLQhZ.d.cts +0 -276
  223. package/dist/metrics-Dc4jR-IC.d.ts +0 -276
  224. package/dist/mod-B0hW12_O.d.cts +0 -307
  225. package/dist/mod-COIAjwRS.d.ts +0 -307
  226. package/dist/owner-Br9YYzn_.mjs +0 -120
  227. package/dist/proof-B4ngoaNQ.mjs +0 -316
  228. package/dist/proof-B6E0XUXJ.js +0 -1415
  229. package/dist/proof-NN4xCQGL.cjs +0 -1562
  230. package/dist/send-CxHKUNzv.mjs +0 -239
  231. package/dist/std__assert-B2NnY9bq.mjs +0 -79
  232. package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
  233. /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
  234. /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
  235. /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
  236. /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
  237. /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
  238. /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
  239. /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
  240. /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
  241. /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
  242. /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
  243. /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
  244. /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
@@ -1,6 +1,9 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { a as testDefinitions, n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-BJXxPElm.mjs";
5
- import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, r as envelopeSchema, t as MockQueue } from "../testing-CO7ftO8A.mjs";
6
- export { MockQueue, baseOptions, createInboxContext, createOutboxContext, createRequestContext, envelopeSchema, makeSchema, numberSchema, stringSchema, testDefinitions };
4
+ import { r as testDefinitions } from "../dist-DJQM4u7i.mjs";
5
+ import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-8Tu9kea0.mjs";
6
+ import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
7
+ import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, r as envelopeSchema, t as MockQueue } from "../tasks-IG3pHfvZ.mjs";
8
+ import "../testing-t8xDeJZ5.mjs";
9
+ export { ManualClockKvStore, MockQueue, baseOptions, createInboxContext, createOutboxContext, createRequestContext, envelopeSchema, makeSchema, numberSchema, stringSchema, testDefinitions };
@@ -0,0 +1,8 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import "./dist-DJQM4u7i.mjs";
5
+ import "./context-8Tu9kea0.mjs";
6
+ import "./kv-DBRWVx3Q.mjs";
7
+ import "./tasks-IG3pHfvZ.mjs";
8
+ export {};
@@ -1,5 +1,4 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- require("./chunk-Czpeue4M.cjs");
3
2
  //#region src/compat/transformers.ts
4
3
  const logger = (0, require("@logtape/logtape").getLogger)([
5
4
  "fedify",
@@ -1,22 +1,157 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { getLogger } from "@logtape/logtape";
4
- import { getUserAgent } from "@fedify/vocab-runtime";
4
+ import { FetchError, UrlError, getUserAgent, validatePublicUrl } from "@fedify/vocab-runtime";
5
+ //#region src/utils/body.ts
6
+ /** Decoded JSON body limit: 16 MiB. @internal */
7
+ const MAX_BODY_SIZE = 16 * 1024 * 1024;
8
+ /** A body exceeded the byte limit. @internal */
9
+ var BodyTooLargeError = class extends FetchError {
10
+ /** Creates an error for a body exceeding the given limit. */
11
+ constructor(url, maxBytes) {
12
+ super(url, `Body exceeds the limit of ${maxBytes} bytes`);
13
+ this.name = "BodyTooLargeError";
14
+ }
15
+ };
16
+ /** Validates a finite, positive byte limit. @internal */
17
+ function validateBodySizeLimit(maxBytes) {
18
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
19
+ }
20
+ /**
21
+ * Reads UTF-8 text while limiting bytes received from the body stream.
22
+ * Fetch responses are already decompressed, so the stream count is authoritative.
23
+ * @param message The response or request to read.
24
+ * @param maxBytes The maximum number of decoded bytes to read.
25
+ * @param url The URL to include in errors and logs.
26
+ * @returns The decoded body text.
27
+ * @throws {BodyTooLargeError} If the body exceeds the limit.
28
+ * @internal
29
+ */
30
+ async function readBoundedText(message, maxBytes, url) {
31
+ validateBodySizeLimit(maxBytes);
32
+ const reader = message.body?.getReader();
33
+ const tooLarge = () => {
34
+ getLogger([
35
+ "fedify",
36
+ "runtime",
37
+ "body"
38
+ ]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
39
+ url: url.toString(),
40
+ maxBytes
41
+ });
42
+ throw new BodyTooLargeError(url, maxBytes);
43
+ };
44
+ try {
45
+ const length = message.headers.get("Content-Length");
46
+ const encoding = message.headers.get("Content-Encoding");
47
+ if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
48
+ if (reader == null) return "";
49
+ let size = 0;
50
+ const textReader = new ReadableStream({ async pull(controller) {
51
+ const { done, value } = await reader.read();
52
+ if (done) {
53
+ controller.close();
54
+ return;
55
+ }
56
+ size += value.byteLength;
57
+ if (size > maxBytes) tooLarge();
58
+ controller.enqueue(value);
59
+ } }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
60
+ try {
61
+ const chunks = [];
62
+ while (true) {
63
+ const { done, value } = await textReader.read();
64
+ if (done) break;
65
+ chunks.push(value);
66
+ }
67
+ return chunks.join("");
68
+ } finally {
69
+ textReader.releaseLock();
70
+ }
71
+ } catch (error) {
72
+ if (reader != null) reader.cancel(error).catch(() => {});
73
+ throw error;
74
+ } finally {
75
+ reader?.releaseLock();
76
+ }
77
+ }
78
+ //#endregion
5
79
  //#region src/nodeinfo/client.ts
6
80
  const logger = getLogger([
7
81
  "fedify",
8
82
  "nodeinfo",
9
83
  "client"
10
84
  ]);
85
+ const DEFAULT_MAX_REDIRECTION = 5;
86
+ /**
87
+ * Fetches a URL while guarding against SSRF. Before every request—including
88
+ * each redirect hop—the destination is validated with
89
+ * {@link validatePublicUrl}, so that loopback, link-local, and private
90
+ * addresses, as well as non-HTTP(S) schemes such as `data:`, are rejected.
91
+ * Redirects are followed manually with a cap, so that a public URL cannot
92
+ * bounce the request to an internal address.
93
+ * @param url The URL to fetch.
94
+ * @param headers The request headers.
95
+ * @returns The final response, or `null` if the URL is disallowed, its
96
+ * hostname cannot be resolved, or there are too many redirects.
97
+ */
98
+ async function fetchPublicUrl(url, headers) {
99
+ let current = url instanceof URL ? url : new URL(url);
100
+ let redirected = 0;
101
+ while (true) {
102
+ try {
103
+ await validatePublicUrl(current.href);
104
+ } catch (e) {
105
+ if (e instanceof UrlError) {
106
+ if (e.reason === "dns") logger.error("DNS lookup failed for {url}: {error}", {
107
+ url: current.href,
108
+ error: e
109
+ });
110
+ else logger.error("Refused to fetch a private or invalid URL {url}: {error}", {
111
+ url: current.href,
112
+ error: e
113
+ });
114
+ return null;
115
+ }
116
+ throw e;
117
+ }
118
+ const response = await fetch(current, {
119
+ headers,
120
+ redirect: "manual"
121
+ });
122
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
123
+ redirected++;
124
+ if (redirected >= DEFAULT_MAX_REDIRECTION) {
125
+ logger.error("Too many redirections ({redirections}) while fetching {url}.", {
126
+ redirections: redirected,
127
+ url: current.href
128
+ });
129
+ return null;
130
+ }
131
+ const next = new URL(response.headers.get("Location"), response.url == null || response.url === "" ? current : response.url);
132
+ if (next.protocol !== current.protocol) {
133
+ logger.error("Refused a redirect to a different protocol ({protocol} to {redirectedProtocol}) while fetching NodeInfo.", {
134
+ protocol: current.protocol,
135
+ redirectedProtocol: next.protocol
136
+ });
137
+ return null;
138
+ }
139
+ current = next;
140
+ continue;
141
+ }
142
+ return response;
143
+ }
144
+ }
11
145
  async function getNodeInfo(url, options = {}) {
12
146
  try {
13
147
  let nodeInfoUrl = url;
14
148
  if (!options.direct) {
15
149
  const wellKnownUrl = new URL("/.well-known/nodeinfo", url);
16
- const wellKnownResponse = await fetch(wellKnownUrl, { headers: {
150
+ const wellKnownResponse = await fetchPublicUrl(wellKnownUrl, {
17
151
  Accept: "application/json",
18
152
  "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
19
- } });
153
+ });
154
+ if (wellKnownResponse == null) return void 0;
20
155
  if (!wellKnownResponse.ok) {
21
156
  logger.error("Failed to fetch {url}: {status} {statusText}", {
22
157
  url: wellKnownUrl.href,
@@ -25,7 +160,7 @@ async function getNodeInfo(url, options = {}) {
25
160
  });
26
161
  return;
27
162
  }
28
- const wellKnownRd = await wellKnownResponse.json();
163
+ const wellKnownRd = JSON.parse(await readBoundedText(wellKnownResponse, MAX_BODY_SIZE, wellKnownUrl));
29
164
  const link = wellKnownRd?.links?.find((link) => link != null && "rel" in link && (link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.0" || link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.1") && "href" in link && link.href != null);
30
165
  if (link == null || link.href == null) {
31
166
  logger.error("Failed to find a NodeInfo document link from {url}: {resourceDescriptor}", {
@@ -36,10 +171,11 @@ async function getNodeInfo(url, options = {}) {
36
171
  }
37
172
  nodeInfoUrl = link.href;
38
173
  }
39
- const response = await fetch(nodeInfoUrl, { headers: {
174
+ const response = await fetchPublicUrl(nodeInfoUrl, {
40
175
  Accept: "application/json",
41
176
  "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
42
- } });
177
+ });
178
+ if (response == null) return void 0;
43
179
  if (!response.ok) {
44
180
  logger.error("Failed to fetch NodeInfo document from {url}: {status} {statusText}", {
45
181
  url: nodeInfoUrl.toString(),
@@ -48,7 +184,7 @@ async function getNodeInfo(url, options = {}) {
48
184
  });
49
185
  return;
50
186
  }
51
- const data = await response.json();
187
+ const data = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, nodeInfoUrl));
52
188
  if (options.parse === "none") return data;
53
189
  return parseNodeInfo(data, { tryBestEffort: options.parse === "best-effort" }) ?? void 0;
54
190
  } catch (error) {
@@ -294,4 +430,4 @@ function nodeInfoToJson(nodeInfo) {
294
430
  };
295
431
  }
296
432
  //#endregion
297
- export { getNodeInfo as n, parseNodeInfo as r, nodeInfoToJson as t };
433
+ export { MAX_BODY_SIZE as a, BodyTooLargeError as i, getNodeInfo as n, readBoundedText as o, parseNodeInfo as r, nodeInfoToJson as t };
@@ -1,22 +1,156 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- require("./chunk-Czpeue4M.cjs");
3
2
  let _logtape_logtape = require("@logtape/logtape");
4
3
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
4
+ //#region src/utils/body.ts
5
+ /** Decoded JSON body limit: 16 MiB. @internal */
6
+ const MAX_BODY_SIZE = 16 * 1024 * 1024;
7
+ /** A body exceeded the byte limit. @internal */
8
+ var BodyTooLargeError = class extends _fedify_vocab_runtime.FetchError {
9
+ /** Creates an error for a body exceeding the given limit. */
10
+ constructor(url, maxBytes) {
11
+ super(url, `Body exceeds the limit of ${maxBytes} bytes`);
12
+ this.name = "BodyTooLargeError";
13
+ }
14
+ };
15
+ /** Validates a finite, positive byte limit. @internal */
16
+ function validateBodySizeLimit(maxBytes) {
17
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
18
+ }
19
+ /**
20
+ * Reads UTF-8 text while limiting bytes received from the body stream.
21
+ * Fetch responses are already decompressed, so the stream count is authoritative.
22
+ * @param message The response or request to read.
23
+ * @param maxBytes The maximum number of decoded bytes to read.
24
+ * @param url The URL to include in errors and logs.
25
+ * @returns The decoded body text.
26
+ * @throws {BodyTooLargeError} If the body exceeds the limit.
27
+ * @internal
28
+ */
29
+ async function readBoundedText(message, maxBytes, url) {
30
+ validateBodySizeLimit(maxBytes);
31
+ const reader = message.body?.getReader();
32
+ const tooLarge = () => {
33
+ (0, _logtape_logtape.getLogger)([
34
+ "fedify",
35
+ "runtime",
36
+ "body"
37
+ ]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
38
+ url: url.toString(),
39
+ maxBytes
40
+ });
41
+ throw new BodyTooLargeError(url, maxBytes);
42
+ };
43
+ try {
44
+ const length = message.headers.get("Content-Length");
45
+ const encoding = message.headers.get("Content-Encoding");
46
+ if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
47
+ if (reader == null) return "";
48
+ let size = 0;
49
+ const textReader = new ReadableStream({ async pull(controller) {
50
+ const { done, value } = await reader.read();
51
+ if (done) {
52
+ controller.close();
53
+ return;
54
+ }
55
+ size += value.byteLength;
56
+ if (size > maxBytes) tooLarge();
57
+ controller.enqueue(value);
58
+ } }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
59
+ try {
60
+ const chunks = [];
61
+ while (true) {
62
+ const { done, value } = await textReader.read();
63
+ if (done) break;
64
+ chunks.push(value);
65
+ }
66
+ return chunks.join("");
67
+ } finally {
68
+ textReader.releaseLock();
69
+ }
70
+ } catch (error) {
71
+ if (reader != null) reader.cancel(error).catch(() => {});
72
+ throw error;
73
+ } finally {
74
+ reader?.releaseLock();
75
+ }
76
+ }
77
+ //#endregion
5
78
  //#region src/nodeinfo/client.ts
6
79
  const logger = (0, _logtape_logtape.getLogger)([
7
80
  "fedify",
8
81
  "nodeinfo",
9
82
  "client"
10
83
  ]);
84
+ const DEFAULT_MAX_REDIRECTION = 5;
85
+ /**
86
+ * Fetches a URL while guarding against SSRF. Before every request—including
87
+ * each redirect hop—the destination is validated with
88
+ * {@link validatePublicUrl}, so that loopback, link-local, and private
89
+ * addresses, as well as non-HTTP(S) schemes such as `data:`, are rejected.
90
+ * Redirects are followed manually with a cap, so that a public URL cannot
91
+ * bounce the request to an internal address.
92
+ * @param url The URL to fetch.
93
+ * @param headers The request headers.
94
+ * @returns The final response, or `null` if the URL is disallowed, its
95
+ * hostname cannot be resolved, or there are too many redirects.
96
+ */
97
+ async function fetchPublicUrl(url, headers) {
98
+ let current = url instanceof URL ? url : new URL(url);
99
+ let redirected = 0;
100
+ while (true) {
101
+ try {
102
+ await (0, _fedify_vocab_runtime.validatePublicUrl)(current.href);
103
+ } catch (e) {
104
+ if (e instanceof _fedify_vocab_runtime.UrlError) {
105
+ if (e.reason === "dns") logger.error("DNS lookup failed for {url}: {error}", {
106
+ url: current.href,
107
+ error: e
108
+ });
109
+ else logger.error("Refused to fetch a private or invalid URL {url}: {error}", {
110
+ url: current.href,
111
+ error: e
112
+ });
113
+ return null;
114
+ }
115
+ throw e;
116
+ }
117
+ const response = await fetch(current, {
118
+ headers,
119
+ redirect: "manual"
120
+ });
121
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
122
+ redirected++;
123
+ if (redirected >= DEFAULT_MAX_REDIRECTION) {
124
+ logger.error("Too many redirections ({redirections}) while fetching {url}.", {
125
+ redirections: redirected,
126
+ url: current.href
127
+ });
128
+ return null;
129
+ }
130
+ const next = new URL(response.headers.get("Location"), response.url == null || response.url === "" ? current : response.url);
131
+ if (next.protocol !== current.protocol) {
132
+ logger.error("Refused a redirect to a different protocol ({protocol} to {redirectedProtocol}) while fetching NodeInfo.", {
133
+ protocol: current.protocol,
134
+ redirectedProtocol: next.protocol
135
+ });
136
+ return null;
137
+ }
138
+ current = next;
139
+ continue;
140
+ }
141
+ return response;
142
+ }
143
+ }
11
144
  async function getNodeInfo(url, options = {}) {
12
145
  try {
13
146
  let nodeInfoUrl = url;
14
147
  if (!options.direct) {
15
148
  const wellKnownUrl = new URL("/.well-known/nodeinfo", url);
16
- const wellKnownResponse = await fetch(wellKnownUrl, { headers: {
149
+ const wellKnownResponse = await fetchPublicUrl(wellKnownUrl, {
17
150
  Accept: "application/json",
18
151
  "User-Agent": typeof options.userAgent === "string" ? options.userAgent : (0, _fedify_vocab_runtime.getUserAgent)(options.userAgent)
19
- } });
152
+ });
153
+ if (wellKnownResponse == null) return void 0;
20
154
  if (!wellKnownResponse.ok) {
21
155
  logger.error("Failed to fetch {url}: {status} {statusText}", {
22
156
  url: wellKnownUrl.href,
@@ -25,7 +159,7 @@ async function getNodeInfo(url, options = {}) {
25
159
  });
26
160
  return;
27
161
  }
28
- const wellKnownRd = await wellKnownResponse.json();
162
+ const wellKnownRd = JSON.parse(await readBoundedText(wellKnownResponse, MAX_BODY_SIZE, wellKnownUrl));
29
163
  const link = wellKnownRd?.links?.find((link) => link != null && "rel" in link && (link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.0" || link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.1") && "href" in link && link.href != null);
30
164
  if (link == null || link.href == null) {
31
165
  logger.error("Failed to find a NodeInfo document link from {url}: {resourceDescriptor}", {
@@ -36,10 +170,11 @@ async function getNodeInfo(url, options = {}) {
36
170
  }
37
171
  nodeInfoUrl = link.href;
38
172
  }
39
- const response = await fetch(nodeInfoUrl, { headers: {
173
+ const response = await fetchPublicUrl(nodeInfoUrl, {
40
174
  Accept: "application/json",
41
175
  "User-Agent": typeof options.userAgent === "string" ? options.userAgent : (0, _fedify_vocab_runtime.getUserAgent)(options.userAgent)
42
- } });
176
+ });
177
+ if (response == null) return void 0;
43
178
  if (!response.ok) {
44
179
  logger.error("Failed to fetch NodeInfo document from {url}: {status} {statusText}", {
45
180
  url: nodeInfoUrl.toString(),
@@ -48,7 +183,7 @@ async function getNodeInfo(url, options = {}) {
48
183
  });
49
184
  return;
50
185
  }
51
- const data = await response.json();
186
+ const data = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, nodeInfoUrl));
52
187
  if (options.parse === "none") return data;
53
188
  return parseNodeInfo(data, { tryBestEffort: options.parse === "best-effort" }) ?? void 0;
54
189
  } catch (error) {
@@ -294,6 +429,18 @@ function nodeInfoToJson(nodeInfo) {
294
429
  };
295
430
  }
296
431
  //#endregion
432
+ Object.defineProperty(exports, "BodyTooLargeError", {
433
+ enumerable: true,
434
+ get: function() {
435
+ return BodyTooLargeError;
436
+ }
437
+ });
438
+ Object.defineProperty(exports, "MAX_BODY_SIZE", {
439
+ enumerable: true,
440
+ get: function() {
441
+ return MAX_BODY_SIZE;
442
+ }
443
+ });
297
444
  Object.defineProperty(exports, "getNodeInfo", {
298
445
  enumerable: true,
299
446
  get: function() {
@@ -312,3 +459,9 @@ Object.defineProperty(exports, "parseNodeInfo", {
312
459
  return parseNodeInfo;
313
460
  }
314
461
  });
462
+ Object.defineProperty(exports, "readBoundedText", {
463
+ enumerable: true,
464
+ get: function() {
465
+ return readBoundedText;
466
+ }
467
+ });