@fedify/fedify 2.4.0-dev.1964 → 2.4.0-dev.1995
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.
- package/dist/{builder-B4zpKIFU.mjs → builder-D0DxgOV5.mjs} +2 -2
- package/dist/{client-CtxvmazV.mjs → client-DpnpgjyX.mjs} +78 -4
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/compound-proof-CJnWc6c-.mjs +495 -0
- package/dist/{context-COLwHogJ.d.cts → context-BeDnDuF3.d.cts} +4 -3
- package/dist/{context-DiiCt4S7.d.ts → context-uyRUWoZQ.d.ts} +4 -3
- package/dist/{deno-CtG93ohj.mjs → deno-CABhzCb_.mjs} +1 -1
- package/dist/{docloader-dk_k3S8d.mjs → docloader-BbGSL0Xq.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler-compound-proof.test.d.mts +2 -0
- package/dist/federation/handler-compound-proof.test.mjs +152 -0
- package/dist/federation/handler.test.mjs +122 -4
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +52 -9
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +111 -15
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +139 -4
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-Drjlrrax.cjs → federation-DCEHQk4G.cjs} +632 -57
- package/dist/{http-6TI0tI03.d.cts → http-65PYi9jY.d.ts} +34 -2
- package/dist/{http-Dj4s1UQ0.js → http-9WyuldYh.js} +170 -8
- package/dist/{http-CdrSn2ip.d.ts → http-CP-7OdSq.d.cts} +34 -2
- package/dist/{http-B3skRvGo.cjs → http-CRFns-UE.cjs} +181 -7
- package/dist/{http-D7LX-BL9.mjs → http-D9urSO2D.mjs} +3 -3
- package/dist/{key-BVjgbs_9.mjs → key-Ccs8cnd7.mjs} +169 -7
- package/dist/{keycache-ZLqIfrkL.mjs → keycache-BUDHB5rY.mjs} +12 -4
- package/dist/{kv-cache-DtsjH_bY.cjs → kv-cache-DXT9vmIg.cjs} +2 -2
- package/dist/{kv-cache-tGNbvDCF.js → kv-cache-DtmXhPXG.js} +2 -2
- package/dist/{kv-cache-xRcC_xxP.mjs → kv-cache-lKyFn_Dn.mjs} +1 -1
- package/dist/{ld-OuCeQFny.mjs → ld-qup__MAW.mjs} +3 -3
- package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
- package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
- package/dist/{metrics-B9N27cCB.mjs → metrics-CISXCvB1.mjs} +1 -1
- package/dist/{middleware-TeLjzoxn.mjs → middleware-CzvJvPHK.mjs} +58 -15
- package/dist/{middleware-Cd_vPqDH.mjs → middleware-DkrU2WhY.mjs} +1 -1
- package/dist/{middleware-CPTDFMmJ.js → middleware-DsfWajaD.js} +593 -18
- package/dist/{mod-DypxPS9H.d.ts → mod-C0BYc0OU.d.ts} +1 -1
- package/dist/{mod-CnGLNoVw.d.cts → mod-CbKDOnCt.d.cts} +1 -1
- package/dist/{mod-CLHkCwLm.d.cts → mod-CyiSjRkk.d.cts} +1 -1
- package/dist/{mod-CSH1Ee-I.d.ts → mod-D882_Xsg.d.ts} +1 -1
- package/dist/{mod-DzRXACM5.d.ts → mod-UDwMjuHM.d.ts} +1 -1
- package/dist/{mod-BZldCEU0.d.cts → mod-x9W0d0Pk.d.cts} +1 -1
- package/dist/mod.cjs +20 -20
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +28 -1
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/{owner-DGSfntI-.d.ts → owner-BF0L9cMt.d.cts} +12 -1
- package/dist/{proof-BO_esQii.js → owner-BIPEZGiU.js} +1255 -1098
- package/dist/{owner-r_waTiQj.d.cts → owner-BSzr5-I8.d.ts} +12 -1
- package/dist/owner-CRoq8Spb.mjs +175 -0
- package/dist/{proof-Dua0d906.cjs → owner-CVA7L9MA.cjs} +1266 -1097
- package/dist/{proof-D5Ajk4DJ.mjs → proof-BDxNVZ3E.mjs} +186 -83
- package/dist/{send-DVXyUqhO.mjs → send-BzM5GLyn.mjs} +68 -11
- package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
- package/dist/sig/compound-proof-discovery.test.mjs +331 -0
- package/dist/sig/compound-proof-verification.test.d.mts +2 -0
- package/dist/sig/compound-proof-verification.test.mjs +465 -0
- package/dist/sig/compound-proof.test.d.mts +2 -0
- package/dist/sig/compound-proof.test.mjs +324 -0
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +169 -2
- package/dist/sig/ld.test.mjs +55 -2
- package/dist/sig/mod.cjs +17 -17
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +103 -4
- package/dist/sig/proof.test.mjs +120 -1
- package/dist/{temporal-BoDw51A-.mjs → temporal-Dv6Qv-8k.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +2 -1
- package/dist/{types-RXm5YxpC.cjs → types-Be2zbyeG.cjs} +94 -2
- package/dist/{types-Dtyf8lVj.js → types-C_cNlNVK.js} +78 -4
- package/dist/utils/docloader.test.mjs +27 -3
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
- package/dist/owner-DTEqTzSr.mjs +0 -120
|
@@ -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-
|
|
4
|
+
import { n as version, t as name } from "./deno-CABhzCb_.mjs";
|
|
5
5
|
import { t as ActivityListenerSet } from "./activity-listener-C-BSbKI7.mjs";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { Router, RouterError, assertPath } from "@fedify/uri-template";
|
|
@@ -85,7 +85,7 @@ var FederationBuilderImpl = class {
|
|
|
85
85
|
* specified.
|
|
86
86
|
*/
|
|
87
87
|
async build(options) {
|
|
88
|
-
const { FederationImpl } = await import("./middleware-
|
|
88
|
+
const { FederationImpl } = await import("./middleware-DkrU2WhY.mjs");
|
|
89
89
|
const f = new FederationImpl(options);
|
|
90
90
|
const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
|
|
91
91
|
f.router = this.router.clone();
|
|
@@ -2,7 +2,81 @@ import "temporal-polyfill";
|
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
|
-
import { UrlError, getUserAgent, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
5
|
+
import { FetchError, UrlError, getUserAgent, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
6
|
+
//#region src/utils/body.ts
|
|
7
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
8
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
9
|
+
/** A body exceeded the byte limit. @internal */
|
|
10
|
+
var BodyTooLargeError = class extends FetchError {
|
|
11
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
12
|
+
constructor(url, maxBytes) {
|
|
13
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
14
|
+
this.name = "BodyTooLargeError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
18
|
+
function validateBodySizeLimit(maxBytes) {
|
|
19
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
23
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
24
|
+
* @param message The response or request to read.
|
|
25
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
26
|
+
* @param url The URL to include in errors and logs.
|
|
27
|
+
* @returns The decoded body text.
|
|
28
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
32
|
+
validateBodySizeLimit(maxBytes);
|
|
33
|
+
const reader = message.body?.getReader();
|
|
34
|
+
const tooLarge = () => {
|
|
35
|
+
getLogger([
|
|
36
|
+
"fedify",
|
|
37
|
+
"runtime",
|
|
38
|
+
"body"
|
|
39
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
40
|
+
url: url.toString(),
|
|
41
|
+
maxBytes
|
|
42
|
+
});
|
|
43
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
const length = message.headers.get("Content-Length");
|
|
47
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
48
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
49
|
+
if (reader == null) return "";
|
|
50
|
+
let size = 0;
|
|
51
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done) {
|
|
54
|
+
controller.close();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
size += value.byteLength;
|
|
58
|
+
if (size > maxBytes) tooLarge();
|
|
59
|
+
controller.enqueue(value);
|
|
60
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
61
|
+
try {
|
|
62
|
+
const chunks = [];
|
|
63
|
+
while (true) {
|
|
64
|
+
const { done, value } = await textReader.read();
|
|
65
|
+
if (done) break;
|
|
66
|
+
chunks.push(value);
|
|
67
|
+
}
|
|
68
|
+
return chunks.join("");
|
|
69
|
+
} finally {
|
|
70
|
+
textReader.releaseLock();
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
74
|
+
throw error;
|
|
75
|
+
} finally {
|
|
76
|
+
reader?.releaseLock();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
6
80
|
//#region src/nodeinfo/client.ts
|
|
7
81
|
const logger = getLogger([
|
|
8
82
|
"fedify",
|
|
@@ -83,7 +157,7 @@ async function getNodeInfo(url, options = {}) {
|
|
|
83
157
|
});
|
|
84
158
|
return;
|
|
85
159
|
}
|
|
86
|
-
const wellKnownRd = await wellKnownResponse
|
|
160
|
+
const wellKnownRd = JSON.parse(await readBoundedText(wellKnownResponse, MAX_BODY_SIZE, wellKnownUrl));
|
|
87
161
|
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);
|
|
88
162
|
if (link == null || link.href == null) {
|
|
89
163
|
logger.error("Failed to find a NodeInfo document link from {url}: {resourceDescriptor}", {
|
|
@@ -107,7 +181,7 @@ async function getNodeInfo(url, options = {}) {
|
|
|
107
181
|
});
|
|
108
182
|
return;
|
|
109
183
|
}
|
|
110
|
-
const data = await response
|
|
184
|
+
const data = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, nodeInfoUrl));
|
|
111
185
|
if (options.parse === "none") return data;
|
|
112
186
|
return parseNodeInfo(data, { tryBestEffort: options.parse === "best-effort" }) ?? void 0;
|
|
113
187
|
} catch (error) {
|
|
@@ -311,4 +385,4 @@ function parseUsage(data, options = {}) {
|
|
|
311
385
|
};
|
|
312
386
|
}
|
|
313
387
|
//#endregion
|
|
314
|
-
export { parseProtocol as a, parseUsage as c, parseOutboundService as i, parseInboundService as n, parseServices as o, parseNodeInfo as r, parseSoftware as s, getNodeInfo as t };
|
|
388
|
+
export { parseProtocol as a, parseUsage as c, readBoundedText as d, parseOutboundService as i, BodyTooLargeError as l, parseInboundService as n, parseServices as o, parseNodeInfo as r, parseSoftware as s, getNodeInfo as t, MAX_BODY_SIZE as u };
|
package/dist/compat/mod.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
|
-
import { Gt as ActivityTransformer, n as Context } from "../context-
|
|
2
|
+
import { Gt as ActivityTransformer, n as Context } from "../context-BeDnDuF3.cjs";
|
|
3
3
|
import { Activity } from "@fedify/vocab";
|
|
4
4
|
|
|
5
5
|
//#region src/compat/transformers.d.ts
|
package/dist/compat/mod.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
|
-
import { Gt as ActivityTransformer, n as Context } from "../context-
|
|
2
|
+
import { Gt as ActivityTransformer, n as Context } from "../context-uyRUWoZQ.js";
|
|
3
3
|
import { Activity } from "@fedify/vocab";
|
|
4
4
|
|
|
5
5
|
//#region src/compat/transformers.d.ts
|
|
@@ -5,7 +5,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
6
6
|
import { t as assertInstanceOf } from "../assert_instance_of-g3WcyYNR.mjs";
|
|
7
7
|
import { t as assert } from "../assert-CebeUf0C.mjs";
|
|
8
|
-
import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-
|
|
8
|
+
import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-CzvJvPHK.mjs";
|
|
9
9
|
import { Follow, Person } from "@fedify/vocab";
|
|
10
10
|
import { test } from "@fedify/fixture";
|
|
11
11
|
//#region src/compat/transformers.test.ts
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { i as verifyMapLocalProof, s as verifyPortableObjectProofPolicy } from "./proof-BDxNVZ3E.mjs";
|
|
5
|
+
//#region src/sig/compound-proof.ts
|
|
6
|
+
const textEncoder = new TextEncoder();
|
|
7
|
+
const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
|
|
8
|
+
function isJsonMap(value) {
|
|
9
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
function isCompoundProofJsonObject(value) {
|
|
12
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function hasOnlyDataProperties(value) {
|
|
15
|
+
try {
|
|
16
|
+
return Reflect.ownKeys(value).every((key) => {
|
|
17
|
+
if (typeof key !== "string") return false;
|
|
18
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
19
|
+
return descriptor != null && "value" in descriptor && descriptor.enumerable;
|
|
20
|
+
});
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function hasOnlyJsonArrayProperties(value) {
|
|
26
|
+
try {
|
|
27
|
+
const keys = Reflect.ownKeys(value);
|
|
28
|
+
if (keys.length !== value.length + 1) return false;
|
|
29
|
+
for (const key of keys) {
|
|
30
|
+
if (key === "length") continue;
|
|
31
|
+
if (typeof key !== "string") return false;
|
|
32
|
+
const index = Number(key);
|
|
33
|
+
if (!Number.isInteger(index) || index < 0 || index >= value.length || String(index) !== key) return false;
|
|
34
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
35
|
+
if (descriptor == null || !descriptor.enumerable || !("value" in descriptor)) return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function escapeJsonPointerSegment(segment) {
|
|
43
|
+
return segment.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
44
|
+
}
|
|
45
|
+
function childPath(path, segment) {
|
|
46
|
+
return `${path}/${escapeJsonPointerSegment(segment)}`;
|
|
47
|
+
}
|
|
48
|
+
function comparePaths(left, right) {
|
|
49
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
50
|
+
}
|
|
51
|
+
function encodedLength(value) {
|
|
52
|
+
return textEncoder.encode(value).byteLength;
|
|
53
|
+
}
|
|
54
|
+
function validateLimits(limits) {
|
|
55
|
+
for (const [name, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a non-negative safe integer.`);
|
|
56
|
+
}
|
|
57
|
+
function limitExceeded(limit, maximum, actual, path) {
|
|
58
|
+
return {
|
|
59
|
+
status: "unsupported",
|
|
60
|
+
reason: {
|
|
61
|
+
type: "limitExceeded",
|
|
62
|
+
limit,
|
|
63
|
+
maximum,
|
|
64
|
+
actual,
|
|
65
|
+
path
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function addBytes(statistics, bytes, limits, path) {
|
|
70
|
+
statistics.byteLength += bytes;
|
|
71
|
+
if (statistics.byteLength > limits.maxBytes) return limitExceeded("bytes", limits.maxBytes, statistics.byteLength, path);
|
|
72
|
+
}
|
|
73
|
+
function inspectJsonTree(root, limits) {
|
|
74
|
+
if (!isJsonMap(root)) return {
|
|
75
|
+
status: "unsupported",
|
|
76
|
+
reason: {
|
|
77
|
+
type: "invalidJsonTree",
|
|
78
|
+
path: ""
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const statistics = {
|
|
82
|
+
byteLength: 0,
|
|
83
|
+
mapCount: 0,
|
|
84
|
+
proofCount: 0,
|
|
85
|
+
maxDepth: 0
|
|
86
|
+
};
|
|
87
|
+
const seen = /* @__PURE__ */ new Set();
|
|
88
|
+
const pending = [{
|
|
89
|
+
value: root,
|
|
90
|
+
path: "",
|
|
91
|
+
depth: 0,
|
|
92
|
+
insideProof: false,
|
|
93
|
+
insideContext: false
|
|
94
|
+
}];
|
|
95
|
+
while (pending.length > 0) {
|
|
96
|
+
const current = pending.pop();
|
|
97
|
+
statistics.maxDepth = Math.max(statistics.maxDepth, current.depth);
|
|
98
|
+
if (current.depth > limits.maxDepth) return limitExceeded("depth", limits.maxDepth, current.depth, current.path);
|
|
99
|
+
const value = current.value;
|
|
100
|
+
if (value === null) {
|
|
101
|
+
const failure = addBytes(statistics, 4, limits, current.path);
|
|
102
|
+
if (failure != null) return failure;
|
|
103
|
+
} else if (typeof value === "boolean") {
|
|
104
|
+
const failure = addBytes(statistics, value ? 4 : 5, limits, current.path);
|
|
105
|
+
if (failure != null) return failure;
|
|
106
|
+
} else if (typeof value === "string") {
|
|
107
|
+
const failure = addBytes(statistics, encodedLength(JSON.stringify(value)), limits, current.path);
|
|
108
|
+
if (failure != null) return failure;
|
|
109
|
+
} else if (typeof value === "number") {
|
|
110
|
+
if (!Number.isFinite(value)) return {
|
|
111
|
+
status: "unsupported",
|
|
112
|
+
reason: {
|
|
113
|
+
type: "invalidJsonTree",
|
|
114
|
+
path: current.path
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const failure = addBytes(statistics, encodedLength(JSON.stringify(value)), limits, current.path);
|
|
118
|
+
if (failure != null) return failure;
|
|
119
|
+
} else if (Array.isArray(value)) {
|
|
120
|
+
if (seen.has(value) || !hasOnlyJsonArrayProperties(value)) return {
|
|
121
|
+
status: "unsupported",
|
|
122
|
+
reason: {
|
|
123
|
+
type: "invalidJsonTree",
|
|
124
|
+
path: current.path
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
seen.add(value);
|
|
128
|
+
const failure = addBytes(statistics, 2 + Math.max(0, value.length - 1), limits, current.path);
|
|
129
|
+
if (failure != null) return failure;
|
|
130
|
+
for (let index = value.length - 1; index >= 0; index--) pending.push({
|
|
131
|
+
value: value[index],
|
|
132
|
+
path: childPath(current.path, String(index)),
|
|
133
|
+
depth: current.depth + 1,
|
|
134
|
+
insideProof: current.insideProof,
|
|
135
|
+
insideContext: current.insideContext
|
|
136
|
+
});
|
|
137
|
+
} else if (isJsonMap(value)) {
|
|
138
|
+
const prototype = Object.getPrototypeOf(value);
|
|
139
|
+
if (seen.has(value) || !hasOnlyDataProperties(value) || prototype !== Object.prototype && prototype !== null) return {
|
|
140
|
+
status: "unsupported",
|
|
141
|
+
reason: {
|
|
142
|
+
type: "invalidJsonTree",
|
|
143
|
+
path: current.path
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
seen.add(value);
|
|
147
|
+
statistics.mapCount++;
|
|
148
|
+
if (statistics.mapCount > limits.maxMaps) return limitExceeded("maps", limits.maxMaps, statistics.mapCount, current.path);
|
|
149
|
+
const entries = Object.entries(value).sort(([left], [right]) => comparePaths(left, right));
|
|
150
|
+
let objectBytes = 2 + Math.max(0, entries.length - 1);
|
|
151
|
+
for (const [key] of entries) objectBytes += encodedLength(JSON.stringify(key)) + 1;
|
|
152
|
+
const failure = addBytes(statistics, objectBytes, limits, current.path);
|
|
153
|
+
if (failure != null) return failure;
|
|
154
|
+
const hasDirectProof = !current.insideProof && !current.insideContext && Object.hasOwn(value, "proof");
|
|
155
|
+
if (hasDirectProof) {
|
|
156
|
+
statistics.proofCount++;
|
|
157
|
+
if (statistics.proofCount > limits.maxProofs) return limitExceeded("proofs", limits.maxProofs, statistics.proofCount, childPath(current.path, "proof"));
|
|
158
|
+
if (!isJsonMap(value.proof)) return {
|
|
159
|
+
status: "unsupported",
|
|
160
|
+
reason: {
|
|
161
|
+
type: "unsupportedProofShape",
|
|
162
|
+
path: childPath(current.path, "proof")
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
167
|
+
const [key, child] = entries[index];
|
|
168
|
+
pending.push({
|
|
169
|
+
value: child,
|
|
170
|
+
path: childPath(current.path, key),
|
|
171
|
+
depth: current.depth + 1,
|
|
172
|
+
insideProof: current.insideProof || hasDirectProof && key === "proof",
|
|
173
|
+
insideContext: current.insideContext || key === "@context"
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
} else return {
|
|
177
|
+
status: "unsupported",
|
|
178
|
+
reason: {
|
|
179
|
+
type: "invalidJsonTree",
|
|
180
|
+
path: current.path
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return statistics;
|
|
185
|
+
}
|
|
186
|
+
function freezeJsonTree(root) {
|
|
187
|
+
const pending = [root];
|
|
188
|
+
while (pending.length > 0) {
|
|
189
|
+
const value = pending.pop();
|
|
190
|
+
if (typeof value !== "object" || value == null) continue;
|
|
191
|
+
if (Array.isArray(value)) for (const child of value) pending.push(child);
|
|
192
|
+
else for (const child of Object.values(value)) pending.push(child);
|
|
193
|
+
Object.freeze(value);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function collectDocuments(snapshot) {
|
|
197
|
+
const documents = [];
|
|
198
|
+
const pending = [{
|
|
199
|
+
value: snapshot,
|
|
200
|
+
path: "",
|
|
201
|
+
depth: 0
|
|
202
|
+
}];
|
|
203
|
+
while (pending.length > 0) {
|
|
204
|
+
const current = pending.pop();
|
|
205
|
+
if (Array.isArray(current.value)) {
|
|
206
|
+
for (let index = current.value.length - 1; index >= 0; index--) pending.push({
|
|
207
|
+
value: current.value[index],
|
|
208
|
+
path: childPath(current.path, String(index)),
|
|
209
|
+
depth: current.depth + 1
|
|
210
|
+
});
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (!isCompoundProofJsonObject(current.value)) continue;
|
|
214
|
+
const document = current.value;
|
|
215
|
+
if (Object.hasOwn(document, "proof")) {
|
|
216
|
+
const proof = document.proof;
|
|
217
|
+
const id = typeof document.id === "string" ? document.id : typeof document["@id"] === "string" ? document["@id"] : void 0;
|
|
218
|
+
documents.push(Object.freeze({
|
|
219
|
+
path: current.path,
|
|
220
|
+
id,
|
|
221
|
+
depth: current.depth,
|
|
222
|
+
securedDocument: document,
|
|
223
|
+
proof
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
const entries = Object.entries(document).sort(([left], [right]) => comparePaths(left, right));
|
|
227
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
228
|
+
const [key, value] = entries[index];
|
|
229
|
+
if (key === "proof" || key === "@context") continue;
|
|
230
|
+
pending.push({
|
|
231
|
+
value,
|
|
232
|
+
path: childPath(current.path, key),
|
|
233
|
+
depth: current.depth + 1
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
documents.sort((left, right) => right.depth - left.depth || comparePaths(left.path, right.path));
|
|
238
|
+
return documents;
|
|
239
|
+
}
|
|
240
|
+
function collectPortableObjects(snapshot) {
|
|
241
|
+
const objects = [];
|
|
242
|
+
const pending = [{
|
|
243
|
+
value: snapshot,
|
|
244
|
+
path: "",
|
|
245
|
+
depth: 0
|
|
246
|
+
}];
|
|
247
|
+
while (pending.length > 0) {
|
|
248
|
+
const current = pending.pop();
|
|
249
|
+
if (Array.isArray(current.value)) {
|
|
250
|
+
for (let index = current.value.length - 1; index >= 0; index--) pending.push({
|
|
251
|
+
value: current.value[index],
|
|
252
|
+
path: childPath(current.path, String(index)),
|
|
253
|
+
depth: current.depth + 1
|
|
254
|
+
});
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (!isCompoundProofJsonObject(current.value)) continue;
|
|
258
|
+
const document = current.value;
|
|
259
|
+
const id = [document.id, document["@id"]].find((value) => typeof value === "string" && PORTABLE_OBJECT_ID_PATTERN.test(value));
|
|
260
|
+
if (typeof id === "string") objects.push(Object.freeze({
|
|
261
|
+
path: current.path,
|
|
262
|
+
id,
|
|
263
|
+
depth: current.depth,
|
|
264
|
+
document
|
|
265
|
+
}));
|
|
266
|
+
const entries = Object.entries(document).sort(([left], [right]) => comparePaths(left, right));
|
|
267
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
268
|
+
const [key, value] = entries[index];
|
|
269
|
+
if (key === "proof" || key === "@context") continue;
|
|
270
|
+
pending.push({
|
|
271
|
+
value,
|
|
272
|
+
path: childPath(current.path, key),
|
|
273
|
+
depth: current.depth + 1
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
objects.sort((left, right) => right.depth - left.depth || comparePaths(left.path, right.path));
|
|
278
|
+
return objects;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Determines whether a received JSON tree contains a portable object ID
|
|
282
|
+
* outside a proof or context definition, without exceeding discovery limits.
|
|
283
|
+
*
|
|
284
|
+
* This is only an applicability check. It does not authenticate the input;
|
|
285
|
+
* callers must still use {@link verifyCompoundPortableObjectProofs} to obtain
|
|
286
|
+
* an atomic result from an immutable snapshot. `indeterminate` means the
|
|
287
|
+
* applicability scan itself reached a limit, so a caller must not treat the
|
|
288
|
+
* document as outside the compound profile.
|
|
289
|
+
*
|
|
290
|
+
* @internal
|
|
291
|
+
*/
|
|
292
|
+
function inspectCompoundPortableObjectApplicability(json, limits) {
|
|
293
|
+
validateLimits(limits);
|
|
294
|
+
let byteLength = 0;
|
|
295
|
+
let mapCount = 0;
|
|
296
|
+
const pending = [{
|
|
297
|
+
iterator: [json].values(),
|
|
298
|
+
depth: 0
|
|
299
|
+
}];
|
|
300
|
+
const addByteLength = (length) => {
|
|
301
|
+
byteLength += length;
|
|
302
|
+
return byteLength <= limits.maxBytes;
|
|
303
|
+
};
|
|
304
|
+
while (pending.length > 0) {
|
|
305
|
+
const frame = pending[pending.length - 1];
|
|
306
|
+
const next = frame.iterator.next();
|
|
307
|
+
if (next.done) {
|
|
308
|
+
pending.pop();
|
|
309
|
+
if (byteLength > limits.maxBytes) return "indeterminate";
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const value = next.value;
|
|
313
|
+
if (frame.depth > limits.maxDepth) return "indeterminate";
|
|
314
|
+
if (value === null) {
|
|
315
|
+
if (!addByteLength(4)) return "indeterminate";
|
|
316
|
+
} else if (typeof value === "boolean") {
|
|
317
|
+
if (!addByteLength(value ? 4 : 5)) return "indeterminate";
|
|
318
|
+
} else if (typeof value === "string" || typeof value === "number") {
|
|
319
|
+
if (!addByteLength(encodedLength(JSON.stringify(value)))) return "indeterminate";
|
|
320
|
+
} else if (Array.isArray(value)) {
|
|
321
|
+
if (!addByteLength(2 + Math.max(0, value.length - 1))) return "indeterminate";
|
|
322
|
+
pending.push({
|
|
323
|
+
iterator: value.values(),
|
|
324
|
+
depth: frame.depth + 1
|
|
325
|
+
});
|
|
326
|
+
} else if (isJsonMap(value)) {
|
|
327
|
+
mapCount++;
|
|
328
|
+
if (mapCount > limits.maxMaps) return "indeterminate";
|
|
329
|
+
if ([value.id, value["@id"]].some((id) => typeof id === "string" && PORTABLE_OBJECT_ID_PATTERN.test(id))) return "present";
|
|
330
|
+
if (!addByteLength(2)) return "indeterminate";
|
|
331
|
+
const children = function* () {
|
|
332
|
+
let first = true;
|
|
333
|
+
for (const key in value) {
|
|
334
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
335
|
+
if (key === "proof" || key === "@context") continue;
|
|
336
|
+
if (!first && !addByteLength(1)) return;
|
|
337
|
+
first = false;
|
|
338
|
+
if (!addByteLength(encodedLength(JSON.stringify(key)) + 1)) return;
|
|
339
|
+
yield value[key];
|
|
340
|
+
}
|
|
341
|
+
}();
|
|
342
|
+
pending.push({
|
|
343
|
+
iterator: children,
|
|
344
|
+
depth: frame.depth + 1
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
if (byteLength > limits.maxBytes) return "indeterminate";
|
|
348
|
+
}
|
|
349
|
+
return "absent";
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Discovers direct literal proof-bearing maps in an immutable JSON snapshot.
|
|
353
|
+
*
|
|
354
|
+
* Discovery is bounded and atomic. Unsupported proof shapes, non-JSON input,
|
|
355
|
+
* or a resource-limit failure return no partial candidate list. Proof
|
|
356
|
+
* configuration subtrees are validated and counted as JSON, but are not
|
|
357
|
+
* themselves searched for secured documents.
|
|
358
|
+
*/
|
|
359
|
+
function discoverCompoundProofDocuments(json, limits) {
|
|
360
|
+
validateLimits(limits);
|
|
361
|
+
let inspected;
|
|
362
|
+
try {
|
|
363
|
+
inspected = inspectJsonTree(json, limits);
|
|
364
|
+
} catch {
|
|
365
|
+
return {
|
|
366
|
+
status: "unsupported",
|
|
367
|
+
reason: {
|
|
368
|
+
type: "invalidJsonTree",
|
|
369
|
+
path: ""
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
if ("status" in inspected) return inspected;
|
|
374
|
+
let snapshot;
|
|
375
|
+
try {
|
|
376
|
+
snapshot = structuredClone(json);
|
|
377
|
+
} catch {
|
|
378
|
+
return {
|
|
379
|
+
status: "unsupported",
|
|
380
|
+
reason: {
|
|
381
|
+
type: "invalidJsonTree",
|
|
382
|
+
path: ""
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
freezeJsonTree(snapshot);
|
|
387
|
+
return {
|
|
388
|
+
status: "ok",
|
|
389
|
+
snapshot,
|
|
390
|
+
documents: Object.freeze(collectDocuments(snapshot)),
|
|
391
|
+
statistics: Object.freeze({ ...inspected })
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Verifies every direct literal proof found in one immutable JSON snapshot.
|
|
396
|
+
*
|
|
397
|
+
* Each current map is verified independently. Only its literal `proof`
|
|
398
|
+
* member is removed from its JCS input, so descendant proofs and proof aliases
|
|
399
|
+
* remain unchanged. This is the lower-level compound-document mechanism; it
|
|
400
|
+
* does not apply portable-object policy or authenticate an inbox activity.
|
|
401
|
+
*
|
|
402
|
+
* @internal
|
|
403
|
+
*/
|
|
404
|
+
async function verifyCompoundProofDocuments(json, limits, options = {}) {
|
|
405
|
+
const discovered = discoverCompoundProofDocuments(json, limits);
|
|
406
|
+
if (discovered.status !== "ok") return discovered;
|
|
407
|
+
const documents = await verifyDiscoveredProofDocuments(discovered, options);
|
|
408
|
+
return {
|
|
409
|
+
status: "ok",
|
|
410
|
+
verified: documents.length > 0 && documents.every((document) => document.verified),
|
|
411
|
+
snapshot: discovered.snapshot,
|
|
412
|
+
documents,
|
|
413
|
+
statistics: discovered.statistics
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
async function verifyDiscoveredProofDocuments(discovered, options) {
|
|
417
|
+
const documents = await Promise.all(discovered.documents.map(async (document) => {
|
|
418
|
+
const metadata = {
|
|
419
|
+
path: document.path,
|
|
420
|
+
...document.id == null ? {} : { id: document.id },
|
|
421
|
+
depth: document.depth
|
|
422
|
+
};
|
|
423
|
+
if (document.depth > 0 && !Object.hasOwn(document.securedDocument, "@context")) return Object.freeze({
|
|
424
|
+
...metadata,
|
|
425
|
+
verified: false,
|
|
426
|
+
reason: { type: "missingContext" }
|
|
427
|
+
});
|
|
428
|
+
const key = await verifyMapLocalProof(document.securedDocument, options);
|
|
429
|
+
return key == null ? Object.freeze({
|
|
430
|
+
...metadata,
|
|
431
|
+
verified: false,
|
|
432
|
+
reason: { type: "invalidProof" }
|
|
433
|
+
}) : Object.freeze({
|
|
434
|
+
...metadata,
|
|
435
|
+
verified: true,
|
|
436
|
+
key
|
|
437
|
+
});
|
|
438
|
+
}));
|
|
439
|
+
return Object.freeze(documents);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Applies FEP-ef61 portable-object policy to every portable map in a compound
|
|
443
|
+
* snapshot without changing the map-local cryptographic inputs.
|
|
444
|
+
*
|
|
445
|
+
* @internal
|
|
446
|
+
*/
|
|
447
|
+
async function verifyCompoundPortableObjectProofs(json, limits, options = {}) {
|
|
448
|
+
const discovered = discoverCompoundProofDocuments(json, limits);
|
|
449
|
+
if (discovered.status !== "ok") return discovered;
|
|
450
|
+
const proofs = await verifyDiscoveredProofDocuments(discovered, options);
|
|
451
|
+
const proofByPath = new Map(proofs.map((proof) => [proof.path, proof]));
|
|
452
|
+
const portableObjects = await Promise.all(collectPortableObjects(discovered.snapshot).map(async (object) => {
|
|
453
|
+
const metadata = {
|
|
454
|
+
path: object.path,
|
|
455
|
+
id: object.id,
|
|
456
|
+
depth: object.depth
|
|
457
|
+
};
|
|
458
|
+
if (object.depth > 0 && !Object.hasOwn(object.document, "@context")) return Object.freeze({
|
|
459
|
+
...metadata,
|
|
460
|
+
verified: false,
|
|
461
|
+
reason: { type: "missingContext" }
|
|
462
|
+
});
|
|
463
|
+
const proof = proofByPath.get(object.path);
|
|
464
|
+
const key = proof?.verified === true ? proof.key : null;
|
|
465
|
+
try {
|
|
466
|
+
const policy = await verifyPortableObjectProofPolicy(object.document, key, options);
|
|
467
|
+
return policy.verified ? Object.freeze({
|
|
468
|
+
...metadata,
|
|
469
|
+
id: policy.objectId,
|
|
470
|
+
verified: true,
|
|
471
|
+
keys: policy.keys
|
|
472
|
+
}) : Object.freeze({
|
|
473
|
+
...metadata,
|
|
474
|
+
verified: false,
|
|
475
|
+
reason: policy.reason
|
|
476
|
+
});
|
|
477
|
+
} catch {
|
|
478
|
+
return Object.freeze({
|
|
479
|
+
...metadata,
|
|
480
|
+
verified: false,
|
|
481
|
+
reason: { type: "invalidPortableObject" }
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}));
|
|
485
|
+
return {
|
|
486
|
+
status: "ok",
|
|
487
|
+
verified: portableObjects.length > 0 && proofs.every((proof) => proof.verified) && portableObjects.every((object) => object.verified),
|
|
488
|
+
snapshot: discovered.snapshot,
|
|
489
|
+
proofs,
|
|
490
|
+
portableObjects: Object.freeze(portableObjects),
|
|
491
|
+
statistics: discovered.statistics
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
495
|
+
export { verifyCompoundProofDocuments as i, inspectCompoundPortableObjectApplicability as n, verifyCompoundPortableObjectProofs as r, discoverCompoundProofDocuments as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { o as JsonValue, s as NodeInfo, t as GetNodeInfoOptions } from "./client-D-XP_Ccb.cjs";
|
|
3
|
-
import { o as VerifyRequestFailureReason, t as HttpMessageSignaturesSpec } from "./http-
|
|
4
|
-
import { n as GetKeyOwnerOptions } from "./owner-
|
|
3
|
+
import { o as VerifyRequestFailureReason, t as HttpMessageSignaturesSpec } from "./http-CP-7OdSq.cjs";
|
|
4
|
+
import { n as GetKeyOwnerOptions } from "./owner-BF0L9cMt.cjs";
|
|
5
5
|
import { n as KvStore, t as KvKey } from "./kv-BhNbN8Ed.cjs";
|
|
6
6
|
import { Activity, Actor, Collection, CryptographicKey, Hashtag, Link, LookupObjectOptions, Multikey, Object as Object$1, Recipient, Tombstone, TraverseCollectionOptions } from "@fedify/vocab";
|
|
7
7
|
import { AuthenticatedDocumentLoaderFactory, DocumentLoader, DocumentLoaderFactory, GetUserAgentOptions } from "@fedify/vocab-runtime";
|
|
@@ -2052,7 +2052,8 @@ interface FederationOptions<TContextData> {
|
|
|
2052
2052
|
*/
|
|
2053
2053
|
authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory;
|
|
2054
2054
|
/**
|
|
2055
|
-
* Whether to allow
|
|
2055
|
+
* Whether to allow private network addresses in the document loader and
|
|
2056
|
+
* outbound activity delivery, including redirects.
|
|
2056
2057
|
*
|
|
2057
2058
|
* If turned on, {@link FederationOptions.documentLoader},
|
|
2058
2059
|
* {@link FederationOptions.contextLoader}, and
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { o as JsonValue, s as NodeInfo, t as GetNodeInfoOptions } from "./client-D-XP_Ccb.js";
|
|
3
|
-
import { o as VerifyRequestFailureReason, t as HttpMessageSignaturesSpec } from "./http-
|
|
4
|
-
import { n as GetKeyOwnerOptions } from "./owner-
|
|
3
|
+
import { o as VerifyRequestFailureReason, t as HttpMessageSignaturesSpec } from "./http-65PYi9jY.js";
|
|
4
|
+
import { n as GetKeyOwnerOptions } from "./owner-BSzr5-I8.js";
|
|
5
5
|
import { n as KvStore, t as KvKey } from "./kv-BhNbN8Ed.js";
|
|
6
6
|
import { Router } from "@fedify/uri-template";
|
|
7
7
|
import { Activity, Actor, Collection, CryptographicKey, Hashtag, Link, LookupObjectOptions, Multikey, Object as Object$1, Recipient, Tombstone, TraverseCollectionOptions } from "@fedify/vocab";
|
|
@@ -2054,7 +2054,8 @@ interface FederationOptions<TContextData> {
|
|
|
2054
2054
|
*/
|
|
2055
2055
|
authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory;
|
|
2056
2056
|
/**
|
|
2057
|
-
* Whether to allow
|
|
2057
|
+
* Whether to allow private network addresses in the document loader and
|
|
2058
|
+
* outbound activity delivery, including redirects.
|
|
2058
2059
|
*
|
|
2059
2060
|
* If turned on, {@link FederationOptions.documentLoader},
|
|
2060
2061
|
* {@link FederationOptions.contextLoader}, and
|