@fedify/vocab-runtime 2.4.0-pr.936.41 → 2.4.1
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-Ck8bcBir.mjs → contexts-B09jb9Pi.cjs} +173 -274
- package/dist/{tests/docloader-S_gQZ2tt.cjs → contexts-DWrj4AGR.js} +169 -293
- package/dist/{docloader-C_dir7Xb.d.ts → docloader-BMHljyYb.d.cts} +68 -2
- package/dist/{docloader-D2DTRiyA.d.cts → docloader-BMHljyYb.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 +762 -4529
- package/dist/mod.d.cts +427 -9
- package/dist/mod.d.ts +427 -10
- package/dist/mod.js +742 -4524
- package/dist/portable-6Aj-7dZJ.d.cts +160 -0
- package/dist/portable-nkTiqH0w.d.ts +160 -0
- package/dist/tests/body-DpC9p-iG.mjs +130 -0
- package/dist/tests/body-cKLBprYm.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-C9Lj11SC.cjs +4533 -0
- package/dist/tests/contexts-duRA_dDC.mjs +4522 -0
- package/dist/tests/decimal.test.cjs +9 -7
- package/dist/tests/decimal.test.mjs +9 -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-DyZ1JGIl.cjs +398 -0
- package/dist/tests/docloader-tUmMeXjU.mjs +375 -0
- package/dist/tests/docloader.test.cjs +816 -39
- package/dist/tests/docloader.test.mjs +810 -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-CmDhHNTx.cjs +12 -0
- package/dist/tests/jsonld-DweIJ07b.mjs +5 -0
- package/dist/tests/jsonld-cache-BEXiiM-V.cjs +386 -0
- package/dist/tests/jsonld-cache-DFnWsLuZ.mjs +339 -0
- package/dist/tests/jsonld-cache.test.cjs +102 -298
- package/dist/tests/jsonld-cache.test.mjs +95 -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-Di5AvKpB.mjs → langstr-DYRmJqFn.mjs} +5 -2
- package/dist/tests/{langstr-CbAxaeEZ.cjs → langstr-_PqsL1ae.cjs} +5 -2
- package/dist/tests/langstr.test.cjs +11 -4
- package/dist/tests/langstr.test.mjs +9 -2
- package/dist/tests/langtag-CUppHs_s.mjs +45 -0
- package/dist/tests/langtag-Czjvxfzx.cjs +50 -0
- package/dist/tests/langtag.test.cjs +45 -0
- package/dist/tests/langtag.test.d.cts +1 -0
- package/dist/tests/langtag.test.d.mts +1 -0
- package/dist/tests/langtag.test.mjs +46 -0
- 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-DmTYDy2p.mjs +250 -0
- package/dist/tests/portable-dereference-qTLMqe2_.cjs +279 -0
- package/dist/tests/portable-media-5p8azal0.cjs +153 -0
- package/dist/tests/portable-media-B_lt9MMA.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-uk51rkhO.cjs → request-BgjA9wyl.cjs} +10 -4
- package/dist/tests/{request-C8CaGwtt.mjs → request-DodZNukP.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 +489 -6
- package/dist/tests/url.test.mjs +489 -5
- 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-6757.json +12 -0
- package/src/contexts/joinmastodon.json +1 -1
- package/src/contexts/miscellany.json +17 -0
- package/src/contexts.ts +47 -1
- package/src/digest.test.ts +220 -0
- package/src/digest.ts +229 -0
- package/src/docloader.test.ts +1066 -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/langstr.test.ts +18 -1
- package/src/langstr.ts +6 -2
- package/src/langtag.test.ts +53 -0
- package/src/langtag.ts +76 -0
- package/src/mod.ts +29 -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 +823 -3
- package/src/url.ts +700 -23
- package/tsdown.config.ts +2 -0
- package/dist/tests/url-CsOV_B_P.cjs +0 -482
- package/dist/tests/url-Du7RQQgP.mjs +0 -392
- package/dist/url-DD4F0ULf.cjs +0 -483
- package/dist/url-DGVbSVVi.js +0 -393
- /package/dist/{chunk-M78iaK0I.cjs → rolldown-runtime-B7lfambq.cjs} +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
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const require_url = require("./url-
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
|
|
2
|
+
const require_request = require("./request-BgjA9wyl.cjs");
|
|
3
|
+
const require_contexts = require("./contexts-C9Lj11SC.cjs");
|
|
4
|
+
const require_url = require("./url-BNakuZ8k.cjs");
|
|
5
|
+
const require_docloader = require("./docloader-DyZ1JGIl.cjs");
|
|
6
|
+
const require_jsonld = require("./jsonld-CmDhHNTx.cjs");
|
|
7
|
+
let node_zlib = require("node:zlib");
|
|
8
|
+
let _logtape_logtape = require("@logtape/logtape");
|
|
5
9
|
let node_assert = require("node:assert");
|
|
6
|
-
let
|
|
10
|
+
let node_dns_promises = require("node:dns/promises");
|
|
11
|
+
node_dns_promises = require_rolldown_runtime.__toESM(node_dns_promises, 1);
|
|
12
|
+
let _fedify_fixture = require("@fedify/fixture");
|
|
13
|
+
let node_http = require("node:http");
|
|
7
14
|
//#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
|
|
8
|
-
var require_glob_to_regexp = /* @__PURE__ */
|
|
15
|
+
var require_glob_to_regexp = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
|
|
9
16
|
module.exports = function(glob, opts) {
|
|
10
17
|
if (typeof glob !== "string") throw new TypeError("Expected a string");
|
|
11
18
|
var str = String(glob);
|
|
@@ -79,7 +86,7 @@ var require_glob_to_regexp = /* @__PURE__ */ require_chunk.__commonJSMin(((expor
|
|
|
79
86
|
}));
|
|
80
87
|
//#endregion
|
|
81
88
|
//#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
|
|
82
|
-
var import_glob_to_regexp = /* @__PURE__ */
|
|
89
|
+
var import_glob_to_regexp = /* @__PURE__ */ require_rolldown_runtime.__toESM(require_glob_to_regexp(), 1);
|
|
83
90
|
/**
|
|
84
91
|
* @param {string|RegExp} input The route pattern
|
|
85
92
|
* @param {boolean} [loose] Allow open-ended matching. Ignored with `RegExp` input.
|
|
@@ -111,27 +118,27 @@ function parse(input, loose) {
|
|
|
111
118
|
}
|
|
112
119
|
//#endregion
|
|
113
120
|
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/TypeDescriptor.js
|
|
114
|
-
const valueTypes = new Set([
|
|
121
|
+
const valueTypes = /* @__PURE__ */ new Set([
|
|
115
122
|
"boolean",
|
|
116
123
|
"number",
|
|
117
124
|
"null",
|
|
118
125
|
"string",
|
|
119
126
|
"undefined"
|
|
120
127
|
]);
|
|
121
|
-
const referenceTypes = new Set([
|
|
128
|
+
const referenceTypes = /* @__PURE__ */ new Set([
|
|
122
129
|
"array",
|
|
123
130
|
"function",
|
|
124
131
|
"object",
|
|
125
132
|
"symbol"
|
|
126
133
|
]);
|
|
127
|
-
const detectableTypes = new Set([
|
|
134
|
+
const detectableTypes = /* @__PURE__ */ new Set([
|
|
128
135
|
"boolean",
|
|
129
136
|
"function",
|
|
130
137
|
"number",
|
|
131
138
|
"string",
|
|
132
139
|
"symbol"
|
|
133
140
|
]);
|
|
134
|
-
const typeConstructors = new Set([
|
|
141
|
+
const typeConstructors = /* @__PURE__ */ new Set([
|
|
135
142
|
Boolean,
|
|
136
143
|
Number,
|
|
137
144
|
String
|
|
@@ -202,7 +209,7 @@ var TypeDescriptor = class TypeDescriptor {
|
|
|
202
209
|
};
|
|
203
210
|
//#endregion
|
|
204
211
|
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/IsSubsetOf.js
|
|
205
|
-
const allowedTypes = new Set([
|
|
212
|
+
const allowedTypes = /* @__PURE__ */ new Set([
|
|
206
213
|
"array",
|
|
207
214
|
"object",
|
|
208
215
|
"function",
|
|
@@ -694,7 +701,14 @@ var __classPrivateFieldGet = function(receiver, state, kind, f) {
|
|
|
694
701
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
695
702
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
696
703
|
};
|
|
697
|
-
var _Route_instances
|
|
704
|
+
var _Route_instances;
|
|
705
|
+
var _a;
|
|
706
|
+
var _Route_responseSubscriptions;
|
|
707
|
+
var _Route_validate;
|
|
708
|
+
var _Route_sanitize;
|
|
709
|
+
var _Route_generateMatcher;
|
|
710
|
+
var _Route_limit;
|
|
711
|
+
var _Route_delayResponse;
|
|
698
712
|
var RouteConfigWrapper = class {
|
|
699
713
|
constructor(config) {
|
|
700
714
|
Object.assign(this, config);
|
|
@@ -1206,7 +1220,53 @@ var esm_default = new class FetchMock {
|
|
|
1206
1220
|
}({ ...defaultFetchMockConfig });
|
|
1207
1221
|
//#endregion
|
|
1208
1222
|
//#region src/docloader.test.ts
|
|
1209
|
-
(0,
|
|
1223
|
+
(0, _fedify_fixture.test)("getRemoteDocument() forwards options to alternate documents", async () => {
|
|
1224
|
+
const records = [];
|
|
1225
|
+
await (0, _logtape_logtape.reset)();
|
|
1226
|
+
await (0, _logtape_logtape.configure)({
|
|
1227
|
+
sinks: { capture: (record) => records.push(record) },
|
|
1228
|
+
loggers: [{
|
|
1229
|
+
category: ["fedify"],
|
|
1230
|
+
lowestLevel: "debug",
|
|
1231
|
+
sinks: ["capture"]
|
|
1232
|
+
}]
|
|
1233
|
+
});
|
|
1234
|
+
try {
|
|
1235
|
+
for (const linkHeader of [true, false]) for (const suppressError of [
|
|
1236
|
+
true,
|
|
1237
|
+
false,
|
|
1238
|
+
void 0
|
|
1239
|
+
]) {
|
|
1240
|
+
records.length = 0;
|
|
1241
|
+
const url = "https://example.com/alternate-source";
|
|
1242
|
+
const alternateUrl = "https://example.com/missing-alternate";
|
|
1243
|
+
const options = {
|
|
1244
|
+
suppressError,
|
|
1245
|
+
signal: new AbortController().signal
|
|
1246
|
+
};
|
|
1247
|
+
const response = new Response(`<link rel="alternate" type="application/activity+json" href="${alternateUrl}">`, { headers: {
|
|
1248
|
+
"Content-Type": "text/html",
|
|
1249
|
+
...linkHeader ? { Link: `<${alternateUrl}>; rel="alternate"; type="application/activity+json"` } : {}
|
|
1250
|
+
} });
|
|
1251
|
+
let alternateRequests = 0;
|
|
1252
|
+
await (0, node_assert.rejects)(() => require_docloader.getRemoteDocument(url, response, async (requestedUrl, forwarded) => {
|
|
1253
|
+
alternateRequests++;
|
|
1254
|
+
(0, node_assert.deepStrictEqual)(requestedUrl, alternateUrl);
|
|
1255
|
+
(0, node_assert.deepStrictEqual)(forwarded, options);
|
|
1256
|
+
return await require_docloader.getRemoteDocument(requestedUrl, new Response(null, { status: 404 }), () => {
|
|
1257
|
+
throw new Error("Unexpected additional alternate");
|
|
1258
|
+
}, forwarded);
|
|
1259
|
+
}, options), require_request.FetchError);
|
|
1260
|
+
(0, node_assert.deepStrictEqual)(alternateRequests, 1);
|
|
1261
|
+
const failures = records.filter((record) => record.rawMessage === "Failed to fetch document: {status} {url} {headers}");
|
|
1262
|
+
(0, node_assert.deepStrictEqual)(failures.length, 1);
|
|
1263
|
+
(0, node_assert.deepStrictEqual)(failures[0].level, suppressError ? "warning" : "error");
|
|
1264
|
+
}
|
|
1265
|
+
} finally {
|
|
1266
|
+
await (0, _logtape_logtape.reset)();
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
(0, _fedify_fixture.test)("new FetchError()", () => {
|
|
1210
1270
|
const e = new require_request.FetchError("https://example.com/", "An error message.");
|
|
1211
1271
|
(0, node_assert.deepStrictEqual)(e.name, "FetchError");
|
|
1212
1272
|
(0, node_assert.deepStrictEqual)(e.url, new URL("https://example.com/"));
|
|
@@ -1219,7 +1279,7 @@ var esm_default = new class FetchMock {
|
|
|
1219
1279
|
(0, node_assert.ok)(e2.response != null);
|
|
1220
1280
|
(0, node_assert.deepStrictEqual)(e2.response.status, 410);
|
|
1221
1281
|
});
|
|
1222
|
-
(0,
|
|
1282
|
+
(0, _fedify_fixture.test)("getDocumentLoader()", async (t) => {
|
|
1223
1283
|
const fetchDocumentLoader = require_docloader.getDocumentLoader();
|
|
1224
1284
|
esm_default.spyGlobal();
|
|
1225
1285
|
esm_default.get("https://example.com/object", { body: {
|
|
@@ -1228,7 +1288,7 @@ var esm_default = new class FetchMock {
|
|
|
1228
1288
|
name: "Fetched object",
|
|
1229
1289
|
type: "Object"
|
|
1230
1290
|
} });
|
|
1231
|
-
await t.
|
|
1291
|
+
await t.step("ok", async () => {
|
|
1232
1292
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/object"), {
|
|
1233
1293
|
contextUrl: null,
|
|
1234
1294
|
documentUrl: "https://example.com/object",
|
|
@@ -1271,7 +1331,7 @@ var esm_default = new class FetchMock {
|
|
|
1271
1331
|
Link: "<https://example.com/object>; rel=\"alternate\"; type=\"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\""
|
|
1272
1332
|
}
|
|
1273
1333
|
});
|
|
1274
|
-
await t.
|
|
1334
|
+
await t.step("Link header", async () => {
|
|
1275
1335
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/link-ctx"), {
|
|
1276
1336
|
contextUrl: "https://www.w3.org/ns/activitystreams",
|
|
1277
1337
|
documentUrl: "https://example.com/link-ctx",
|
|
@@ -1292,7 +1352,7 @@ var esm_default = new class FetchMock {
|
|
|
1292
1352
|
}
|
|
1293
1353
|
});
|
|
1294
1354
|
});
|
|
1295
|
-
await t.
|
|
1355
|
+
await t.step("Link header relative url", async () => {
|
|
1296
1356
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/link-ctx"), {
|
|
1297
1357
|
contextUrl: "https://www.w3.org/ns/activitystreams",
|
|
1298
1358
|
documentUrl: "https://example.com/link-ctx",
|
|
@@ -1313,7 +1373,7 @@ var esm_default = new class FetchMock {
|
|
|
1313
1373
|
}
|
|
1314
1374
|
});
|
|
1315
1375
|
});
|
|
1316
|
-
await t.
|
|
1376
|
+
await t.step("wrong Link header syntax", async () => {
|
|
1317
1377
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/obj-w-wrong-link"), {
|
|
1318
1378
|
contextUrl: null,
|
|
1319
1379
|
documentUrl: "https://example.com/obj-w-wrong-link",
|
|
@@ -1337,7 +1397,7 @@ var esm_default = new class FetchMock {
|
|
|
1337
1397
|
</html>`,
|
|
1338
1398
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1339
1399
|
});
|
|
1340
|
-
await t.
|
|
1400
|
+
await t.step("HTML <link>", async () => {
|
|
1341
1401
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/html-link"), {
|
|
1342
1402
|
contextUrl: null,
|
|
1343
1403
|
documentUrl: "https://example.com/object",
|
|
@@ -1361,7 +1421,7 @@ var esm_default = new class FetchMock {
|
|
|
1361
1421
|
</html>`,
|
|
1362
1422
|
headers: { "Content-Type": "application/xhtml+xml; charset=utf-8" }
|
|
1363
1423
|
});
|
|
1364
|
-
await t.
|
|
1424
|
+
await t.step("XHTML <link>", async () => {
|
|
1365
1425
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/xhtml-link"), {
|
|
1366
1426
|
contextUrl: null,
|
|
1367
1427
|
documentUrl: "https://example.com/object",
|
|
@@ -1387,7 +1447,7 @@ var esm_default = new class FetchMock {
|
|
|
1387
1447
|
</html>`,
|
|
1388
1448
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1389
1449
|
});
|
|
1390
|
-
await t.
|
|
1450
|
+
await t.step("HTML <a>", async () => {
|
|
1391
1451
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/html-a"), {
|
|
1392
1452
|
contextUrl: null,
|
|
1393
1453
|
documentUrl: "https://example.com/object",
|
|
@@ -1409,7 +1469,7 @@ var esm_default = new class FetchMock {
|
|
|
1409
1469
|
</html>`,
|
|
1410
1470
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1411
1471
|
});
|
|
1412
|
-
await t.
|
|
1472
|
+
await t.step("HTML without ActivityPub alternate link", async () => {
|
|
1413
1473
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/html-no-alternate"), (error) => {
|
|
1414
1474
|
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1415
1475
|
(0, node_assert.ok)(error.message.includes("HTML document has no ActivityPub alternate link"));
|
|
@@ -1429,7 +1489,7 @@ var esm_default = new class FetchMock {
|
|
|
1429
1489
|
},
|
|
1430
1490
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1431
1491
|
});
|
|
1432
|
-
await t.
|
|
1492
|
+
await t.step("wrong Content-Type with JSON body", async () => {
|
|
1433
1493
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/wrong-content-type"), {
|
|
1434
1494
|
contextUrl: null,
|
|
1435
1495
|
documentUrl: "https://example.com/wrong-content-type",
|
|
@@ -1448,7 +1508,7 @@ var esm_default = new class FetchMock {
|
|
|
1448
1508
|
"Content-Type": "text/html; charset=utf-8"
|
|
1449
1509
|
}
|
|
1450
1510
|
});
|
|
1451
|
-
await t.
|
|
1511
|
+
await t.step("HTML Content-Length over limit", async () => {
|
|
1452
1512
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/large-html"), (error) => {
|
|
1453
1513
|
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1454
1514
|
(0, node_assert.ok)(error.message.includes("HTML document is too large to scan for an ActivityPub alternate link"));
|
|
@@ -1458,32 +1518,211 @@ var esm_default = new class FetchMock {
|
|
|
1458
1518
|
return true;
|
|
1459
1519
|
});
|
|
1460
1520
|
});
|
|
1461
|
-
await t.
|
|
1521
|
+
await t.step("HTML Content-Length over limit cancels body", async () => {
|
|
1462
1522
|
let canceled = false;
|
|
1463
1523
|
const response = new Response("<!DOCTYPE html>", { headers: {
|
|
1464
1524
|
"Content-Length": String(1048577),
|
|
1465
1525
|
"Content-Type": "text/html; charset=utf-8"
|
|
1466
1526
|
} });
|
|
1467
|
-
Object.defineProperty(response, "body", { value: { cancel
|
|
1527
|
+
Object.defineProperty(response, "body", { value: new ReadableStream({ cancel() {
|
|
1468
1528
|
canceled = true;
|
|
1469
|
-
} } });
|
|
1529
|
+
} }) });
|
|
1470
1530
|
await (0, node_assert.rejects)(() => require_docloader.getRemoteDocument("https://example.com/large-html-cancel", response, () => {
|
|
1471
1531
|
throw new Error("unexpected alternate fetch");
|
|
1472
1532
|
}), require_request.FetchError);
|
|
1473
1533
|
(0, node_assert.deepStrictEqual)(canceled, true);
|
|
1474
1534
|
});
|
|
1475
1535
|
esm_default.get("https://example.com/404", { status: 404 });
|
|
1476
|
-
await t.
|
|
1536
|
+
await t.step("not ok", async () => {
|
|
1477
1537
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/404"), require_request.FetchError, "HTTP 404: https://example.com/404");
|
|
1478
1538
|
});
|
|
1479
|
-
await t.
|
|
1480
|
-
for (const [url, document] of Object.entries(
|
|
1539
|
+
await t.step("preloaded contexts", async () => {
|
|
1540
|
+
for (const [url, document] of Object.entries(require_contexts.preloadedContexts)) (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1481
1541
|
contextUrl: null,
|
|
1482
1542
|
documentUrl: url,
|
|
1483
1543
|
document
|
|
1484
1544
|
});
|
|
1485
1545
|
});
|
|
1486
|
-
await t.
|
|
1546
|
+
await t.step("preloaded Mastodon attribution domains", async () => {
|
|
1547
|
+
const context = ["https://www.w3.org/ns/activitystreams", "http://joinmastodon.org/ns"];
|
|
1548
|
+
const expanded = await require_jsonld.jsonld_default.expand({
|
|
1549
|
+
"@context": context,
|
|
1550
|
+
id: "https://social.example/users/alice",
|
|
1551
|
+
type: "Person",
|
|
1552
|
+
attributionDomains: ["blog.example"]
|
|
1553
|
+
}, { documentLoader: fetchDocumentLoader });
|
|
1554
|
+
(0, node_assert.deepStrictEqual)(expanded, [{
|
|
1555
|
+
"@id": "https://social.example/users/alice",
|
|
1556
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Person"],
|
|
1557
|
+
"http://joinmastodon.org/ns#attributionDomains": [{ "@value": "blog.example" }]
|
|
1558
|
+
}]);
|
|
1559
|
+
(0, node_assert.deepStrictEqual)((await require_jsonld.jsonld_default.compact(expanded, context, { documentLoader: fetchDocumentLoader })).attributionDomains, ["blog.example"]);
|
|
1560
|
+
});
|
|
1561
|
+
esm_default.get("https://www.w3.org/ns/cid/v1", { status: 503 });
|
|
1562
|
+
await t.step("preloaded CID v1 context", async () => {
|
|
1563
|
+
const url = "https://www.w3.org/ns/cid/v1";
|
|
1564
|
+
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1565
|
+
contextUrl: null,
|
|
1566
|
+
documentUrl: url,
|
|
1567
|
+
document: require_contexts.cid_v1_default
|
|
1568
|
+
});
|
|
1569
|
+
});
|
|
1570
|
+
await t.step("preloaded FEP-ef61 context", async () => {
|
|
1571
|
+
const url = "https://w3id.org/fep/ef61";
|
|
1572
|
+
(0, node_assert.ok)(url in require_contexts.preloadedContexts);
|
|
1573
|
+
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1574
|
+
contextUrl: null,
|
|
1575
|
+
documentUrl: url,
|
|
1576
|
+
document: { "@context": {
|
|
1577
|
+
gateways: {
|
|
1578
|
+
"@id": "https://w3id.org/fep/ef61/gateways",
|
|
1579
|
+
"@type": "@id",
|
|
1580
|
+
"@container": "@list"
|
|
1581
|
+
},
|
|
1582
|
+
digestMultibase: "https://www.w3.org/ns/credentials/v2#digestMultibase"
|
|
1583
|
+
} }
|
|
1584
|
+
});
|
|
1585
|
+
});
|
|
1586
|
+
esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
|
|
1587
|
+
await t.step("preloaded FEP-7aa9 context", async () => {
|
|
1588
|
+
const url = "https://w3id.org/fep/7aa9";
|
|
1589
|
+
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1590
|
+
contextUrl: null,
|
|
1591
|
+
documentUrl: url,
|
|
1592
|
+
document: { "@context": {
|
|
1593
|
+
"FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
|
|
1594
|
+
"FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
|
|
1595
|
+
"FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
|
|
1596
|
+
"FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
|
|
1597
|
+
"topic": {
|
|
1598
|
+
"@id": "https://w3id.org/fep/7aa9#topic",
|
|
1599
|
+
"@type": "@id"
|
|
1600
|
+
},
|
|
1601
|
+
"featuredObject": {
|
|
1602
|
+
"@id": "https://w3id.org/fep/7aa9#featuredObject",
|
|
1603
|
+
"@type": "@id"
|
|
1604
|
+
},
|
|
1605
|
+
"canFeature": {
|
|
1606
|
+
"@id": "https://w3id.org/fep/7aa9#canFeature",
|
|
1607
|
+
"@type": "@id"
|
|
1608
|
+
},
|
|
1609
|
+
"featureAuthorization": {
|
|
1610
|
+
"@id": "https://w3id.org/fep/7aa9#featureAuthorization",
|
|
1611
|
+
"@type": "@id"
|
|
1612
|
+
}
|
|
1613
|
+
} }
|
|
1614
|
+
});
|
|
1615
|
+
(0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
|
|
1616
|
+
});
|
|
1617
|
+
esm_default.get("https://w3id.org/fep/6757", { status: 502 });
|
|
1618
|
+
await t.step("preloaded FEP-6757 context", async () => {
|
|
1619
|
+
const url = "https://w3id.org/fep/6757";
|
|
1620
|
+
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1621
|
+
contextUrl: null,
|
|
1622
|
+
documentUrl: url,
|
|
1623
|
+
document: { "@context": {
|
|
1624
|
+
license: {
|
|
1625
|
+
"@id": "http://purl.org/dc/terms/license",
|
|
1626
|
+
"@type": "@id"
|
|
1627
|
+
},
|
|
1628
|
+
preferredLicense: {
|
|
1629
|
+
"@id": "https://w3id.org/fep/6757#preferredLicense",
|
|
1630
|
+
"@type": "@id"
|
|
1631
|
+
}
|
|
1632
|
+
} }
|
|
1633
|
+
});
|
|
1634
|
+
(0, node_assert.deepStrictEqual)(await require_jsonld.jsonld_default.expand({
|
|
1635
|
+
"@context": ["https://www.w3.org/ns/activitystreams", url],
|
|
1636
|
+
type: "Person",
|
|
1637
|
+
license: "https://creativecommons.org/licenses/by/4.0/",
|
|
1638
|
+
preferredLicense: "https://creativecommons.org/licenses/by-nc/4.0/"
|
|
1639
|
+
}, { documentLoader: fetchDocumentLoader }), [{
|
|
1640
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Person"],
|
|
1641
|
+
"http://purl.org/dc/terms/license": [{ "@id": "https://creativecommons.org/licenses/by/4.0/" }],
|
|
1642
|
+
"https://w3id.org/fep/6757#preferredLicense": [{ "@id": "https://creativecommons.org/licenses/by-nc/4.0/" }]
|
|
1643
|
+
}]);
|
|
1644
|
+
(0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
|
|
1645
|
+
});
|
|
1646
|
+
esm_default.get("https://w3id.org/fep/22cd", { status: 502 });
|
|
1647
|
+
await t.step("preloaded FEP-22cd context", async () => {
|
|
1648
|
+
const url = "https://w3id.org/fep/22cd";
|
|
1649
|
+
const articleId = "https://example.com/articles/1";
|
|
1650
|
+
const translatorId = "https://example.com/users/alice";
|
|
1651
|
+
const revisionId = "https://example.com/articles/1/revisions/1";
|
|
1652
|
+
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1653
|
+
contextUrl: null,
|
|
1654
|
+
documentUrl: url,
|
|
1655
|
+
document: { "@context": {
|
|
1656
|
+
"fep-22cd": "https://w3id.org/fep/22cd#",
|
|
1657
|
+
schema: "https://schema.org/",
|
|
1658
|
+
xsd: "http://www.w3.org/2001/XMLSchema#",
|
|
1659
|
+
translations: {
|
|
1660
|
+
"@id": "fep-22cd:translations",
|
|
1661
|
+
"@container": "@set"
|
|
1662
|
+
},
|
|
1663
|
+
Translation: "fep-22cd:Translation",
|
|
1664
|
+
sourceUpdated: {
|
|
1665
|
+
"@id": "fep-22cd:sourceUpdated",
|
|
1666
|
+
"@type": "xsd:dateTime"
|
|
1667
|
+
},
|
|
1668
|
+
translator: {
|
|
1669
|
+
"@id": "schema:translator",
|
|
1670
|
+
"@type": "@id",
|
|
1671
|
+
"@container": "@set"
|
|
1672
|
+
},
|
|
1673
|
+
inLanguage: "schema:inLanguage",
|
|
1674
|
+
translationOfWork: {
|
|
1675
|
+
"@id": "schema:translationOfWork",
|
|
1676
|
+
"@type": "@id"
|
|
1677
|
+
},
|
|
1678
|
+
isBasedOn: {
|
|
1679
|
+
"@id": "schema:isBasedOn",
|
|
1680
|
+
"@type": "@id"
|
|
1681
|
+
}
|
|
1682
|
+
} }
|
|
1683
|
+
});
|
|
1684
|
+
(0, node_assert.deepStrictEqual)(await require_jsonld.jsonld_default.expand({
|
|
1685
|
+
"@context": ["https://www.w3.org/ns/activitystreams", url],
|
|
1686
|
+
id: articleId,
|
|
1687
|
+
type: "Article",
|
|
1688
|
+
contentMap: {
|
|
1689
|
+
en: "Original",
|
|
1690
|
+
ko: "Translation"
|
|
1691
|
+
},
|
|
1692
|
+
translations: [{
|
|
1693
|
+
type: "Translation",
|
|
1694
|
+
inLanguage: "ko",
|
|
1695
|
+
translator: [translatorId],
|
|
1696
|
+
translationOfWork: articleId,
|
|
1697
|
+
sourceUpdated: "2026-09-01T00:00:00Z",
|
|
1698
|
+
isBasedOn: revisionId
|
|
1699
|
+
}]
|
|
1700
|
+
}, { documentLoader: fetchDocumentLoader }), [{
|
|
1701
|
+
"@id": articleId,
|
|
1702
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Article"],
|
|
1703
|
+
"https://www.w3.org/ns/activitystreams#content": [{
|
|
1704
|
+
"@language": "en",
|
|
1705
|
+
"@value": "Original"
|
|
1706
|
+
}, {
|
|
1707
|
+
"@language": "ko",
|
|
1708
|
+
"@value": "Translation"
|
|
1709
|
+
}],
|
|
1710
|
+
"https://w3id.org/fep/22cd#translations": [{
|
|
1711
|
+
"@type": ["https://w3id.org/fep/22cd#Translation"],
|
|
1712
|
+
"https://schema.org/inLanguage": [{ "@value": "ko" }],
|
|
1713
|
+
"https://schema.org/translator": [{ "@id": translatorId }],
|
|
1714
|
+
"https://schema.org/translationOfWork": [{ "@id": articleId }],
|
|
1715
|
+
"https://w3id.org/fep/22cd#sourceUpdated": [{
|
|
1716
|
+
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
|
|
1717
|
+
"@value": "2026-09-01T00:00:00Z"
|
|
1718
|
+
}],
|
|
1719
|
+
"https://schema.org/isBasedOn": [{ "@id": revisionId }]
|
|
1720
|
+
}]
|
|
1721
|
+
}]);
|
|
1722
|
+
(0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
|
|
1723
|
+
(0, node_assert.deepStrictEqual)(esm_default.callHistory.calls("https://www.w3.org/ns/activitystreams").length, 0);
|
|
1724
|
+
});
|
|
1725
|
+
await t.step("deny non-HTTP/HTTPS", async () => {
|
|
1487
1726
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("ftp://localhost"), require_url.UrlError);
|
|
1488
1727
|
});
|
|
1489
1728
|
esm_default.get("https://example.com/localhost-redirect", {
|
|
@@ -1508,13 +1747,13 @@ var esm_default = new class FetchMock {
|
|
|
1508
1747
|
name: "Fetched object",
|
|
1509
1748
|
type: "Object"
|
|
1510
1749
|
} });
|
|
1511
|
-
await t.
|
|
1750
|
+
await t.step("allowPrivateAddress: false", async () => {
|
|
1512
1751
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://localhost/object"), require_url.UrlError);
|
|
1513
1752
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/localhost-redirect"), require_url.UrlError);
|
|
1514
1753
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/localhost-link"), require_url.UrlError);
|
|
1515
1754
|
});
|
|
1516
1755
|
const fetchDocumentLoader2 = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1517
|
-
await t.
|
|
1756
|
+
await t.step("allowPrivateAddress: true", async () => {
|
|
1518
1757
|
const expected = {
|
|
1519
1758
|
contextUrl: null,
|
|
1520
1759
|
documentUrl: "https://localhost/object",
|
|
@@ -1537,12 +1776,12 @@ var esm_default = new class FetchMock {
|
|
|
1537
1776
|
headers: { Location: `https://example.com/too-many-redirects/${Number(cl.url.split("/").at(-1)) + 1}` }
|
|
1538
1777
|
};
|
|
1539
1778
|
});
|
|
1540
|
-
await t.
|
|
1779
|
+
await t.step("too many redirects", async () => {
|
|
1541
1780
|
redirectAttempts = 0;
|
|
1542
1781
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/too-many-redirects/0"), require_request.FetchError, "Too many redirections");
|
|
1543
1782
|
(0, node_assert.deepStrictEqual)(redirectAttempts, 21);
|
|
1544
1783
|
});
|
|
1545
|
-
await t.
|
|
1784
|
+
await t.step("custom max redirection", async () => {
|
|
1546
1785
|
redirectAttempts = 0;
|
|
1547
1786
|
const loader = require_docloader.getDocumentLoader({ maxRedirection: 1 });
|
|
1548
1787
|
await (0, node_assert.rejects)(() => loader("https://example.com/too-many-redirects/0"), require_request.FetchError, "Too many redirections");
|
|
@@ -1563,7 +1802,7 @@ var esm_default = new class FetchMock {
|
|
|
1563
1802
|
headers: { Location: "https://example.com/redirect-loop-a" }
|
|
1564
1803
|
};
|
|
1565
1804
|
});
|
|
1566
|
-
await t.
|
|
1805
|
+
await t.step("redirect loop", async () => {
|
|
1567
1806
|
loopAttempts = 0;
|
|
1568
1807
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redirect-loop-a"), require_request.FetchError, "Redirect loop detected");
|
|
1569
1808
|
(0, node_assert.deepStrictEqual)(loopAttempts, 2);
|
|
@@ -1576,7 +1815,7 @@ var esm_default = new class FetchMock {
|
|
|
1576
1815
|
headers: { Location: "/redirect-loop-relative" }
|
|
1577
1816
|
};
|
|
1578
1817
|
});
|
|
1579
|
-
await t.
|
|
1818
|
+
await t.step("redirect loop with relative location", async () => {
|
|
1580
1819
|
relativeLoopAttempts = 0;
|
|
1581
1820
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redirect-loop-relative"), require_request.FetchError, "Redirect loop detected");
|
|
1582
1821
|
(0, node_assert.deepStrictEqual)(relativeLoopAttempts, 1);
|
|
@@ -1586,7 +1825,7 @@ var esm_default = new class FetchMock {
|
|
|
1586
1825
|
body: maliciousPayload,
|
|
1587
1826
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1588
1827
|
});
|
|
1589
|
-
await t.
|
|
1828
|
+
await t.step("ReDoS resistance (CVE-2025-68475)", async () => {
|
|
1590
1829
|
const start = performance.now();
|
|
1591
1830
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redos"), require_request.FetchError);
|
|
1592
1831
|
const elapsed = performance.now() - start;
|
|
@@ -1594,4 +1833,542 @@ var esm_default = new class FetchMock {
|
|
|
1594
1833
|
});
|
|
1595
1834
|
esm_default.hardReset();
|
|
1596
1835
|
});
|
|
1836
|
+
(0, _fedify_fixture.test)("getDocumentLoader() bounds JSON, HTML, and alternate documents", async () => {
|
|
1837
|
+
const url = "https://example.com/bounded";
|
|
1838
|
+
esm_default.mockGlobal();
|
|
1839
|
+
let oversized = true;
|
|
1840
|
+
try {
|
|
1841
|
+
esm_default.get(url, () => new Response("{\"name\":\"hello\"}", { headers: {
|
|
1842
|
+
"Content-Type": "application/activity+json",
|
|
1843
|
+
...oversized ? { "Content-Length": String(16777217) } : {}
|
|
1844
|
+
} }));
|
|
1845
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1846
|
+
await (0, node_assert.rejects)(loader(url), require_request.FetchError);
|
|
1847
|
+
oversized = false;
|
|
1848
|
+
(0, node_assert.deepStrictEqual)((await loader(url)).document, { name: "hello" });
|
|
1849
|
+
oversized = true;
|
|
1850
|
+
esm_default.get(`${url}/html`, () => new Response(" ".repeat(1048577), { headers: { "Content-Type": "text/html" } }));
|
|
1851
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(`${url}/html`), require_request.FetchError);
|
|
1852
|
+
esm_default.get(`${url}/alternate`, () => new Response("", { headers: {
|
|
1853
|
+
"Content-Type": "text/html",
|
|
1854
|
+
Link: `<${url}>; rel="alternate"; type="application/activity+json"`
|
|
1855
|
+
} }));
|
|
1856
|
+
await (0, node_assert.rejects)(loader(`${url}/alternate`), require_request.FetchError);
|
|
1857
|
+
esm_default.get(`${url}/redirect`, {
|
|
1858
|
+
status: 302,
|
|
1859
|
+
headers: { Location: url }
|
|
1860
|
+
});
|
|
1861
|
+
await (0, node_assert.rejects)(loader(`${url}/redirect`), require_request.FetchError);
|
|
1862
|
+
} finally {
|
|
1863
|
+
esm_default.hardReset();
|
|
1864
|
+
}
|
|
1865
|
+
});
|
|
1866
|
+
(0, _fedify_fixture.test)("getDocumentLoader() rejects oversized gzip responses from fetch", async () => {
|
|
1867
|
+
const document = { name: "a".repeat(16 * 1024 * 1024) };
|
|
1868
|
+
const compressed = (0, node_zlib.gzipSync)(JSON.stringify(document));
|
|
1869
|
+
(0, node_assert.ok)(compressed.byteLength < 64 * 1024);
|
|
1870
|
+
const server = (0, node_http.createServer)((request, response) => {
|
|
1871
|
+
const body = request.url === "/small" ? (0, node_zlib.gzipSync)("{\"name\":\"hello\"}") : compressed;
|
|
1872
|
+
response.writeHead(200, {
|
|
1873
|
+
"Content-Type": "application/activity+json",
|
|
1874
|
+
"Content-Encoding": "gzip",
|
|
1875
|
+
"Content-Length": body.byteLength
|
|
1876
|
+
});
|
|
1877
|
+
response.end(body);
|
|
1878
|
+
});
|
|
1879
|
+
await new Promise((resolve, reject) => {
|
|
1880
|
+
server.once("error", reject);
|
|
1881
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
1882
|
+
});
|
|
1883
|
+
try {
|
|
1884
|
+
const address = server.address();
|
|
1885
|
+
(0, node_assert.ok)(address != null && typeof address !== "string");
|
|
1886
|
+
const url = `http://127.0.0.1:${address.port}/document`;
|
|
1887
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(url), require_request.FetchError);
|
|
1888
|
+
(0, node_assert.deepStrictEqual)((await require_docloader.getDocumentLoader({ allowPrivateAddress: true })(new URL("/small", url).href)).document, { name: "hello" });
|
|
1889
|
+
} finally {
|
|
1890
|
+
await new Promise((resolve, reject) => {
|
|
1891
|
+
server.close((error) => error == null ? resolve() : reject(error));
|
|
1892
|
+
server.closeAllConnections();
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
(0, _fedify_fixture.test)("getRemoteDocument() bounds JSON by default", async () => {
|
|
1897
|
+
let canceled = false;
|
|
1898
|
+
let pulls = 0;
|
|
1899
|
+
await (0, node_assert.rejects)(require_docloader.getRemoteDocument("https://example.com/document", new Response(new ReadableStream({
|
|
1900
|
+
pull(controller) {
|
|
1901
|
+
pulls++;
|
|
1902
|
+
controller.enqueue(new Uint8Array(64 * 1024).fill(32));
|
|
1903
|
+
},
|
|
1904
|
+
cancel() {
|
|
1905
|
+
canceled = true;
|
|
1906
|
+
}
|
|
1907
|
+
}, { highWaterMark: 0 })), () => {
|
|
1908
|
+
throw new Error("Unexpected alternate document");
|
|
1909
|
+
}), require_request.FetchError);
|
|
1910
|
+
(0, node_assert.deepStrictEqual)(canceled, true);
|
|
1911
|
+
(0, node_assert.deepStrictEqual)(pulls, 257);
|
|
1912
|
+
});
|
|
1913
|
+
(0, _fedify_fixture.test)("getDocumentLoader() logs DNS failures as such", async (t) => {
|
|
1914
|
+
if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
|
|
1915
|
+
const loader = require_docloader.getDocumentLoader();
|
|
1916
|
+
for (const result of [
|
|
1917
|
+
"throws",
|
|
1918
|
+
"empty",
|
|
1919
|
+
"private"
|
|
1920
|
+
]) await t.step(result, async () => {
|
|
1921
|
+
const originalLookup = node_dns_promises.default.lookup;
|
|
1922
|
+
node_dns_promises.default.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
|
|
1923
|
+
address: "127.0.0.1",
|
|
1924
|
+
family: 4
|
|
1925
|
+
}]));
|
|
1926
|
+
const records = [];
|
|
1927
|
+
await (0, _logtape_logtape.configure)({
|
|
1928
|
+
sinks: { buffer: (record) => records.push(record) },
|
|
1929
|
+
loggers: [{
|
|
1930
|
+
category: "fedify",
|
|
1931
|
+
sinks: ["buffer"],
|
|
1932
|
+
lowestLevel: "debug"
|
|
1933
|
+
}, {
|
|
1934
|
+
category: ["logtape", "meta"],
|
|
1935
|
+
sinks: []
|
|
1936
|
+
}],
|
|
1937
|
+
reset: true
|
|
1938
|
+
});
|
|
1939
|
+
try {
|
|
1940
|
+
const url = "https://dns-failure.invalid/object";
|
|
1941
|
+
let error;
|
|
1942
|
+
await (0, node_assert.rejects)(() => loader(url), (e) => {
|
|
1943
|
+
error = e;
|
|
1944
|
+
return true;
|
|
1945
|
+
});
|
|
1946
|
+
(0, node_assert.ok)(error instanceof require_url.UrlError);
|
|
1947
|
+
(0, node_assert.deepStrictEqual)(error.reason, result === "private" ? "disallowed" : "dns");
|
|
1948
|
+
(0, node_assert.deepStrictEqual)(records.map((r) => [
|
|
1949
|
+
r.level,
|
|
1950
|
+
r.rawMessage,
|
|
1951
|
+
r.properties.url
|
|
1952
|
+
]), [result === "private" ? [
|
|
1953
|
+
"error",
|
|
1954
|
+
"Disallowed private URL: {url}",
|
|
1955
|
+
url
|
|
1956
|
+
] : [
|
|
1957
|
+
"debug",
|
|
1958
|
+
"DNS lookup failed for {url}",
|
|
1959
|
+
url
|
|
1960
|
+
]]);
|
|
1961
|
+
(0, node_assert.ok)(records[0].properties.error === error);
|
|
1962
|
+
} finally {
|
|
1963
|
+
await (0, _logtape_logtape.reset)();
|
|
1964
|
+
node_dns_promises.default.lookup = originalLookup;
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
});
|
|
1968
|
+
(0, _fedify_fixture.test)("getDocumentLoader() bounds alternate document chains", async (t) => {
|
|
1969
|
+
const base = "https://example.com/alternate-chain/";
|
|
1970
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1971
|
+
for (const mode of [
|
|
1972
|
+
"header",
|
|
1973
|
+
"html",
|
|
1974
|
+
"mixed"
|
|
1975
|
+
]) for (const hops of [20, 21]) await t.step(`${mode}: ${hops} hops`, async () => {
|
|
1976
|
+
esm_default.mockGlobal();
|
|
1977
|
+
let requests = 0;
|
|
1978
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1979
|
+
requests++;
|
|
1980
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1981
|
+
if (index === hops || requests > 30) return Response.json({ done: true });
|
|
1982
|
+
const next = `${base}${index + 1}`;
|
|
1983
|
+
if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
|
|
1984
|
+
return alternate(next, mode === "html");
|
|
1985
|
+
});
|
|
1986
|
+
try {
|
|
1987
|
+
if (hops === 20) (0, node_assert.deepStrictEqual)((await loader(`${base}0`)).document, { done: true });
|
|
1988
|
+
else await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
1989
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1990
|
+
(0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
|
|
1991
|
+
return true;
|
|
1992
|
+
});
|
|
1993
|
+
(0, node_assert.deepStrictEqual)(requests, 21);
|
|
1994
|
+
} finally {
|
|
1995
|
+
esm_default.hardReset();
|
|
1996
|
+
}
|
|
1997
|
+
});
|
|
1998
|
+
for (const mode of [
|
|
1999
|
+
"header",
|
|
2000
|
+
"html",
|
|
2001
|
+
"alternate-redirect",
|
|
2002
|
+
"redirect-alternate",
|
|
2003
|
+
"redirect-intermediate"
|
|
2004
|
+
]) await t.step(`${mode}: cycle`, async () => {
|
|
2005
|
+
esm_default.mockGlobal();
|
|
2006
|
+
let requests = 0;
|
|
2007
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
2008
|
+
requests++;
|
|
2009
|
+
if (requests > 30) return Response.json({ stopped: true });
|
|
2010
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
2011
|
+
const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
|
|
2012
|
+
if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
|
|
2013
|
+
return alternate(next, mode === "html");
|
|
2014
|
+
});
|
|
2015
|
+
try {
|
|
2016
|
+
await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
2017
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
2018
|
+
(0, node_assert.ok)(error.message.includes("Redirect loop detected:"));
|
|
2019
|
+
return true;
|
|
2020
|
+
});
|
|
2021
|
+
(0, node_assert.deepStrictEqual)(requests, mode === "redirect-intermediate" ? 3 : 2);
|
|
2022
|
+
} finally {
|
|
2023
|
+
esm_default.hardReset();
|
|
2024
|
+
}
|
|
2025
|
+
});
|
|
2026
|
+
await t.step("alternate followed by 20 redirects shares the limit", async () => {
|
|
2027
|
+
esm_default.mockGlobal();
|
|
2028
|
+
let requests = 0;
|
|
2029
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
2030
|
+
requests++;
|
|
2031
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
2032
|
+
if (index === 21) return Response.json({ done: true });
|
|
2033
|
+
return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
|
|
2034
|
+
});
|
|
2035
|
+
try {
|
|
2036
|
+
await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
2037
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
2038
|
+
(0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
|
|
2039
|
+
return true;
|
|
2040
|
+
});
|
|
2041
|
+
(0, node_assert.deepStrictEqual)(requests, 21);
|
|
2042
|
+
} finally {
|
|
2043
|
+
esm_default.hardReset();
|
|
2044
|
+
}
|
|
2045
|
+
});
|
|
2046
|
+
await t.step("relative alternate after redirect and isolated calls", async () => {
|
|
2047
|
+
esm_default.mockGlobal();
|
|
2048
|
+
esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
|
|
2049
|
+
esm_default.get(`${base}html`, alternate("./document", true));
|
|
2050
|
+
esm_default.get(`${base}document`, Response.json({ done: true }));
|
|
2051
|
+
try {
|
|
2052
|
+
for (let i = 0; i < 2; i++) {
|
|
2053
|
+
const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
|
|
2054
|
+
for (const result of results) {
|
|
2055
|
+
(0, node_assert.deepStrictEqual)(result.document, { done: true });
|
|
2056
|
+
(0, node_assert.deepStrictEqual)(result.documentUrl, `${base}document`);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
} finally {
|
|
2060
|
+
esm_default.hardReset();
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
function alternate(next, html) {
|
|
2064
|
+
return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
|
|
2065
|
+
"Content-Type": "text/plain",
|
|
2066
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
2067
|
+
} });
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
(0, _fedify_fixture.test)("getDocumentLoader() preserves cancellation across alternates", async (t) => {
|
|
2071
|
+
const base = "https://example.com/alternate-abort/";
|
|
2072
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
2073
|
+
for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
|
|
2074
|
+
esm_default.mockGlobal();
|
|
2075
|
+
const controller = new AbortController();
|
|
2076
|
+
let requests = 0;
|
|
2077
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
2078
|
+
requests++;
|
|
2079
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
2080
|
+
if (index === 2) return Response.json({ done: true });
|
|
2081
|
+
if (index === 1) controller.abort();
|
|
2082
|
+
const next = `${base}${index + 1}`;
|
|
2083
|
+
return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
|
|
2084
|
+
"Content-Type": "text/plain",
|
|
2085
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
2086
|
+
} });
|
|
2087
|
+
});
|
|
2088
|
+
try {
|
|
2089
|
+
await (0, node_assert.rejects)(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
|
|
2090
|
+
(0, node_assert.deepStrictEqual)(requests, 2);
|
|
2091
|
+
} finally {
|
|
2092
|
+
esm_default.hardReset();
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
2095
|
+
});
|
|
2096
|
+
(0, _fedify_fixture.test)("getDocumentLoader() rejects cancellation before fetching", async () => {
|
|
2097
|
+
esm_default.mockGlobal();
|
|
2098
|
+
let requests = 0;
|
|
2099
|
+
const url = "https://example.com/pre-aborted-alternate";
|
|
2100
|
+
esm_default.get(url, () => {
|
|
2101
|
+
requests++;
|
|
2102
|
+
return Response.json({ done: true });
|
|
2103
|
+
});
|
|
2104
|
+
try {
|
|
2105
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
2106
|
+
const controller = new AbortController();
|
|
2107
|
+
controller.abort();
|
|
2108
|
+
await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), { name: "AbortError" });
|
|
2109
|
+
(0, node_assert.deepStrictEqual)(requests, 0);
|
|
2110
|
+
} finally {
|
|
2111
|
+
esm_default.hardReset();
|
|
2112
|
+
}
|
|
2113
|
+
});
|
|
2114
|
+
async function withServer(handler, callback) {
|
|
2115
|
+
const server = (0, node_http.createServer)(handler);
|
|
2116
|
+
await new Promise((resolve, reject) => {
|
|
2117
|
+
server.once("error", reject);
|
|
2118
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
2119
|
+
});
|
|
2120
|
+
try {
|
|
2121
|
+
const address = server.address();
|
|
2122
|
+
(0, node_assert.ok)(address != null && typeof address !== "string");
|
|
2123
|
+
return await callback(`http://127.0.0.1:${address.port}`);
|
|
2124
|
+
} finally {
|
|
2125
|
+
await new Promise((resolve, reject) => {
|
|
2126
|
+
server.close((error) => error == null ? resolve() : reject(error));
|
|
2127
|
+
server.closeAllConnections();
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
function isTimeoutFetchError(error, url) {
|
|
2132
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError, String(error));
|
|
2133
|
+
(0, node_assert.deepStrictEqual)(error.url.href, url);
|
|
2134
|
+
(0, node_assert.deepStrictEqual)(error.response, void 0);
|
|
2135
|
+
(0, node_assert.ok)(error.cause instanceof DOMException);
|
|
2136
|
+
(0, node_assert.deepStrictEqual)(error.cause.name, "TimeoutError");
|
|
2137
|
+
return true;
|
|
2138
|
+
}
|
|
2139
|
+
(0, _fedify_fixture.test)("resolveDocumentLoaderTimeout() validates timeouts", () => {
|
|
2140
|
+
(0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(void 0), 1e4);
|
|
2141
|
+
(0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(null), null);
|
|
2142
|
+
(0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(1500), 1500);
|
|
2143
|
+
(0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(.5), 1);
|
|
2144
|
+
(0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(2147483647), 2147483647);
|
|
2145
|
+
for (const invalid of [
|
|
2146
|
+
0,
|
|
2147
|
+
-1,
|
|
2148
|
+
NaN,
|
|
2149
|
+
Infinity,
|
|
2150
|
+
2147483648
|
|
2151
|
+
]) (0, node_assert.throws)(() => require_docloader.resolveDocumentLoaderTimeout(invalid), RangeError);
|
|
2152
|
+
(0, node_assert.throws)(() => require_docloader.getDocumentLoader({ timeout: 0 }), RangeError);
|
|
2153
|
+
});
|
|
2154
|
+
(0, _fedify_fixture.test)("getDocumentLoader() times out a request without a response", async () => {
|
|
2155
|
+
await withServer(() => {}, async (baseUrl) => {
|
|
2156
|
+
const url = `${baseUrl}/hang`;
|
|
2157
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
|
|
2158
|
+
allowPrivateAddress: true,
|
|
2159
|
+
timeout: 100
|
|
2160
|
+
})(url), (e) => isTimeoutFetchError(e, url));
|
|
2161
|
+
});
|
|
2162
|
+
});
|
|
2163
|
+
(0, _fedify_fixture.test)("getDocumentLoader() times out a stalled response body", async () => {
|
|
2164
|
+
for (const status of [200, 404]) await withServer((_, response) => {
|
|
2165
|
+
response.writeHead(status, { "Content-Type": "application/activity+json" });
|
|
2166
|
+
response.write("{\"id\":");
|
|
2167
|
+
}, async (baseUrl) => {
|
|
2168
|
+
const url = `${baseUrl}/stalled`;
|
|
2169
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
|
|
2170
|
+
allowPrivateAddress: true,
|
|
2171
|
+
timeout: 200
|
|
2172
|
+
})(url), (e) => isTimeoutFetchError(e, url));
|
|
2173
|
+
});
|
|
2174
|
+
});
|
|
2175
|
+
(0, _fedify_fixture.test)("getDocumentLoader() shares the timeout across redirects", async () => {
|
|
2176
|
+
let requests = 0;
|
|
2177
|
+
await withServer((request, response) => {
|
|
2178
|
+
requests++;
|
|
2179
|
+
const index = Number(request.url?.split("/").at(-1));
|
|
2180
|
+
setTimeout(() => {
|
|
2181
|
+
if (index >= 15) {
|
|
2182
|
+
response.writeHead(200, { "Content-Type": "application/activity+json" });
|
|
2183
|
+
response.end("{\"done\":true}");
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
response.writeHead(302, { Location: `/redirect/${index + 1}` });
|
|
2187
|
+
response.end();
|
|
2188
|
+
}, 50);
|
|
2189
|
+
}, async (baseUrl) => {
|
|
2190
|
+
const url = `${baseUrl}/redirect/0`;
|
|
2191
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
|
|
2192
|
+
allowPrivateAddress: true,
|
|
2193
|
+
timeout: 300
|
|
2194
|
+
})(url), (e) => isTimeoutFetchError(e, url));
|
|
2195
|
+
(0, node_assert.ok)(requests < 16, `requests: ${requests}`);
|
|
2196
|
+
(0, node_assert.deepStrictEqual)((await require_docloader.getDocumentLoader({
|
|
2197
|
+
allowPrivateAddress: true,
|
|
2198
|
+
timeout: null
|
|
2199
|
+
})(url)).document, { done: true });
|
|
2200
|
+
});
|
|
2201
|
+
});
|
|
2202
|
+
(0, _fedify_fixture.test)("getDocumentLoader() shares the timeout across alternates", async (t) => {
|
|
2203
|
+
for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
|
|
2204
|
+
await withServer((request, response) => {
|
|
2205
|
+
const index = Number(request.url?.split("/").at(-1));
|
|
2206
|
+
setTimeout(() => {
|
|
2207
|
+
if (index >= 15) {
|
|
2208
|
+
response.writeHead(200, { "Content-Type": "application/activity+json" });
|
|
2209
|
+
response.end("{\"done\":true}");
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
const next = `/alternate/${index + 1}`;
|
|
2213
|
+
response.writeHead(200, html ? { "Content-Type": "text/html" } : {
|
|
2214
|
+
"Content-Type": "text/plain",
|
|
2215
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
2216
|
+
});
|
|
2217
|
+
response.end(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON");
|
|
2218
|
+
}, 50);
|
|
2219
|
+
}, async (baseUrl) => {
|
|
2220
|
+
const url = `${baseUrl}/alternate/0`;
|
|
2221
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
|
|
2222
|
+
allowPrivateAddress: true,
|
|
2223
|
+
timeout: 300
|
|
2224
|
+
})(url), (e) => isTimeoutFetchError(e, url));
|
|
2225
|
+
});
|
|
2226
|
+
});
|
|
2227
|
+
});
|
|
2228
|
+
(0, _fedify_fixture.test)("getDocumentLoader() lets the caller's signal abort a request", async () => {
|
|
2229
|
+
await withServer(() => {}, async (baseUrl) => {
|
|
2230
|
+
const url = `${baseUrl}/hang`;
|
|
2231
|
+
for (const timeout of [
|
|
2232
|
+
void 0,
|
|
2233
|
+
6e4,
|
|
2234
|
+
null
|
|
2235
|
+
]) {
|
|
2236
|
+
const loader = require_docloader.getDocumentLoader({
|
|
2237
|
+
allowPrivateAddress: true,
|
|
2238
|
+
timeout
|
|
2239
|
+
});
|
|
2240
|
+
const controller = new AbortController();
|
|
2241
|
+
const reason = /* @__PURE__ */ new Error("Canceled by the caller");
|
|
2242
|
+
setTimeout(() => controller.abort(reason), 50);
|
|
2243
|
+
await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), (e) => {
|
|
2244
|
+
(0, node_assert.deepStrictEqual)(e, reason);
|
|
2245
|
+
return true;
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
const loader = require_docloader.getDocumentLoader({
|
|
2249
|
+
allowPrivateAddress: true,
|
|
2250
|
+
timeout: 50
|
|
2251
|
+
});
|
|
2252
|
+
const controller = new AbortController();
|
|
2253
|
+
const reason = /* @__PURE__ */ new Error("Canceled by the caller");
|
|
2254
|
+
controller.abort(reason);
|
|
2255
|
+
await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), (e) => {
|
|
2256
|
+
(0, node_assert.deepStrictEqual)(e, reason);
|
|
2257
|
+
return true;
|
|
2258
|
+
});
|
|
2259
|
+
});
|
|
2260
|
+
});
|
|
2261
|
+
(0, _fedify_fixture.test)("getDocumentLoader() bounds DNS lookups that ignore the timeout", async () => {
|
|
2262
|
+
if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
|
|
2263
|
+
esm_default.mockGlobal();
|
|
2264
|
+
let requests = 0;
|
|
2265
|
+
const url = "https://slow-dns.example/object";
|
|
2266
|
+
esm_default.get(url, () => {
|
|
2267
|
+
requests++;
|
|
2268
|
+
return Response.json({ id: url });
|
|
2269
|
+
});
|
|
2270
|
+
const originalLookup = node_dns_promises.default.lookup;
|
|
2271
|
+
let resolveLookup = () => {};
|
|
2272
|
+
const lookedUp = new Promise((resolve) => resolveLookup = resolve);
|
|
2273
|
+
node_dns_promises.default.lookup = (() => new Promise((resolve) => setTimeout(() => {
|
|
2274
|
+
resolve([{
|
|
2275
|
+
address: "93.184.215.14",
|
|
2276
|
+
family: 4
|
|
2277
|
+
}]);
|
|
2278
|
+
resolveLookup();
|
|
2279
|
+
}, 200)));
|
|
2280
|
+
try {
|
|
2281
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ timeout: 50 })(url), (e) => isTimeoutFetchError(e, url));
|
|
2282
|
+
await lookedUp;
|
|
2283
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2284
|
+
(0, node_assert.deepStrictEqual)(requests, 0);
|
|
2285
|
+
} finally {
|
|
2286
|
+
node_dns_promises.default.lookup = originalLookup;
|
|
2287
|
+
esm_default.hardReset();
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
(0, _fedify_fixture.test)("withDocumentLoaderTimeout() cleans up after each call", async () => {
|
|
2291
|
+
const signals = [];
|
|
2292
|
+
const loader = require_docloader.withDocumentLoaderTimeout((url, options) => {
|
|
2293
|
+
signals.push(options.signal);
|
|
2294
|
+
return Promise.resolve({
|
|
2295
|
+
contextUrl: null,
|
|
2296
|
+
document: {},
|
|
2297
|
+
documentUrl: url
|
|
2298
|
+
});
|
|
2299
|
+
}, 50);
|
|
2300
|
+
(0, node_assert.deepStrictEqual)((await Promise.all([loader("https://example.com/a"), loader("https://example.com/b")])).map((r) => r.documentUrl), ["https://example.com/a", "https://example.com/b"]);
|
|
2301
|
+
(0, node_assert.ok)(signals[0] !== signals[1]);
|
|
2302
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2303
|
+
(0, node_assert.ok)(signals.every((signal) => !signal.aborted));
|
|
2304
|
+
const inner = () => Promise.reject(/* @__PURE__ */ new Error("unused"));
|
|
2305
|
+
(0, node_assert.ok)(require_docloader.withDocumentLoaderTimeout(inner, null) === inner);
|
|
2306
|
+
});
|
|
2307
|
+
(0, _fedify_fixture.test)("getRemoteDocument() keeps error bodies byte for byte", async () => {
|
|
2308
|
+
const bytes = new Uint8Array([
|
|
2309
|
+
239,
|
|
2310
|
+
187,
|
|
2311
|
+
191,
|
|
2312
|
+
78,
|
|
2313
|
+
111,
|
|
2314
|
+
255,
|
|
2315
|
+
254
|
|
2316
|
+
]);
|
|
2317
|
+
const url = "https://example.com/error";
|
|
2318
|
+
let error;
|
|
2319
|
+
await (0, node_assert.rejects)(require_docloader.getRemoteDocument(url, new Response(bytes, { status: 404 }), () => Promise.reject(/* @__PURE__ */ new Error("unused"))), (e) => {
|
|
2320
|
+
error = e;
|
|
2321
|
+
return true;
|
|
2322
|
+
});
|
|
2323
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
2324
|
+
(0, node_assert.ok)(error.response != null);
|
|
2325
|
+
(0, node_assert.deepStrictEqual)(error.response.status, 404);
|
|
2326
|
+
(0, node_assert.deepStrictEqual)(new Uint8Array(await error.response.arrayBuffer()), bytes);
|
|
2327
|
+
});
|
|
2328
|
+
(0, _fedify_fixture.test)("getRemoteDocument() keeps metadata of bodiless or large errors", async () => {
|
|
2329
|
+
const url = "https://example.com/error";
|
|
2330
|
+
for (const response of [new Response(null, {
|
|
2331
|
+
status: 304,
|
|
2332
|
+
headers: { ETag: "\"a\"" }
|
|
2333
|
+
}), new Response(/* @__PURE__ */ new Uint8Array(1048577), {
|
|
2334
|
+
status: 500,
|
|
2335
|
+
headers: { ETag: "\"a\"" }
|
|
2336
|
+
})]) await (0, node_assert.rejects)(require_docloader.getRemoteDocument(url, response, () => Promise.reject(/* @__PURE__ */ new Error("unused"))), (e) => {
|
|
2337
|
+
(0, node_assert.ok)(e instanceof require_request.FetchError);
|
|
2338
|
+
(0, node_assert.ok)(e.response != null);
|
|
2339
|
+
(0, node_assert.deepStrictEqual)(e.response.status, response.status);
|
|
2340
|
+
(0, node_assert.deepStrictEqual)(e.response.headers.get("ETag"), "\"a\"");
|
|
2341
|
+
(0, node_assert.deepStrictEqual)(e.response.body, null);
|
|
2342
|
+
return true;
|
|
2343
|
+
});
|
|
2344
|
+
});
|
|
2345
|
+
(0, _fedify_fixture.test)("getDocumentLoader() reports statuses that Response cannot hold", async () => {
|
|
2346
|
+
await withServer((_, response) => {
|
|
2347
|
+
response.writeHead(999);
|
|
2348
|
+
response.end("Request denied");
|
|
2349
|
+
}, async (baseUrl) => {
|
|
2350
|
+
const url = `${baseUrl}/denied`;
|
|
2351
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
2352
|
+
let error;
|
|
2353
|
+
await (0, node_assert.rejects)(loader(url), (e) => {
|
|
2354
|
+
error = e;
|
|
2355
|
+
return true;
|
|
2356
|
+
});
|
|
2357
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError, String(error));
|
|
2358
|
+
(0, node_assert.deepStrictEqual)(error.response?.status, 999);
|
|
2359
|
+
(0, node_assert.deepStrictEqual)(await error.response.text(), "Request denied");
|
|
2360
|
+
});
|
|
2361
|
+
});
|
|
2362
|
+
(0, _fedify_fixture.test)("getDocumentLoader() times out a stalled body of such a status", async () => {
|
|
2363
|
+
await withServer((_, response) => {
|
|
2364
|
+
response.writeHead(999);
|
|
2365
|
+
response.write("Request");
|
|
2366
|
+
}, async (baseUrl) => {
|
|
2367
|
+
const url = `${baseUrl}/stalled-denied`;
|
|
2368
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
|
|
2369
|
+
allowPrivateAddress: true,
|
|
2370
|
+
timeout: 200
|
|
2371
|
+
})(url), (e) => isTimeoutFetchError(e, url));
|
|
2372
|
+
});
|
|
2373
|
+
});
|
|
1597
2374
|
//#endregion
|