@fedify/vocab-runtime 2.3.9-dev.2048 → 2.3.10
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 +52 -7
- package/dist/mod.d.cts +10 -0
- package/dist/mod.d.ts +10 -0
- package/dist/mod.js +52 -8
- package/dist/tests/{body-C0gO8fmt.cjs → body-BBcqb6fc.cjs} +1 -1
- package/dist/tests/{body-DYKjs6_2.mjs → body-Dl_2ZsMa.mjs} +1 -1
- package/dist/tests/body.test.cjs +1 -1
- package/dist/tests/body.test.mjs +1 -1
- package/dist/tests/decimal.test.cjs +2 -2
- package/dist/tests/decimal.test.mjs +2 -2
- package/dist/tests/{docloader-B3cffcK1.mjs → docloader-Ciq51f7p.mjs} +40 -6
- package/dist/tests/{docloader-BqznqaJT.cjs → docloader-D8C3FEvV.cjs} +40 -6
- package/dist/tests/docloader.test.cjs +241 -3
- package/dist/tests/docloader.test.mjs +240 -3
- package/dist/tests/{request-89V7ieWl.mjs → request-8CWJM_ZM.mjs} +1 -1
- package/dist/tests/{request-4o-Cs0CR.cjs → request-DWx4I133.cjs} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.mjs +1 -1
- package/dist/tests/{url-BBfduh6o.mjs → url-3fyJuqC0.mjs} +14 -4
- package/dist/tests/{url-BbEC9H_R.cjs → url-4PAkYhZC.cjs} +15 -4
- package/dist/tests/url.test.cjs +36 -3
- package/dist/tests/url.test.mjs +37 -4
- package/package.json +1 -1
- package/src/contexts/fep-7aa9.json +24 -0
- package/src/contexts.ts +8 -0
- package/src/docloader.test.ts +307 -0
- package/src/docloader.ts +35 -6
- package/src/url.test.ts +29 -3
- package/src/url.ts +21 -5
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-C2EiDwsr.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
3
|
-
const require_docloader = require("./docloader-
|
|
4
|
-
const require_url = require("./url-
|
|
2
|
+
const require_request = require("./request-DWx4I133.cjs");
|
|
3
|
+
const require_docloader = require("./docloader-D8C3FEvV.cjs");
|
|
4
|
+
const require_url = require("./url-4PAkYhZC.cjs");
|
|
5
5
|
let node_test = require("node:test");
|
|
6
6
|
let node_zlib = require("node:zlib");
|
|
7
|
+
let _logtape_logtape = require("@logtape/logtape");
|
|
7
8
|
let node_assert = require("node:assert");
|
|
9
|
+
let node_dns_promises = require("node:dns/promises");
|
|
10
|
+
node_dns_promises = require_chunk.__toESM(node_dns_promises, 1);
|
|
8
11
|
let node_http = require("node:http");
|
|
9
12
|
//#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
|
|
10
13
|
var require_glob_to_regexp = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, module) => {
|
|
@@ -1458,6 +1461,37 @@ var esm_default = new class FetchMock {
|
|
|
1458
1461
|
} }
|
|
1459
1462
|
});
|
|
1460
1463
|
});
|
|
1464
|
+
esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
|
|
1465
|
+
await t.test("preloaded FEP-7aa9 context", async () => {
|
|
1466
|
+
const url = "https://w3id.org/fep/7aa9";
|
|
1467
|
+
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1468
|
+
contextUrl: null,
|
|
1469
|
+
documentUrl: url,
|
|
1470
|
+
document: { "@context": {
|
|
1471
|
+
"FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
|
|
1472
|
+
"FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
|
|
1473
|
+
"FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
|
|
1474
|
+
"FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
|
|
1475
|
+
"topic": {
|
|
1476
|
+
"@id": "https://w3id.org/fep/7aa9#topic",
|
|
1477
|
+
"@type": "@id"
|
|
1478
|
+
},
|
|
1479
|
+
"featuredObject": {
|
|
1480
|
+
"@id": "https://w3id.org/fep/7aa9#featuredObject",
|
|
1481
|
+
"@type": "@id"
|
|
1482
|
+
},
|
|
1483
|
+
"canFeature": {
|
|
1484
|
+
"@id": "https://w3id.org/fep/7aa9#canFeature",
|
|
1485
|
+
"@type": "@id"
|
|
1486
|
+
},
|
|
1487
|
+
"featureAuthorization": {
|
|
1488
|
+
"@id": "https://w3id.org/fep/7aa9#featureAuthorization",
|
|
1489
|
+
"@type": "@id"
|
|
1490
|
+
}
|
|
1491
|
+
} }
|
|
1492
|
+
});
|
|
1493
|
+
(0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
|
|
1494
|
+
});
|
|
1461
1495
|
await t.test("deny non-HTTP/HTTPS", async () => {
|
|
1462
1496
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("ftp://localhost"), require_url.UrlError);
|
|
1463
1497
|
});
|
|
@@ -1646,4 +1680,208 @@ var esm_default = new class FetchMock {
|
|
|
1646
1680
|
(0, node_assert.deepStrictEqual)(canceled, true);
|
|
1647
1681
|
(0, node_assert.deepStrictEqual)(pulls, 257);
|
|
1648
1682
|
});
|
|
1683
|
+
(0, node_test.test)("getDocumentLoader() logs DNS failures as such", async (t) => {
|
|
1684
|
+
if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") {
|
|
1685
|
+
t.skip("requires the net permission");
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
const loader = require_docloader.getDocumentLoader();
|
|
1689
|
+
for (const result of [
|
|
1690
|
+
"throws",
|
|
1691
|
+
"empty",
|
|
1692
|
+
"private"
|
|
1693
|
+
]) await t.test(result, async () => {
|
|
1694
|
+
const originalLookup = node_dns_promises.default.lookup;
|
|
1695
|
+
node_dns_promises.default.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
|
|
1696
|
+
address: "127.0.0.1",
|
|
1697
|
+
family: 4
|
|
1698
|
+
}]));
|
|
1699
|
+
const records = [];
|
|
1700
|
+
await (0, _logtape_logtape.configure)({
|
|
1701
|
+
sinks: { buffer: (record) => records.push(record) },
|
|
1702
|
+
loggers: [{
|
|
1703
|
+
category: "fedify",
|
|
1704
|
+
sinks: ["buffer"],
|
|
1705
|
+
lowestLevel: "debug"
|
|
1706
|
+
}, {
|
|
1707
|
+
category: ["logtape", "meta"],
|
|
1708
|
+
sinks: []
|
|
1709
|
+
}],
|
|
1710
|
+
reset: true
|
|
1711
|
+
});
|
|
1712
|
+
try {
|
|
1713
|
+
const url = "https://dns-failure.invalid/object";
|
|
1714
|
+
let error;
|
|
1715
|
+
await (0, node_assert.rejects)(() => loader(url), (e) => {
|
|
1716
|
+
error = e;
|
|
1717
|
+
return true;
|
|
1718
|
+
});
|
|
1719
|
+
(0, node_assert.ok)(error instanceof require_url.UrlError);
|
|
1720
|
+
(0, node_assert.deepStrictEqual)(error.reason, result === "private" ? "disallowed" : "dns");
|
|
1721
|
+
(0, node_assert.deepStrictEqual)(records.map((r) => [
|
|
1722
|
+
r.level,
|
|
1723
|
+
r.rawMessage,
|
|
1724
|
+
r.properties.url
|
|
1725
|
+
]), [result === "private" ? [
|
|
1726
|
+
"error",
|
|
1727
|
+
"Disallowed private URL: {url}",
|
|
1728
|
+
url
|
|
1729
|
+
] : [
|
|
1730
|
+
"debug",
|
|
1731
|
+
"DNS lookup failed for {url}",
|
|
1732
|
+
url
|
|
1733
|
+
]]);
|
|
1734
|
+
(0, node_assert.ok)(records[0].properties.error === error);
|
|
1735
|
+
} finally {
|
|
1736
|
+
await (0, _logtape_logtape.reset)();
|
|
1737
|
+
node_dns_promises.default.lookup = originalLookup;
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
});
|
|
1741
|
+
(0, node_test.test)("getDocumentLoader() bounds alternate document chains", async (t) => {
|
|
1742
|
+
const base = "https://example.com/alternate-chain/";
|
|
1743
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1744
|
+
for (const mode of [
|
|
1745
|
+
"header",
|
|
1746
|
+
"html",
|
|
1747
|
+
"mixed"
|
|
1748
|
+
]) for (const hops of [20, 21]) await t.test(`${mode}: ${hops} hops`, async () => {
|
|
1749
|
+
esm_default.mockGlobal();
|
|
1750
|
+
let requests = 0;
|
|
1751
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1752
|
+
requests++;
|
|
1753
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1754
|
+
if (index === hops || requests > 30) return Response.json({ done: true });
|
|
1755
|
+
const next = `${base}${index + 1}`;
|
|
1756
|
+
if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
|
|
1757
|
+
return alternate(next, mode === "html");
|
|
1758
|
+
});
|
|
1759
|
+
try {
|
|
1760
|
+
if (hops === 20) (0, node_assert.deepStrictEqual)((await loader(`${base}0`)).document, { done: true });
|
|
1761
|
+
else await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
1762
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1763
|
+
(0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
|
|
1764
|
+
return true;
|
|
1765
|
+
});
|
|
1766
|
+
(0, node_assert.deepStrictEqual)(requests, 21);
|
|
1767
|
+
} finally {
|
|
1768
|
+
esm_default.hardReset();
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1771
|
+
for (const mode of [
|
|
1772
|
+
"header",
|
|
1773
|
+
"html",
|
|
1774
|
+
"alternate-redirect",
|
|
1775
|
+
"redirect-alternate",
|
|
1776
|
+
"redirect-intermediate"
|
|
1777
|
+
]) await t.test(`${mode}: cycle`, async () => {
|
|
1778
|
+
esm_default.mockGlobal();
|
|
1779
|
+
let requests = 0;
|
|
1780
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1781
|
+
requests++;
|
|
1782
|
+
if (requests > 30) return Response.json({ stopped: true });
|
|
1783
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1784
|
+
const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
|
|
1785
|
+
if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
|
|
1786
|
+
return alternate(next, mode === "html");
|
|
1787
|
+
});
|
|
1788
|
+
try {
|
|
1789
|
+
await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
1790
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1791
|
+
(0, node_assert.ok)(error.message.includes("Redirect loop detected:"));
|
|
1792
|
+
return true;
|
|
1793
|
+
});
|
|
1794
|
+
(0, node_assert.deepStrictEqual)(requests, mode === "redirect-intermediate" ? 3 : 2);
|
|
1795
|
+
} finally {
|
|
1796
|
+
esm_default.hardReset();
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
await t.test("alternate followed by 20 redirects shares the limit", async () => {
|
|
1800
|
+
esm_default.mockGlobal();
|
|
1801
|
+
let requests = 0;
|
|
1802
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1803
|
+
requests++;
|
|
1804
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1805
|
+
if (index === 21) return Response.json({ done: true });
|
|
1806
|
+
return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
|
|
1807
|
+
});
|
|
1808
|
+
try {
|
|
1809
|
+
await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
1810
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1811
|
+
(0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
|
|
1812
|
+
return true;
|
|
1813
|
+
});
|
|
1814
|
+
(0, node_assert.deepStrictEqual)(requests, 21);
|
|
1815
|
+
} finally {
|
|
1816
|
+
esm_default.hardReset();
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
await t.test("relative alternate after redirect and isolated calls", async () => {
|
|
1820
|
+
esm_default.mockGlobal();
|
|
1821
|
+
esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
|
|
1822
|
+
esm_default.get(`${base}html`, alternate("./document", true));
|
|
1823
|
+
esm_default.get(`${base}document`, Response.json({ done: true }));
|
|
1824
|
+
try {
|
|
1825
|
+
for (let i = 0; i < 2; i++) {
|
|
1826
|
+
const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
|
|
1827
|
+
for (const result of results) {
|
|
1828
|
+
(0, node_assert.deepStrictEqual)(result.document, { done: true });
|
|
1829
|
+
(0, node_assert.deepStrictEqual)(result.documentUrl, `${base}document`);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
} finally {
|
|
1833
|
+
esm_default.hardReset();
|
|
1834
|
+
}
|
|
1835
|
+
});
|
|
1836
|
+
function alternate(next, html) {
|
|
1837
|
+
return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
|
|
1838
|
+
"Content-Type": "text/plain",
|
|
1839
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
1840
|
+
} });
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
(0, node_test.test)("getDocumentLoader() preserves cancellation across alternates", async (t) => {
|
|
1844
|
+
const base = "https://example.com/alternate-abort/";
|
|
1845
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1846
|
+
for (const html of [false, true]) await t.test(html ? "HTML" : "Link header", async () => {
|
|
1847
|
+
esm_default.mockGlobal();
|
|
1848
|
+
const controller = new AbortController();
|
|
1849
|
+
let requests = 0;
|
|
1850
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1851
|
+
requests++;
|
|
1852
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1853
|
+
if (index === 2) return Response.json({ done: true });
|
|
1854
|
+
if (index === 1) controller.abort();
|
|
1855
|
+
const next = `${base}${index + 1}`;
|
|
1856
|
+
return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
|
|
1857
|
+
"Content-Type": "text/plain",
|
|
1858
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
1859
|
+
} });
|
|
1860
|
+
});
|
|
1861
|
+
try {
|
|
1862
|
+
await (0, node_assert.rejects)(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
|
|
1863
|
+
(0, node_assert.deepStrictEqual)(requests, 2);
|
|
1864
|
+
} finally {
|
|
1865
|
+
esm_default.hardReset();
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
});
|
|
1869
|
+
(0, node_test.test)("getDocumentLoader() rejects cancellation before fetching", async () => {
|
|
1870
|
+
esm_default.mockGlobal();
|
|
1871
|
+
let requests = 0;
|
|
1872
|
+
const url = "https://example.com/pre-aborted-alternate";
|
|
1873
|
+
esm_default.get(url, () => {
|
|
1874
|
+
requests++;
|
|
1875
|
+
return Response.json({ done: true });
|
|
1876
|
+
});
|
|
1877
|
+
try {
|
|
1878
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1879
|
+
const controller = new AbortController();
|
|
1880
|
+
controller.abort();
|
|
1881
|
+
await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), { name: "AbortError" });
|
|
1882
|
+
(0, node_assert.deepStrictEqual)(requests, 0);
|
|
1883
|
+
} finally {
|
|
1884
|
+
esm_default.hardReset();
|
|
1885
|
+
}
|
|
1886
|
+
});
|
|
1649
1887
|
//#endregion
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { t as FetchError } from "./request-
|
|
2
|
-
import { i as cid_v1_default, n as getRemoteDocument, r as preloadedContexts, t as getDocumentLoader } from "./docloader-
|
|
3
|
-
import { t as UrlError } from "./url-
|
|
1
|
+
import { t as FetchError } from "./request-8CWJM_ZM.mjs";
|
|
2
|
+
import { i as cid_v1_default, n as getRemoteDocument, r as preloadedContexts, t as getDocumentLoader } from "./docloader-Ciq51f7p.mjs";
|
|
3
|
+
import { t as UrlError } from "./url-3fyJuqC0.mjs";
|
|
4
4
|
import { test } from "node:test";
|
|
5
5
|
import { gzipSync } from "node:zlib";
|
|
6
|
+
import { configure, reset } from "@logtape/logtape";
|
|
6
7
|
import { deepStrictEqual, ok, rejects } from "node:assert";
|
|
8
|
+
import dns from "node:dns/promises";
|
|
7
9
|
import { createServer } from "node:http";
|
|
8
10
|
//#region \0rolldown/runtime.js
|
|
9
11
|
var __create = Object.create;
|
|
@@ -1477,6 +1479,37 @@ test("getDocumentLoader()", async (t) => {
|
|
|
1477
1479
|
} }
|
|
1478
1480
|
});
|
|
1479
1481
|
});
|
|
1482
|
+
esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
|
|
1483
|
+
await t.test("preloaded FEP-7aa9 context", async () => {
|
|
1484
|
+
const url = "https://w3id.org/fep/7aa9";
|
|
1485
|
+
deepStrictEqual(await fetchDocumentLoader(url), {
|
|
1486
|
+
contextUrl: null,
|
|
1487
|
+
documentUrl: url,
|
|
1488
|
+
document: { "@context": {
|
|
1489
|
+
"FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
|
|
1490
|
+
"FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
|
|
1491
|
+
"FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
|
|
1492
|
+
"FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
|
|
1493
|
+
"topic": {
|
|
1494
|
+
"@id": "https://w3id.org/fep/7aa9#topic",
|
|
1495
|
+
"@type": "@id"
|
|
1496
|
+
},
|
|
1497
|
+
"featuredObject": {
|
|
1498
|
+
"@id": "https://w3id.org/fep/7aa9#featuredObject",
|
|
1499
|
+
"@type": "@id"
|
|
1500
|
+
},
|
|
1501
|
+
"canFeature": {
|
|
1502
|
+
"@id": "https://w3id.org/fep/7aa9#canFeature",
|
|
1503
|
+
"@type": "@id"
|
|
1504
|
+
},
|
|
1505
|
+
"featureAuthorization": {
|
|
1506
|
+
"@id": "https://w3id.org/fep/7aa9#featureAuthorization",
|
|
1507
|
+
"@type": "@id"
|
|
1508
|
+
}
|
|
1509
|
+
} }
|
|
1510
|
+
});
|
|
1511
|
+
deepStrictEqual(esm_default.callHistory.calls(url).length, 0);
|
|
1512
|
+
});
|
|
1480
1513
|
await t.test("deny non-HTTP/HTTPS", async () => {
|
|
1481
1514
|
await rejects(() => fetchDocumentLoader("ftp://localhost"), UrlError);
|
|
1482
1515
|
});
|
|
@@ -1665,5 +1698,209 @@ test("getRemoteDocument() bounds JSON by default", async () => {
|
|
|
1665
1698
|
deepStrictEqual(canceled, true);
|
|
1666
1699
|
deepStrictEqual(pulls, 257);
|
|
1667
1700
|
});
|
|
1701
|
+
test("getDocumentLoader() logs DNS failures as such", async (t) => {
|
|
1702
|
+
if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") {
|
|
1703
|
+
t.skip("requires the net permission");
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
const loader = getDocumentLoader();
|
|
1707
|
+
for (const result of [
|
|
1708
|
+
"throws",
|
|
1709
|
+
"empty",
|
|
1710
|
+
"private"
|
|
1711
|
+
]) await t.test(result, async () => {
|
|
1712
|
+
const originalLookup = dns.lookup;
|
|
1713
|
+
dns.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
|
|
1714
|
+
address: "127.0.0.1",
|
|
1715
|
+
family: 4
|
|
1716
|
+
}]));
|
|
1717
|
+
const records = [];
|
|
1718
|
+
await configure({
|
|
1719
|
+
sinks: { buffer: (record) => records.push(record) },
|
|
1720
|
+
loggers: [{
|
|
1721
|
+
category: "fedify",
|
|
1722
|
+
sinks: ["buffer"],
|
|
1723
|
+
lowestLevel: "debug"
|
|
1724
|
+
}, {
|
|
1725
|
+
category: ["logtape", "meta"],
|
|
1726
|
+
sinks: []
|
|
1727
|
+
}],
|
|
1728
|
+
reset: true
|
|
1729
|
+
});
|
|
1730
|
+
try {
|
|
1731
|
+
const url = "https://dns-failure.invalid/object";
|
|
1732
|
+
let error;
|
|
1733
|
+
await rejects(() => loader(url), (e) => {
|
|
1734
|
+
error = e;
|
|
1735
|
+
return true;
|
|
1736
|
+
});
|
|
1737
|
+
ok(error instanceof UrlError);
|
|
1738
|
+
deepStrictEqual(error.reason, result === "private" ? "disallowed" : "dns");
|
|
1739
|
+
deepStrictEqual(records.map((r) => [
|
|
1740
|
+
r.level,
|
|
1741
|
+
r.rawMessage,
|
|
1742
|
+
r.properties.url
|
|
1743
|
+
]), [result === "private" ? [
|
|
1744
|
+
"error",
|
|
1745
|
+
"Disallowed private URL: {url}",
|
|
1746
|
+
url
|
|
1747
|
+
] : [
|
|
1748
|
+
"debug",
|
|
1749
|
+
"DNS lookup failed for {url}",
|
|
1750
|
+
url
|
|
1751
|
+
]]);
|
|
1752
|
+
ok(records[0].properties.error === error);
|
|
1753
|
+
} finally {
|
|
1754
|
+
await reset();
|
|
1755
|
+
dns.lookup = originalLookup;
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
});
|
|
1759
|
+
test("getDocumentLoader() bounds alternate document chains", async (t) => {
|
|
1760
|
+
const base = "https://example.com/alternate-chain/";
|
|
1761
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
1762
|
+
for (const mode of [
|
|
1763
|
+
"header",
|
|
1764
|
+
"html",
|
|
1765
|
+
"mixed"
|
|
1766
|
+
]) for (const hops of [20, 21]) await t.test(`${mode}: ${hops} hops`, async () => {
|
|
1767
|
+
esm_default.mockGlobal();
|
|
1768
|
+
let requests = 0;
|
|
1769
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1770
|
+
requests++;
|
|
1771
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1772
|
+
if (index === hops || requests > 30) return Response.json({ done: true });
|
|
1773
|
+
const next = `${base}${index + 1}`;
|
|
1774
|
+
if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
|
|
1775
|
+
return alternate(next, mode === "html");
|
|
1776
|
+
});
|
|
1777
|
+
try {
|
|
1778
|
+
if (hops === 20) deepStrictEqual((await loader(`${base}0`)).document, { done: true });
|
|
1779
|
+
else await rejects(loader(`${base}0`), (error) => {
|
|
1780
|
+
ok(error instanceof FetchError);
|
|
1781
|
+
ok(error.message.includes("Too many redirections (21)"));
|
|
1782
|
+
return true;
|
|
1783
|
+
});
|
|
1784
|
+
deepStrictEqual(requests, 21);
|
|
1785
|
+
} finally {
|
|
1786
|
+
esm_default.hardReset();
|
|
1787
|
+
}
|
|
1788
|
+
});
|
|
1789
|
+
for (const mode of [
|
|
1790
|
+
"header",
|
|
1791
|
+
"html",
|
|
1792
|
+
"alternate-redirect",
|
|
1793
|
+
"redirect-alternate",
|
|
1794
|
+
"redirect-intermediate"
|
|
1795
|
+
]) await t.test(`${mode}: cycle`, async () => {
|
|
1796
|
+
esm_default.mockGlobal();
|
|
1797
|
+
let requests = 0;
|
|
1798
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1799
|
+
requests++;
|
|
1800
|
+
if (requests > 30) return Response.json({ stopped: true });
|
|
1801
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1802
|
+
const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
|
|
1803
|
+
if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
|
|
1804
|
+
return alternate(next, mode === "html");
|
|
1805
|
+
});
|
|
1806
|
+
try {
|
|
1807
|
+
await rejects(loader(`${base}0`), (error) => {
|
|
1808
|
+
ok(error instanceof FetchError);
|
|
1809
|
+
ok(error.message.includes("Redirect loop detected:"));
|
|
1810
|
+
return true;
|
|
1811
|
+
});
|
|
1812
|
+
deepStrictEqual(requests, mode === "redirect-intermediate" ? 3 : 2);
|
|
1813
|
+
} finally {
|
|
1814
|
+
esm_default.hardReset();
|
|
1815
|
+
}
|
|
1816
|
+
});
|
|
1817
|
+
await t.test("alternate followed by 20 redirects shares the limit", async () => {
|
|
1818
|
+
esm_default.mockGlobal();
|
|
1819
|
+
let requests = 0;
|
|
1820
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1821
|
+
requests++;
|
|
1822
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1823
|
+
if (index === 21) return Response.json({ done: true });
|
|
1824
|
+
return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
|
|
1825
|
+
});
|
|
1826
|
+
try {
|
|
1827
|
+
await rejects(loader(`${base}0`), (error) => {
|
|
1828
|
+
ok(error instanceof FetchError);
|
|
1829
|
+
ok(error.message.includes("Too many redirections (21)"));
|
|
1830
|
+
return true;
|
|
1831
|
+
});
|
|
1832
|
+
deepStrictEqual(requests, 21);
|
|
1833
|
+
} finally {
|
|
1834
|
+
esm_default.hardReset();
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
await t.test("relative alternate after redirect and isolated calls", async () => {
|
|
1838
|
+
esm_default.mockGlobal();
|
|
1839
|
+
esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
|
|
1840
|
+
esm_default.get(`${base}html`, alternate("./document", true));
|
|
1841
|
+
esm_default.get(`${base}document`, Response.json({ done: true }));
|
|
1842
|
+
try {
|
|
1843
|
+
for (let i = 0; i < 2; i++) {
|
|
1844
|
+
const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
|
|
1845
|
+
for (const result of results) {
|
|
1846
|
+
deepStrictEqual(result.document, { done: true });
|
|
1847
|
+
deepStrictEqual(result.documentUrl, `${base}document`);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
} finally {
|
|
1851
|
+
esm_default.hardReset();
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1854
|
+
function alternate(next, html) {
|
|
1855
|
+
return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
|
|
1856
|
+
"Content-Type": "text/plain",
|
|
1857
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
1858
|
+
} });
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
test("getDocumentLoader() preserves cancellation across alternates", async (t) => {
|
|
1862
|
+
const base = "https://example.com/alternate-abort/";
|
|
1863
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
1864
|
+
for (const html of [false, true]) await t.test(html ? "HTML" : "Link header", async () => {
|
|
1865
|
+
esm_default.mockGlobal();
|
|
1866
|
+
const controller = new AbortController();
|
|
1867
|
+
let requests = 0;
|
|
1868
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1869
|
+
requests++;
|
|
1870
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1871
|
+
if (index === 2) return Response.json({ done: true });
|
|
1872
|
+
if (index === 1) controller.abort();
|
|
1873
|
+
const next = `${base}${index + 1}`;
|
|
1874
|
+
return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
|
|
1875
|
+
"Content-Type": "text/plain",
|
|
1876
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
1877
|
+
} });
|
|
1878
|
+
});
|
|
1879
|
+
try {
|
|
1880
|
+
await rejects(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
|
|
1881
|
+
deepStrictEqual(requests, 2);
|
|
1882
|
+
} finally {
|
|
1883
|
+
esm_default.hardReset();
|
|
1884
|
+
}
|
|
1885
|
+
});
|
|
1886
|
+
});
|
|
1887
|
+
test("getDocumentLoader() rejects cancellation before fetching", async () => {
|
|
1888
|
+
esm_default.mockGlobal();
|
|
1889
|
+
let requests = 0;
|
|
1890
|
+
const url = "https://example.com/pre-aborted-alternate";
|
|
1891
|
+
esm_default.get(url, () => {
|
|
1892
|
+
requests++;
|
|
1893
|
+
return Response.json({ done: true });
|
|
1894
|
+
});
|
|
1895
|
+
try {
|
|
1896
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
1897
|
+
const controller = new AbortController();
|
|
1898
|
+
controller.abort();
|
|
1899
|
+
await rejects(loader(url, { signal: controller.signal }), { name: "AbortError" });
|
|
1900
|
+
deepStrictEqual(requests, 0);
|
|
1901
|
+
} finally {
|
|
1902
|
+
esm_default.hardReset();
|
|
1903
|
+
}
|
|
1904
|
+
});
|
|
1668
1905
|
//#endregion
|
|
1669
1906
|
export {};
|
|
@@ -3,7 +3,7 @@ let node_process = require("node:process");
|
|
|
3
3
|
node_process = require_chunk.__toESM(node_process, 1);
|
|
4
4
|
//#region deno.json
|
|
5
5
|
var name = "@fedify/vocab-runtime";
|
|
6
|
-
var version = "2.3.
|
|
6
|
+
var version = "2.3.10";
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/request.ts
|
|
9
9
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-C2EiDwsr.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
2
|
+
const require_request = require("./request-DWx4I133.cjs");
|
|
3
3
|
let node_test = require("node:test");
|
|
4
4
|
let node_process = require("node:process");
|
|
5
5
|
node_process = require_chunk.__toESM(node_process, 1);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { o as version, r as getUserAgent } from "./request-
|
|
1
|
+
import { o as version, r as getUserAgent } from "./request-8CWJM_ZM.mjs";
|
|
2
2
|
import { test } from "node:test";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { deepStrictEqual } from "node:assert";
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import dns from "node:dns/promises";
|
|
2
2
|
import { isIP } from "node:net";
|
|
3
3
|
//#region src/url.ts
|
|
4
4
|
var UrlError = class extends Error {
|
|
5
|
+
/**
|
|
6
|
+
* The failure category. Defaults to `"disallowed"`.
|
|
7
|
+
* `"dns"` includes lookup errors and results with no usable IP addresses.
|
|
8
|
+
* @since 2.0.29
|
|
9
|
+
*/
|
|
10
|
+
reason;
|
|
5
11
|
constructor(message, options) {
|
|
6
12
|
super(message, options);
|
|
7
13
|
this.name = "UrlError";
|
|
14
|
+
this.reason = options?.reason ?? "disallowed";
|
|
8
15
|
}
|
|
9
16
|
};
|
|
10
17
|
/**
|
|
@@ -30,9 +37,12 @@ async function validatePublicUrl(url) {
|
|
|
30
37
|
}
|
|
31
38
|
let addresses;
|
|
32
39
|
try {
|
|
33
|
-
addresses = await lookup(hostname, { all: true });
|
|
40
|
+
addresses = await dns.lookup(hostname, { all: true });
|
|
34
41
|
} catch (error) {
|
|
35
|
-
throw new UrlError("DNS lookup failed", {
|
|
42
|
+
throw new UrlError("DNS lookup failed", {
|
|
43
|
+
cause: error,
|
|
44
|
+
reason: "dns"
|
|
45
|
+
});
|
|
36
46
|
}
|
|
37
47
|
validateLookupAddresses(addresses);
|
|
38
48
|
}
|
|
@@ -61,7 +71,7 @@ function validateLookupAddresses(addresses) {
|
|
|
61
71
|
ipAddressCount++;
|
|
62
72
|
validatePublicIpAddress(address, family);
|
|
63
73
|
}
|
|
64
|
-
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
|
|
74
|
+
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address", { reason: "dns" });
|
|
65
75
|
}
|
|
66
76
|
function validatePublicIpAddress(address, family) {
|
|
67
77
|
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
require("./chunk-C2EiDwsr.cjs");
|
|
1
|
+
const require_chunk = require("./chunk-C2EiDwsr.cjs");
|
|
2
2
|
let node_dns_promises = require("node:dns/promises");
|
|
3
|
+
node_dns_promises = require_chunk.__toESM(node_dns_promises, 1);
|
|
3
4
|
let node_net = require("node:net");
|
|
4
5
|
//#region src/url.ts
|
|
5
6
|
var UrlError = class extends Error {
|
|
7
|
+
/**
|
|
8
|
+
* The failure category. Defaults to `"disallowed"`.
|
|
9
|
+
* `"dns"` includes lookup errors and results with no usable IP addresses.
|
|
10
|
+
* @since 2.0.29
|
|
11
|
+
*/
|
|
12
|
+
reason;
|
|
6
13
|
constructor(message, options) {
|
|
7
14
|
super(message, options);
|
|
8
15
|
this.name = "UrlError";
|
|
16
|
+
this.reason = options?.reason ?? "disallowed";
|
|
9
17
|
}
|
|
10
18
|
};
|
|
11
19
|
/**
|
|
@@ -31,9 +39,12 @@ async function validatePublicUrl(url) {
|
|
|
31
39
|
}
|
|
32
40
|
let addresses;
|
|
33
41
|
try {
|
|
34
|
-
addresses = await
|
|
42
|
+
addresses = await node_dns_promises.default.lookup(hostname, { all: true });
|
|
35
43
|
} catch (error) {
|
|
36
|
-
throw new UrlError("DNS lookup failed", {
|
|
44
|
+
throw new UrlError("DNS lookup failed", {
|
|
45
|
+
cause: error,
|
|
46
|
+
reason: "dns"
|
|
47
|
+
});
|
|
37
48
|
}
|
|
38
49
|
validateLookupAddresses(addresses);
|
|
39
50
|
}
|
|
@@ -62,7 +73,7 @@ function validateLookupAddresses(addresses) {
|
|
|
62
73
|
ipAddressCount++;
|
|
63
74
|
validatePublicIpAddress(address, family);
|
|
64
75
|
}
|
|
65
|
-
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
|
|
76
|
+
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address", { reason: "dns" });
|
|
66
77
|
}
|
|
67
78
|
function validatePublicIpAddress(address, family) {
|
|
68
79
|
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
|