@fedify/vocab-runtime 2.4.0-dev.1962 → 2.4.0-dev.1988
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 +1 -1
- package/dist/mod.cjs +204 -152
- package/dist/mod.js +204 -152
- package/dist/tests/body-Cg5W_IdS.cjs +94 -0
- package/dist/tests/body-DRVriaJO.mjs +77 -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/decimal.test.cjs +2 -2
- package/dist/tests/decimal.test.mjs +2 -2
- package/dist/tests/digest.test.cjs +1 -1
- package/dist/tests/digest.test.mjs +1 -1
- package/dist/tests/{docloader-cQ5_cV6R.mjs → docloader-ZSduXDDg.mjs} +61 -82
- package/dist/tests/{docloader-CbeAECB4.cjs → docloader-j_N6ZEZx.cjs} +61 -82
- package/dist/tests/docloader.test.cjs +84 -5
- package/dist/tests/docloader.test.mjs +84 -5
- package/dist/tests/internal/multicodec.test.cjs +1 -1
- package/dist/tests/internal/multicodec.test.mjs +1 -1
- package/dist/tests/jsonld-cache.test.cjs +1 -1
- package/dist/tests/jsonld-cache.test.mjs +1 -1
- package/dist/tests/key.test.cjs +1 -1
- package/dist/tests/key.test.mjs +1 -1
- package/dist/tests/langstr.test.cjs +1 -1
- package/dist/tests/langstr.test.mjs +1 -1
- package/dist/tests/link.test.cjs +1 -1
- package/dist/tests/link.test.mjs +1 -1
- package/dist/tests/multibase/multibase.test.cjs +1 -1
- package/dist/tests/multibase/multibase.test.mjs +1 -1
- package/dist/tests/{request-me46Cb_a.cjs → request-B3YkikPO.cjs} +1 -1
- package/dist/tests/{request-CywSvOoa.mjs → request-Bo1iqGDW.mjs} +1 -1
- package/dist/tests/request.test.cjs +2 -2
- package/dist/tests/request.test.mjs +2 -2
- package/dist/tests/temporal.test.cjs +1 -1
- package/dist/tests/temporal.test.mjs +1 -1
- package/dist/tests/url.test.cjs +1 -1
- package/dist/tests/url.test.mjs +1 -1
- package/package.json +1 -1
- package/src/body.test.ts +125 -0
- package/src/body.ts +95 -0
- package/src/contexts/fep-22cd.json +21 -0
- package/src/contexts.ts +2 -0
- package/src/docloader.test.ts +128 -3
- package/src/docloader.ts +52 -93
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
1
|
+
import { t as FetchError } from "./request-Bo1iqGDW.mjs";
|
|
2
|
+
import { i as cid_v1_default, n as getRemoteDocument, r as preloadedContexts, t as getDocumentLoader } from "./docloader-ZSduXDDg.mjs";
|
|
3
3
|
import { t as UrlError } from "./url-Basm1L_c.mjs";
|
|
4
|
-
import { deepStrictEqual, ok, rejects } from "node:assert";
|
|
5
4
|
import { test } from "node:test";
|
|
5
|
+
import { gzipSync } from "node:zlib";
|
|
6
|
+
import { deepStrictEqual, ok, rejects } from "node:assert";
|
|
7
|
+
import { createServer } from "node:http";
|
|
6
8
|
//#region \0rolldown/runtime.js
|
|
7
9
|
var __create = Object.create;
|
|
8
10
|
var __defProp = Object.defineProperty;
|
|
@@ -1490,9 +1492,9 @@ test("getDocumentLoader()", async (t) => {
|
|
|
1490
1492
|
"Content-Length": String(1048577),
|
|
1491
1493
|
"Content-Type": "text/html; charset=utf-8"
|
|
1492
1494
|
} });
|
|
1493
|
-
Object.defineProperty(response, "body", { value: { cancel
|
|
1495
|
+
Object.defineProperty(response, "body", { value: new ReadableStream({ cancel() {
|
|
1494
1496
|
canceled = true;
|
|
1495
|
-
} } });
|
|
1497
|
+
} }) });
|
|
1496
1498
|
await rejects(() => getRemoteDocument("https://example.com/large-html-cancel", response, () => {
|
|
1497
1499
|
throw new Error("unexpected alternate fetch");
|
|
1498
1500
|
}), FetchError);
|
|
@@ -1645,5 +1647,82 @@ test("getDocumentLoader()", async (t) => {
|
|
|
1645
1647
|
});
|
|
1646
1648
|
esm_default.hardReset();
|
|
1647
1649
|
});
|
|
1650
|
+
test("getDocumentLoader() bounds JSON, HTML, and alternate documents", async () => {
|
|
1651
|
+
const url = "https://example.com/bounded";
|
|
1652
|
+
esm_default.mockGlobal();
|
|
1653
|
+
let oversized = true;
|
|
1654
|
+
try {
|
|
1655
|
+
esm_default.get(url, () => new Response("{\"name\":\"hello\"}", { headers: {
|
|
1656
|
+
"Content-Type": "application/activity+json",
|
|
1657
|
+
...oversized ? { "Content-Length": String(16777217) } : {}
|
|
1658
|
+
} }));
|
|
1659
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
1660
|
+
await rejects(loader(url), FetchError);
|
|
1661
|
+
oversized = false;
|
|
1662
|
+
deepStrictEqual((await loader(url)).document, { name: "hello" });
|
|
1663
|
+
oversized = true;
|
|
1664
|
+
esm_default.get(`${url}/html`, () => new Response(" ".repeat(1048577), { headers: { "Content-Type": "text/html" } }));
|
|
1665
|
+
await rejects(getDocumentLoader({ allowPrivateAddress: true })(`${url}/html`), FetchError);
|
|
1666
|
+
esm_default.get(`${url}/alternate`, () => new Response("", { headers: {
|
|
1667
|
+
"Content-Type": "text/html",
|
|
1668
|
+
Link: `<${url}>; rel="alternate"; type="application/activity+json"`
|
|
1669
|
+
} }));
|
|
1670
|
+
await rejects(loader(`${url}/alternate`), FetchError);
|
|
1671
|
+
esm_default.get(`${url}/redirect`, {
|
|
1672
|
+
status: 302,
|
|
1673
|
+
headers: { Location: url }
|
|
1674
|
+
});
|
|
1675
|
+
await rejects(loader(`${url}/redirect`), FetchError);
|
|
1676
|
+
} finally {
|
|
1677
|
+
esm_default.hardReset();
|
|
1678
|
+
}
|
|
1679
|
+
});
|
|
1680
|
+
test("getDocumentLoader() rejects oversized gzip responses from fetch", async () => {
|
|
1681
|
+
const document = { name: "a".repeat(16 * 1024 * 1024) };
|
|
1682
|
+
const compressed = gzipSync(JSON.stringify(document));
|
|
1683
|
+
ok(compressed.byteLength < 64 * 1024);
|
|
1684
|
+
const server = createServer((request, response) => {
|
|
1685
|
+
const body = request.url === "/small" ? gzipSync("{\"name\":\"hello\"}") : compressed;
|
|
1686
|
+
response.writeHead(200, {
|
|
1687
|
+
"Content-Type": "application/activity+json",
|
|
1688
|
+
"Content-Encoding": "gzip",
|
|
1689
|
+
"Content-Length": body.byteLength
|
|
1690
|
+
});
|
|
1691
|
+
response.end(body);
|
|
1692
|
+
});
|
|
1693
|
+
await new Promise((resolve, reject) => {
|
|
1694
|
+
server.once("error", reject);
|
|
1695
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
1696
|
+
});
|
|
1697
|
+
try {
|
|
1698
|
+
const address = server.address();
|
|
1699
|
+
ok(address != null && typeof address !== "string");
|
|
1700
|
+
const url = `http://127.0.0.1:${address.port}/document`;
|
|
1701
|
+
await rejects(getDocumentLoader({ allowPrivateAddress: true })(url), FetchError);
|
|
1702
|
+
deepStrictEqual((await getDocumentLoader({ allowPrivateAddress: true })(new URL("/small", url).href)).document, { name: "hello" });
|
|
1703
|
+
} finally {
|
|
1704
|
+
await new Promise((resolve, reject) => {
|
|
1705
|
+
server.close((error) => error == null ? resolve() : reject(error));
|
|
1706
|
+
server.closeAllConnections();
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
test("getRemoteDocument() bounds JSON by default", async () => {
|
|
1711
|
+
let canceled = false;
|
|
1712
|
+
let pulls = 0;
|
|
1713
|
+
await rejects(getRemoteDocument("https://example.com/document", new Response(new ReadableStream({
|
|
1714
|
+
pull(controller) {
|
|
1715
|
+
pulls++;
|
|
1716
|
+
controller.enqueue(new Uint8Array(64 * 1024).fill(32));
|
|
1717
|
+
},
|
|
1718
|
+
cancel() {
|
|
1719
|
+
canceled = true;
|
|
1720
|
+
}
|
|
1721
|
+
}, { highWaterMark: 0 })), () => {
|
|
1722
|
+
throw new Error("Unexpected alternate document");
|
|
1723
|
+
}), FetchError);
|
|
1724
|
+
deepStrictEqual(canceled, true);
|
|
1725
|
+
deepStrictEqual(pulls, 257);
|
|
1726
|
+
});
|
|
1648
1727
|
//#endregion
|
|
1649
1728
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_multicodec = require("../multicodec-CLRPeW4N.cjs");
|
|
2
|
-
let node_assert = require("node:assert");
|
|
3
2
|
let node_test = require("node:test");
|
|
3
|
+
let node_assert = require("node:assert");
|
|
4
4
|
//#region src/internal/multicodec.test.ts
|
|
5
5
|
(0, node_test.test)("getMulticodecPrefix() decodes supported multicodec prefixes", () => {
|
|
6
6
|
(0, node_assert.deepStrictEqual)(require_multicodec.getMulticodecPrefix(Uint8Array.from([
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as getMulticodecPrefix, r as removeMulticodecPrefix, t as addMulticodecPrefix } from "../multicodec-CRIj_05H.mjs";
|
|
2
|
-
import { deepStrictEqual, throws } from "node:assert";
|
|
3
2
|
import { test } from "node:test";
|
|
3
|
+
import { deepStrictEqual, throws } from "node:assert";
|
|
4
4
|
//#region src/internal/multicodec.test.ts
|
|
5
5
|
test("getMulticodecPrefix() decodes supported multicodec prefixes", () => {
|
|
6
6
|
deepStrictEqual(getMulticodecPrefix(Uint8Array.from([
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
|
|
2
2
|
const require_url = require("./url-CPfMatUL.cjs");
|
|
3
|
-
let node_assert = require("node:assert");
|
|
4
3
|
let node_test = require("node:test");
|
|
4
|
+
let node_assert = require("node:assert");
|
|
5
5
|
let jsonld_dist_jsonld_esm_js = require("jsonld/dist/jsonld.esm.js");
|
|
6
6
|
jsonld_dist_jsonld_esm_js = require_rolldown_runtime.__toESM(jsonld_dist_jsonld_esm_js, 1);
|
|
7
7
|
//#region src/jsonld.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as formatIri, c as haveSameIriOrigin, p as parseIri, s as haveSameFe34Origin } from "./url-Basm1L_c.mjs";
|
|
2
|
-
import { deepStrictEqual, ok, strictEqual } from "node:assert";
|
|
3
2
|
import { test } from "node:test";
|
|
3
|
+
import { deepStrictEqual, ok, strictEqual } from "node:assert";
|
|
4
4
|
import jsonld from "jsonld/dist/jsonld.esm.js";
|
|
5
5
|
//#region src/jsonld.ts
|
|
6
6
|
var jsonld_default = jsonld;
|
package/dist/tests/key.test.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const require_multicodec = require("./multicodec-CLRPeW4N.cjs");
|
|
2
2
|
const require_key = require("./key-8Qg2SbZi.cjs");
|
|
3
3
|
const require_multibase = require("./multibase-B5Mea7Ip.cjs");
|
|
4
|
-
let node_assert = require("node:assert");
|
|
5
4
|
let node_test = require("node:test");
|
|
5
|
+
let node_assert = require("node:assert");
|
|
6
6
|
//#region src/key.test.ts
|
|
7
7
|
const rsaSpki = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxsRuvCkgJtflBTl4OVsm\nnt/J1mQfZasfJtN33dcZ3d1lJroxmgmMu69zjGEAwkNbMQaWNLqC4eogkJaeJ4RR\n5MHYXkL9nNilVoTkjX5BVit3puzs7XJ7WQnKQgQMI+ezn24GHsZ/v1JIo77lerX5\nk4HNwTNVt+yaZVQWaOMR3+6FwziQR6kd0VuG9/a9dgAnz2cEoORRC1i4W7IZaB1s\nZnh1WbHbevlGd72HSXll5rocPIHn8gq6xpBgpHwRphlRsgn4KHaJ6brXDIJjrnQh\nIe/YUBOGj/ImSEXhRwlFerKsoAVnZ0Hwbfa46qk44TAt8CyoPMWmpK6pt0ng4pQ2\nuwIDAQAB\n-----END PUBLIC KEY-----\n";
|
|
8
8
|
const rsaPkcs1 = "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxsRuvCkgJtflBTl4OVsmnt/J1mQfZasfJtN33dcZ3d1lJroxmgmM\nu69zjGEAwkNbMQaWNLqC4eogkJaeJ4RR5MHYXkL9nNilVoTkjX5BVit3puzs7XJ7\nWQnKQgQMI+ezn24GHsZ/v1JIo77lerX5k4HNwTNVt+yaZVQWaOMR3+6FwziQR6kd\n0VuG9/a9dgAnz2cEoORRC1i4W7IZaB1sZnh1WbHbevlGd72HSXll5rocPIHn8gq6\nxpBgpHwRphlRsgn4KHaJ6brXDIJjrnQhIe/YUBOGj/ImSEXhRwlFerKsoAVnZ0Hw\nbfa46qk44TAt8CyoPMWmpK6pt0ng4pQ2uwIDAQAB\n-----END RSA PUBLIC KEY-----\n";
|
package/dist/tests/key.test.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as addMulticodecPrefix } from "./multicodec-CRIj_05H.mjs";
|
|
2
2
|
import { a as importMultibaseKey, c as importSpki, d as importJwk, i as importDidKey, l as parseDidKeyVerificationMethod, n as exportMultibaseKey, o as importPem, r as exportSpki, s as importPkcs1, t as exportDidKey, u as exportJwk } from "./key-C7MNhlkO.mjs";
|
|
3
3
|
import { n as encodeMultibase } from "./multibase-BPnF_L4e.mjs";
|
|
4
|
-
import { deepStrictEqual, rejects } from "node:assert";
|
|
5
4
|
import { test } from "node:test";
|
|
5
|
+
import { deepStrictEqual, rejects } from "node:assert";
|
|
6
6
|
//#region src/key.test.ts
|
|
7
7
|
const rsaSpki = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxsRuvCkgJtflBTl4OVsm\nnt/J1mQfZasfJtN33dcZ3d1lJroxmgmMu69zjGEAwkNbMQaWNLqC4eogkJaeJ4RR\n5MHYXkL9nNilVoTkjX5BVit3puzs7XJ7WQnKQgQMI+ezn24GHsZ/v1JIo77lerX5\nk4HNwTNVt+yaZVQWaOMR3+6FwziQR6kd0VuG9/a9dgAnz2cEoORRC1i4W7IZaB1s\nZnh1WbHbevlGd72HSXll5rocPIHn8gq6xpBgpHwRphlRsgn4KHaJ6brXDIJjrnQh\nIe/YUBOGj/ImSEXhRwlFerKsoAVnZ0Hwbfa46qk44TAt8CyoPMWmpK6pt0ng4pQ2\nuwIDAQAB\n-----END PUBLIC KEY-----\n";
|
|
8
8
|
const rsaPkcs1 = "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxsRuvCkgJtflBTl4OVsmnt/J1mQfZasfJtN33dcZ3d1lJroxmgmM\nu69zjGEAwkNbMQaWNLqC4eogkJaeJ4RR5MHYXkL9nNilVoTkjX5BVit3puzs7XJ7\nWQnKQgQMI+ezn24GHsZ/v1JIo77lerX5k4HNwTNVt+yaZVQWaOMR3+6FwziQR6kd\n0VuG9/a9dgAnz2cEoORRC1i4W7IZaB1sZnh1WbHbevlGd72HSXll5rocPIHn8gq6\nxpBgpHwRphlRsgn4KHaJ6brXDIJjrnQhIe/YUBOGj/ImSEXhRwlFerKsoAVnZ0Hw\nbfa46qk44TAt8CyoPMWmpK6pt0ng4pQ2uwIDAQAB\n-----END RSA PUBLIC KEY-----\n";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
|
|
2
2
|
const require_langstr = require("./langstr-C4Fl80ae.cjs");
|
|
3
|
-
let node_assert = require("node:assert");
|
|
4
3
|
let node_test = require("node:test");
|
|
4
|
+
let node_assert = require("node:assert");
|
|
5
5
|
let node_util = require("node:util");
|
|
6
6
|
node_util = require_rolldown_runtime.__toESM(node_util, 1);
|
|
7
7
|
//#region src/langstr.test.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as LanguageString } from "./langstr-CQ26J_L7.mjs";
|
|
2
|
-
import { deepStrictEqual } from "node:assert";
|
|
3
2
|
import { test } from "node:test";
|
|
3
|
+
import { deepStrictEqual } from "node:assert";
|
|
4
4
|
import util from "node:util";
|
|
5
5
|
//#region src/langstr.test.ts
|
|
6
6
|
test("new LanguageString()", () => {
|
package/dist/tests/link.test.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_link = require("./link-DlKm8bEr.cjs");
|
|
2
|
-
let node_assert = require("node:assert");
|
|
3
2
|
let node_test = require("node:test");
|
|
3
|
+
let node_assert = require("node:assert");
|
|
4
4
|
//#region src/link.test.ts
|
|
5
5
|
(0, node_test.test)("String Good 1", () => {
|
|
6
6
|
const instance = new require_link.HttpHeaderLink(`<https://example.com>; rel="preconnect"`);
|
package/dist/tests/link.test.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as HttpHeaderLink } from "./link-Cevmc87v.mjs";
|
|
2
|
-
import { deepStrictEqual, throws } from "node:assert";
|
|
3
2
|
import { test } from "node:test";
|
|
3
|
+
import { deepStrictEqual, throws } from "node:assert";
|
|
4
4
|
//#region src/link.test.ts
|
|
5
5
|
test("String Good 1", () => {
|
|
6
6
|
const instance = new HttpHeaderLink(`<https://example.com>; rel="preconnect"`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_multibase = require("../multibase-B5Mea7Ip.cjs");
|
|
2
|
-
let node_assert = require("node:assert");
|
|
3
2
|
let node_test = require("node:test");
|
|
3
|
+
let node_assert = require("node:assert");
|
|
4
4
|
//#region src/multibase/multibase.test.ts
|
|
5
5
|
(0, node_test.test)("multibase.encode and decode", async (t) => {
|
|
6
6
|
const testCases = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as names, i as codes, n as encodeMultibase, o as decodeText, s as encodeText, t as decodeMultibase } from "../multibase-BPnF_L4e.mjs";
|
|
2
|
-
import { deepStrictEqual } from "node:assert";
|
|
3
2
|
import { test } from "node:test";
|
|
3
|
+
import { deepStrictEqual } from "node:assert";
|
|
4
4
|
//#region src/multibase/multibase.test.ts
|
|
5
5
|
test("multibase.encode and decode", async (t) => {
|
|
6
6
|
const testCases = [
|
|
@@ -3,7 +3,7 @@ let node_process = require("node:process");
|
|
|
3
3
|
node_process = require_rolldown_runtime.__toESM(node_process, 1);
|
|
4
4
|
//#region deno.json
|
|
5
5
|
var name = "@fedify/vocab-runtime";
|
|
6
|
-
var version = "2.4.0-dev.
|
|
6
|
+
var version = "2.4.0-dev.1988+c5c9d617";
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/request.ts
|
|
9
9
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
3
|
-
let node_assert = require("node:assert");
|
|
2
|
+
const require_request = require("./request-B3YkikPO.cjs");
|
|
4
3
|
let node_test = require("node:test");
|
|
5
4
|
let node_process = require("node:process");
|
|
6
5
|
node_process = require_rolldown_runtime.__toESM(node_process, 1);
|
|
6
|
+
let node_assert = require("node:assert");
|
|
7
7
|
//#region src/request.test.ts
|
|
8
8
|
(0, node_test.test)("getUserAgent()", () => {
|
|
9
9
|
if ("Deno" in globalThis) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { o as version, r as getUserAgent } from "./request-
|
|
2
|
-
import { deepStrictEqual } from "node:assert";
|
|
1
|
+
import { o as version, r as getUserAgent } from "./request-Bo1iqGDW.mjs";
|
|
3
2
|
import { test } from "node:test";
|
|
4
3
|
import process from "node:process";
|
|
4
|
+
import { deepStrictEqual } from "node:assert";
|
|
5
5
|
//#region src/request.test.ts
|
|
6
6
|
test("getUserAgent()", () => {
|
|
7
7
|
if ("Deno" in globalThis) {
|
package/dist/tests/url.test.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_url = require("./url-CPfMatUL.cjs");
|
|
2
|
-
let node_assert = require("node:assert");
|
|
3
2
|
let node_test = require("node:test");
|
|
3
|
+
let node_assert = require("node:assert");
|
|
4
4
|
//#region src/url.test.ts
|
|
5
5
|
(0, node_test.test)("parseIri() accepts portable ActivityPub URI schemes", () => {
|
|
6
6
|
for (const iri of [
|
package/dist/tests/url.test.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as formatIri, c as haveSameIriOrigin, d as isValidPublicIPv6Address, f as parseGatewayUrl, g as validatePublicUrl, h as validateLookupAddresses, i as expandIPv6Address, l as isGatewayUrl, m as parseJsonLdId, n as arePortableUrisEqual, o as getFe34Origin, p as parseIri, r as canonicalizePortableUri, s as haveSameFe34Origin, t as UrlError, u as isValidPublicIPv4Address } from "./url-Basm1L_c.mjs";
|
|
2
|
-
import { deepStrictEqual, ok, rejects, throws } from "node:assert";
|
|
3
2
|
import { test } from "node:test";
|
|
3
|
+
import { deepStrictEqual, ok, rejects, throws } from "node:assert";
|
|
4
4
|
//#region src/url.test.ts
|
|
5
5
|
test("parseIri() accepts portable ActivityPub URI schemes", () => {
|
|
6
6
|
for (const iri of [
|
package/package.json
CHANGED
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,95 @@
|
|
|
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
|
+
}
|
|
@@ -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
|
+
}
|
package/src/contexts.ts
CHANGED
|
@@ -7,6 +7,7 @@ import activitystreams from "./contexts/activitystreams.json" with {
|
|
|
7
7
|
};
|
|
8
8
|
import cidV1Context from "./contexts/cid-v1.json" with { type: "json" };
|
|
9
9
|
import didV1 from "./contexts/did-v1.json" with { type: "json" };
|
|
10
|
+
import fep22cd from "./contexts/fep-22cd.json" with { type: "json" };
|
|
10
11
|
import fep5711 from "./contexts/fep-5711.json" with { type: "json" };
|
|
11
12
|
import fep7aa9 from "./contexts/fep-7aa9.json" with { type: "json" };
|
|
12
13
|
import fepEf61 from "./contexts/fep-ef61.json" with { type: "json" };
|
|
@@ -38,6 +39,7 @@ const preloadedContexts: Record<string, unknown> = {
|
|
|
38
39
|
"https://purl.archive.org/socialweb/webfinger": webfinger,
|
|
39
40
|
"http://schema.org/": schemaorg,
|
|
40
41
|
"https://gotosocial.org/ns": gotosocial,
|
|
42
|
+
"https://w3id.org/fep/22cd": fep22cd,
|
|
41
43
|
"https://w3id.org/fep/5711": fep5711,
|
|
42
44
|
"https://w3id.org/fep/7aa9": fep7aa9,
|
|
43
45
|
|