@fedify/vocab-runtime 2.4.0-dev.1564 → 2.4.0-dev.1570

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.
Files changed (42) hide show
  1. package/deno.json +1 -2
  2. package/dist/mod.cjs +190 -107
  3. package/dist/mod.d.cts +200 -18
  4. package/dist/mod.d.ts +200 -18
  5. package/dist/mod.js +184 -97
  6. package/dist/tests/decimal.test.cjs +2 -2
  7. package/dist/tests/decimal.test.mjs +2 -2
  8. package/dist/tests/{docloader-CYQvKbtL.mjs → docloader-C76ldE5C.mjs} +10 -97
  9. package/dist/tests/{docloader-UAdXnDwt.cjs → docloader-mvgIWKI7.cjs} +9 -102
  10. package/dist/tests/docloader.test.cjs +6 -58
  11. package/dist/tests/docloader.test.mjs +6 -58
  12. package/dist/tests/{request-DgAlI7RF.mjs → request-BEXkv1ul.mjs} +1 -1
  13. package/dist/tests/{request-DnzhAfki.cjs → request-SworbvLc.cjs} +1 -1
  14. package/dist/tests/request.test.cjs +1 -1
  15. package/dist/tests/request.test.mjs +1 -1
  16. package/dist/tests/{url-2XwVbUS_.cjs → url-C20FhC7p.cjs} +0 -108
  17. package/dist/tests/{url-YWJbnRlf.mjs → url-m9Qzxy-Y.mjs} +1 -85
  18. package/dist/tests/url.test.cjs +1 -104
  19. package/dist/tests/url.test.mjs +2 -105
  20. package/package.json +1 -11
  21. package/src/contexts.ts +0 -2
  22. package/src/docloader.test.ts +6 -102
  23. package/src/docloader.ts +8 -76
  24. package/src/mod.ts +0 -4
  25. package/src/url.test.ts +1 -252
  26. package/src/url.ts +0 -141
  27. package/tsdown.config.ts +1 -6
  28. package/dist/docloader-DnUMWHaJ.d.cts +0 -202
  29. package/dist/docloader-xRGn1azD.d.ts +0 -202
  30. package/dist/internal/jsonld-cache.cjs +0 -279
  31. package/dist/internal/jsonld-cache.d.cts +0 -48
  32. package/dist/internal/jsonld-cache.d.ts +0 -48
  33. package/dist/internal/jsonld-cache.js +0 -275
  34. package/dist/tests/jsonld-cache.test.cjs +0 -652
  35. package/dist/tests/jsonld-cache.test.d.cts +0 -1
  36. package/dist/tests/jsonld-cache.test.d.mts +0 -1
  37. package/dist/tests/jsonld-cache.test.mjs +0 -651
  38. package/dist/url-BAdyyqAa.cjs +0 -315
  39. package/dist/url-BuxPHxK2.js +0 -261
  40. package/src/contexts/fep-7aa9.json +0 -24
  41. package/src/internal/jsonld-cache.ts +0 -538
  42. package/src/jsonld-cache.test.ts +0 -554
@@ -1,6 +1,6 @@
1
- import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-DgAlI7RF.mjs";
1
+ import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-BEXkv1ul.mjs";
2
2
  import { t as HttpHeaderLink } from "./link-NUUWCdnK.mjs";
3
- import { l as validatePublicUrl, t as UrlError } from "./url-YWJbnRlf.mjs";
3
+ import { a as validatePublicUrl, t as UrlError } from "./url-m9Qzxy-Y.mjs";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
6
6
  //#endregion
@@ -4277,28 +4277,6 @@ const preloadedContexts = {
4277
4277
  "@type": "@id"
4278
4278
  }
4279
4279
  } },
