@fedify/fedify 2.4.0-dev.2155 → 2.4.0-dev.2159

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 (102) hide show
  1. package/dist/{builder-CxHALSwA.mjs → builder-D89oOfHC.mjs} +2 -2
  2. package/dist/{client-DpnpgjyX.mjs → client-CEnNz8O7.mjs} +7 -3
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{compound-proof-nhrXTRQO.mjs → compound-proof-B02Os8h2.mjs} +2 -2
  7. package/dist/{context-OrnjnuMq.mjs → context-B2rlkkyJ.mjs} +1 -1
  8. package/dist/{context-BMggDR-l.d.cts → context-DpsaHkk1.d.cts} +119 -2
  9. package/dist/{context-exYu7gDU.d.ts → context-JDNkUtnu.d.ts} +119 -2
  10. package/dist/{deno-YLLbRq06.mjs → deno-CE_s0yQb.mjs} +1 -1
  11. package/dist/{dist-BQvArojP.mjs → dist-DJQM4u7i.mjs} +2 -1
  12. package/dist/docloader-C5p26tpa.mjs +79 -0
  13. package/dist/federation/builder.test.mjs +1 -1
  14. package/dist/federation/compound-fanout.test.mjs +3 -3
  15. package/dist/federation/handler-compound-proof.test.mjs +4 -4
  16. package/dist/federation/handler.test.mjs +6 -6
  17. package/dist/federation/hashlink-media.test.mjs +2 -2
  18. package/dist/federation/idempotency.test.mjs +2 -2
  19. package/dist/federation/metrics.test.mjs +1 -1
  20. package/dist/federation/middleware.test.mjs +187 -10
  21. package/dist/federation/mod.cjs +1 -1
  22. package/dist/federation/mod.d.cts +3 -3
  23. package/dist/federation/mod.d.ts +3 -3
  24. package/dist/federation/mod.js +1 -1
  25. package/dist/federation/mq.test.mjs +98 -69
  26. package/dist/federation/outgoing-proof.test.mjs +5 -5
  27. package/dist/federation/portable-actor.test.mjs +3 -3
  28. package/dist/federation/portable-chain.test.mjs +3 -3
  29. package/dist/federation/portable-collection.test.mjs +4 -4
  30. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  31. package/dist/federation/portable-inbox.test.mjs +4 -4
  32. package/dist/federation/portable-parse-uri.test.d.mts +2 -0
  33. package/dist/federation/portable-parse-uri.test.mjs +348 -0
  34. package/dist/federation/portable.test.mjs +3 -3
  35. package/dist/federation/send.test.mjs +84 -3
  36. package/dist/federation/tasks/enqueue.test.mjs +3 -3
  37. package/dist/federation/tasks/tasks.test.mjs +4 -4
  38. package/dist/federation/temporal.test.mjs +1 -1
  39. package/dist/federation/webfinger.test.mjs +2 -2
  40. package/dist/{federation-D9TufmIO.cjs → federation-dBOqmJcA.cjs} +74 -10
  41. package/dist/{http-CXnNbRe_.mjs → http-DV_yW4dv.mjs} +9 -5
  42. package/dist/{http-Dk4O3WM0.js → http-nTZuTacw.js} +7 -3
  43. package/dist/{http-eqYXHRkz.cjs → http-wdsia0-O.cjs} +7 -3
  44. package/dist/{key-BZ-Z4uez.mjs → key-C6jnekFI.mjs} +2 -2
  45. package/dist/{kv-cache-DzClEalR.cjs → kv-cache-BHNtQi9e.cjs} +34 -8
  46. package/dist/{kv-cache-DZr8HTel.mjs → kv-cache-Bxm0a0Ww.mjs} +1 -1
  47. package/dist/{kv-cache-3uKc0G76.js → kv-cache-DhIl9YlV.js} +35 -9
  48. package/dist/{ld-Djxsq6JR.mjs → ld-ya2C1Xip.mjs} +4 -4
  49. package/dist/{metrics-CBHhbHgG.mjs → metrics-ByOQOHHS.mjs} +1 -1
  50. package/dist/{middleware-DDa8pCUa.mjs → middleware-Bdwd_Gg8.mjs} +70 -20
  51. package/dist/{middleware-BahNnLfD.mjs → middleware-DoVWq8mE.mjs} +1 -1
  52. package/dist/{middleware-BDUR99k3.js → middleware-WiaN4RS3.js} +74 -10
  53. package/dist/{mod-Decmur1U.d.cts → mod-DSKkTwH2.d.cts} +1 -1
  54. package/dist/{mod-CZetqT5h.d.ts → mod-DaiBrWDW.d.ts} +2 -0
  55. package/dist/{mod-D4g9ffEn.d.cts → mod-Mt4aO3DX.d.cts} +2 -0
  56. package/dist/{mod-CnGtADvO.d.ts → mod-_4K05HZF.d.ts} +1 -1
  57. package/dist/mod.cjs +5 -5
  58. package/dist/mod.d.cts +5 -5
  59. package/dist/mod.d.ts +5 -5
  60. package/dist/mod.js +5 -5
  61. package/dist/nodeinfo/client.test.mjs +49 -1
  62. package/dist/nodeinfo/handler.test.mjs +2 -2
  63. package/dist/nodeinfo/mod.cjs +1 -1
  64. package/dist/nodeinfo/mod.js +1 -1
  65. package/dist/{owner-OdWSE8am.mjs → owner-C6Nv5yyw.mjs} +3 -3
  66. package/dist/{portable-collection-BCy06GiS.js → portable-collection-B-mU1F1-.js} +1 -1
  67. package/dist/{portable-collection-wBWbuHOH.cjs → portable-collection-C65SwL2l.cjs} +1 -1
  68. package/dist/{portable-collection-DAV7Ho3m.mjs → portable-collection-uWj3hfzv.mjs} +2 -2
  69. package/dist/{portable-key-BL6K88p2.mjs → portable-key-BXsVLSMa.mjs} +1 -1
  70. package/dist/{proof-vZ04IAjq.mjs → proof-BdO3BbLh.mjs} +4 -4
  71. package/dist/{send-ylV8KCLx.mjs → send-BwANxryy.mjs} +18 -4
  72. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  73. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  74. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  75. package/dist/sig/compound-proof.test.mjs +2 -2
  76. package/dist/sig/http.test.mjs +42 -2
  77. package/dist/sig/key.test.mjs +1 -1
  78. package/dist/sig/ld.test.mjs +5 -2
  79. package/dist/sig/mod.cjs +2 -2
  80. package/dist/sig/mod.js +2 -2
  81. package/dist/sig/owner.test.mjs +1 -1
  82. package/dist/sig/portable-collection.test.mjs +2 -2
  83. package/dist/sig/portable-key.test.mjs +6 -6
  84. package/dist/sig/proof.test.mjs +3 -3
  85. package/dist/{tasks-dbCShlCb.mjs → tasks-IG3pHfvZ.mjs} +1 -1
  86. package/dist/{temporal-BTD4f_mZ.mjs → temporal-CbPfRGZq.mjs} +1 -1
  87. package/dist/testing/context.test.mjs +1 -1
  88. package/dist/testing/kv.test.mjs +3 -3
  89. package/dist/testing/mod.d.mts +118 -1
  90. package/dist/testing/mod.mjs +4 -4
  91. package/dist/{testing-CKaJhcA0.mjs → testing-C1itIjqW.mjs} +3 -3
  92. package/dist/{types-C_cNlNVK.js → types-_S2_MeLF.js} +7 -3
  93. package/dist/{types-Be2zbyeG.cjs → types-poIao40f.cjs} +7 -3
  94. package/dist/utils/docloader.test.mjs +297 -2
  95. package/dist/utils/kv-cache.test.mjs +1 -1
  96. package/dist/utils/mod.cjs +1 -1
  97. package/dist/utils/mod.d.cts +1 -1
  98. package/dist/utils/mod.d.ts +1 -1
  99. package/dist/utils/mod.js +1 -1
  100. package/dist/vocab/cjs.test.mjs +1 -1
  101. package/package.json +7 -7
  102. package/dist/docloader-vc-pSu9-.mjs +0 -53
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-eqYXHRkz.cjs");
5
- const require_types = require("./types-Be2zbyeG.cjs");
6
- const require_portable_collection = require("./portable-collection-wBWbuHOH.cjs");
7
- const require_kv_cache = require("./kv-cache-DzClEalR.cjs");
4
+ const require_http = require("./http-wdsia0-O.cjs");
5
+ const require_types = require("./types-poIao40f.cjs");
6
+ const require_portable_collection = require("./portable-collection-C65SwL2l.cjs");
7
+ const require_kv_cache = require("./kv-cache-BHNtQi9e.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -2823,6 +2823,43 @@ function parsePortableGatewayRequest(url) {
2823
2823
  };
