@fedify/vocab-runtime 2.4.0-pr.934.40 → 2.4.0
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/deno.json +4 -2
- package/dist/{tests/docloader-Xi61Le8w.mjs → contexts-BK1CqBR5.cjs} +164 -269
- package/dist/{tests/docloader-BFedvemb.cjs → contexts-DPuJ4UYL.js} +160 -288
- package/dist/{docloader-DnUMWHaJ.d.cts → docloader-CYwqh5Df.d.cts} +68 -2
- package/dist/{docloader-xRGn1azD.d.ts → docloader-CYwqh5Df.d.ts} +68 -2
- package/dist/internal/jsonld-cache.cjs +70 -1
- package/dist/internal/jsonld-cache.d.cts +35 -2
- package/dist/internal/jsonld-cache.d.ts +35 -2
- package/dist/internal/jsonld-cache.js +67 -2
- package/dist/internal/portable-dereference.cjs +721 -0
- package/dist/internal/portable-dereference.d.cts +349 -0
- package/dist/internal/portable-dereference.d.ts +349 -0
- package/dist/internal/portable-dereference.js +702 -0
- package/dist/internal/signed-representation.cjs +378 -0
- package/dist/internal/signed-representation.d.cts +144 -0
- package/dist/internal/signed-representation.d.ts +144 -0
- package/dist/internal/signed-representation.js +371 -0
- package/dist/jsonld.cjs +2 -2
- package/dist/mod.cjs +715 -4523
- package/dist/mod.d.cts +406 -6
- package/dist/mod.d.ts +406 -7
- package/dist/mod.js +694 -4518
- package/dist/portable-DqtfLy_1.d.cts +160 -0
- package/dist/portable-DtWsu2yU.d.ts +160 -0
- package/dist/tests/body-CYiu9CO-.mjs +130 -0
- package/dist/tests/body-CkEwROsn.cjs +153 -0
- package/dist/tests/body.test.cjs +89 -0
- package/dist/tests/body.test.d.cts +1 -0
- package/dist/tests/body.test.d.mts +1 -0
- package/dist/tests/body.test.mjs +90 -0
- package/dist/tests/contexts-CIKsin4e.mjs +4512 -0
- package/dist/tests/contexts-DizzBjz4.cjs +4523 -0
- package/dist/tests/decimal.test.cjs +8 -7
- package/dist/tests/decimal.test.mjs +8 -6
- package/dist/tests/digest-3FeH2Y-Q.cjs +176 -0
- package/dist/tests/digest-COC7xDiQ.mjs +141 -0
- package/dist/tests/digest.test.cjs +102 -0
- package/dist/tests/digest.test.d.cts +1 -0
- package/dist/tests/digest.test.d.mts +1 -0
- package/dist/tests/digest.test.mjs +103 -0
- package/dist/tests/docloader-C3YFl6y4.cjs +396 -0
- package/dist/tests/docloader-CvmS4sVq.mjs +373 -0
- package/dist/tests/docloader.test.cjs +692 -39
- package/dist/tests/docloader.test.mjs +686 -34
- package/dist/tests/internal/multicodec.test.cjs +2 -3
- package/dist/tests/internal/multicodec.test.mjs +2 -2
- package/dist/tests/internal/portable-dereference.test.cjs +172 -0
- package/dist/tests/internal/portable-dereference.test.d.cts +1 -0
- package/dist/tests/internal/portable-dereference.test.d.mts +1 -0
- package/dist/tests/internal/portable-dereference.test.mjs +173 -0
- package/dist/tests/jsonld-cache-BPQmOZWD.mjs +342 -0
- package/dist/tests/jsonld-cache-C07AyNOY.cjs +397 -0
- package/dist/tests/jsonld-cache.test.cjs +101 -298
- package/dist/tests/jsonld-cache.test.mjs +94 -289
- package/dist/tests/{key-_wXwomh_.cjs → key-C-AYkdJJ.cjs} +11 -4
- package/dist/tests/{key-CDGDH_vC.mjs → key-C2Db_TAJ.mjs} +11 -3
- package/dist/tests/key.test.cjs +6 -5
- package/dist/tests/key.test.mjs +6 -4
- package/dist/tests/langstr.test.cjs +4 -4
- package/dist/tests/langstr.test.mjs +2 -2
- package/dist/tests/link.test.cjs +2 -3
- package/dist/tests/link.test.mjs +2 -2
- package/dist/tests/multibase/multibase.test.cjs +8 -9
- package/dist/tests/multibase/multibase.test.mjs +6 -6
- package/dist/tests/{multibase-Bz_UUDtL.cjs → multibase-B5Mea7Ip.cjs} +19 -2
- package/dist/tests/{multibase-B4bvakyA.mjs → multibase-BPnF_L4e.mjs} +12 -1
- package/dist/tests/portable-dereference-BNXtgg5U.cjs +278 -0
- package/dist/tests/portable-dereference-DRE5bz-l.mjs +249 -0
- package/dist/tests/portable-media-BLKllU56.cjs +153 -0
- package/dist/tests/portable-media-DRSGBORB.mjs +148 -0
- package/dist/tests/portable-media.test.cjs +222 -0
- package/dist/tests/portable-media.test.d.cts +1 -0
- package/dist/tests/portable-media.test.d.mts +1 -0
- package/dist/tests/portable-media.test.mjs +223 -0
- package/dist/tests/portable-workers.test.cjs +36 -0
- package/dist/tests/portable-workers.test.d.cts +2 -0
- package/dist/tests/portable-workers.test.d.mts +2 -0
- package/dist/tests/portable-workers.test.mjs +35 -0
- package/dist/tests/{request-cCPgOxYG.cjs → request-8vPWtV1-.cjs} +10 -4
- package/dist/tests/{request-BOS-hNaf.mjs → request-DRaOaTqD.mjs} +8 -2
- package/dist/tests/request.test.cjs +7 -4
- package/dist/tests/request.test.mjs +5 -2
- package/dist/tests/signed-representation.test.cjs +588 -0
- package/dist/tests/signed-representation.test.d.cts +1 -0
- package/dist/tests/signed-representation.test.d.mts +1 -0
- package/dist/tests/signed-representation.test.mjs +589 -0
- package/dist/tests/temporal.test.cjs +1 -2
- package/dist/tests/temporal.test.mjs +1 -1
- package/dist/tests/url-BNakuZ8k.cjs +998 -0
- package/dist/tests/url-DMxmp7ZG.mjs +859 -0
- package/dist/tests/url.test.cjs +503 -3
- package/dist/tests/url.test.mjs +503 -2
- package/dist/url-DrGTR8yv.cjs +993 -0
- package/dist/url-Dzyp-NsC.js +860 -0
- package/package.json +27 -4
- package/scripts/test-bun.mjs +17 -0
- package/src/body.test.ts +125 -0
- package/src/body.ts +152 -0
- package/src/contexts/cid-v1.json +114 -0
- package/src/contexts/fep-22cd.json +21 -0
- package/src/contexts/fep-ef61.json +10 -0
- package/src/contexts/miscellany.json +17 -0
- package/src/contexts/security-data-integrity-v1.json +0 -4
- package/src/contexts.ts +40 -0
- package/src/digest.test.ts +220 -0
- package/src/digest.ts +229 -0
- package/src/docloader.test.ts +918 -27
- package/src/docloader.ts +322 -105
- package/src/internal/jsonld-cache.ts +97 -1
- package/src/internal/portable-dereference.test.ts +254 -0
- package/src/internal/portable-dereference.ts +1000 -0
- package/src/internal/signed-representation.ts +565 -0
- package/src/jsonld-cache.test.ts +89 -0
- package/src/key.test.ts +9 -0
- package/src/key.ts +11 -1
- package/src/mod.ts +30 -0
- package/src/multibase/multibase.test.ts +5 -5
- package/src/portable-media.test.ts +293 -0
- package/src/portable-media.ts +264 -0
- package/src/portable-workers.test.ts +81 -0
- package/src/portable.ts +180 -0
- package/src/preprocessor.ts +7 -0
- package/src/request.test.ts +10 -1
- package/src/request.ts +9 -1
- package/src/signed-representation.test.ts +338 -0
- package/src/url.test.ts +844 -1
- package/src/url.ts +717 -17
- package/tsdown.config.ts +2 -0
- package/dist/tests/url-BvjYQdxL.cjs +0 -456
- package/dist/tests/url-a2D8NAgh.mjs +0 -378
- package/dist/url-Ck3dGEwH.cjs +0 -457
- package/dist/url-m1YxGNZ0.js +0 -379
- /package/dist/{chunk-M78iaK0I.cjs → rolldown-runtime-B7lfambq.cjs} +0 -0
- /package/dist/tests/{langstr-CbAxaeEZ.cjs → langstr-C4Fl80ae.cjs} +0 -0
- /package/dist/tests/{langstr-Di5AvKpB.mjs → langstr-CQ26J_L7.mjs} +0 -0
- /package/dist/tests/{link-NUUWCdnK.mjs → link-Cevmc87v.mjs} +0 -0
- /package/dist/tests/{link-FguCydMA.cjs → link-DlKm8bEr.cjs} +0 -0
- /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-CLRPeW4N.cjs} +0 -0
- /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-CRIj_05H.mjs} +0 -0
- /package/dist/tests/{chunk-C2EiDwsr.cjs → rolldown-runtime-emK7D4bc.cjs} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/vocab-runtime",
|
|
3
|
-
"version": "2.4.0
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"homepage": "https://fedify.dev/",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -52,6 +52,26 @@
|
|
|
52
52
|
"require": "./dist/internal/jsonld-cache.cjs",
|
|
53
53
|
"default": "./dist/internal/jsonld-cache.js"
|
|
54
54
|
},
|
|
55
|
+
"./internal/portable-dereference": {
|
|
56
|
+
"types": {
|
|
57
|
+
"import": "./dist/internal/portable-dereference.d.ts",
|
|
58
|
+
"require": "./dist/internal/portable-dereference.d.cts",
|
|
59
|
+
"default": "./dist/internal/portable-dereference.d.ts"
|
|
60
|
+
},
|
|
61
|
+
"import": "./dist/internal/portable-dereference.js",
|
|
62
|
+
"require": "./dist/internal/portable-dereference.cjs",
|
|
63
|
+
"default": "./dist/internal/portable-dereference.js"
|
|
64
|
+
},
|
|
65
|
+
"./internal/signed-representation": {
|
|
66
|
+
"types": {
|
|
67
|
+
"import": "./dist/internal/signed-representation.d.ts",
|
|
68
|
+
"require": "./dist/internal/signed-representation.d.cts",
|
|
69
|
+
"default": "./dist/internal/signed-representation.d.ts"
|
|
70
|
+
},
|
|
71
|
+
"import": "./dist/internal/signed-representation.js",
|
|
72
|
+
"require": "./dist/internal/signed-representation.cjs",
|
|
73
|
+
"default": "./dist/internal/signed-representation.js"
|
|
74
|
+
},
|
|
55
75
|
"./temporal": {
|
|
56
76
|
"types": {
|
|
57
77
|
"import": "./dist/temporal.d.ts",
|
|
@@ -76,10 +96,11 @@
|
|
|
76
96
|
},
|
|
77
97
|
"license": "MIT",
|
|
78
98
|
"devDependencies": {
|
|
79
|
-
"@types/node": "^24.2
|
|
99
|
+
"@types/node": "^24.12.2",
|
|
80
100
|
"fetch-mock": "^12.5.4",
|
|
81
101
|
"tsdown": "^0.22.0",
|
|
82
|
-
"typescript": "^6.0.0"
|
|
102
|
+
"typescript": "^6.0.0",
|
|
103
|
+
"@fedify/fixture": "2.0.0"
|
|
83
104
|
},
|
|
84
105
|
"dependencies": {
|
|
85
106
|
"@js-temporal/polyfill": "^0.5.1",
|
|
@@ -96,6 +117,8 @@
|
|
|
96
117
|
"build": "pnpm --filter @fedify/vocab-runtime... run build:self",
|
|
97
118
|
"prepublish": "pnpm build",
|
|
98
119
|
"pretest": "pnpm build",
|
|
99
|
-
"test": "cd dist/tests && node --test"
|
|
120
|
+
"test": "cd dist/tests && node --test",
|
|
121
|
+
"pretest:bun": "pnpm build",
|
|
122
|
+
"test:bun": "node scripts/test-bun.mjs"
|
|
100
123
|
}
|
|
101
124
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { access, glob } from "node:fs/promises";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
const sources = await Array.fromAsync(glob("src/**/*.test.ts"));
|
|
5
|
+
if (sources.length === 0) throw new Error("No vocab-runtime tests found.");
|
|
6
|
+
|
|
7
|
+
const tests = sources.map((source) =>
|
|
8
|
+
source.replaceAll("\\", "/").replace(/^src\//, "dist/tests/")
|
|
9
|
+
.replace(/\.ts$/, ".mjs")
|
|
10
|
+
).sort();
|
|
11
|
+
for (const test of tests) await access(test);
|
|
12
|
+
|
|
13
|
+
const result = spawnSync("bun", ["test", "--timeout", "60000", ...tests], {
|
|
14
|
+
stdio: "inherit",
|
|
15
|
+
});
|
|
16
|
+
if (result.error) throw result.error;
|
|
17
|
+
process.exitCode = result.status ?? 1;
|
package/src/body.test.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { deepStrictEqual, ok, rejects } from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { gunzipSync, gzipSync } from "node:zlib";
|
|
4
|
+
import { BodyTooLargeError, readBoundedText } from "./body.ts";
|
|
5
|
+
|
|
6
|
+
const url = "https://example.com/document";
|
|
7
|
+
|
|
8
|
+
test("readBoundedText handles empty bodies and flushes incomplete UTF-8", async () => {
|
|
9
|
+
deepStrictEqual(await readBoundedText(new Response(null), 1, url), "");
|
|
10
|
+
deepStrictEqual(await readBoundedText(new Response(""), 1, url), "");
|
|
11
|
+
deepStrictEqual(
|
|
12
|
+
await readBoundedText(new Response(new Uint8Array([0xe2, 0x82])), 2, url),
|
|
13
|
+
"\ufffd",
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("readBoundedText counts bytes and preserves split UTF-8", async () => {
|
|
18
|
+
const bytes = new TextEncoder().encode('"안녕"');
|
|
19
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
20
|
+
start(controller) {
|
|
21
|
+
for (const byte of bytes) controller.enqueue(new Uint8Array([byte]));
|
|
22
|
+
controller.close();
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
deepStrictEqual(
|
|
26
|
+
await readBoundedText(new Response(stream), bytes.length, url),
|
|
27
|
+
'"안녕"',
|
|
28
|
+
);
|
|
29
|
+
await rejects(
|
|
30
|
+
readBoundedText(new Response(bytes), bytes.length - 1, url),
|
|
31
|
+
BodyTooLargeError,
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("readBoundedText rejects a declared oversized body without pulling", async () => {
|
|
36
|
+
let pulled = false;
|
|
37
|
+
let canceled = false;
|
|
38
|
+
const response = new Response(
|
|
39
|
+
new ReadableStream({
|
|
40
|
+
pull() {
|
|
41
|
+
pulled = true;
|
|
42
|
+
},
|
|
43
|
+
cancel() {
|
|
44
|
+
canceled = true;
|
|
45
|
+
},
|
|
46
|
+
}, { highWaterMark: 0 }),
|
|
47
|
+
{ headers: { "Content-Length": "100" } },
|
|
48
|
+
);
|
|
49
|
+
await rejects(readBoundedText(response, 10, url), BodyTooLargeError);
|
|
50
|
+
deepStrictEqual(pulled, false);
|
|
51
|
+
deepStrictEqual(canceled, true);
|
|
52
|
+
deepStrictEqual(response.body?.locked, false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("readBoundedText cancels chunked bodies despite absent or false lengths", async () => {
|
|
56
|
+
for (const length of [undefined, "1", "invalid"]) {
|
|
57
|
+
let pulls = 0;
|
|
58
|
+
let canceled = false;
|
|
59
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
60
|
+
pull(controller) {
|
|
61
|
+
pulls++;
|
|
62
|
+
controller.enqueue(new Uint8Array(4));
|
|
63
|
+
},
|
|
64
|
+
cancel() {
|
|
65
|
+
canceled = true;
|
|
66
|
+
},
|
|
67
|
+
}, { highWaterMark: 0 });
|
|
68
|
+
const response = new Response(stream, {
|
|
69
|
+
headers: length == null ? {} : { "Content-Length": length },
|
|
70
|
+
});
|
|
71
|
+
await rejects(readBoundedText(response, 8, url), BodyTooLargeError);
|
|
72
|
+
deepStrictEqual(pulls, 3);
|
|
73
|
+
deepStrictEqual(canceled, true);
|
|
74
|
+
deepStrictEqual(response.body?.locked, false);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("readBoundedText limits decoded gzip bytes, not the compressed length", async () => {
|
|
79
|
+
const compressed = gzipSync('"' + "a".repeat(4096) + '"');
|
|
80
|
+
ok(compressed.length < 128);
|
|
81
|
+
const decoded = gunzipSync(compressed);
|
|
82
|
+
await rejects(
|
|
83
|
+
readBoundedText(
|
|
84
|
+
new Response(decoded, {
|
|
85
|
+
headers: {
|
|
86
|
+
"Content-Encoding": "gzip",
|
|
87
|
+
"Content-Length": String(compressed.length),
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
128,
|
|
91
|
+
url,
|
|
92
|
+
),
|
|
93
|
+
BodyTooLargeError,
|
|
94
|
+
);
|
|
95
|
+
// The encoded representation can also be larger than a valid decoded body.
|
|
96
|
+
deepStrictEqual(
|
|
97
|
+
await readBoundedText(
|
|
98
|
+
new Response("{}", {
|
|
99
|
+
headers: { "Content-Encoding": "gzip", "Content-Length": "22" },
|
|
100
|
+
}),
|
|
101
|
+
2,
|
|
102
|
+
url,
|
|
103
|
+
),
|
|
104
|
+
"{}",
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("readBoundedText rejects invalid limits and releases failed streams", async () => {
|
|
109
|
+
for (const limit of [0, -1, NaN, Infinity, 1.5]) {
|
|
110
|
+
await rejects(readBoundedText(new Response("{}"), limit, url), RangeError);
|
|
111
|
+
}
|
|
112
|
+
const failure = new Error("broken stream");
|
|
113
|
+
const response = new Response(
|
|
114
|
+
new ReadableStream({
|
|
115
|
+
pull(controller) {
|
|
116
|
+
controller.error(failure);
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
await rejects(
|
|
121
|
+
readBoundedText(response, 10, url),
|
|
122
|
+
(error) => error === failure,
|
|
123
|
+
);
|
|
124
|
+
deepStrictEqual(response.body?.locked, false);
|
|
125
|
+
});
|
package/src/body.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Kept private in each package to avoid adding public API in a patch release.
|
|
2
|
+
// TODO(Jiwon Kwon): Consolidate the duplicated body helpers in @fedify/vocab-runtime,
|
|
3
|
+
// @fedify/webfinger, and @fedify/fedify into a shared implementation.
|
|
4
|
+
import { getLogger } from "@logtape/logtape";
|
|
5
|
+
import { FetchError } from "./request.ts";
|
|
6
|
+
|
|
7
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
8
|
+
export const MAX_BODY_SIZE: number = 16 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
/** A body exceeded the byte limit. @internal */
|
|
11
|
+
export class BodyTooLargeError extends FetchError {
|
|
12
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
13
|
+
constructor(url: string | URL, maxBytes: number) {
|
|
14
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
15
|
+
this.name = "BodyTooLargeError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
20
|
+
function validateBodySizeLimit(maxBytes: number): void {
|
|
21
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
22
|
+
throw new RangeError(
|
|
23
|
+
"The body size limit must be a positive safe integer.",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
30
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
31
|
+
* @param message The response or request to read.
|
|
32
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
33
|
+
* @param url The URL to include in errors and logs.
|
|
34
|
+
* @returns The decoded body text.
|
|
35
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export async function readBoundedText(
|
|
39
|
+
message: Pick<Request, "body" | "headers">,
|
|
40
|
+
maxBytes: number,
|
|
41
|
+
url: string | URL,
|
|
42
|
+
): Promise<string> {
|
|
43
|
+
validateBodySizeLimit(maxBytes);
|
|
44
|
+
const reader = message.body?.getReader();
|
|
45
|
+
const tooLarge = (): never => {
|
|
46
|
+
getLogger(["fedify", "runtime", "body"]).warn(
|
|
47
|
+
"Body from {url} exceeds the limit of {maxBytes} bytes.",
|
|
48
|
+
{ url: url.toString(), maxBytes },
|
|
49
|
+
);
|
|
50
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
// Content-Length describes the encoded body when compression is used.
|
|
54
|
+
// Only use it as an early rejection for an unencoded body.
|
|
55
|
+
const length = message.headers.get("Content-Length");
|
|
56
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
57
|
+
if (
|
|
58
|
+
(encoding == null || encoding.toLowerCase() === "identity") &&
|
|
59
|
+
length != null && /^\d+$/.test(length) && Number(length) > maxBytes
|
|
60
|
+
) tooLarge();
|
|
61
|
+
if (reader == null) return "";
|
|
62
|
+
let size = 0;
|
|
63
|
+
const bounded = new ReadableStream<BufferSource>({
|
|
64
|
+
async pull(controller) {
|
|
65
|
+
const { done, value } = await reader.read();
|
|
66
|
+
if (done) {
|
|
67
|
+
controller.close();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
size += value.byteLength;
|
|
71
|
+
if (size > maxBytes) tooLarge();
|
|
72
|
+
controller.enqueue(value);
|
|
73
|
+
},
|
|
74
|
+
}, { highWaterMark: 0 });
|
|
75
|
+
const textReader = bounded.pipeThrough(new TextDecoderStream()).getReader();
|
|
76
|
+
try {
|
|
77
|
+
const chunks: string[] = [];
|
|
78
|
+
while (true) {
|
|
79
|
+
const { done, value } = await textReader.read();
|
|
80
|
+
if (done) break;
|
|
81
|
+
chunks.push(value);
|
|
82
|
+
}
|
|
83
|
+
return chunks.join("");
|
|
84
|
+
} finally {
|
|
85
|
+
textReader.releaseLock();
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// A cloned inbox request is a tee. Awaiting one branch's cancellation
|
|
89
|
+
// would deadlock until the other branch is canceled by the inbox handler.
|
|
90
|
+
if (reader != null) void reader.cancel(error).catch(() => {});
|
|
91
|
+
throw error;
|
|
92
|
+
} finally {
|
|
93
|
+
reader?.releaseLock();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Reads raw bytes while limiting bytes received from the body stream.
|
|
99
|
+
* Unlike {@link readBoundedText}, the bytes are returned as they were
|
|
100
|
+
* received, without decoding.
|
|
101
|
+
* @param message The response or request to read.
|
|
102
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
103
|
+
* @param url The URL to include in errors and logs.
|
|
104
|
+
* @returns The body bytes.
|
|
105
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
106
|
+
* @internal
|
|
107
|
+
*/
|
|
108
|
+
export async function readBoundedBytes(
|
|
109
|
+
message: Pick<Request, "body" | "headers">,
|
|
110
|
+
maxBytes: number,
|
|
111
|
+
url: string | URL,
|
|
112
|
+
): Promise<Uint8Array<ArrayBuffer>> {
|
|
113
|
+
validateBodySizeLimit(maxBytes);
|
|
114
|
+
const reader = message.body?.getReader();
|
|
115
|
+
const tooLarge = (): never => {
|
|
116
|
+
getLogger(["fedify", "runtime", "body"]).warn(
|
|
117
|
+
"Body from {url} exceeds the limit of {maxBytes} bytes.",
|
|
118
|
+
{ url: url.toString(), maxBytes },
|
|
119
|
+
);
|
|
120
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
121
|
+
};
|
|
122
|
+
try {
|
|
123
|
+
const length = message.headers.get("Content-Length");
|
|
124
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
125
|
+
if (
|
|
126
|
+
(encoding == null || encoding.toLowerCase() === "identity") &&
|
|
127
|
+
length != null && /^\d+$/.test(length) && Number(length) > maxBytes
|
|
128
|
+
) tooLarge();
|
|
129
|
+
if (reader == null) return new Uint8Array(0);
|
|
130
|
+
const chunks: Uint8Array[] = [];
|
|
131
|
+
let size = 0;
|
|
132
|
+
while (true) {
|
|
133
|
+
const { done, value } = await reader.read();
|
|
134
|
+
if (done) break;
|
|
135
|
+
size += value.byteLength;
|
|
136
|
+
if (size > maxBytes) tooLarge();
|
|
137
|
+
chunks.push(value);
|
|
138
|
+
}
|
|
139
|
+
const bytes = new Uint8Array(size);
|
|
140
|
+
let offset = 0;
|
|
141
|
+
for (const chunk of chunks) {
|
|
142
|
+
bytes.set(chunk, offset);
|
|
143
|
+
offset += chunk.byteLength;
|
|
144
|
+
}
|
|
145
|
+
return bytes;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (reader != null) void reader.cancel(error).catch(() => {});
|
|
148
|
+
throw error;
|
|
149
|
+
} finally {
|
|
150
|
+
reader?.releaseLock();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"@protected": true,
|
|
4
|
+
"id": "@id",
|
|
5
|
+
"type": "@type",
|
|
6
|
+
|
|
7
|
+
"alsoKnownAs": {
|
|
8
|
+
"@id": "https://www.w3.org/ns/activitystreams#alsoKnownAs",
|
|
9
|
+
"@type": "@id"
|
|
10
|
+
},
|
|
11
|
+
"assertionMethod": {
|
|
12
|
+
"@id": "https://w3id.org/security#assertionMethod",
|
|
13
|
+
"@type": "@id",
|
|
14
|
+
"@container": "@set"
|
|
15
|
+
},
|
|
16
|
+
"authentication": {
|
|
17
|
+
"@id": "https://w3id.org/security#authenticationMethod",
|
|
18
|
+
"@type": "@id",
|
|
19
|
+
"@container": "@set"
|
|
20
|
+
},
|
|
21
|
+
"capabilityDelegation": {
|
|
22
|
+
"@id": "https://w3id.org/security#capabilityDelegationMethod",
|
|
23
|
+
"@type": "@id",
|
|
24
|
+
"@container": "@set"
|
|
25
|
+
},
|
|
26
|
+
"capabilityInvocation": {
|
|
27
|
+
"@id": "https://w3id.org/security#capabilityInvocationMethod",
|
|
28
|
+
"@type": "@id",
|
|
29
|
+
"@container": "@set"
|
|
30
|
+
},
|
|
31
|
+
"controller": {
|
|
32
|
+
"@id": "https://w3id.org/security#controller",
|
|
33
|
+
"@type": "@id"
|
|
34
|
+
},
|
|
35
|
+
"keyAgreement": {
|
|
36
|
+
"@id": "https://w3id.org/security#keyAgreementMethod",
|
|
37
|
+
"@type": "@id",
|
|
38
|
+
"@container": "@set"
|
|
39
|
+
},
|
|
40
|
+
"service": {
|
|
41
|
+
"@id": "https://www.w3.org/ns/did#service",
|
|
42
|
+
"@type": "@id",
|
|
43
|
+
"@context": {
|
|
44
|
+
"@protected": true,
|
|
45
|
+
"id": "@id",
|
|
46
|
+
"type": "@type",
|
|
47
|
+
"serviceEndpoint": {
|
|
48
|
+
"@id": "https://www.w3.org/ns/did#serviceEndpoint",
|
|
49
|
+
"@type": "@id"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"verificationMethod": {
|
|
54
|
+
"@id": "https://w3id.org/security#verificationMethod",
|
|
55
|
+
"@type": "@id"
|
|
56
|
+
},
|
|
57
|
+
"Multikey": {
|
|
58
|
+
"@id": "https://w3id.org/security#Multikey",
|
|
59
|
+
"@context": {
|
|
60
|
+
"@protected": true,
|
|
61
|
+
"id": "@id",
|
|
62
|
+
"type": "@type",
|
|
63
|
+
"controller": {
|
|
64
|
+
"@id": "https://w3id.org/security#controller",
|
|
65
|
+
"@type": "@id"
|
|
66
|
+
},
|
|
67
|
+
"revoked": {
|
|
68
|
+
"@id": "https://w3id.org/security#revoked",
|
|
69
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
70
|
+
},
|
|
71
|
+
"expires": {
|
|
72
|
+
"@id": "https://w3id.org/security#expiration",
|
|
73
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
74
|
+
},
|
|
75
|
+
"publicKeyMultibase": {
|
|
76
|
+
"@id": "https://w3id.org/security#publicKeyMultibase",
|
|
77
|
+
"@type": "https://w3id.org/security#multibase"
|
|
78
|
+
},
|
|
79
|
+
"secretKeyMultibase": {
|
|
80
|
+
"@id": "https://w3id.org/security#secretKeyMultibase",
|
|
81
|
+
"@type": "https://w3id.org/security#multibase"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"JsonWebKey": {
|
|
86
|
+
"@id": "https://w3id.org/security#JsonWebKey",
|
|
87
|
+
"@context": {
|
|
88
|
+
"@protected": true,
|
|
89
|
+
"id": "@id",
|
|
90
|
+
"type": "@type",
|
|
91
|
+
"controller": {
|
|
92
|
+
"@id": "https://w3id.org/security#controller",
|
|
93
|
+
"@type": "@id"
|
|
94
|
+
},
|
|
95
|
+
"revoked": {
|
|
96
|
+
"@id": "https://w3id.org/security#revoked",
|
|
97
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
98
|
+
},
|
|
99
|
+
"expires": {
|
|
100
|
+
"@id": "https://w3id.org/security#expiration",
|
|
101
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
102
|
+
},
|
|
103
|
+
"publicKeyJwk": {
|
|
104
|
+
"@id": "https://w3id.org/security#publicKeyJwk",
|
|
105
|
+
"@type": "@json"
|
|
106
|
+
},
|
|
107
|
+
"secretKeyJwk": {
|
|
108
|
+
"@id": "https://w3id.org/security#secretKeyJwk",
|
|
109
|
+
"@type": "@json"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"fep-22cd": "https://w3id.org/fep/22cd#",
|
|
4
|
+
"schema": "https://schema.org/",
|
|
5
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
6
|
+
"translations": { "@id": "fep-22cd:translations", "@container": "@set" },
|
|
7
|
+
"Translation": "fep-22cd:Translation",
|
|
8
|
+
"sourceUpdated": {
|
|
9
|
+
"@id": "fep-22cd:sourceUpdated",
|
|
10
|
+
"@type": "xsd:dateTime"
|
|
11
|
+
},
|
|
12
|
+
"translator": {
|
|
13
|
+
"@id": "schema:translator",
|
|
14
|
+
"@type": "@id",
|
|
15
|
+
"@container": "@set"
|
|
16
|
+
},
|
|
17
|
+
"inLanguage": "schema:inLanguage",
|
|
18
|
+
"translationOfWork": { "@id": "schema:translationOfWork", "@type": "@id" },
|
|
19
|
+
"isBasedOn": { "@id": "schema:isBasedOn", "@type": "@id" }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"as": "https://www.w3.org/ns/activitystreams#",
|
|
4
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
5
|
+
"Hashtag": "as:Hashtag",
|
|
6
|
+
"manuallyApprovesFollowers": {
|
|
7
|
+
"@id": "as:manuallyApprovesFollowers"
|
|
8
|
+
},
|
|
9
|
+
"movedTo": {
|
|
10
|
+
"@id": "as:movedTo",
|
|
11
|
+
"@type": "@id"
|
|
12
|
+
},
|
|
13
|
+
"sensitive": {
|
|
14
|
+
"@id": "as:sensitive"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
"id": "@id",
|
|
4
4
|
"type": "@type",
|
|
5
5
|
"@protected": true,
|
|
6
|
-
"digestMultibase": {
|
|
7
|
-
"@id": "https://w3id.org/security#digestMultibase",
|
|
8
|
-
"@type": "https://w3id.org/security#multibase"
|
|
9
|
-
},
|
|
10
6
|
"proof": {
|
|
11
7
|
"@id": "https://w3id.org/security#proof",
|
|
12
8
|
"@type": "@id",
|
package/src/contexts.ts
CHANGED
|
@@ -5,13 +5,17 @@
|
|
|
5
5
|
import activitystreams from "./contexts/activitystreams.json" with {
|
|
6
6
|
type: "json",
|
|
7
7
|
};
|
|
8
|
+
import cidV1Context from "./contexts/cid-v1.json" with { type: "json" };
|
|
8
9
|
import didV1 from "./contexts/did-v1.json" with { type: "json" };
|
|
10
|
+
import fep22cd from "./contexts/fep-22cd.json" with { type: "json" };
|
|
9
11
|
import fep5711 from "./contexts/fep-5711.json" with { type: "json" };
|
|
10
12
|
import fep7aa9 from "./contexts/fep-7aa9.json" with { type: "json" };
|
|
13
|
+
import fepEf61 from "./contexts/fep-ef61.json" with { type: "json" };
|
|
11
14
|
import gotosocial from "./contexts/gotosocial.json" with { type: "json" };
|
|
12
15
|
import identityV1 from "./contexts/identity-v1.json" with { type: "json" };
|
|
13
16
|
import joinLemmyContext from "./contexts/join-lemmy.json" with { type: "json" };
|
|
14
17
|
import joinmastodon from "./contexts/joinmastodon.json" with { type: "json" };
|
|
18
|
+
import miscellany from "./contexts/miscellany.json" with { type: "json" };
|
|
15
19
|
import schemaorg from "./contexts/schemaorg.json" with { type: "json" };
|
|
16
20
|
import securityDataIntegrityV1 from "./contexts/security-data-integrity-v1.json" with {
|
|
17
21
|
type: "json",
|
|
@@ -35,7 +39,31 @@ const preloadedContexts: Record<string, unknown> = {
|
|
|
35
39
|
"https://purl.archive.org/socialweb/webfinger": webfinger,
|
|
36
40
|
"http://schema.org/": schemaorg,
|
|
37
41
|
"https://gotosocial.org/ns": gotosocial,
|
|
42
|
+
"https://w3id.org/fep/22cd": fep22cd,
|
|
38
43
|
"https://w3id.org/fep/5711": fep5711,
|
|
44
|
+
|
|
45
|
+
// Controlled Identifiers v1.0 requires JSON-LD processors to treat this
|
|
46
|
+
// context URL as already resolved. We ship the official W3C context so that
|
|
47
|
+
// controlled identifier documents can be processed without depending on the
|
|
48
|
+
// availability of the remote context server.
|
|
49
|
+
// See: https://www.w3.org/TR/cid-1.0/#json-ld-context
|
|
50
|
+
// https://github.com/fedify-dev/fedify/issues/932
|
|
51
|
+
"https://www.w3.org/ns/cid/v1": cidV1Context,
|
|
52
|
+
|
|
53
|
+
// The FEP-ef61 context. The w3id.org URL redirects to Codeberg Pages, which
|
|
54
|
+
// suffers recurring outages; while it is unreachable, every document
|
|
55
|
+
// referencing this URL fails JSON-LD expansion before application handlers
|
|
56
|
+
// run. We ship a built-in copy so that portable objects can be processed
|
|
57
|
+
// without depending on the availability of the remote context server.
|
|
58
|
+
// See: https://w3id.org/fep/ef61
|
|
59
|
+
// https://github.com/fedify-dev/fedify/issues/982
|
|
60
|
+
"https://w3id.org/fep/ef61": fepEf61,
|
|
61
|
+
|
|
62
|
+
// The FEP-7aa9 context is hosted on Codeberg Pages, whose outages can
|
|
63
|
+
// prevent actor documents from being parsed. Resolve it locally so that
|
|
64
|
+
// parsing does not depend on the remote context server's availability.
|
|
65
|
+
// See: https://w3id.org/fep/7aa9
|
|
66
|
+
// https://github.com/fedify-dev/fedify/issues/1078
|
|
39
67
|
"https://w3id.org/fep/7aa9": fep7aa9,
|
|
40
68
|
|
|
41
69
|
// Lemmy's context document is served as application/json without the JSON-LD
|
|
@@ -57,6 +85,18 @@ const preloadedContexts: Record<string, unknown> = {
|
|
|
57
85
|
// See: https://github.com/mastodon/joinmastodon/issues/148
|
|
58
86
|
// https://github.com/fedify-dev/fedify/issues/630
|
|
59
87
|
"http://joinmastodon.org/ns": joinmastodon,
|
|
88
|
+
|
|
89
|
+
// The SWICG "ActivityPub Miscellaneous Terms" context. Bridgy Fed (via
|
|
90
|
+
// granary) references this URL in the @context of every activity it sends,
|
|
91
|
+
// and other implementations use it as the recommended home for widely
|
|
92
|
+
// deployed extension terms like as:Hashtag and as:sensitive. It is served
|
|
93
|
+
// through purl.archive.org (Internet Archive's PURL service), which suffers
|
|
94
|
+
// recurring outages; while it is unreachable, every activity referencing
|
|
95
|
+
// this URL fails JSON-LD expansion before application handlers run. The
|
|
96
|
+
// document is a deliberately stable, versioned SWICG deliverable, so we
|
|
97
|
+
// ship a built-in copy.
|
|
98
|
+
// See: https://swicg.github.io/miscellany/
|
|
99
|
+
"https://purl.archive.org/miscellany": miscellany,
|
|
60
100
|
};
|
|
61
101
|
|
|
62
102
|
export default preloadedContexts;
|