@fedify/vocab-runtime 2.4.0-pr.936.41 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +4 -2
- package/dist/{tests/docloader-Ck8bcBir.mjs → contexts-BK1CqBR5.cjs} +164 -275
- package/dist/{tests/docloader-S_gQZ2tt.cjs → contexts-DPuJ4UYL.js} +160 -294
- package/dist/{docloader-C_dir7Xb.d.ts → docloader-CYwqh5Df.d.cts} +66 -2
- package/dist/{docloader-D2DTRiyA.d.cts → docloader-CYwqh5Df.d.ts} +66 -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 +711 -4527
- package/dist/mod.d.cts +400 -8
- package/dist/mod.d.ts +400 -9
- package/dist/mod.js +692 -4522
- package/dist/portable-DqtfLy_1.d.cts +160 -0
- package/dist/portable-DtWsu2yU.d.ts +160 -0
- package/dist/tests/body-CYiu9CO-.mjs +130 -0
- package/dist/tests/body-CkEwROsn.cjs +153 -0
- package/dist/tests/body.test.cjs +89 -0
- package/dist/tests/body.test.d.cts +1 -0
- package/dist/tests/body.test.d.mts +1 -0
- package/dist/tests/body.test.mjs +90 -0
- package/dist/tests/contexts-CIKsin4e.mjs +4512 -0
- package/dist/tests/contexts-DizzBjz4.cjs +4523 -0
- package/dist/tests/decimal.test.cjs +8 -7
- package/dist/tests/decimal.test.mjs +8 -6
- package/dist/tests/digest-3FeH2Y-Q.cjs +176 -0
- package/dist/tests/digest-COC7xDiQ.mjs +141 -0
- package/dist/tests/digest.test.cjs +102 -0
- package/dist/tests/digest.test.d.cts +1 -0
- package/dist/tests/digest.test.d.mts +1 -0
- package/dist/tests/digest.test.mjs +103 -0
- package/dist/tests/docloader-C3YFl6y4.cjs +396 -0
- package/dist/tests/docloader-CvmS4sVq.mjs +373 -0
- package/dist/tests/docloader.test.cjs +692 -39
- package/dist/tests/docloader.test.mjs +686 -34
- package/dist/tests/internal/multicodec.test.cjs +2 -3
- package/dist/tests/internal/multicodec.test.mjs +2 -2
- package/dist/tests/internal/portable-dereference.test.cjs +172 -0
- package/dist/tests/internal/portable-dereference.test.d.cts +1 -0
- package/dist/tests/internal/portable-dereference.test.d.mts +1 -0
- package/dist/tests/internal/portable-dereference.test.mjs +173 -0
- package/dist/tests/jsonld-cache-BPQmOZWD.mjs +342 -0
- package/dist/tests/jsonld-cache-C07AyNOY.cjs +397 -0
- package/dist/tests/jsonld-cache.test.cjs +101 -298
- package/dist/tests/jsonld-cache.test.mjs +94 -289
- package/dist/tests/{key-_wXwomh_.cjs → key-C-AYkdJJ.cjs} +11 -4
- package/dist/tests/{key-CDGDH_vC.mjs → key-C2Db_TAJ.mjs} +11 -3
- package/dist/tests/key.test.cjs +6 -5
- package/dist/tests/key.test.mjs +6 -4
- package/dist/tests/langstr.test.cjs +4 -4
- package/dist/tests/langstr.test.mjs +2 -2
- package/dist/tests/link.test.cjs +2 -3
- package/dist/tests/link.test.mjs +2 -2
- package/dist/tests/multibase/multibase.test.cjs +8 -9
- package/dist/tests/multibase/multibase.test.mjs +6 -6
- package/dist/tests/{multibase-Bz_UUDtL.cjs → multibase-B5Mea7Ip.cjs} +19 -2
- package/dist/tests/{multibase-B4bvakyA.mjs → multibase-BPnF_L4e.mjs} +12 -1
- package/dist/tests/portable-dereference-BNXtgg5U.cjs +278 -0
- package/dist/tests/portable-dereference-DRE5bz-l.mjs +249 -0
- package/dist/tests/portable-media-BLKllU56.cjs +153 -0
- package/dist/tests/portable-media-DRSGBORB.mjs +148 -0
- package/dist/tests/portable-media.test.cjs +222 -0
- package/dist/tests/portable-media.test.d.cts +1 -0
- package/dist/tests/portable-media.test.d.mts +1 -0
- package/dist/tests/portable-media.test.mjs +223 -0
- package/dist/tests/portable-workers.test.cjs +36 -0
- package/dist/tests/portable-workers.test.d.cts +2 -0
- package/dist/tests/portable-workers.test.d.mts +2 -0
- package/dist/tests/portable-workers.test.mjs +35 -0
- package/dist/tests/{request-uk51rkhO.cjs → request-8vPWtV1-.cjs} +10 -4
- package/dist/tests/{request-C8CaGwtt.mjs → request-DRaOaTqD.mjs} +8 -2
- package/dist/tests/request.test.cjs +7 -4
- package/dist/tests/request.test.mjs +5 -2
- package/dist/tests/signed-representation.test.cjs +588 -0
- package/dist/tests/signed-representation.test.d.cts +1 -0
- package/dist/tests/signed-representation.test.d.mts +1 -0
- package/dist/tests/signed-representation.test.mjs +589 -0
- package/dist/tests/temporal.test.cjs +1 -2
- package/dist/tests/temporal.test.mjs +1 -1
- package/dist/tests/url-BNakuZ8k.cjs +998 -0
- package/dist/tests/url-DMxmp7ZG.mjs +859 -0
- package/dist/tests/url.test.cjs +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/miscellany.json +17 -0
- package/src/contexts.ts +39 -1
- package/src/digest.test.ts +220 -0
- package/src/digest.ts +229 -0
- package/src/docloader.test.ts +918 -27
- package/src/docloader.ts +320 -105
- package/src/internal/jsonld-cache.ts +97 -1
- package/src/internal/portable-dereference.test.ts +254 -0
- package/src/internal/portable-dereference.ts +1000 -0
- package/src/internal/signed-representation.ts +565 -0
- package/src/jsonld-cache.test.ts +89 -0
- package/src/key.test.ts +9 -0
- package/src/key.ts +11 -1
- package/src/mod.ts +28 -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/{langstr-CbAxaeEZ.cjs → langstr-C4Fl80ae.cjs} +0 -0
- /package/dist/tests/{langstr-Di5AvKpB.mjs → langstr-CQ26J_L7.mjs} +0 -0
- /package/dist/tests/{link-NUUWCdnK.mjs → link-Cevmc87v.mjs} +0 -0
- /package/dist/tests/{link-FguCydMA.cjs → link-DlKm8bEr.cjs} +0 -0
- /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-CLRPeW4N.cjs} +0 -0
- /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-CRIj_05H.mjs} +0 -0
- /package/dist/tests/{chunk-C2EiDwsr.cjs → rolldown-runtime-emK7D4bc.cjs} +0 -0
|
@@ -1,11 +1,17 @@
|
|
|
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-8vPWtV1-.cjs");
|
|
3
|
+
const require_contexts = require("./contexts-DizzBjz4.cjs");
|
|
4
|
+
const require_url = require("./url-BNakuZ8k.cjs");
|
|
5
|
+
const require_docloader = require("./docloader-C3YFl6y4.cjs");
|
|
6
|
+
let node_zlib = require("node:zlib");
|
|
7
|
+
let _logtape_logtape = require("@logtape/logtape");
|
|
5
8
|
let node_assert = require("node:assert");
|
|
6
|
-
let
|
|
9
|
+
let node_dns_promises = require("node:dns/promises");
|
|
10
|
+
node_dns_promises = require_rolldown_runtime.__toESM(node_dns_promises, 1);
|
|
11
|
+
let _fedify_fixture = require("@fedify/fixture");
|
|
12
|
+
let node_http = require("node:http");
|
|
7
13
|
//#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
|
|
8
|
-
var require_glob_to_regexp = /* @__PURE__ */
|
|
14
|
+
var require_glob_to_regexp = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
|
|
9
15
|
module.exports = function(glob, opts) {
|
|
10
16
|
if (typeof glob !== "string") throw new TypeError("Expected a string");
|
|
11
17
|
var str = String(glob);
|
|
@@ -79,7 +85,7 @@ var require_glob_to_regexp = /* @__PURE__ */ require_chunk.__commonJSMin(((expor
|
|
|
79
85
|
}));
|
|
80
86
|
//#endregion
|
|
81
87
|
//#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
|
|
82
|
-
var import_glob_to_regexp = /* @__PURE__ */
|
|
88
|
+
var import_glob_to_regexp = /* @__PURE__ */ require_rolldown_runtime.__toESM(require_glob_to_regexp(), 1);
|
|
83
89
|
/**
|
|
84
90
|
* @param {string|RegExp} input The route pattern
|
|
85
91
|
* @param {boolean} [loose] Allow open-ended matching. Ignored with `RegExp` input.
|
|
@@ -111,27 +117,27 @@ function parse(input, loose) {
|
|
|
111
117
|
}
|
|
112
118
|
//#endregion
|
|
113
119
|
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/TypeDescriptor.js
|
|
114
|
-
const valueTypes = new Set([
|
|
120
|
+
const valueTypes = /* @__PURE__ */ new Set([
|
|
115
121
|
"boolean",
|
|
116
122
|
"number",
|
|
117
123
|
"null",
|
|
118
124
|
"string",
|
|
119
125
|
"undefined"
|
|
120
126
|
]);
|
|
121
|
-
const referenceTypes = new Set([
|
|
127
|
+
const referenceTypes = /* @__PURE__ */ new Set([
|
|
122
128
|
"array",
|
|
123
129
|
"function",
|
|
124
130
|
"object",
|
|
125
131
|
"symbol"
|
|
126
132
|
]);
|
|
127
|
-
const detectableTypes = new Set([
|
|
133
|
+
const detectableTypes = /* @__PURE__ */ new Set([
|
|
128
134
|
"boolean",
|
|
129
135
|
"function",
|
|
130
136
|
"number",
|
|
131
137
|
"string",
|
|
132
138
|
"symbol"
|
|
133
139
|
]);
|
|
134
|
-
const typeConstructors = new Set([
|
|
140
|
+
const typeConstructors = /* @__PURE__ */ new Set([
|
|
135
141
|
Boolean,
|
|
136
142
|
Number,
|
|
137
143
|
String
|
|
@@ -202,7 +208,7 @@ var TypeDescriptor = class TypeDescriptor {
|
|
|
202
208
|
};
|
|
203
209
|
//#endregion
|
|
204
210
|
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/IsSubsetOf.js
|
|
205
|
-
const allowedTypes = new Set([
|
|
211
|
+
const allowedTypes = /* @__PURE__ */ new Set([
|
|
206
212
|
"array",
|
|
207
213
|
"object",
|
|
208
214
|
"function",
|
|
@@ -694,7 +700,14 @@ var __classPrivateFieldGet = function(receiver, state, kind, f) {
|
|
|
694
700
|
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
701
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
696
702
|
};
|
|
697
|
-
var _Route_instances
|
|
703
|
+
var _Route_instances;
|
|
704
|
+
var _a;
|
|
705
|
+
var _Route_responseSubscriptions;
|
|
706
|
+
var _Route_validate;
|
|
707
|
+
var _Route_sanitize;
|
|
708
|
+
var _Route_generateMatcher;
|
|
709
|
+
var _Route_limit;
|
|
710
|
+
var _Route_delayResponse;
|
|
698
711
|
var RouteConfigWrapper = class {
|
|
699
712
|
constructor(config) {
|
|
700
713
|
Object.assign(this, config);
|
|
@@ -1206,7 +1219,53 @@ var esm_default = new class FetchMock {
|
|
|
1206
1219
|
}({ ...defaultFetchMockConfig });
|
|
1207
1220
|
//#endregion
|
|
1208
1221
|
//#region src/docloader.test.ts
|
|
1209
|
-
(0,
|
|
1222
|
+
(0, _fedify_fixture.test)("getRemoteDocument() forwards options to alternate documents", async () => {
|
|
1223
|
+
const records = [];
|
|
1224
|
+
await (0, _logtape_logtape.reset)();
|
|
1225
|
+
await (0, _logtape_logtape.configure)({
|
|
1226
|
+
sinks: { capture: (record) => records.push(record) },
|
|
1227
|
+
loggers: [{
|
|
1228
|
+
category: ["fedify"],
|
|
1229
|
+
lowestLevel: "debug",
|
|
1230
|
+
sinks: ["capture"]
|
|
1231
|
+
}]
|
|
1232
|
+
});
|
|
1233
|
+
try {
|
|
1234
|
+
for (const linkHeader of [true, false]) for (const suppressError of [
|
|
1235
|
+
true,
|
|
1236
|
+
false,
|
|
1237
|
+
void 0
|
|
1238
|
+
]) {
|
|
1239
|
+
records.length = 0;
|
|
1240
|
+
const url = "https://example.com/alternate-source";
|
|
1241
|
+
const alternateUrl = "https://example.com/missing-alternate";
|
|
1242
|
+
const options = {
|
|
1243
|
+
suppressError,
|
|
1244
|
+
signal: new AbortController().signal
|
|
1245
|
+
};
|
|
1246
|
+
const response = new Response(`<link rel="alternate" type="application/activity+json" href="${alternateUrl}">`, { headers: {
|
|
1247
|
+
"Content-Type": "text/html",
|
|
1248
|
+
...linkHeader ? { Link: `<${alternateUrl}>; rel="alternate"; type="application/activity+json"` } : {}
|
|
1249
|
+
} });
|
|
1250
|
+
let alternateRequests = 0;
|
|
1251
|
+
await (0, node_assert.rejects)(() => require_docloader.getRemoteDocument(url, response, async (requestedUrl, forwarded) => {
|
|
1252
|
+
alternateRequests++;
|
|
1253
|
+
(0, node_assert.deepStrictEqual)(requestedUrl, alternateUrl);
|
|
1254
|
+
(0, node_assert.deepStrictEqual)(forwarded, options);
|
|
1255
|
+
return await require_docloader.getRemoteDocument(requestedUrl, new Response(null, { status: 404 }), () => {
|
|
1256
|
+
throw new Error("Unexpected additional alternate");
|
|
1257
|
+
}, forwarded);
|
|
1258
|
+
}, options), require_request.FetchError);
|
|
1259
|
+
(0, node_assert.deepStrictEqual)(alternateRequests, 1);
|
|
1260
|
+
const failures = records.filter((record) => record.rawMessage === "Failed to fetch document: {status} {url} {headers}");
|
|
1261
|
+
(0, node_assert.deepStrictEqual)(failures.length, 1);
|
|
1262
|
+
(0, node_assert.deepStrictEqual)(failures[0].level, suppressError ? "warning" : "error");
|
|
1263
|
+
}
|
|
1264
|
+
} finally {
|
|
1265
|
+
await (0, _logtape_logtape.reset)();
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
(0, _fedify_fixture.test)("new FetchError()", () => {
|
|
1210
1269
|
const e = new require_request.FetchError("https://example.com/", "An error message.");
|
|
1211
1270
|
(0, node_assert.deepStrictEqual)(e.name, "FetchError");
|
|
1212
1271
|
(0, node_assert.deepStrictEqual)(e.url, new URL("https://example.com/"));
|
|
@@ -1219,7 +1278,7 @@ var esm_default = new class FetchMock {
|
|
|
1219
1278
|
(0, node_assert.ok)(e2.response != null);
|
|
1220
1279
|
(0, node_assert.deepStrictEqual)(e2.response.status, 410);
|
|
1221
1280
|
});
|
|
1222
|
-
(0,
|
|
1281
|
+
(0, _fedify_fixture.test)("getDocumentLoader()", async (t) => {
|
|
1223
1282
|
const fetchDocumentLoader = require_docloader.getDocumentLoader();
|
|
1224
1283
|
esm_default.spyGlobal();
|
|
1225
1284
|
esm_default.get("https://example.com/object", { body: {
|
|
@@ -1228,7 +1287,7 @@ var esm_default = new class FetchMock {
|
|
|
1228
1287
|
name: "Fetched object",
|
|
1229
1288
|
type: "Object"
|
|
1230
1289
|
} });
|
|
1231
|
-
await t.
|
|
1290
|
+
await t.step("ok", async () => {
|
|
1232
1291
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/object"), {
|
|
1233
1292
|
contextUrl: null,
|
|
1234
1293
|
documentUrl: "https://example.com/object",
|
|
@@ -1271,7 +1330,7 @@ var esm_default = new class FetchMock {
|
|
|
1271
1330
|
Link: "<https://example.com/object>; rel=\"alternate\"; type=\"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\""
|
|
1272
1331
|
}
|
|
1273
1332
|
});
|
|
1274
|
-
await t.
|
|
1333
|
+
await t.step("Link header", async () => {
|
|
1275
1334
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/link-ctx"), {
|
|
1276
1335
|
contextUrl: "https://www.w3.org/ns/activitystreams",
|
|
1277
1336
|
documentUrl: "https://example.com/link-ctx",
|
|
@@ -1292,7 +1351,7 @@ var esm_default = new class FetchMock {
|
|
|
1292
1351
|
}
|
|
1293
1352
|
});
|
|
1294
1353
|
});
|
|
1295
|
-
await t.
|
|
1354
|
+
await t.step("Link header relative url", async () => {
|
|
1296
1355
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/link-ctx"), {
|
|
1297
1356
|
contextUrl: "https://www.w3.org/ns/activitystreams",
|
|
1298
1357
|
documentUrl: "https://example.com/link-ctx",
|
|
@@ -1313,7 +1372,7 @@ var esm_default = new class FetchMock {
|
|
|
1313
1372
|
}
|
|
1314
1373
|
});
|
|
1315
1374
|
});
|
|
1316
|
-
await t.
|
|
1375
|
+
await t.step("wrong Link header syntax", async () => {
|
|
1317
1376
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/obj-w-wrong-link"), {
|
|
1318
1377
|
contextUrl: null,
|
|
1319
1378
|
documentUrl: "https://example.com/obj-w-wrong-link",
|
|
@@ -1337,7 +1396,7 @@ var esm_default = new class FetchMock {
|
|
|
1337
1396
|
</html>`,
|
|
1338
1397
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1339
1398
|
});
|
|
1340
|
-
await t.
|
|
1399
|
+
await t.step("HTML <link>", async () => {
|
|
1341
1400
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/html-link"), {
|
|
1342
1401
|
contextUrl: null,
|
|
1343
1402
|
documentUrl: "https://example.com/object",
|
|
@@ -1361,7 +1420,7 @@ var esm_default = new class FetchMock {
|
|
|
1361
1420
|
</html>`,
|
|
1362
1421
|
headers: { "Content-Type": "application/xhtml+xml; charset=utf-8" }
|
|
1363
1422
|
});
|
|
1364
|
-
await t.
|
|
1423
|
+
await t.step("XHTML <link>", async () => {
|
|
1365
1424
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/xhtml-link"), {
|
|
1366
1425
|
contextUrl: null,
|
|
1367
1426
|
documentUrl: "https://example.com/object",
|
|
@@ -1387,7 +1446,7 @@ var esm_default = new class FetchMock {
|
|
|
1387
1446
|
</html>`,
|
|
1388
1447
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1389
1448
|
});
|
|
1390
|
-
await t.
|
|
1449
|
+
await t.step("HTML <a>", async () => {
|
|
1391
1450
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/html-a"), {
|
|
1392
1451
|
contextUrl: null,
|
|
1393
1452
|
documentUrl: "https://example.com/object",
|
|
@@ -1409,7 +1468,7 @@ var esm_default = new class FetchMock {
|
|
|
1409
1468
|
</html>`,
|
|
1410
1469
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1411
1470
|
});
|
|
1412
|
-
await t.
|
|
1471
|
+
await t.step("HTML without ActivityPub alternate link", async () => {
|
|
1413
1472
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/html-no-alternate"), (error) => {
|
|
1414
1473
|
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1415
1474
|
(0, node_assert.ok)(error.message.includes("HTML document has no ActivityPub alternate link"));
|
|
@@ -1429,7 +1488,7 @@ var esm_default = new class FetchMock {
|
|
|
1429
1488
|
},
|
|
1430
1489
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1431
1490
|
});
|
|
1432
|
-
await t.
|
|
1491
|
+
await t.step("wrong Content-Type with JSON body", async () => {
|
|
1433
1492
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/wrong-content-type"), {
|
|
1434
1493
|
contextUrl: null,
|
|
1435
1494
|
documentUrl: "https://example.com/wrong-content-type",
|
|
@@ -1448,7 +1507,7 @@ var esm_default = new class FetchMock {
|
|
|
1448
1507
|
"Content-Type": "text/html; charset=utf-8"
|
|
1449
1508
|
}
|
|
1450
1509
|
});
|
|
1451
|
-
await t.
|
|
1510
|
+
await t.step("HTML Content-Length over limit", async () => {
|
|
1452
1511
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/large-html"), (error) => {
|
|
1453
1512
|
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1454
1513
|
(0, node_assert.ok)(error.message.includes("HTML document is too large to scan for an ActivityPub alternate link"));
|
|
@@ -1458,32 +1517,88 @@ var esm_default = new class FetchMock {
|
|
|
1458
1517
|
return true;
|
|
1459
1518
|
});
|
|
1460
1519
|
});
|
|
1461
|
-
await t.
|
|
1520
|
+
await t.step("HTML Content-Length over limit cancels body", async () => {
|
|
1462
1521
|
let canceled = false;
|
|
1463
1522
|
const response = new Response("<!DOCTYPE html>", { headers: {
|
|
1464
1523
|
"Content-Length": String(1048577),
|
|
1465
1524
|
"Content-Type": "text/html; charset=utf-8"
|
|
1466
1525
|
} });
|
|
1467
|
-
Object.defineProperty(response, "body", { value: { cancel
|
|
1526
|
+
Object.defineProperty(response, "body", { value: new ReadableStream({ cancel() {
|
|
1468
1527
|
canceled = true;
|
|
1469
|
-
} } });
|
|
1528
|
+
} }) });
|
|
1470
1529
|
await (0, node_assert.rejects)(() => require_docloader.getRemoteDocument("https://example.com/large-html-cancel", response, () => {
|
|
1471
1530
|
throw new Error("unexpected alternate fetch");
|
|
1472
1531
|
}), require_request.FetchError);
|
|
1473
1532
|
(0, node_assert.deepStrictEqual)(canceled, true);
|
|
1474
1533
|
});
|
|
1475
1534
|
esm_default.get("https://example.com/404", { status: 404 });
|
|
1476
|
-
await t.
|
|
1535
|
+
await t.step("not ok", async () => {
|
|
1477
1536
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/404"), require_request.FetchError, "HTTP 404: https://example.com/404");
|
|
1478
1537
|
});
|
|
1479
|
-
await t.
|
|
1480
|
-
for (const [url, document] of Object.entries(
|
|
1538
|
+
await t.step("preloaded contexts", async () => {
|
|
1539
|
+
for (const [url, document] of Object.entries(require_contexts.preloadedContexts)) (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1481
1540
|
contextUrl: null,
|
|
1482
1541
|
documentUrl: url,
|
|
1483
1542
|
document
|
|
1484
1543
|
});
|
|
1485
1544
|
});
|
|
1486
|
-
|
|
1545
|
+
esm_default.get("https://www.w3.org/ns/cid/v1", { status: 503 });
|
|
1546
|
+
await t.step("preloaded CID v1 context", async () => {
|
|
1547
|
+
const url = "https://www.w3.org/ns/cid/v1";
|
|
1548
|
+
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1549
|
+
contextUrl: null,
|
|
1550
|
+
documentUrl: url,
|
|
1551
|
+
document: require_contexts.cid_v1_default
|
|
1552
|
+
});
|
|
1553
|
+
});
|
|
1554
|
+
await t.step("preloaded FEP-ef61 context", async () => {
|
|
1555
|
+
const url = "https://w3id.org/fep/ef61";
|
|
1556
|
+
(0, node_assert.ok)(url in require_contexts.preloadedContexts);
|
|
1557
|
+
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1558
|
+
contextUrl: null,
|
|
1559
|
+
documentUrl: url,
|
|
1560
|
+
document: { "@context": {
|
|
1561
|
+
gateways: {
|
|
1562
|
+
"@id": "https://w3id.org/fep/ef61/gateways",
|
|
1563
|
+
"@type": "@id",
|
|
1564
|
+
"@container": "@list"
|
|
1565
|
+
},
|
|
1566
|
+
digestMultibase: "https://www.w3.org/ns/credentials/v2#digestMultibase"
|
|
1567
|
+
} }
|
|
1568
|
+
});
|
|
1569
|
+
});
|
|
1570
|
+
esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
|
|
1571
|
+
await t.step("preloaded FEP-7aa9 context", async () => {
|
|
1572
|
+
const url = "https://w3id.org/fep/7aa9";
|
|
1573
|
+
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
|
|
1574
|
+
contextUrl: null,
|
|
1575
|
+
documentUrl: url,
|
|
1576
|
+
document: { "@context": {
|
|
1577
|
+
"FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
|
|
1578
|
+
"FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
|
|
1579
|
+
"FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
|
|
1580
|
+
"FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
|
|
1581
|
+
"topic": {
|
|
1582
|
+
"@id": "https://w3id.org/fep/7aa9#topic",
|
|
1583
|
+
"@type": "@id"
|
|
1584
|
+
},
|
|
1585
|
+
"featuredObject": {
|
|
1586
|
+
"@id": "https://w3id.org/fep/7aa9#featuredObject",
|
|
1587
|
+
"@type": "@id"
|
|
1588
|
+
},
|
|
1589
|
+
"canFeature": {
|
|
1590
|
+
"@id": "https://w3id.org/fep/7aa9#canFeature",
|
|
1591
|
+
"@type": "@id"
|
|
1592
|
+
},
|
|
1593
|
+
"featureAuthorization": {
|
|
1594
|
+
"@id": "https://w3id.org/fep/7aa9#featureAuthorization",
|
|
1595
|
+
"@type": "@id"
|
|
1596
|
+
}
|
|
1597
|
+
} }
|
|
1598
|
+
});
|
|
1599
|
+
(0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
|
|
1600
|
+
});
|
|
1601
|
+
await t.step("deny non-HTTP/HTTPS", async () => {
|
|
1487
1602
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("ftp://localhost"), require_url.UrlError);
|
|
1488
1603
|
});
|
|
1489
1604
|
esm_default.get("https://example.com/localhost-redirect", {
|
|
@@ -1508,13 +1623,13 @@ var esm_default = new class FetchMock {
|
|
|
1508
1623
|
name: "Fetched object",
|
|
1509
1624
|
type: "Object"
|
|
1510
1625
|
} });
|
|
1511
|
-
await t.
|
|
1626
|
+
await t.step("allowPrivateAddress: false", async () => {
|
|
1512
1627
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://localhost/object"), require_url.UrlError);
|
|
1513
1628
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/localhost-redirect"), require_url.UrlError);
|
|
1514
1629
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/localhost-link"), require_url.UrlError);
|
|
1515
1630
|
});
|
|
1516
1631
|
const fetchDocumentLoader2 = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1517
|
-
await t.
|
|
1632
|
+
await t.step("allowPrivateAddress: true", async () => {
|
|
1518
1633
|
const expected = {
|
|
1519
1634
|
contextUrl: null,
|
|
1520
1635
|
documentUrl: "https://localhost/object",
|
|
@@ -1537,12 +1652,12 @@ var esm_default = new class FetchMock {
|
|
|
1537
1652
|
headers: { Location: `https://example.com/too-many-redirects/${Number(cl.url.split("/").at(-1)) + 1}` }
|
|
1538
1653
|
};
|
|
1539
1654
|
});
|
|
1540
|
-
await t.
|
|
1655
|
+
await t.step("too many redirects", async () => {
|
|
1541
1656
|
redirectAttempts = 0;
|
|
1542
1657
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/too-many-redirects/0"), require_request.FetchError, "Too many redirections");
|
|
1543
1658
|
(0, node_assert.deepStrictEqual)(redirectAttempts, 21);
|
|
1544
1659
|
});
|
|
1545
|
-
await t.
|
|
1660
|
+
await t.step("custom max redirection", async () => {
|
|
1546
1661
|
redirectAttempts = 0;
|
|
1547
1662
|
const loader = require_docloader.getDocumentLoader({ maxRedirection: 1 });
|
|
1548
1663
|
await (0, node_assert.rejects)(() => loader("https://example.com/too-many-redirects/0"), require_request.FetchError, "Too many redirections");
|
|
@@ -1563,7 +1678,7 @@ var esm_default = new class FetchMock {
|
|
|
1563
1678
|
headers: { Location: "https://example.com/redirect-loop-a" }
|
|
1564
1679
|
};
|
|
1565
1680
|
});
|
|
1566
|
-
await t.
|
|
1681
|
+
await t.step("redirect loop", async () => {
|
|
1567
1682
|
loopAttempts = 0;
|
|
1568
1683
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redirect-loop-a"), require_request.FetchError, "Redirect loop detected");
|
|
1569
1684
|
(0, node_assert.deepStrictEqual)(loopAttempts, 2);
|
|
@@ -1576,7 +1691,7 @@ var esm_default = new class FetchMock {
|
|
|
1576
1691
|
headers: { Location: "/redirect-loop-relative" }
|
|
1577
1692
|
};
|
|
1578
1693
|
});
|
|
1579
|
-
await t.
|
|
1694
|
+
await t.step("redirect loop with relative location", async () => {
|
|
1580
1695
|
relativeLoopAttempts = 0;
|
|
1581
1696
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redirect-loop-relative"), require_request.FetchError, "Redirect loop detected");
|
|
1582
1697
|
(0, node_assert.deepStrictEqual)(relativeLoopAttempts, 1);
|
|
@@ -1586,7 +1701,7 @@ var esm_default = new class FetchMock {
|
|
|
1586
1701
|
body: maliciousPayload,
|
|
1587
1702
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1588
1703
|
});
|
|
1589
|
-
await t.
|
|
1704
|
+
await t.step("ReDoS resistance (CVE-2025-68475)", async () => {
|
|
1590
1705
|
const start = performance.now();
|
|
1591
1706
|
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redos"), require_request.FetchError);
|
|
1592
1707
|
const elapsed = performance.now() - start;
|
|
@@ -1594,4 +1709,542 @@ var esm_default = new class FetchMock {
|
|
|
1594
1709
|
});
|
|
1595
1710
|
esm_default.hardReset();
|
|
1596
1711
|
});
|
|
1712
|
+
(0, _fedify_fixture.test)("getDocumentLoader() bounds JSON, HTML, and alternate documents", async () => {
|
|
1713
|
+
const url = "https://example.com/bounded";
|
|
1714
|
+
esm_default.mockGlobal();
|
|
1715
|
+
let oversized = true;
|
|
1716
|
+
try {
|
|
1717
|
+
esm_default.get(url, () => new Response("{\"name\":\"hello\"}", { headers: {
|
|
1718
|
+
"Content-Type": "application/activity+json",
|
|
1719
|
+
...oversized ? { "Content-Length": String(16777217) } : {}
|
|
1720
|
+
} }));
|
|
1721
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1722
|
+
await (0, node_assert.rejects)(loader(url), require_request.FetchError);
|
|
1723
|
+
oversized = false;
|
|
1724
|
+
(0, node_assert.deepStrictEqual)((await loader(url)).document, { name: "hello" });
|
|
1725
|
+
oversized = true;
|
|
1726
|
+
esm_default.get(`${url}/html`, () => new Response(" ".repeat(1048577), { headers: { "Content-Type": "text/html" } }));
|
|
1727
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(`${url}/html`), require_request.FetchError);
|
|
1728
|
+
esm_default.get(`${url}/alternate`, () => new Response("", { headers: {
|
|
1729
|
+
"Content-Type": "text/html",
|
|
1730
|
+
Link: `<${url}>; rel="alternate"; type="application/activity+json"`
|
|
1731
|
+
} }));
|
|
1732
|
+
await (0, node_assert.rejects)(loader(`${url}/alternate`), require_request.FetchError);
|
|
1733
|
+
esm_default.get(`${url}/redirect`, {
|
|
1734
|
+
status: 302,
|
|
1735
|
+
headers: { Location: url }
|
|
1736
|
+
});
|
|
1737
|
+
await (0, node_assert.rejects)(loader(`${url}/redirect`), require_request.FetchError);
|
|
1738
|
+
} finally {
|
|
1739
|
+
esm_default.hardReset();
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
(0, _fedify_fixture.test)("getDocumentLoader() rejects oversized gzip responses from fetch", async () => {
|
|
1743
|
+
const document = { name: "a".repeat(16 * 1024 * 1024) };
|
|
1744
|
+
const compressed = (0, node_zlib.gzipSync)(JSON.stringify(document));
|
|
1745
|
+
(0, node_assert.ok)(compressed.byteLength < 64 * 1024);
|
|
1746
|
+
const server = (0, node_http.createServer)((request, response) => {
|
|
1747
|
+
const body = request.url === "/small" ? (0, node_zlib.gzipSync)("{\"name\":\"hello\"}") : compressed;
|
|
1748
|
+
response.writeHead(200, {
|
|
1749
|
+
"Content-Type": "application/activity+json",
|
|
1750
|
+
"Content-Encoding": "gzip",
|
|
1751
|
+
"Content-Length": body.byteLength
|
|
1752
|
+
});
|
|
1753
|
+
response.end(body);
|
|
1754
|
+
});
|
|
1755
|
+
await new Promise((resolve, reject) => {
|
|
1756
|
+
server.once("error", reject);
|
|
1757
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
1758
|
+
});
|
|
1759
|
+
try {
|
|
1760
|
+
const address = server.address();
|
|
1761
|
+
(0, node_assert.ok)(address != null && typeof address !== "string");
|
|
1762
|
+
const url = `http://127.0.0.1:${address.port}/document`;
|
|
1763
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(url), require_request.FetchError);
|
|
1764
|
+
(0, node_assert.deepStrictEqual)((await require_docloader.getDocumentLoader({ allowPrivateAddress: true })(new URL("/small", url).href)).document, { name: "hello" });
|
|
1765
|
+
} finally {
|
|
1766
|
+
await new Promise((resolve, reject) => {
|
|
1767
|
+
server.close((error) => error == null ? resolve() : reject(error));
|
|
1768
|
+
server.closeAllConnections();
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
(0, _fedify_fixture.test)("getRemoteDocument() bounds JSON by default", async () => {
|
|
1773
|
+
let canceled = false;
|
|
1774
|
+
let pulls = 0;
|
|
1775
|
+
await (0, node_assert.rejects)(require_docloader.getRemoteDocument("https://example.com/document", new Response(new ReadableStream({
|
|
1776
|
+
pull(controller) {
|
|
1777
|
+
pulls++;
|
|
1778
|
+
controller.enqueue(new Uint8Array(64 * 1024).fill(32));
|
|
1779
|
+
},
|
|
1780
|
+
cancel() {
|
|
1781
|
+
canceled = true;
|
|
1782
|
+
}
|
|
1783
|
+
}, { highWaterMark: 0 })), () => {
|
|
1784
|
+
throw new Error("Unexpected alternate document");
|
|
1785
|
+
}), require_request.FetchError);
|
|
1786
|
+
(0, node_assert.deepStrictEqual)(canceled, true);
|
|
1787
|
+
(0, node_assert.deepStrictEqual)(pulls, 257);
|
|
1788
|
+
});
|
|
1789
|
+
(0, _fedify_fixture.test)("getDocumentLoader() logs DNS failures as such", async (t) => {
|
|
1790
|
+
if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
|
|
1791
|
+
const loader = require_docloader.getDocumentLoader();
|
|
1792
|
+
for (const result of [
|
|
1793
|
+
"throws",
|
|
1794
|
+
"empty",
|
|
1795
|
+
"private"
|
|
1796
|
+
]) await t.step(result, async () => {
|
|
1797
|
+
const originalLookup = node_dns_promises.default.lookup;
|
|
1798
|
+
node_dns_promises.default.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
|
|
1799
|
+
address: "127.0.0.1",
|
|
1800
|
+
family: 4
|
|
1801
|
+
}]));
|
|
1802
|
+
const records = [];
|
|
1803
|
+
await (0, _logtape_logtape.configure)({
|
|
1804
|
+
sinks: { buffer: (record) => records.push(record) },
|
|
1805
|
+
loggers: [{
|
|
1806
|
+
category: "fedify",
|
|
1807
|
+
sinks: ["buffer"],
|
|
1808
|
+
lowestLevel: "debug"
|
|
1809
|
+
}, {
|
|
1810
|
+
category: ["logtape", "meta"],
|
|
1811
|
+
sinks: []
|
|
1812
|
+
}],
|
|
1813
|
+
reset: true
|
|
1814
|
+
});
|
|
1815
|
+
try {
|
|
1816
|
+
const url = "https://dns-failure.invalid/object";
|
|
1817
|
+
let error;
|
|
1818
|
+
await (0, node_assert.rejects)(() => loader(url), (e) => {
|
|
1819
|
+
error = e;
|
|
1820
|
+
return true;
|
|
1821
|
+
});
|
|
1822
|
+
(0, node_assert.ok)(error instanceof require_url.UrlError);
|
|
1823
|
+
(0, node_assert.deepStrictEqual)(error.reason, result === "private" ? "disallowed" : "dns");
|
|
1824
|
+
(0, node_assert.deepStrictEqual)(records.map((r) => [
|
|
1825
|
+
r.level,
|
|
1826
|
+
r.rawMessage,
|
|
1827
|
+
r.properties.url
|
|
1828
|
+
]), [result === "private" ? [
|
|
1829
|
+
"error",
|
|
1830
|
+
"Disallowed private URL: {url}",
|
|
1831
|
+
url
|
|
1832
|
+
] : [
|
|
1833
|
+
"debug",
|
|
1834
|
+
"DNS lookup failed for {url}",
|
|
1835
|
+
url
|
|
1836
|
+
]]);
|
|
1837
|
+
(0, node_assert.ok)(records[0].properties.error === error);
|
|
1838
|
+
} finally {
|
|
1839
|
+
await (0, _logtape_logtape.reset)();
|
|
1840
|
+
node_dns_promises.default.lookup = originalLookup;
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
});
|
|
1844
|
+
(0, _fedify_fixture.test)("getDocumentLoader() bounds alternate document chains", async (t) => {
|
|
1845
|
+
const base = "https://example.com/alternate-chain/";
|
|
1846
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1847
|
+
for (const mode of [
|
|
1848
|
+
"header",
|
|
1849
|
+
"html",
|
|
1850
|
+
"mixed"
|
|
1851
|
+
]) for (const hops of [20, 21]) await t.step(`${mode}: ${hops} hops`, async () => {
|
|
1852
|
+
esm_default.mockGlobal();
|
|
1853
|
+
let requests = 0;
|
|
1854
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1855
|
+
requests++;
|
|
1856
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1857
|
+
if (index === hops || requests > 30) return Response.json({ done: true });
|
|
1858
|
+
const next = `${base}${index + 1}`;
|
|
1859
|
+
if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
|
|
1860
|
+
return alternate(next, mode === "html");
|
|
1861
|
+
});
|
|
1862
|
+
try {
|
|
1863
|
+
if (hops === 20) (0, node_assert.deepStrictEqual)((await loader(`${base}0`)).document, { done: true });
|
|
1864
|
+
else await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
1865
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1866
|
+
(0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
|
|
1867
|
+
return true;
|
|
1868
|
+
});
|
|
1869
|
+
(0, node_assert.deepStrictEqual)(requests, 21);
|
|
1870
|
+
} finally {
|
|
1871
|
+
esm_default.hardReset();
|
|
1872
|
+
}
|
|
1873
|
+
});
|
|
1874
|
+
for (const mode of [
|
|
1875
|
+
"header",
|
|
1876
|
+
"html",
|
|
1877
|
+
"alternate-redirect",
|
|
1878
|
+
"redirect-alternate",
|
|
1879
|
+
"redirect-intermediate"
|
|
1880
|
+
]) await t.step(`${mode}: cycle`, async () => {
|
|
1881
|
+
esm_default.mockGlobal();
|
|
1882
|
+
let requests = 0;
|
|
1883
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1884
|
+
requests++;
|
|
1885
|
+
if (requests > 30) return Response.json({ stopped: true });
|
|
1886
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1887
|
+
const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
|
|
1888
|
+
if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
|
|
1889
|
+
return alternate(next, mode === "html");
|
|
1890
|
+
});
|
|
1891
|
+
try {
|
|
1892
|
+
await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
1893
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1894
|
+
(0, node_assert.ok)(error.message.includes("Redirect loop detected:"));
|
|
1895
|
+
return true;
|
|
1896
|
+
});
|
|
1897
|
+
(0, node_assert.deepStrictEqual)(requests, mode === "redirect-intermediate" ? 3 : 2);
|
|
1898
|
+
} finally {
|
|
1899
|
+
esm_default.hardReset();
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1902
|
+
await t.step("alternate followed by 20 redirects shares the limit", async () => {
|
|
1903
|
+
esm_default.mockGlobal();
|
|
1904
|
+
let requests = 0;
|
|
1905
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1906
|
+
requests++;
|
|
1907
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1908
|
+
if (index === 21) return Response.json({ done: true });
|
|
1909
|
+
return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
|
|
1910
|
+
});
|
|
1911
|
+
try {
|
|
1912
|
+
await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
1913
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
1914
|
+
(0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
|
|
1915
|
+
return true;
|
|
1916
|
+
});
|
|
1917
|
+
(0, node_assert.deepStrictEqual)(requests, 21);
|
|
1918
|
+
} finally {
|
|
1919
|
+
esm_default.hardReset();
|
|
1920
|
+
}
|
|
1921
|
+
});
|
|
1922
|
+
await t.step("relative alternate after redirect and isolated calls", async () => {
|
|
1923
|
+
esm_default.mockGlobal();
|
|
1924
|
+
esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
|
|
1925
|
+
esm_default.get(`${base}html`, alternate("./document", true));
|
|
1926
|
+
esm_default.get(`${base}document`, Response.json({ done: true }));
|
|
1927
|
+
try {
|
|
1928
|
+
for (let i = 0; i < 2; i++) {
|
|
1929
|
+
const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
|
|
1930
|
+
for (const result of results) {
|
|
1931
|
+
(0, node_assert.deepStrictEqual)(result.document, { done: true });
|
|
1932
|
+
(0, node_assert.deepStrictEqual)(result.documentUrl, `${base}document`);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
} finally {
|
|
1936
|
+
esm_default.hardReset();
|
|
1937
|
+
}
|
|
1938
|
+
});
|
|
1939
|
+
function alternate(next, html) {
|
|
1940
|
+
return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
|
|
1941
|
+
"Content-Type": "text/plain",
|
|
1942
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
1943
|
+
} });
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
1946
|
+
(0, _fedify_fixture.test)("getDocumentLoader() preserves cancellation across alternates", async (t) => {
|
|
1947
|
+
const base = "https://example.com/alternate-abort/";
|
|
1948
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1949
|
+
for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
|
|
1950
|
+
esm_default.mockGlobal();
|
|
1951
|
+
const controller = new AbortController();
|
|
1952
|
+
let requests = 0;
|
|
1953
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
1954
|
+
requests++;
|
|
1955
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
1956
|
+
if (index === 2) return Response.json({ done: true });
|
|
1957
|
+
if (index === 1) controller.abort();
|
|
1958
|
+
const next = `${base}${index + 1}`;
|
|
1959
|
+
return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
|
|
1960
|
+
"Content-Type": "text/plain",
|
|
1961
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
1962
|
+
} });
|
|
1963
|
+
});
|
|
1964
|
+
try {
|
|
1965
|
+
await (0, node_assert.rejects)(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
|
|
1966
|
+
(0, node_assert.deepStrictEqual)(requests, 2);
|
|
1967
|
+
} finally {
|
|
1968
|
+
esm_default.hardReset();
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
});
|
|
1972
|
+
(0, _fedify_fixture.test)("getDocumentLoader() rejects cancellation before fetching", async () => {
|
|
1973
|
+
esm_default.mockGlobal();
|
|
1974
|
+
let requests = 0;
|
|
1975
|
+
const url = "https://example.com/pre-aborted-alternate";
|
|
1976
|
+
esm_default.get(url, () => {
|
|
1977
|
+
requests++;
|
|
1978
|
+
return Response.json({ done: true });
|
|
1979
|
+
});
|
|
1980
|
+
try {
|
|
1981
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1982
|
+
const controller = new AbortController();
|
|
1983
|
+
controller.abort();
|
|
1984
|
+
await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), { name: "AbortError" });
|
|
1985
|
+
(0, node_assert.deepStrictEqual)(requests, 0);
|
|
1986
|
+
} finally {
|
|
1987
|
+
esm_default.hardReset();
|
|
1988
|
+
}
|
|
1989
|
+
});
|
|
1990
|
+
async function withServer(handler, callback) {
|
|
1991
|
+
const server = (0, node_http.createServer)(handler);
|
|
1992
|
+
await new Promise((resolve, reject) => {
|
|
1993
|
+
server.once("error", reject);
|
|
1994
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
1995
|
+
});
|
|
1996
|
+
try {
|
|
1997
|
+
const address = server.address();
|
|
1998
|
+
(0, node_assert.ok)(address != null && typeof address !== "string");
|
|
1999
|
+
return await callback(`http://127.0.0.1:${address.port}`);
|
|
2000
|
+
} finally {
|
|
2001
|
+
await new Promise((resolve, reject) => {
|
|
2002
|
+
server.close((error) => error == null ? resolve() : reject(error));
|
|
2003
|
+
server.closeAllConnections();
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
function isTimeoutFetchError(error, url) {
|
|
2008
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError, String(error));
|
|
2009
|
+
(0, node_assert.deepStrictEqual)(error.url.href, url);
|
|
2010
|
+
(0, node_assert.deepStrictEqual)(error.response, void 0);
|
|
2011
|
+
(0, node_assert.ok)(error.cause instanceof DOMException);
|
|
2012
|
+
(0, node_assert.deepStrictEqual)(error.cause.name, "TimeoutError");
|
|
2013
|
+
return true;
|
|
2014
|
+
}
|
|
2015
|
+
(0, _fedify_fixture.test)("resolveDocumentLoaderTimeout() validates timeouts", () => {
|
|
2016
|
+
(0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(void 0), 1e4);
|
|
2017
|
+
(0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(null), null);
|
|
2018
|
+
(0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(1500), 1500);
|
|
2019
|
+
(0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(.5), 1);
|
|
2020
|
+
(0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(2147483647), 2147483647);
|
|
2021
|
+
for (const invalid of [
|
|
2022
|
+
0,
|
|
2023
|
+
-1,
|
|
2024
|
+
NaN,
|
|
2025
|
+
Infinity,
|
|
2026
|
+
2147483648
|
|
2027
|
+
]) (0, node_assert.throws)(() => require_docloader.resolveDocumentLoaderTimeout(invalid), RangeError);
|
|
2028
|
+
(0, node_assert.throws)(() => require_docloader.getDocumentLoader({ timeout: 0 }), RangeError);
|
|
2029
|
+
});
|
|
2030
|
+
(0, _fedify_fixture.test)("getDocumentLoader() times out a request without a response", async () => {
|
|
2031
|
+
await withServer(() => {}, async (baseUrl) => {
|
|
2032
|
+
const url = `${baseUrl}/hang`;
|
|
2033
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
|
|
2034
|
+
allowPrivateAddress: true,
|
|
2035
|
+
timeout: 100
|
|
2036
|
+
})(url), (e) => isTimeoutFetchError(e, url));
|
|
2037
|
+
});
|
|
2038
|
+
});
|
|
2039
|
+
(0, _fedify_fixture.test)("getDocumentLoader() times out a stalled response body", async () => {
|
|
2040
|
+
for (const status of [200, 404]) await withServer((_, response) => {
|
|
2041
|
+
response.writeHead(status, { "Content-Type": "application/activity+json" });
|
|
2042
|
+
response.write("{\"id\":");
|
|
2043
|
+
}, async (baseUrl) => {
|
|
2044
|
+
const url = `${baseUrl}/stalled`;
|
|
2045
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
|
|
2046
|
+
allowPrivateAddress: true,
|
|
2047
|
+
timeout: 200
|
|
2048
|
+
})(url), (e) => isTimeoutFetchError(e, url));
|
|
2049
|
+
});
|
|
2050
|
+
});
|
|
2051
|
+
(0, _fedify_fixture.test)("getDocumentLoader() shares the timeout across redirects", async () => {
|
|
2052
|
+
let requests = 0;
|
|
2053
|
+
await withServer((request, response) => {
|
|
2054
|
+
requests++;
|
|
2055
|
+
const index = Number(request.url?.split("/").at(-1));
|
|
2056
|
+
setTimeout(() => {
|
|
2057
|
+
if (index >= 15) {
|
|
2058
|
+
response.writeHead(200, { "Content-Type": "application/activity+json" });
|
|
2059
|
+
response.end("{\"done\":true}");
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
response.writeHead(302, { Location: `/redirect/${index + 1}` });
|
|
2063
|
+
response.end();
|
|
2064
|
+
}, 50);
|
|
2065
|
+
}, async (baseUrl) => {
|
|
2066
|
+
const url = `${baseUrl}/redirect/0`;
|
|
2067
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
|
|
2068
|
+
allowPrivateAddress: true,
|
|
2069
|
+
timeout: 300
|
|
2070
|
+
})(url), (e) => isTimeoutFetchError(e, url));
|
|
2071
|
+
(0, node_assert.ok)(requests < 16, `requests: ${requests}`);
|
|
2072
|
+
(0, node_assert.deepStrictEqual)((await require_docloader.getDocumentLoader({
|
|
2073
|
+
allowPrivateAddress: true,
|
|
2074
|
+
timeout: null
|
|
2075
|
+
})(url)).document, { done: true });
|
|
2076
|
+
});
|
|
2077
|
+
});
|
|
2078
|
+
(0, _fedify_fixture.test)("getDocumentLoader() shares the timeout across alternates", async (t) => {
|
|
2079
|
+
for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
|
|
2080
|
+
await withServer((request, response) => {
|
|
2081
|
+
const index = Number(request.url?.split("/").at(-1));
|
|
2082
|
+
setTimeout(() => {
|
|
2083
|
+
if (index >= 15) {
|
|
2084
|
+
response.writeHead(200, { "Content-Type": "application/activity+json" });
|
|
2085
|
+
response.end("{\"done\":true}");
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
const next = `/alternate/${index + 1}`;
|
|
2089
|
+
response.writeHead(200, html ? { "Content-Type": "text/html" } : {
|
|
2090
|
+
"Content-Type": "text/plain",
|
|
2091
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
2092
|
+
});
|
|
2093
|
+
response.end(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON");
|
|
2094
|
+
}, 50);
|
|
2095
|
+
}, async (baseUrl) => {
|
|
2096
|
+
const url = `${baseUrl}/alternate/0`;
|
|
2097
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
|
|
2098
|
+
allowPrivateAddress: true,
|
|
2099
|
+
timeout: 300
|
|
2100
|
+
})(url), (e) => isTimeoutFetchError(e, url));
|
|
2101
|
+
});
|
|
2102
|
+
});
|
|
2103
|
+
});
|
|
2104
|
+
(0, _fedify_fixture.test)("getDocumentLoader() lets the caller's signal abort a request", async () => {
|
|
2105
|
+
await withServer(() => {}, async (baseUrl) => {
|
|
2106
|
+
const url = `${baseUrl}/hang`;
|
|
2107
|
+
for (const timeout of [
|
|
2108
|
+
void 0,
|
|
2109
|
+
6e4,
|
|
2110
|
+
null
|
|
2111
|
+
]) {
|
|
2112
|
+
const loader = require_docloader.getDocumentLoader({
|
|
2113
|
+
allowPrivateAddress: true,
|
|
2114
|
+
timeout
|
|
2115
|
+
});
|
|
2116
|
+
const controller = new AbortController();
|
|
2117
|
+
const reason = /* @__PURE__ */ new Error("Canceled by the caller");
|
|
2118
|
+
setTimeout(() => controller.abort(reason), 50);
|
|
2119
|
+
await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), (e) => {
|
|
2120
|
+
(0, node_assert.deepStrictEqual)(e, reason);
|
|
2121
|
+
return true;
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
const loader = require_docloader.getDocumentLoader({
|
|
2125
|
+
allowPrivateAddress: true,
|
|
2126
|
+
timeout: 50
|
|
2127
|
+
});
|
|
2128
|
+
const controller = new AbortController();
|
|
2129
|
+
const reason = /* @__PURE__ */ new Error("Canceled by the caller");
|
|
2130
|
+
controller.abort(reason);
|
|
2131
|
+
await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), (e) => {
|
|
2132
|
+
(0, node_assert.deepStrictEqual)(e, reason);
|
|
2133
|
+
return true;
|
|
2134
|
+
});
|
|
2135
|
+
});
|
|
2136
|
+
});
|
|
2137
|
+
(0, _fedify_fixture.test)("getDocumentLoader() bounds DNS lookups that ignore the timeout", async () => {
|
|
2138
|
+
if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
|
|
2139
|
+
esm_default.mockGlobal();
|
|
2140
|
+
let requests = 0;
|
|
2141
|
+
const url = "https://slow-dns.example/object";
|
|
2142
|
+
esm_default.get(url, () => {
|
|
2143
|
+
requests++;
|
|
2144
|
+
return Response.json({ id: url });
|
|
2145
|
+
});
|
|
2146
|
+
const originalLookup = node_dns_promises.default.lookup;
|
|
2147
|
+
let resolveLookup = () => {};
|
|
2148
|
+
const lookedUp = new Promise((resolve) => resolveLookup = resolve);
|
|
2149
|
+
node_dns_promises.default.lookup = (() => new Promise((resolve) => setTimeout(() => {
|
|
2150
|
+
resolve([{
|
|
2151
|
+
address: "93.184.215.14",
|
|
2152
|
+
family: 4
|
|
2153
|
+
}]);
|
|
2154
|
+
resolveLookup();
|
|
2155
|
+
}, 200)));
|
|
2156
|
+
try {
|
|
2157
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ timeout: 50 })(url), (e) => isTimeoutFetchError(e, url));
|
|
2158
|
+
await lookedUp;
|
|
2159
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2160
|
+
(0, node_assert.deepStrictEqual)(requests, 0);
|
|
2161
|
+
} finally {
|
|
2162
|
+
node_dns_promises.default.lookup = originalLookup;
|
|
2163
|
+
esm_default.hardReset();
|
|
2164
|
+
}
|
|
2165
|
+
});
|
|
2166
|
+
(0, _fedify_fixture.test)("withDocumentLoaderTimeout() cleans up after each call", async () => {
|
|
2167
|
+
const signals = [];
|
|
2168
|
+
const loader = require_docloader.withDocumentLoaderTimeout((url, options) => {
|
|
2169
|
+
signals.push(options.signal);
|
|
2170
|
+
return Promise.resolve({
|
|
2171
|
+
contextUrl: null,
|
|
2172
|
+
document: {},
|
|
2173
|
+
documentUrl: url
|
|
2174
|
+
});
|
|
2175
|
+
}, 50);
|
|
2176
|
+
(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"]);
|
|
2177
|
+
(0, node_assert.ok)(signals[0] !== signals[1]);
|
|
2178
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2179
|
+
(0, node_assert.ok)(signals.every((signal) => !signal.aborted));
|
|
2180
|
+
const inner = () => Promise.reject(/* @__PURE__ */ new Error("unused"));
|
|
2181
|
+
(0, node_assert.ok)(require_docloader.withDocumentLoaderTimeout(inner, null) === inner);
|
|
2182
|
+
});
|
|
2183
|
+
(0, _fedify_fixture.test)("getRemoteDocument() keeps error bodies byte for byte", async () => {
|
|
2184
|
+
const bytes = new Uint8Array([
|
|
2185
|
+
239,
|
|
2186
|
+
187,
|
|
2187
|
+
191,
|
|
2188
|
+
78,
|
|
2189
|
+
111,
|
|
2190
|
+
255,
|
|
2191
|
+
254
|
|
2192
|
+
]);
|
|
2193
|
+
const url = "https://example.com/error";
|
|
2194
|
+
let error;
|
|
2195
|
+
await (0, node_assert.rejects)(require_docloader.getRemoteDocument(url, new Response(bytes, { status: 404 }), () => Promise.reject(/* @__PURE__ */ new Error("unused"))), (e) => {
|
|
2196
|
+
error = e;
|
|
2197
|
+
return true;
|
|
2198
|
+
});
|
|
2199
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
2200
|
+
(0, node_assert.ok)(error.response != null);
|
|
2201
|
+
(0, node_assert.deepStrictEqual)(error.response.status, 404);
|
|
2202
|
+
(0, node_assert.deepStrictEqual)(new Uint8Array(await error.response.arrayBuffer()), bytes);
|
|
2203
|
+
});
|
|
2204
|
+
(0, _fedify_fixture.test)("getRemoteDocument() keeps metadata of bodiless or large errors", async () => {
|
|
2205
|
+
const url = "https://example.com/error";
|
|
2206
|
+
for (const response of [new Response(null, {
|
|
2207
|
+
status: 304,
|
|
2208
|
+
headers: { ETag: "\"a\"" }
|
|
2209
|
+
}), new Response(/* @__PURE__ */ new Uint8Array(1048577), {
|
|
2210
|
+
status: 500,
|
|
2211
|
+
headers: { ETag: "\"a\"" }
|
|
2212
|
+
})]) await (0, node_assert.rejects)(require_docloader.getRemoteDocument(url, response, () => Promise.reject(/* @__PURE__ */ new Error("unused"))), (e) => {
|
|
2213
|
+
(0, node_assert.ok)(e instanceof require_request.FetchError);
|
|
2214
|
+
(0, node_assert.ok)(e.response != null);
|
|
2215
|
+
(0, node_assert.deepStrictEqual)(e.response.status, response.status);
|
|
2216
|
+
(0, node_assert.deepStrictEqual)(e.response.headers.get("ETag"), "\"a\"");
|
|
2217
|
+
(0, node_assert.deepStrictEqual)(e.response.body, null);
|
|
2218
|
+
return true;
|
|
2219
|
+
});
|
|
2220
|
+
});
|
|
2221
|
+
(0, _fedify_fixture.test)("getDocumentLoader() reports statuses that Response cannot hold", async () => {
|
|
2222
|
+
await withServer((_, response) => {
|
|
2223
|
+
response.writeHead(999);
|
|
2224
|
+
response.end("Request denied");
|
|
2225
|
+
}, async (baseUrl) => {
|
|
2226
|
+
const url = `${baseUrl}/denied`;
|
|
2227
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
2228
|
+
let error;
|
|
2229
|
+
await (0, node_assert.rejects)(loader(url), (e) => {
|
|
2230
|
+
error = e;
|
|
2231
|
+
return true;
|
|
2232
|
+
});
|
|
2233
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError, String(error));
|
|
2234
|
+
(0, node_assert.deepStrictEqual)(error.response?.status, 999);
|
|
2235
|
+
(0, node_assert.deepStrictEqual)(await error.response.text(), "Request denied");
|
|
2236
|
+
});
|
|
2237
|
+
});
|
|
2238
|
+
(0, _fedify_fixture.test)("getDocumentLoader() times out a stalled body of such a status", async () => {
|
|
2239
|
+
await withServer((_, response) => {
|
|
2240
|
+
response.writeHead(999);
|
|
2241
|
+
response.write("Request");
|
|
2242
|
+
}, async (baseUrl) => {
|
|
2243
|
+
const url = `${baseUrl}/stalled-denied`;
|
|
2244
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
|
|
2245
|
+
allowPrivateAddress: true,
|
|
2246
|
+
timeout: 200
|
|
2247
|
+
})(url), (e) => isTimeoutFetchError(e, url));
|
|
2248
|
+
});
|
|
2249
|
+
});
|
|
1597
2250
|
//#endregion
|