2824
2824
  }
2825
2825
  /**
2826
+ * Recognizes an FEP-ef61 portable ID, i.e., an `ap:` or `ap+ef61:` URI, or
2827
+ * a compatible identifier on any gateway, and splits it into its DID and
2828
+ * the object path to route.
2829
+ *
2830
+ * It accepts the same IDs as the gateway endpoint does: the query, e.g.,
2831
+ * location hints, and the fragment are ignored, and a malformed DID, or
2832
+ * a `did:key` DID that is not encoded in base58-btc, is refused.
2833
+ *
2834
+ * @param uri The ID to recognize.
2835
+ * @returns The DID normalized as an FEP-fe34 origin, and the object path,
2836
+ * or `null` if the ID is not a portable ID or is malformed.
2837
+ */
2838
+ function parsePortableId(uri) {
2839
+ if (uri.username !== "" || uri.password !== "") return null;
2840
+ if (uri.protocol !== "ap:" && uri.protocol !== "ap+ef61:") {
2841
+ const request = parsePortableGatewayRequest(uri);
2842
+ if (request?.type !== "object") return null;
2843
+ return {
2844
+ authority: request.portableRequest.authority,
2845
+ path: request.path
2846
+ };
2847
+ }
2848
+ try {
2849
+ const id = (0, _fedify_vocab_runtime.parseIri)(uri);
2850
+ (0, _fedify_vocab_runtime.canonicalizePortableUri)((0, _fedify_vocab_runtime.formatIri)(id));
2851
+ const authority = (0, _fedify_vocab_runtime.getFe34Origin)(id);
2852
+ assertBase58BtcDidKey(authority);
2853
+ return {
2854
+ authority,
2855
+ path: id.pathname
2856
+ };
2857
+ } catch (error) {
2858
+ if (error instanceof TypeError) return null;
2859
+ throw error;
2860
+ }
2861
+ }
2862
+ /**
2826
2863
  * Builds a portable ID from a DID authority and an object path.
2827
2864
  * @param authority The bare DID, e.g., `did:key:z6Mk...`.
2828
2865
  * @param path The object path, e.g., `/notes/123`.
@@ -3016,6 +3053,15 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
3016
3053
  if (!allowPrivateAddress) try {
3017
3054
  await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
3018
3055
  } catch (error) {
3056
+ if (error instanceof _fedify_vocab_runtime.UrlError && error.reason === "dns") {
3057
+ logger.error("DNS resolution failed for URL: {url}", {
3058
+ url,
3059
+ error
3060
+ });
3061
+ const failure = new _fedify_vocab_runtime.FetchError(url, error.message);
3062
+ failure.cause = error;
3063
+ throw failure;
3064
+ }
3019
3065
  if (error instanceof _fedify_vocab_runtime.UrlError) logger.error("Disallowed private URL: {url}", {
3020
3066
  url,
3021
3067
  error
@@ -3023,7 +3069,12 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
3023
3069
  throw error;
3024
3070
  }
3025
3071
  }
3026
- await validateUrl(inbox.href);
3072
+ try {
3073
+ await validateUrl(inbox.href);
3074
+ } catch (error) {
3075
+ if (error instanceof _fedify_vocab_runtime.FetchError) federationMetrics.recordDelivery(inbox, require_http.getDurationMs(started), false, activityType);
3076
+ throw error;
3077
+ }
3027
3078
  headers = new Headers(headers);
3028
3079
  headers.set("Content-Type", "application/activity+json");
3029
3080
  const request = new Request(inbox, {
@@ -9470,12 +9521,25 @@ var ContextImpl = class ContextImpl {
9470
9521
  if (path === null) throw new _fedify_uri_template.RouterError(`No collection dispatcher registered for "${String(name)}".`);
9471
9522
  return buildPortableUri(requirePortableAuthority(authority), path);
9472
9523
  }
9473
- parseUri(uri) {
9524
+ parseUri(uri, options = {}) {
9474
9525
  if (uri == null) return null;
9475
- if (uri.origin !== this.origin && uri.origin !== this.canonicalOrigin) return null;
9476
- const route = this.federation.router.route(uri.pathname);
9477
- if (route == null) return null;
9478
- else if (route.name === "sharedInbox") return {
9526
+ if (uri.origin === this.origin || uri.origin === this.canonicalOrigin) {
9527
+ const route = this.federation.router.route(uri.pathname);
9528
+ if (route != null) return this.#parseRoute(route);
9529
+ }
9530
+ if (!options.portable) return null;
9531
+ const portable = parsePortableId(uri);
9532
+ if (portable == null) return null;
9533
+ const route = this.federation.router.route(portable.path);
9534
+ if (route == null || route.name === "sharedInbox") return null;
9535
+ const result = this.#parseRoute(route);
9536
+ return result == null ? null : {
9537
+ ...result,
9538
+ authority: portable.authority
9539
+ };
9540
+ }
9541
+ #parseRoute(route) {
9542
+ if (route.name === "sharedInbox") return {
9479
9543
  type: "inbox",
9480
9544
  identifier: void 0
9481
9545
  };
@@ -1,12 +1,12 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-YLLbRq06.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CBHhbHgG.mjs";
4
+ import { n as version, t as name } from "./deno-CE_s0yQb.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-ByOQOHHS.mjs";
6
6
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
7
7
  import { f as parseKeyIdString } from "./portable-key-id-Bg259c4Z.mjs";
8
- import { a as fetchKeyDetailed, c as validateCryptoKey, t as bypassKeyCacheReads } from "./key-BZ-Z4uez.mjs";
9
- import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-BL6K88p2.mjs";
8
+ import { a as fetchKeyDetailed, c as validateCryptoKey, t as bypassKeyCacheReads } from "./key-C6jnekFI.mjs";
9
+ import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-BXsVLSMa.mjs";
10
10
  import { getLogger } from "@logtape/logtape";
11
11
  import { CryptographicKey } from "@fedify/vocab";
12
12
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -804,6 +804,8 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
804
804
  failure = result;
805
805
  failureAlgorithm = algorithm;
806
806
  };
807
+ let body = null;
808
+ let digestValid = null;
807
809
  for (const sigName of signatureNames) {
808
810
  if (!signatures[sigName]) {
809
811
  setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
@@ -855,7 +857,9 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
855
857
  setFailure(invalidSignatureResult(keyId));
856
858
  continue;
857
859
  }
858
- if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
860
+ body ??= await request.arrayBuffer();
861
+ digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
862
+ if (!digestValid) {
859
863
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
860
864
  setFailure(invalidSignatureResult(keyId));
861
865
  continue;
@@ -14,7 +14,7 @@ import serialize from "json-canon";
14
14
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
15
15
  //#region deno.json
16
16
  var name = "@fedify/fedify";
17
- var version = "2.4.0-dev.2155+2ccc5085";
17
+ var version = "2.4.0-dev.2159+bb072eda";
18
18
  //#endregion
19
19
  //#region src/sig/portable-key-id.ts
20
20
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -2238,7 +2238,7 @@ function normalizeContextReference(reference, baseUrl) {
2238
2238
  /** @internal */