4280
- "https://w3id.org/fep/7aa9": { "@context": {
4281
- "FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
4282
- "FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
4283
- "FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
4284
- "FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
4285
- "topic": {
4286
- "@id": "https://w3id.org/fep/7aa9#topic",
4287
- "@type": "@id"
4288
- },
4289
- "featuredObject": {
4290
- "@id": "https://w3id.org/fep/7aa9#featuredObject",
4291
- "@type": "@id"
4292
- },
4293
- "canFeature": {
4294
- "@id": "https://w3id.org/fep/7aa9#canFeature",
4295
- "@type": "@id"
4296
- },
4297
- "featureAuthorization": {
4298
- "@id": "https://w3id.org/fep/7aa9#featureAuthorization",
4299
- "@type": "@id"
4300
- }
4301
- } },
4302
4280
  "https://join-lemmy.org/context.json": { "@context": ["https://w3id.org/security/v1", {
4303
4281
  "as": "https://www.w3.org/ns/activitystreams#",
4304
4282
  "lemmy": "https://join-lemmy.org/ns#",
@@ -4362,66 +4340,6 @@ const logger = getLogger([
4362
4340
  "docloader"
4363
4341
  ]);
4364
4342
  const DEFAULT_MAX_REDIRECTION = 20;
4365
- const MAX_HTML_SIZE = 1024 * 1024;
4366
- function createResponseMetadata(response) {
4367
- return new Response(null, {
4368
- headers: response.headers,
4369
- status: response.status,
4370
- statusText: response.statusText
4371
- });
4372
- }
4373
- async function cancelResponseBody(response) {
4374
- if (response.body != null) await response.body.cancel();
4375
- }
4376
- async function readBoundedText(response, maxBytes) {
4377
- const contentLength = response.headers.get("Content-Length");
4378
- if (contentLength != null) {
4379
- const size = Number(contentLength);
4380
- if (size > maxBytes) {
4381
- await cancelResponseBody(response);
4382
- return {
4383
- text: "",
4384
- size,
4385
- tooLarge: true
4386
- };
4387
- }
4388
- }
4389
- if (response.body == null) return {
4390
- text: "",
4391
- size: 0,
4392
- tooLarge: false
4393
- };
4394
- const reader = response.body.getReader();
4395
- const decoder = new TextDecoder();
4396
- let text = "";
4397
- let size = 0;
4398
- try {
4399
- while (true) {
4400
- const result = await reader.read();
4401
- if (result.done) break;
4402
- const chunkSize = result.value.byteLength;
4403
- if (size + chunkSize > maxBytes) {
4404
- size += chunkSize;
4405
- await reader.cancel();
4406
- return {
4407
- text: "",
4408
- size,
4409
- tooLarge: true
4410
- };
4411
- }
4412
- size += chunkSize;
4413
- text += decoder.decode(result.value, { stream: true });
4414
- }
4415
- text += decoder.decode();
4416
- return {
4417
- text,
4418
- size,
4419
- tooLarge: false
4420
- };
4421
- } finally {
4422
- reader.releaseLock();
4423
- }
4424
- }
4425
4343
  /**
4426
4344
  * Gets a {@link RemoteDocument} from the given response.
4427
4345
  * @param url The URL of the document to load.
@@ -4476,19 +4394,19 @@ async function getRemoteDocument(url, response, fetch) {
4476
4394
  }
4477
4395
  let document;
4478
4396
  if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
4479
- const errorResponse = createResponseMetadata(response);
4480
- const html = await readBoundedText(response, MAX_HTML_SIZE);
4481
- if (html.tooLarge) {
4397
+ const MAX_HTML_SIZE = 1024 * 1024;
4398
+ const html = await response.text();
4399
+ if (html.length > MAX_HTML_SIZE) {
4482
4400
  logger.warn("HTML response too large, skipping alternate link discovery: {url}", {
4483
4401
  url: documentUrl,
4484
- size: html.size
4402
+ size: html.length
4485
4403
  });
4486
- throw new FetchError(documentUrl, `HTML document is too large to scan for an ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
4404
+ document = JSON.parse(html);
4487
4405
  } else {
4488
4406
  const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
4489
4407
  const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
4490
4408
  let tagMatch;
4491
- while ((tagMatch = tagPattern.exec(html.text)) !== null) {
4409
+ while ((tagMatch = tagPattern.exec(html)) !== null) {
4492
4410
  const tagContent = tagMatch[2];
4493
4411
  let attrMatch;
4494
4412
  const attribs = {};
@@ -4505,12 +4423,7 @@ async function getRemoteDocument(url, response, fetch) {
4505
4423
  return await fetch(new URL(attribs.href, docUrl).href);
4506
4424
  }
4507
4425
  }
4508
- try {
4509
- document = JSON.parse(html.text);
4510
- } catch (error) {
4511
- if (!(error instanceof SyntaxError)) throw error;
4512
- throw new FetchError(documentUrl, `HTML document has no ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
4513
- }
4426
+ document = JSON.parse(html);
4514
4427
  }
4515
4428
  } else document = await response.json();
4516
4429
  logger.debug("Fetched document: {status} {url} {headers}", {
@@ -4615,4 +4528,4 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
4615
4528
  return load;
4616
4529
  }
4617
4530
  //#endregion
4618
- export { getRemoteDocument as n, preloadedContexts as r, getDocumentLoader as t };
4531
+ export { preloadedContexts as n, getDocumentLoader as t };
@@ -1,7 +1,7 @@
1
1
  require("./chunk-C2EiDwsr.cjs");
2
- const require_request = require("./request-DnzhAfki.cjs");
2
+ const require_request = require("./request-SworbvLc.cjs");
3
3
  const require_link = require("./link-FguCydMA.cjs");
4
- const require_url = require("./url-2XwVbUS_.cjs");
4
+ const require_url = require("./url-C20FhC7p.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let _opentelemetry_api = require("@opentelemetry/api");
7
7
  //#endregion
@@ -4278,28 +4278,6 @@ const preloadedContexts = {
4278
4278
  "@type": "@id"
4279
4279
  }
4280
4280
  } },
4281
- "https://w3id.org/fep/7aa9": { "@context": {
4282
- "FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
4283
- "FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
4284
- "FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
4285
- "FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
4286
- "topic": {
4287
- "@id": "https://w3id.org/fep/7aa9#topic",
4288
- "@type": "@id"
4289
- },
4290
- "featuredObject": {
4291
- "@id": "https://w3id.org/fep/7aa9#featuredObject",
4292
- "@type": "@id"
4293
- },
4294
- "canFeature": {
4295
- "@id": "https://w3id.org/fep/7aa9#canFeature",
4296
- "@type": "@id"
4297
- },
4298
- "featureAuthorization": {
4299
- "@id": "https://w3id.org/fep/7aa9#featureAuthorization",
4300
- "@type": "@id"
4301
- }
4302
- } },
4303
4281
  "https://join-lemmy.org/context.json": { "@context": ["https://w3id.org/security/v1", {
4304
4282
  "as": "https://www.w3.org/ns/activitystreams#",
4305
4283
  "lemmy": "https://join-lemmy.org/ns#",
@@ -4363,66 +4341,6 @@ const logger = (0, _logtape_logtape.getLogger)([
4363
4341
  "docloader"
4364
4342
  ]);
4365
4343
  const DEFAULT_MAX_REDIRECTION = 20;
4366
- const MAX_HTML_SIZE = 1024 * 1024;
4367
- function createResponseMetadata(response) {
4368
- return new Response(null, {
4369
- headers: response.headers,
4370
- status: response.status,
4371
- statusText: response.statusText
4372
- });
4373
- }
4374
- async function cancelResponseBody(response) {
4375
- if (response.body != null) await response.body.cancel();
4376
- }
4377
- async function readBoundedText(response, maxBytes) {
4378
- const contentLength = response.headers.get("Content-Length");
4379
- if (contentLength != null) {
4380
- const size = Number(contentLength);
4381
- if (size > maxBytes) {
4382
- await cancelResponseBody(response);
4383
- return {
4384
- text: "",
4385
- size,
4386
- tooLarge: true
4387
- };
4388
- }
4389
- }
4390
- if (response.body == null) return {
4391
- text: "",
4392
- size: 0,
4393
- tooLarge: false
4394
- };
4395
- const reader = response.body.getReader();
4396
- const decoder = new TextDecoder();
4397
- let text = "";
4398
- let size = 0;
4399
- try {
4400
- while (true) {
4401
- const result = await reader.read();
4402
- if (result.done) break;
4403
- const chunkSize = result.value.byteLength;
4404
- if (size + chunkSize > maxBytes) {
4405
- size += chunkSize;
4406
- await reader.cancel();
4407
- return {
4408
- text: "",
4409
- size,
4410
- tooLarge: true
4411
- };
4412
- }
4413
- size += chunkSize;
4414
- text += decoder.decode(result.value, { stream: true });
4415
- }
4416
- text += decoder.decode();
4417
- return {
4418
- text,
4419
- size,
4420
- tooLarge: false
4421
- };
4422
- } finally {
4423
- reader.releaseLock();
4424
- }
4425
- }
4426
4344
  /**
4427
4345
  * Gets a {@link RemoteDocument} from the given response.
4428
4346
  * @param url The URL of the document to load.
@@ -4477,19 +4395,19 @@ async function getRemoteDocument(url, response, fetch) {
4477
4395
  }
4478
4396
  let document;
4479
4397
  if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
4480
- const errorResponse = createResponseMetadata(response);
4481
- const html = await readBoundedText(response, MAX_HTML_SIZE);
4482
- if (html.tooLarge) {
4398
+ const MAX_HTML_SIZE = 1024 * 1024;
4399
+ const html = await response.text();
4400
+ if (html.length > MAX_HTML_SIZE) {
4483
4401
  logger.warn("HTML response too large, skipping alternate link discovery: {url}", {
4484
4402
  url: documentUrl,
4485
- size: html.size
4403
+ size: html.length
4486
4404
  });
4487
- throw new require_request.FetchError(documentUrl, `HTML document is too large to scan for an ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
4405
+ document = JSON.parse(html);
4488
4406
  } else {
4489
4407
  const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
4490
4408
  const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
4491
4409
  let tagMatch;
4492
- while ((tagMatch = tagPattern.exec(html.text)) !== null) {
4410
+ while ((tagMatch = tagPattern.exec(html)) !== null) {
4493
4411
  const tagContent = tagMatch[2];
4494
4412
  let attrMatch;
4495
4413
  const attribs = {};
@@ -4506,12 +4424,7 @@ async function getRemoteDocument(url, response, fetch) {
4506
4424
  return await fetch(new URL(attribs.href, docUrl).href);
4507
4425
  }
4508
4426
  }
4509
- try {
4510
- document = JSON.parse(html.text);
4511
- } catch (error) {
4512
- if (!(error instanceof SyntaxError)) throw error;
4513
- throw new require_request.FetchError(documentUrl, `HTML document has no ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
4514
- }
4427
+ document = JSON.parse(html);
4515
4428
  }
4516
4429
  } else document = await response.json();
4517
4430
  logger.debug("Fetched document: {status} {url} {headers}", {
@@ -4622,12 +4535,6 @@ Object.defineProperty(exports, "getDocumentLoader", {
4622
4535
  return getDocumentLoader;
4623
4536
  }
4624
4537
  });
4625
- Object.defineProperty(exports, "getRemoteDocument", {
4626
- enumerable: true,
4627
- get: function() {
4628
- return getRemoteDocument;
4629
- }
4630
- });
4631
4538
  Object.defineProperty(exports, "preloadedContexts", {
4632
4539
  enumerable: true,
4633
4540
  get: function() {
@@ -1,7 +1,7 @@
1
1
  const require_chunk = require("./chunk-C2EiDwsr.cjs");
2
- const require_docloader = require("./docloader-UAdXnDwt.cjs");
3
- const require_request = require("./request-DnzhAfki.cjs");
4
- const require_url = require("./url-2XwVbUS_.cjs");
2
+ const require_docloader = require("./docloader-mvgIWKI7.cjs");
3
+ const require_request = require("./request-SworbvLc.cjs");
4
+ const require_url = require("./url-C20FhC7p.cjs");
5
5
  let node_assert = require("node:assert");
6
6
  let node_test = require("node:test");
7
7
  //#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
@@ -1267,7 +1267,7 @@ var esm_default = new class FetchMock {
1267
1267
  type: "Object"
1268
1268
  },
1269
1269
  headers: {
1270
- "Content-Type": "application/activity+json",
1270
+ "Content-Type": "text/html; charset=utf-8",
1271
1271
  Link: "<https://example.com/object>; rel=\"alternate\"; type=\"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\""
1272
1272
  }
1273
1273
  });
@@ -1399,27 +1399,6 @@ var esm_default = new class FetchMock {
1399
1399
  }
1400
1400
  });
1401
1401
  });
1402
- esm_default.get("https://example.com/html-no-alternate", {
1403
- body: `<!DOCTYPE html>
1404
- <html>
1405
- <head>
1406
- <title>Not an ActivityPub document</title>
1407
- </head>
1408
- <body>Not found</body>
1409
- </html>`,
1410
- headers: { "Content-Type": "text/html; charset=utf-8" }
1411
- });
1412
- await t.test("HTML without ActivityPub alternate link", async () => {
1413
- await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/html-no-alternate"), (error) => {
1414
- (0, node_assert.ok)(error instanceof require_request.FetchError);
1415
- (0, node_assert.ok)(error.message.includes("HTML document has no ActivityPub alternate link"));
1416
- (0, node_assert.ok)(error.message.includes("Content-Type: text/html; charset=utf-8"));
1417
- (0, node_assert.deepStrictEqual)(error.url, new URL("https://example.com/html-no-alternate"));
1418
- (0, node_assert.ok)(error.response != null);
1419
- (0, node_assert.deepStrictEqual)(error.response.headers.get("Content-Type"), "text/html; charset=utf-8");
1420
- return true;
1421
- });
1422
- });
1423
1402
  esm_default.get("https://example.com/wrong-content-type", {
1424
1403
  body: {
1425
1404
  "@context": "https://www.w3.org/ns/activitystreams",
@@ -1429,7 +1408,7 @@ var esm_default = new class FetchMock {
1429
1408
  },
1430
1409
  headers: { "Content-Type": "text/html; charset=utf-8" }
1431
1410
  });
1432
- await t.test("wrong Content-Type with JSON body", async () => {
1411
+ await t.test("Wrong Content-Type", async () => {
1433
1412
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/wrong-content-type"), {
1434
1413
  contextUrl: null,
1435
1414
  documentUrl: "https://example.com/wrong-content-type",
@@ -1441,37 +1420,6 @@ var esm_default = new class FetchMock {
1441
1420
  }
1442
1421
  });
1443
1422
  });
1444
- esm_default.get("https://example.com/large-html", {
1445
- body: "<!DOCTYPE html>",
1446
- headers: {
1447
- "Content-Length": String(1048577),
1448
- "Content-Type": "text/html; charset=utf-8"
1449
- }
1450
- });
1451
- await t.test("HTML Content-Length over limit", async () => {
1452
- await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/large-html"), (error) => {
1453
- (0, node_assert.ok)(error instanceof require_request.FetchError);
1454
- (0, node_assert.ok)(error.message.includes("HTML document is too large to scan for an ActivityPub alternate link"));
1455
- (0, node_assert.ok)(error.response != null);
1456
- (0, node_assert.deepStrictEqual)(error.response.status, 200);
1457
- (0, node_assert.deepStrictEqual)(error.response.headers.get("Content-Type"), "text/html; charset=utf-8");
1458
- return true;
1459
- });
1460
- });
1461
- await t.test("HTML Content-Length over limit cancels body", async () => {
1462
- let canceled = false;
1463
- const response = new Response("<!DOCTYPE html>", { headers: {
1464
- "Content-Length": String(1048577),
1465
- "Content-Type": "text/html; charset=utf-8"
1466
- } });
1467
- Object.defineProperty(response, "body", { value: { cancel: () => {
1468
- canceled = true;
1469
- } } });
1470
- await (0, node_assert.rejects)(() => require_docloader.getRemoteDocument("https://example.com/large-html-cancel", response, () => {
1471
- throw new Error("unexpected alternate fetch");
1472
- }), require_request.FetchError);
1473
- (0, node_assert.deepStrictEqual)(canceled, true);
1474
- });
1475
1423
  esm_default.get("https://example.com/404", { status: 404 });
1476
1424
  await t.test("not ok", async () => {
1477
1425
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/404"), require_request.FetchError, "HTTP 404: https://example.com/404");
@@ -1588,7 +1536,7 @@ var esm_default = new class FetchMock {
1588
1536
  });
1589
1537
  await t.test("ReDoS resistance (CVE-2025-68475)", async () => {
1590
1538
  const start = performance.now();
1591
- await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redos"), require_request.FetchError);
1539
+ await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redos"), SyntaxError);
1592
1540
  const elapsed = performance.now() - start;
1593
1541
  (0, node_assert.ok)(elapsed < 1e3, `Potential ReDoS vulnerability detected: ${elapsed}ms (expected < 1000ms)`);
1594
1542
  });
@@ -1,6 +1,6 @@
1
- import { n as getRemoteDocument, r as preloadedContexts, t as getDocumentLoader } from "./docloader-CYQvKbtL.mjs";
2
- import { t as FetchError } from "./request-DgAlI7RF.mjs";
3
- import { t as UrlError } from "./url-YWJbnRlf.mjs";
1
+ import { n as preloadedContexts, t as getDocumentLoader } from "./docloader-C76ldE5C.mjs";
2
+ import { t as FetchError } from "./request-BEXkv1ul.mjs";
3
+ import { t as UrlError } from "./url-m9Qzxy-Y.mjs";
4
4
  import { deepStrictEqual, ok, rejects } from "node:assert";
5
5
  import { test } from "node:test";
6
6
  //#region \0rolldown/runtime.js
@@ -1286,7 +1286,7 @@ test("getDocumentLoader()", async (t) => {
1286
1286
  type: "Object"
1287
1287
  },
1288
1288
  headers: {
1289
- "Content-Type": "application/activity+json",
1289
+ "Content-Type": "text/html; charset=utf-8",
1290
1290
  Link: "<https://example.com/object>; rel=\"alternate\"; type=\"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\""
1291
1291
  }
1292
1292
  });
@@ -1418,27 +1418,6 @@ test("getDocumentLoader()", async (t) => {
1418
1418
  }
1419
1419
  });
1420
1420
  });
1421
- esm_default.get("https://example.com/html-no-alternate", {
1422
- body: `<!DOCTYPE html>
1423
- <html>
1424
- <head>
1425
- <title>Not an ActivityPub document</title>
1426
- </head>
1427
- <body>Not found</body>
1428
- </html>`,
1429
- headers: { "Content-Type": "text/html; charset=utf-8" }
1430
- });
1431
- await t.test("HTML without ActivityPub alternate link", async () => {
1432
- await rejects(() => fetchDocumentLoader("https://example.com/html-no-alternate"), (error) => {
1433
- ok(error instanceof FetchError);
1434
- ok(error.message.includes("HTML document has no ActivityPub alternate link"));
1435
- ok(error.message.includes("Content-Type: text/html; charset=utf-8"));
1436
- deepStrictEqual(error.url, new URL("https://example.com/html-no-alternate"));
1437
- ok(error.response != null);
1438
- deepStrictEqual(error.response.headers.get("Content-Type"), "text/html; charset=utf-8");
1439
- return true;
1440
- });
1441
- });
1442
1421
  esm_default.get("https://example.com/wrong-content-type", {
1443
1422
  body: {
1444
1423
  "@context": "https://www.w3.org/ns/activitystreams",
@@ -1448,7 +1427,7 @@ test("getDocumentLoader()", async (t) => {
1448
1427
  },
1449
1428
  headers: { "Content-Type": "text/html; charset=utf-8" }
1450
1429
  });
1451
- await t.test("wrong Content-Type with JSON body", async () => {
1430
+ await t.test("Wrong Content-Type", async () => {
1452
1431
  deepStrictEqual(await fetchDocumentLoader("https://example.com/wrong-content-type"), {
1453
1432
  contextUrl: null,
1454
1433
  documentUrl: "https://example.com/wrong-content-type",
@@ -1460,37 +1439,6 @@ test("getDocumentLoader()", async (t) => {
1460
1439
  }
1461
1440
  });
1462
1441
  });
1463
- esm_default.get("https://example.com/large-html", {
1464
- body: "<!DOCTYPE html>",
1465
- headers: {
1466
- "Content-Length": String(1048577),
1467
- "Content-Type": "text/html; charset=utf-8"
1468
- }
1469
- });
1470
- await t.test("HTML Content-Length over limit", async () => {
1471
- await rejects(() => fetchDocumentLoader("https://example.com/large-html"), (error) => {
1472
- ok(error instanceof FetchError);
1473
- ok(error.message.includes("HTML document is too large to scan for an ActivityPub alternate link"));
1474
- ok(error.response != null);
1475
- deepStrictEqual(error.response.status, 200);
1476
- deepStrictEqual(error.response.headers.get("Content-Type"), "text/html; charset=utf-8");
1477
- return true;
1478
- });
1479
- });
1480
- await t.test("HTML Content-Length over limit cancels body", async () => {
1481
- let canceled = false;
1482
- const response = new Response("<!DOCTYPE html>", { headers: {
1483
- "Content-Length": String(1048577),
1484
- "Content-Type": "text/html; charset=utf-8"
1485
- } });
1486
- Object.defineProperty(response, "body", { value: { cancel: () => {
1487
- canceled = true;
1488
- } } });
1489
- await rejects(() => getRemoteDocument("https://example.com/large-html-cancel", response, () => {
1490
- throw new Error("unexpected alternate fetch");
1491
- }), FetchError);
1492
- deepStrictEqual(canceled, true);
1493
- });
1494
1442
  esm_default.get("https://example.com/404", { status: 404 });
1495
1443
  await t.test("not ok", async () => {
1496
1444
  await rejects(() => fetchDocumentLoader("https://example.com/404"), FetchError, "HTTP 404: https://example.com/404");
@@ -1607,7 +1555,7 @@ test("getDocumentLoader()", async (t) => {
1607
1555
  });
1608
1556
  await t.test("ReDoS resistance (CVE-2025-68475)", async () => {
1609
1557
  const start = performance.now();
1610
- await rejects(() => fetchDocumentLoader("https://example.com/redos"), FetchError);
1558
+ await rejects(() => fetchDocumentLoader("https://example.com/redos"), SyntaxError);
1611
1559
  const elapsed = performance.now() - start;
1612
1560
  ok(elapsed < 1e3, `Potential ReDoS vulnerability detected: ${elapsed}ms (expected < 1000ms)`);
1613
1561
  });
@@ -1,7 +1,7 @@
1
1
  import process from "node:process";
2
2
  //#region deno.json
3
3
  var name = "@fedify/vocab-runtime";
4
- var version = "2.4.0-dev.1564+5a2c6c2c";
4
+ var version = "2.4.0-dev.1570+f1b6aaa3";
5
5
  //#endregion
6
6
  //#region src/request.ts
7
7
  /**
@@ -3,7 +3,7 @@ let node_process = require("node:process");
3
3
  node_process = require_chunk.__toESM(node_process, 1);
4
4
  //#region deno.json
5
5
  var name = "@fedify/vocab-runtime";
6
- var version = "2.4.0-dev.1564+5a2c6c2c";
6
+ var version = "2.4.0-dev.1570+f1b6aaa3";
7
7
  //#endregion
8
8
  //#region src/request.ts
9
9
  /**
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-C2EiDwsr.cjs");
2
- const require_request = require("./request-DnzhAfki.cjs");
2
+ const require_request = require("./request-SworbvLc.cjs");
3
3
  let node_assert = require("node:assert");
4
4
  let node_test = require("node:test");
5
5
  let node_process = require("node:process");
@@ -1,4 +1,4 @@
1
- import { o as version, r as getUserAgent } from "./request-DgAlI7RF.mjs";
1
+ import { o as version, r as getUserAgent } from "./request-BEXkv1ul.mjs";
2
2
  import { deepStrictEqual } from "node:assert";
3
3
  import { test } from "node:test";
4
4
  import process from "node:process";
@@ -8,90 +8,6 @@ var UrlError = class extends Error {
8
8
  this.name = "UrlError";
9
9
  }
10
10
  };
11
- const PORTABLE_IRI_PATTERN = /^(ap|ap\+ef61):\/\/([^/?#]*)([^?#]*)(\?[^#]*)?(#.*)?$/i;
12
- const INVALID_PERCENT_ENCODING_PATTERN = /%(?![0-9A-Fa-f]{2})/;
13
- const DID_SCHEME_PATTERN = /^did:/i;
14
- const DID_PATTERN = /^did:[a-z0-9]+:[-A-Za-z0-9._%]+(?::[-A-Za-z0-9._%]+)*$/i;
15
- /**
16
- * Parses a JSON-LD `@id` value as an IRI.
17
- */
18
- function parseJsonLdId(id, base) {
19
- if (id == null || id.startsWith("_:")) return void 0;
20
- try {
21
- return parseIri(id, base);
22
- } catch {
23
- throw new TypeError("Invalid @id: " + id);
24
- }
25
- }
26
- /**
27
- * Parses an IRI as a URL, including FEP-ef61 portable ActivityPub IRIs.
28
- */
29
- function parseIri(iri, base) {
30
- if (iri instanceof URL) return normalizePortableUrl(iri) ?? new URL(iri.href);
31
- const portable = parsePortableIri(iri);
32
- if (portable != null) return portable;
33
- base = normalizeBaseIri(base);
34
- if (!URL.canParse(iri, base) && iri.startsWith("at://")) return parseAtUri(iri);
35
- const parsed = new URL(iri, base);
36
- return normalizePortableUrl(parsed) ?? parsed;
37
- }
38
- /**
39
- * Formats a URL as an IRI, including FEP-ef61 portable ActivityPub IRIs.
40
- */
41
- function formatIri(iri) {
42
- const parsed = parsePortableIri(iri instanceof URL ? iri.href : iri);
43
- if (parsed == null) return iri instanceof URL ? iri.href : URL.canParse(iri) ? new URL(iri).href : iri;
44
- return `ap+ef61://${decodePortableAuthority(parsed.host)}${parsed.pathname}${parsed.search}${parsed.hash}`;
45
- }
46
- /**
47
- * Checks whether two IRIs have the same origin.
48
- */
49
- function haveSameIriOrigin(left, right) {
50
- return getComparableIriOrigin(left) === getComparableIriOrigin(right);
51
- }
52
- function getComparableIriOrigin(iri) {
53
- iri = normalizePortableUrl(iri) ?? iri;
54
- if (iri.origin !== "null") return iri.origin;
55
- if (iri.host !== "") {
56
- const host = iri.protocol === "ap+ef61:" ? encodeURIComponent(decodePortableAuthority(iri.host).replace(DID_SCHEME_PATTERN, "did:")) : iri.host;
57
- return `${iri.protocol}//${host}`;
58
- }
59
- return iri.href;
60
- }
61
- function parsePortableIri(iri) {
62
- const match = iri.match(PORTABLE_IRI_PATTERN);
63
- if (match == null) return null;
64
- const authority = decodePortableAuthority(match[2]);
65
- if (!DID_PATTERN.test(authority)) throw new TypeError("Invalid portable ActivityPub IRI authority.");
66
- if (match[3] === "") throw new TypeError("Invalid portable ActivityPub IRI path.");
67
- return new URL(`ap+ef61://${encodeURIComponent(authority)}${match[3]}${match[4] ?? ""}${match[5] ?? ""}`);
68
- }
69
- function normalizePortableUrl(iri) {
70
- if (iri.protocol !== "ap:" && iri.protocol !== "ap+ef61:") return null;
71
- return parsePortableIri(`ap+ef61://${iri.host}${iri.pathname}${iri.search}${iri.hash}`);
72
- }
73
- function normalizeBaseIri(base) {
74
- if (base == null) return void 0;
75
- if (base instanceof URL) return normalizePortableUrl(base) ?? base;
76
- return parsePortableIri(base) ?? (base.startsWith("at://") && !URL.canParse(".", base) ? parseAtUri(base) : base);
77
- }
78
- function decodePortableAuthority(authority) {
79
- if (INVALID_PERCENT_ENCODING_PATTERN.test(authority)) throw new TypeError("Invalid portable ActivityPub IRI authority.");
80
- if (DID_SCHEME_PATTERN.test(authority)) {
81
- const decoded = authority.replace(/%25/gi, "%");
82
- if (INVALID_PERCENT_ENCODING_PATTERN.test(decoded)) throw new TypeError("Invalid portable ActivityPub IRI authority.");
83
- return decoded;
84
- }
85
- const decoded = authority.replace(/%(25|3A)/gi, (match) => match.toLowerCase() === "%3a" ? ":" : "%");
86
- if (INVALID_PERCENT_ENCODING_PATTERN.test(decoded)) throw new TypeError("Invalid portable ActivityPub IRI authority.");
87
- return decoded;
88
- }
89
- function parseAtUri(uri) {
90
- const index = uri.indexOf("/", 5);
91
- const authority = index >= 0 ? uri.slice(5, index) : uri.slice(5);
92
- const path = index >= 0 ? uri.slice(index) : "";
93
- return new URL("at://" + encodeURIComponent(authority) + path);
94
- }
95
11
  /**
96
12
  * Validates a URL to prevent SSRF attacks.
97
13
  */
@@ -270,18 +186,6 @@ Object.defineProperty(exports, "expandIPv6Address", {
270
186
  return expandIPv6Address;
271
187
  }
272
188
  });
273
- Object.defineProperty(exports, "formatIri", {
274
- enumerable: true,
275
- get: function() {
276
- return formatIri;
277
- }
278
- });
279
- Object.defineProperty(exports, "haveSameIriOrigin", {
280
- enumerable: true,
281
- get: function() {
282
- return haveSameIriOrigin;
283
- }
284
- });
285
189
  Object.defineProperty(exports, "isValidPublicIPv4Address", {
286
190
  enumerable: true,
287
191
  get: function() {
@@ -294,18 +198,6 @@ Object.defineProperty(exports, "isValidPublicIPv6Address", {
294
198
  return isValidPublicIPv6Address;
295
199
  }
296
200
  });
297
- Object.defineProperty(exports, "parseIri", {
298
- enumerable: true,
299
- get: function() {
300
- return parseIri;
301
- }
302
- });
303
- Object.defineProperty(exports, "parseJsonLdId", {
304
- enumerable: true,
305
- get: function() {
306
- return parseJsonLdId;
307
- }
308
- });
309
201
  Object.defineProperty(exports, "validatePublicUrl", {
310
202
  enumerable: true,
311
203
  get: function() {