2239
2239
  function isInvalidUrlTypeError(error) {
2240
2240
  const code = error.code;
2241
- return error instanceof TypeError && (code === "ERR_INVALID_URL" || /^Invalid URL(?::|$)/.test(error.message) || / cannot be parsed as a URL\.?$/.test(error.message));
2241
+ return error instanceof TypeError && (code === "ERR_INVALID_URL" || error.message === "Invalid URL string." || /^Invalid URL(?::|$)/.test(error.message) || / cannot be parsed as a URL\.?$/.test(error.message));
2242
2242
  }
2243
2243
  async function applyGraphAliasContext(state, context, documentLoader, remoteContextCache, baseUrl = null, processingContexts = /* @__PURE__ */ new Set()) {
2244
2244
  if (context === null) return createGraphAliasContextState();
@@ -5340,6 +5340,8 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
5340
5340
  failure = result;
5341
5341
  failureAlgorithm = algorithm;
5342
5342
  };
5343
+ let body = null;
5344
+ let digestValid = null;
5343
5345
  for (const sigName of signatureNames) {
5344
5346
  if (!signatures[sigName]) {
5345
5347
  setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
@@ -5391,7 +5393,9 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
5391
5393
  setFailure(invalidSignatureResult(keyId));
5392
5394
  continue;
5393
5395
  }
5394
- if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
5396
+ body ??= await request.arrayBuffer();
5397
+ digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
5398
+ if (!digestValid) {
5395
5399
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
5396
5400
  setFailure(invalidSignatureResult(keyId));
5397
5401
  continue;
@@ -16,7 +16,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
16
16
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
17
17
  //#region deno.json
18
18
  var name = "@fedify/fedify";
19
- var version = "2.4.0-dev.2155+2ccc5085";
19
+ var version = "2.4.0-dev.2159+bb072eda";
20
20
  //#endregion
21
21
  //#region src/sig/portable-key-id.ts
22
22
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -5804,7 +5804,7 @@ function normalizeContextReference(reference, baseUrl) {
5804
5804
  /** @internal */
5805
5805
  function isInvalidUrlTypeError(error) {
5806
5806
  const code = error.code;
5807
- return error instanceof TypeError && (code === "ERR_INVALID_URL" || /^Invalid URL(?::|$)/.test(error.message) || / cannot be parsed as a URL\.?$/.test(error.message));
5807
+ return error instanceof TypeError && (code === "ERR_INVALID_URL" || error.message === "Invalid URL string." || /^Invalid URL(?::|$)/.test(error.message) || / cannot be parsed as a URL\.?$/.test(error.message));
5808
5808
  }
5809
5809
  async function applyGraphAliasContext(state, context, documentLoader, remoteContextCache, baseUrl = null, processingContexts = /* @__PURE__ */ new Set()) {
5810
5810
  if (context === null) return createGraphAliasContextState();
@@ -8908,6 +8908,8 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
8908
8908
  failure = result;
8909
8909
  failureAlgorithm = algorithm;
8910
8910
  };
8911
+ let body = null;
8912
+ let digestValid = null;
8911
8913
  for (const sigName of signatureNames) {
8912
8914
  if (!signatures[sigName]) {
8913
8915
  setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
@@ -8959,7 +8961,9 @@ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoa
8959
8961
  setFailure(invalidSignatureResult(keyId));
8960
8962
  continue;
8961
8963
  }
8962
- if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
8964
+ body ??= await request.arrayBuffer();
8965
+ digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
8966
+ if (!digestValid) {
8963
8967
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
8964
8968
  setFailure(invalidSignatureResult(keyId));
8965
8969
  continue;
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-YLLbRq06.mjs";
5
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-CBHhbHgG.mjs";
4
+ import { n as version, t as name } from "./deno-CE_s0yQb.mjs";
5
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-ByOQOHHS.mjs";
6
6
  import { c as isPortableId, f as parseKeyIdString, o as isCompatibleKeyId, s as isPortableActorDocument, u as isPortableUri } from "./portable-key-id-Bg259c4Z.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-eqYXHRkz.cjs");
2
+ const require_http = require("./http-wdsia0-O.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
5
5
  let es_toolkit = require("es-toolkit");
@@ -9,10 +9,13 @@ const logger$1 = (0, _logtape_logtape.getLogger)([
9
9
  "utils",
10
10
  "docloader"
11
11
  ]);
12
+ const DEFAULT_MAX_REDIRECTION = 20;
12
13
  /**
13
14
  * Gets an authenticated {@link DocumentLoader} for the given identity.
14
15
  * Note that an authenticated document loader intentionally does not cache
15
16
  * the fetched documents.
17
+ * At most 20 HTTP redirects and alternate document links are followed in total
18
+ * per call. Revisiting a URL within that chain throws a {@link FetchError}.
16
19
  * @param identity The identity to get the document loader for.
17
20
  * The actor's key pair.
18
21
  * @param options The options for the document loader.
@@ -22,29 +25,52 @@ const logger$1 = (0, _logtape_logtape.getLogger)([
22
25
  */
23
26
  function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
24
27
  require_http.validateCryptoKey(identity.privateKey);
25
- async function load(url, options) {
26
- await validateUrl(url, options);
27
- return (0, _fedify_vocab_runtime.getRemoteDocument)(url, await require_http.doubleKnock((0, _fedify_vocab_runtime.createActivityPubRequest)(url, { userAgent }), identity, {
28
+ async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
29
+ options?.signal?.throwIfAborted();
30
+ let currentUrl = new URL(url).href;
31
+ await validateUrl(currentUrl, options);
32
+ visited.add(currentUrl);
33
+ const originalRequest = (0, _fedify_vocab_runtime.createActivityPubRequest)(currentUrl, { userAgent });
34
+ function follow(nextUrl) {
35
+ options?.signal?.throwIfAborted();
36
+ if (redirected >= DEFAULT_MAX_REDIRECTION) throw new _fedify_vocab_runtime.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
37
+ if (visited.has(nextUrl)) throw new _fedify_vocab_runtime.FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
38
+ redirected++;
39
+ }
40
+ const response = await require_http.doubleKnock(originalRequest, identity, {
28
41
  maxRedirection,
29
42
  specDeterminer,
30
43
  log: (0, es_toolkit.curry)(_fedify_vocab_runtime.logRequest)(logger$1),
31
44
  tracerProvider,
32
45
  signal: options?.signal,
33
- validateRedirect: (url) => validateUrl(url, options)
34
- }), load, options);
46
+ validateRedirect: async (nextUrl) => {
47
+ follow(nextUrl);
48
+ await validateUrl(nextUrl, options);
49
+ visited.add(nextUrl);
50
+ currentUrl = nextUrl;
51
+ }
52
+ });
53
+ return (0, _fedify_vocab_runtime.getRemoteDocument)(currentUrl, response, (alternateUrl) => {
54
+ follow(alternateUrl);
55
+ return load(alternateUrl, options, redirected, visited);
56
+ }, options);
35
57
  }
36
58
  async function validateUrl(url, options) {
37
59
  if (!allowPrivateAddress) try {
38
60
  await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
39
61
  } catch (error) {
40
- if (error instanceof _fedify_vocab_runtime.UrlError) logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
62
+ if (error instanceof _fedify_vocab_runtime.UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
63
+ url,
64
+ error
65
+ });
66
+ else logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
41
67
  url,
42
68
  error
43
69
  });
44
70
  throw error;
45
71
  }
46
72
  }
47
- return load;
73
+ return (url, options) => load(url, options);
48
74
  }
49
75
  //#endregion
50
76
  //#region src/utils/kv-cache.ts
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-CBHhbHgG.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-ByOQOHHS.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,8 +1,8 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { W as validateCryptoKey, nt as recordDocumentCache, t as doubleKnock } from "./http-Dk4O3WM0.js";
3
+ import { W as validateCryptoKey, nt as recordDocumentCache, t as doubleKnock } from "./http-nTZuTacw.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
- import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
5
+ import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
6
6
  import { curry } from "es-toolkit";
7
7
  //#region src/utils/docloader.ts
8
8
  const logger$1 = getLogger([
@@ -10,10 +10,13 @@ const logger$1 = getLogger([
10
10
  "utils",
11
11
  "docloader"
12
12
  ]);
13
+ const DEFAULT_MAX_REDIRECTION = 20;
13
14
  /**
14
15
  * Gets an authenticated {@link DocumentLoader} for the given identity.
15
16
  * Note that an authenticated document loader intentionally does not cache
16
17
  * the fetched documents.
18
+ * At most 20 HTTP redirects and alternate document links are followed in total
19
+ * per call. Revisiting a URL within that chain throws a {@link FetchError}.
17
20
  * @param identity The identity to get the document loader for.
18
21
  * The actor's key pair.
19
22
  * @param options The options for the document loader.
@@ -23,29 +26,52 @@ const logger$1 = getLogger([
23
26
  */
24
27
  function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
25
28
  validateCryptoKey(identity.privateKey);
26
- async function load(url, options) {
27
- await validateUrl(url, options);
28
- return getRemoteDocument(url, await doubleKnock(createActivityPubRequest(url, { userAgent }), identity, {
29
+ async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
30
+ options?.signal?.throwIfAborted();
31
+ let currentUrl = new URL(url).href;
32
+ await validateUrl(currentUrl, options);
33
+ visited.add(currentUrl);
34
+ const originalRequest = createActivityPubRequest(currentUrl, { userAgent });
35
+ function follow(nextUrl) {
36
+ options?.signal?.throwIfAborted();
37
+ if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
38
+ if (visited.has(nextUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
39
+ redirected++;
40
+ }
41
+ const response = await doubleKnock(originalRequest, identity, {
29
42
  maxRedirection,
30
43
  specDeterminer,
31
44
  log: curry(logRequest)(logger$1),
32
45
  tracerProvider,
33
46
  signal: options?.signal,
34
- validateRedirect: (url) => validateUrl(url, options)
35
- }), load, options);
47
+ validateRedirect: async (nextUrl) => {
48
+ follow(nextUrl);
49
+ await validateUrl(nextUrl, options);
50
+ visited.add(nextUrl);
51
+ currentUrl = nextUrl;
52
+ }
53
+ });
54
+ return getRemoteDocument(currentUrl, response, (alternateUrl) => {
55
+ follow(alternateUrl);
56
+ return load(alternateUrl, options, redirected, visited);
57
+ }, options);
36
58
  }
37
59
  async function validateUrl(url, options) {
38
60
  if (!allowPrivateAddress) try {
39
61
  await validatePublicUrl(url);
40
62
  } catch (error) {
41
- if (error instanceof UrlError) logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
63
+ if (error instanceof UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
64
+ url,
65
+ error
66
+ });
67
+ else logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
42
68
  url,
43
69
  error
44
70
  });
45
71
  throw error;
46
72
  }
47
73
  }
48
- return load;
74
+ return (url, options) => load(url, options);
49
75
  }
50
76
  //#endregion
51
77
  //#region src/utils/kv-cache.ts
@@ -1,9 +1,9 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-YLLbRq06.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CBHhbHgG.mjs";
6
- import { c as validateCryptoKey, i as fetchKey, t as bypassKeyCacheReads } from "./key-BZ-Z4uez.mjs";
4
+ import { n as version, t as name } from "./deno-CE_s0yQb.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-ByOQOHHS.mjs";
6
+ import { c as validateCryptoKey, i as fetchKey, t as bypassKeyCacheReads } from "./key-C6jnekFI.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -206,7 +206,7 @@ function normalizeContextReference(reference, baseUrl) {
206
206
  /** @internal */
207
207
  function isInvalidUrlTypeError(error) {
208
208
  const code = error.code;
209
- return error instanceof TypeError && (code === "ERR_INVALID_URL" || /^Invalid URL(?::|$)/.test(error.message) || / cannot be parsed as a URL\.?$/.test(error.message));
209
+ return error instanceof TypeError && (code === "ERR_INVALID_URL" || error.message === "Invalid URL string." || /^Invalid URL(?::|$)/.test(error.message) || / cannot be parsed as a URL\.?$/.test(error.message));
210
210
  }
211
211
  async function applyGraphAliasContext(state, context, documentLoader, remoteContextCache, baseUrl = null, processingContexts = /* @__PURE__ */ new Set()) {
212
212
  if (context === null) return createGraphAliasContextState();
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-YLLbRq06.mjs";
4
+ import { n as version, t as name } from "./deno-CE_s0yQb.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts
@@ -1,29 +1,29 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-YLLbRq06.mjs";
5
- import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-CBHhbHgG.mjs";
4
+ import { n as version, t as name } from "./deno-CE_s0yQb.mjs";
5
+ import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-ByOQOHHS.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-CPVqki78.mjs";
7
7
  import { a as hasPortableActor, c as isPortableId, l as isPortableKeyId, n as getGatewayKeyBase, o as isCompatibleKeyId, r as getPortableDid, t as getCanonicalPortableId, u as isPortableUri } from "./portable-key-id-Bg259c4Z.mjs";
8
- import { c as validateCryptoKey, n as exportJwk, s as importJwk } from "./key-BZ-Z4uez.mjs";
8
+ import { c as validateCryptoKey, n as exportJwk, s as importJwk } from "./key-C6jnekFI.mjs";
9
9
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
10
- import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-Djxsq6JR.mjs";
11
- import { a as signObject, c as verifyPortableObjectProof, i as hasProofLike, s as verifyObject, t as InvalidPortableObjectIdError, u as verifyPortableObjectProofWithRoot } from "./proof-vZ04IAjq.mjs";
12
- import { a as listRequestSignatures, c as selectRequestSignature, d as verifyRequest, f as verifyRequestDetailed, s as parseRfc9421SignatureInput } from "./http-CXnNbRe_.mjs";
13
- import { t as getAuthenticatedDocumentLoader } from "./docloader-vc-pSu9-.mjs";
14
- import { n as kvCache } from "./kv-cache-DZr8HTel.mjs";
15
- import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-DpnpgjyX.mjs";
10
+ import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-ya2C1Xip.mjs";
11
+ import { a as signObject, c as verifyPortableObjectProof, i as hasProofLike, s as verifyObject, t as InvalidPortableObjectIdError, u as verifyPortableObjectProofWithRoot } from "./proof-BdO3BbLh.mjs";
12
+ import { a as listRequestSignatures, c as selectRequestSignature, d as verifyRequest, f as verifyRequestDetailed, s as parseRfc9421SignatureInput } from "./http-DV_yW4dv.mjs";
13
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-C5p26tpa.mjs";
14
+ import { n as kvCache } from "./kv-cache-Bxm0a0Ww.mjs";
15
+ import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-CEnNz8O7.mjs";
16
16
  import { t as nodeInfoToJson } from "./types-DE_LV1JB.mjs";
17
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-OdWSE8am.mjs";
18
- import { t as verifyPortableObject } from "./portable-collection-DAV7Ho3m.mjs";
19
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-ylV8KCLx.mjs";
20
- import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-CxHALSwA.mjs";
17
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-C6Nv5yyw.mjs";
18
+ import { t as verifyPortableObject } from "./portable-collection-uWj3hfzv.mjs";
19
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-BwANxryy.mjs";
20
+ import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-D89oOfHC.mjs";
21
21
  import { t as CircuitBreaker } from "./circuit-breaker-C5NqoLBJ.mjs";
22
22
  import { t as buildCollectionSynchronizationHeader } from "./collection-CnnP1UAv.mjs";
23
- import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, s as verifyServedPortableObjects, t as containsCompoundPortableObject } from "./compound-proof-nhrXTRQO.mjs";
23
+ import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, s as verifyServedPortableObjects, t as containsCompoundPortableObject } from "./compound-proof-B02Os8h2.mjs";
24
24
  import { t as KvKeyCache } from "./keycache-BInaECGj.mjs";
25
25
  import { t as acceptsJsonLd } from "./negotiation-CCocWT_N.mjs";
26
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-BTD4f_mZ.mjs";
26
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-CbPfRGZq.mjs";
27
27
  import { t as createExponentialBackoffPolicy } from "./retry-zX5xw5e0.mjs";
28
28
  import { t as TaskCodec } from "./codec-C6rTrpIp.mjs";
29
29
  import { getLogger, withContext } from "@logtape/logtape";
@@ -583,6 +583,43 @@ function parsePortableGatewayRequest(url) {
583
583
  };
584
584
  }
585
585
  /**
586
+ * Recognizes an FEP-ef61 portable ID, i.e., an `ap:` or `ap+ef61:` URI, or
587
+ * a compatible identifier on any gateway, and splits it into its DID and
588
+ * the object path to route.
589
+ *
590
+ * It accepts the same IDs as the gateway endpoint does: the query, e.g.,
591
+ * location hints, and the fragment are ignored, and a malformed DID, or
592
+ * a `did:key` DID that is not encoded in base58-btc, is refused.
593
+ *
594
+ * @param uri The ID to recognize.
595
+ * @returns The DID normalized as an FEP-fe34 origin, and the object path,
596
+ * or `null` if the ID is not a portable ID or is malformed.
597
+ */
598
+ function parsePortableId(uri) {
599
+ if (uri.username !== "" || uri.password !== "") return null;
600
+ if (uri.protocol !== "ap:" && uri.protocol !== "ap+ef61:") {
601
+ const request = parsePortableGatewayRequest(uri);
602
+ if (request?.type !== "object") return null;
603
+ return {
604
+ authority: request.portableRequest.authority,
605
+ path: request.path
606
+ };
607
+ }
608
+ try {
609
+ const id = parseIri(uri);
610
+ canonicalizePortableUri(formatIri(id));
611
+ const authority = getFe34Origin(id);
612
+ assertBase58BtcDidKey(authority);
613
+ return {
614
+ authority,
615
+ path: id.pathname
616
+ };
617
+ } catch (error) {
618
+ if (error instanceof TypeError) return null;
619
+ throw error;
620
+ }
621
+ }
622
+ /**
586
623
  * Builds a portable ID from a DID authority and an object path.
587
624
  * @param authority The bare DID, e.g., `did:key:z6Mk...`.
588
625
  * @param path The object path, e.g., `/notes/123`.
@@ -6348,12 +6385,25 @@ var ContextImpl = class ContextImpl {
6348
6385
  if (path === null) throw new RouterError(`No collection dispatcher registered for "${String(name)}".`);
6349
6386
  return buildPortableUri(requirePortableAuthority(authority), path);
6350
6387
  }
6351
- parseUri(uri) {
6388
+ parseUri(uri, options = {}) {
6352
6389
  if (uri == null) return null;
6353
- if (uri.origin !== this.origin && uri.origin !== this.canonicalOrigin) return null;
6354
- const route = this.federation.router.route(uri.pathname);
6355
- if (route == null) return null;
6356
- else if (route.name === "sharedInbox") return {
6390
+ if (uri.origin === this.origin || uri.origin === this.canonicalOrigin) {
6391
+ const route = this.federation.router.route(uri.pathname);
6392
+ if (route != null) return this.#parseRoute(route);
6393
+ }
6394
+ if (!options.portable) return null;
6395
+ const portable = parsePortableId(uri);
6396
+ if (portable == null) return null;
6397
+ const route = this.federation.router.route(portable.path);
6398
+ if (route == null || route.name === "sharedInbox") return null;
6399
+ const result = this.#parseRoute(route);
6400
+ return result == null ? null : {
6401
+ ...result,
6402
+ authority: portable.authority
6403
+ };
6404
+ }
6405
+ #parseRoute(route) {
6406
+ if (route.name === "sharedInbox") return {
6357
6407
  type: "inbox",
6358
6408
  identifier: void 0
6359
6409
  };
@@ -1,5 +1,5 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-DDa8pCUa.mjs";
4
+ import { n as FederationImpl } from "./middleware-Bdwd_Gg8.mjs";
5
5
  export { FederationImpl };