@fedify/vocab-runtime 2.4.0-dev.1570 → 2.4.0-dev.1571
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 +2 -1
- package/dist/docloader-DnUMWHaJ.d.cts +202 -0
- package/dist/docloader-xRGn1azD.d.ts +202 -0
- package/dist/internal/jsonld-cache.cjs +279 -0
- package/dist/internal/jsonld-cache.d.cts +48 -0
- package/dist/internal/jsonld-cache.d.ts +48 -0
- package/dist/internal/jsonld-cache.js +275 -0
- package/dist/mod.cjs +178 -190
- package/dist/mod.d.cts +59 -190
- package/dist/mod.d.ts +59 -190
- package/dist/mod.js +165 -184
- package/dist/tests/decimal.test.cjs +3 -3
- package/dist/tests/decimal.test.mjs +3 -3
- package/dist/tests/{docloader-C76ldE5C.mjs → docloader-DHSAmRW2.mjs} +97 -10
- package/dist/tests/{docloader-mvgIWKI7.cjs → docloader-DkiPIIAk.cjs} +102 -9
- package/dist/tests/docloader.test.cjs +58 -6
- package/dist/tests/docloader.test.mjs +58 -6
- package/dist/tests/jsonld-cache.test.cjs +652 -0
- package/dist/tests/jsonld-cache.test.d.cts +1 -0
- package/dist/tests/jsonld-cache.test.d.mts +1 -0
- package/dist/tests/jsonld-cache.test.mjs +651 -0
- package/dist/tests/{key-CrrK9mYh.mjs → key-CDGDH_vC.mjs} +69 -1
- package/dist/tests/{key-pMmqUKuo.cjs → key-_wXwomh_.cjs} +86 -0
- package/dist/tests/key.test.cjs +48 -1
- package/dist/tests/key.test.mjs +48 -1
- package/dist/tests/{request-BEXkv1ul.mjs → request-CYFeP37O.mjs} +1 -1
- package/dist/tests/{request-SworbvLc.cjs → request-D3H8nWtX.cjs} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.mjs +1 -1
- package/dist/tests/{url-C20FhC7p.cjs → url-2XwVbUS_.cjs} +108 -0
- package/dist/tests/{url-m9Qzxy-Y.mjs → url-YWJbnRlf.mjs} +85 -1
- package/dist/tests/url.test.cjs +104 -1
- package/dist/tests/url.test.mjs +105 -2
- package/dist/url-BAdyyqAa.cjs +315 -0
- package/dist/url-BuxPHxK2.js +261 -0
- package/package.json +11 -1
- package/src/contexts/fep-7aa9.json +24 -0
- package/src/contexts.ts +2 -0
- package/src/docloader.test.ts +102 -6
- package/src/docloader.ts +76 -8
- package/src/internal/jsonld-cache.ts +538 -0
- package/src/jsonld-cache.test.ts +554 -0
- package/src/key.test.ts +86 -0
- package/src/key.ts +125 -0
- package/src/mod.ts +8 -0
- package/src/url.test.ts +252 -1
- package/src/url.ts +141 -0
- package/tsdown.config.ts +6 -1
package/dist/mod.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
|
|
2
|
+
import { a as isValidPublicIPv4Address, c as parseJsonLdId, i as haveSameIriOrigin, l as validatePublicUrl, n as expandIPv6Address, o as isValidPublicIPv6Address, r as formatIri, s as parseIri, t as UrlError } from "./url-BuxPHxK2.js";
|
|
2
3
|
import { getLogger } from "@logtape/logtape";
|
|
3
4
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
4
5
|
import process from "node:process";
|
|
5
|
-
import { lookup } from "node:dns/promises";
|
|
6
|
-
import { isIP } from "node:net";
|
|
7
6
|
import { Integer, Sequence } from "asn1js";
|
|
8
7
|
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
|
|
9
8
|
import { decodeBase64Url } from "byte-encodings/base64url";
|
|
@@ -4284,6 +4283,28 @@ const preloadedContexts = {
|
|
|
4284
4283
|
"@type": "@id"
|
|
4285
4284
|
}
|
|
4286
4285
|
} },
|
|
4286
|
+
"https://w3id.org/fep/7aa9": { "@context": {
|
|
4287
|
+
"FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
|
|
4288
|
+
"FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
|
|
4289
|
+
"FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
|
|
4290
|
+
"FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
|
|
4291
|
+
"topic": {
|
|
4292
|
+
"@id": "https://w3id.org/fep/7aa9#topic",
|
|
4293
|
+
"@type": "@id"
|
|
4294
|
+
},
|
|
4295
|
+
"featuredObject": {
|
|
4296
|
+
"@id": "https://w3id.org/fep/7aa9#featuredObject",
|
|
4297
|
+
"@type": "@id"
|
|
4298
|
+
},
|
|
4299
|
+
"canFeature": {
|
|
4300
|
+
"@id": "https://w3id.org/fep/7aa9#canFeature",
|
|
4301
|
+
"@type": "@id"
|
|
4302
|
+
},
|
|
4303
|
+
"featureAuthorization": {
|
|
4304
|
+
"@id": "https://w3id.org/fep/7aa9#featureAuthorization",
|
|
4305
|
+
"@type": "@id"
|
|
4306
|
+
}
|
|
4307
|
+
} },
|
|
4287
4308
|
"https://join-lemmy.org/context.json": { "@context": ["https://w3id.org/security/v1", {
|
|
4288
4309
|
"as": "https://www.w3.org/ns/activitystreams#",
|
|
4289
4310
|
"lemmy": "https://join-lemmy.org/ns#",
|
|
@@ -4342,7 +4363,7 @@ const preloadedContexts = {
|
|
|
4342
4363
|
//#endregion
|
|
4343
4364
|
//#region deno.json
|
|
4344
4365
|
var name = "@fedify/vocab-runtime";
|
|
4345
|
-
var version = "2.4.0-dev.
|
|
4366
|
+
var version = "2.4.0-dev.1571+31e2786b";
|
|
4346
4367
|
//#endregion
|
|
4347
4368
|
//#region src/link.ts
|
|
4348
4369
|
const parametersNeedLowerCase = ["rel", "type"];
|
|
@@ -4595,179 +4616,6 @@ function logRequest(logger, request) {
|
|
|
4595
4616
|
});
|
|
4596
4617
|
}
|
|
4597
4618
|
//#endregion
|
|
4598
|
-
//#region src/url.ts
|
|
4599
|
-
var UrlError = class extends Error {
|
|
4600
|
-
constructor(message) {
|
|
4601
|
-
super(message);
|
|
4602
|
-
this.name = "UrlError";
|
|
4603
|
-
}
|
|
4604
|
-
};
|
|
4605
|
-
/**
|
|
4606
|
-
* Validates a URL to prevent SSRF attacks.
|
|
4607
|
-
*/
|
|
4608
|
-
async function validatePublicUrl(url) {
|
|
4609
|
-
const parsed = new URL(url);
|
|
4610
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsed.protocol}`);
|
|
4611
|
-
let hostname = parsed.hostname;
|
|
4612
|
-
if (hostname.startsWith("[") && hostname.endsWith("]")) hostname = hostname.slice(1, -1);
|
|
4613
|
-
if (hostname === "localhost") throw new UrlError("Localhost is not allowed");
|
|
4614
|
-
const hostnameFamily = isIP(hostname);
|
|
4615
|
-
if (hostnameFamily !== 0) {
|
|
4616
|
-
validatePublicIpAddress(hostname, hostnameFamily);
|
|
4617
|
-
return;
|
|
4618
|
-
}
|
|
4619
|
-
if ("Deno" in globalThis && !isIP(hostname)) {
|
|
4620
|
-
if ((await Deno.permissions.query({ name: "net" })).state !== "granted") return;
|
|
4621
|
-
}
|
|
4622
|
-
if ("Bun" in globalThis) {
|
|
4623
|
-
if (hostname === "example.com" || hostname.endsWith(".example.com")) return;
|
|
4624
|
-
else if (hostname === "fedify-test.internal") throw new UrlError("Invalid or private address: fedify-test.internal");
|
|
4625
|
-
}
|
|
4626
|
-
let addresses;
|
|
4627
|
-
try {
|
|
4628
|
-
addresses = await lookup(hostname, { all: true });
|
|
4629
|
-
} catch {
|
|
4630
|
-
addresses = [];
|
|
4631
|
-
}
|
|
4632
|
-
for (const { address, family } of addresses) validatePublicIpAddress(address, family);
|
|
4633
|
-
}
|
|
4634
|
-
function validatePublicIpAddress(address, family) {
|
|
4635
|
-
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
|
|
4636
|
-
throw new UrlError(`Invalid or private address: ${address}`);
|
|
4637
|
-
}
|
|
4638
|
-
function isValidPublicIPv4Address(address) {
|
|
4639
|
-
const parts = parseIPv4Address(address);
|
|
4640
|
-
if (parts == null) return false;
|
|
4641
|
-
const value = ipv4PartsToNumber(parts);
|
|
4642
|
-
return !nonPublicIPv4Prefixes.some(({ base, prefix }) => matchesIPv4Prefix(value, base, prefix));
|
|
4643
|
-
}
|
|
4644
|
-
function isValidPublicIPv6Address(address) {
|
|
4645
|
-
const words = parseIPv6Address(address);
|
|
4646
|
-
if (words == null) return false;
|
|
4647
|
-
if (nonPublicIPv6Prefixes.some(({ words: prefixWords, prefix }) => matchesIPv6Prefix(words, prefixWords, prefix))) return false;
|
|
4648
|
-
for (const { extractIPv4, prefix, words: prefixWords } of ipv6WithIPv4Prefixes) {
|
|
4649
|
-
if (!matchesIPv6Prefix(words, prefixWords, prefix)) continue;
|
|
4650
|
-
const ipv4Address = extractIPv4(words);
|
|
4651
|
-
if (ipv4Address != null && !isValidPublicIPv4Address(ipv4Address)) return false;
|
|
4652
|
-
}
|
|
4653
|
-
return true;
|
|
4654
|
-
}
|
|
4655
|
-
function expandIPv6Address(address) {
|
|
4656
|
-
address = address.toLowerCase();
|
|
4657
|
-
const ipv4Delimiter = address.lastIndexOf(":");
|
|
4658
|
-
if (address.includes(".") && ipv4Delimiter >= 0) {
|
|
4659
|
-
const ipv4Parts = parseIPv4Address(address.substring(ipv4Delimiter + 1));
|
|
4660
|
-
if (ipv4Parts == null) return address;
|
|
4661
|
-
const high = (ipv4Parts[0] << 8) + ipv4Parts[1];
|
|
4662
|
-
const low = (ipv4Parts[2] << 8) + ipv4Parts[3];
|
|
4663
|
-
address = address.substring(0, ipv4Delimiter + 1) + high.toString(16) + ":" + low.toString(16);
|
|
4664
|
-
}
|
|
4665
|
-
if (address === "::") return "0000:0000:0000:0000:0000:0000:0000:0000";
|
|
4666
|
-
if (address.startsWith("::")) address = "0000" + address;
|
|
4667
|
-
if (address.endsWith("::")) address = address + "0000";
|
|
4668
|
-
address = address.replace("::", ":0000".repeat(8 - (address.match(/:/g) || []).length) + ":");
|
|
4669
|
-
return address.split(":").map((part) => part.padStart(4, "0")).join(":");
|
|
4670
|
-
}
|
|
4671
|
-
const nonPublicIPv4Prefixes = [
|
|
4672
|
-
ipv4Prefix("0.0.0.0/8", "RFC 6890"),
|
|
4673
|
-
ipv4Prefix("10.0.0.0/8", "RFC 1918"),
|
|
4674
|
-
ipv4Prefix("100.64.0.0/10", "RFC 6598"),
|
|
4675
|
-
ipv4Prefix("127.0.0.0/8", "RFC 1122"),
|
|
4676
|
-
ipv4Prefix("169.254.0.0/16", "RFC 3927"),
|
|
4677
|
-
ipv4Prefix("172.16.0.0/12", "RFC 1918"),
|
|
4678
|
-
ipv4Prefix("192.0.0.0/24", "RFC 6890"),
|
|
4679
|
-
ipv4Prefix("192.0.2.0/24", "RFC 5737"),
|
|
4680
|
-
ipv4Prefix("192.88.99.0/24", "RFC 7526"),
|
|
4681
|
-
ipv4Prefix("192.168.0.0/16", "RFC 1918"),
|
|
4682
|
-
ipv4Prefix("198.18.0.0/15", "RFC 2544"),
|
|
4683
|
-
ipv4Prefix("198.51.100.0/24", "RFC 5737"),
|
|
4684
|
-
ipv4Prefix("203.0.113.0/24", "RFC 5737"),
|
|
4685
|
-
ipv4Prefix("224.0.0.0/4", "RFC 5771"),
|
|
4686
|
-
ipv4Prefix("240.0.0.0/4", "RFC 1112")
|
|
4687
|
-
];
|
|
4688
|
-
const nonPublicIPv6Prefixes = [
|
|
4689
|
-
ipv6Prefix("::/16", "RFC 4291"),
|
|
4690
|
-
ipv6Prefix("2001::/32", "RFC 4380"),
|
|
4691
|
-
ipv6Prefix("2002::/16", "RFC 3056"),
|
|
4692
|
-
ipv6Prefix("64:ff9b:1::/48", "RFC 8215"),
|
|
4693
|
-
ipv6Prefix("fc00::/7", "RFC 4193"),
|
|
4694
|
-
ipv6Prefix("fe80::/10", "RFC 4291"),
|
|
4695
|
-
ipv6Prefix("ff00::/8", "RFC 4291")
|
|
4696
|
-
];
|
|
4697
|
-
const ipv6WithIPv4Prefixes = [{
|
|
4698
|
-
...ipv6Prefix("64:ff9b::/96", "RFC 6052"),
|
|
4699
|
-
extractIPv4: (words) => ipv4FromWords(words[6], words[7])
|
|
4700
|
-
}];
|
|
4701
|
-
function ipv4Prefix(cidr, rfc) {
|
|
4702
|
-
const [address, prefixText] = cidr.split("/");
|
|
4703
|
-
const prefix = parseInt(prefixText, 10);
|
|
4704
|
-
const parts = parseIPv4Address(address);
|
|
4705
|
-
if (parts == null || !Number.isInteger(prefix) || prefix < 0 || prefix > 32) throw new Error(`Invalid IPv4 prefix: ${cidr}`);
|
|
4706
|
-
return {
|
|
4707
|
-
cidr,
|
|
4708
|
-
base: ipv4PartsToNumber(parts),
|
|
4709
|
-
prefix,
|
|
4710
|
-
rfc
|
|
4711
|
-
};
|
|
4712
|
-
}
|
|
4713
|
-
function ipv6Prefix(cidr, rfc) {
|
|
4714
|
-
const [address, prefixText] = cidr.split("/");
|
|
4715
|
-
const prefix = parseInt(prefixText, 10);
|
|
4716
|
-
const words = parseIPv6Address(address);
|
|
4717
|
-
if (words == null || !Number.isInteger(prefix) || prefix < 0 || prefix > 128) throw new Error(`Invalid IPv6 prefix: ${cidr}`);
|
|
4718
|
-
return {
|
|
4719
|
-
cidr,
|
|
4720
|
-
words,
|
|
4721
|
-
prefix,
|
|
4722
|
-
rfc
|
|
4723
|
-
};
|
|
4724
|
-
}
|
|
4725
|
-
function parseIPv4Address(address) {
|
|
4726
|
-
const parts = address.split(".").map((part) => {
|
|
4727
|
-
if (!/^\d+$/.test(part)) return NaN;
|
|
4728
|
-
return parseInt(part, 10);
|
|
4729
|
-
});
|
|
4730
|
-
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return null;
|
|
4731
|
-
return parts;
|
|
4732
|
-
}
|
|
4733
|
-
function parseIPv6Address(address) {
|
|
4734
|
-
const parts = expandIPv6Address(address).split(":");
|
|
4735
|
-
if (parts.length !== 8) return null;
|
|
4736
|
-
const words = parts.map((part) => {
|
|
4737
|
-
if (!/^[0-9a-f]{1,4}$/i.test(part)) return NaN;
|
|
4738
|
-
return parseInt(part, 16);
|
|
4739
|
-
});
|
|
4740
|
-
if (words.some((word) => !Number.isInteger(word) || word < 0 || word > 65535)) return null;
|
|
4741
|
-
return words;
|
|
4742
|
-
}
|
|
4743
|
-
function ipv4PartsToNumber(parts) {
|
|
4744
|
-
return parts[0] * 2 ** 24 + parts[1] * 2 ** 16 + parts[2] * 2 ** 8 + parts[3];
|
|
4745
|
-
}
|
|
4746
|
-
function ipv4FromWords(highWord, lowWord) {
|
|
4747
|
-
return [
|
|
4748
|
-
highWord >> 8,
|
|
4749
|
-
highWord & 255,
|
|
4750
|
-
lowWord >> 8,
|
|
4751
|
-
lowWord & 255
|
|
4752
|
-
].join(".");
|
|
4753
|
-
}
|
|
4754
|
-
function matchesIPv4Prefix(address, prefixBase, prefixLength) {
|
|
4755
|
-
const blockSize = 2 ** (32 - prefixLength);
|
|
4756
|
-
return Math.floor(address / blockSize) === Math.floor(prefixBase / blockSize);
|
|
4757
|
-
}
|
|
4758
|
-
function matchesIPv6Prefix(address, prefixWords, prefixLength) {
|
|
4759
|
-
let remaining = prefixLength;
|
|
4760
|
-
for (let i = 0; i < 8 && remaining > 0; i++) if (remaining >= 16) {
|
|
4761
|
-
if (address[i] !== prefixWords[i]) return false;
|
|
4762
|
-
remaining -= 16;
|
|
4763
|
-
} else {
|
|
4764
|
-
const mask = 65535 << 16 - remaining & 65535;
|
|
4765
|
-
if ((address[i] & mask) !== (prefixWords[i] & mask)) return false;
|
|
4766
|
-
remaining = 0;
|
|
4767
|
-
}
|
|
4768
|
-
return true;
|
|
4769
|
-
}
|
|
4770
|
-
//#endregion
|
|
4771
4619
|
//#region src/docloader.ts
|
|
4772
4620
|
const logger = getLogger([
|
|
4773
4621
|
"fedify",
|
|
@@ -4775,6 +4623,66 @@ const logger = getLogger([
|
|
|
4775
4623
|
"docloader"
|
|
4776
4624
|
]);
|
|
4777
4625
|
const DEFAULT_MAX_REDIRECTION = 20;
|
|
4626
|
+
const MAX_HTML_SIZE = 1024 * 1024;
|
|
4627
|
+
function createResponseMetadata(response) {
|
|
4628
|
+
return new Response(null, {
|
|
4629
|
+
headers: response.headers,
|
|
4630
|
+
status: response.status,
|
|
4631
|
+
statusText: response.statusText
|
|
4632
|
+
});
|
|
4633
|
+
}
|
|
4634
|
+
async function cancelResponseBody(response) {
|
|
4635
|
+
if (response.body != null) await response.body.cancel();
|
|
4636
|
+
}
|
|
4637
|
+
async function readBoundedText(response, maxBytes) {
|
|
4638
|
+
const contentLength = response.headers.get("Content-Length");
|
|
4639
|
+
if (contentLength != null) {
|
|
4640
|
+
const size = Number(contentLength);
|
|
4641
|
+
if (size > maxBytes) {
|
|
4642
|
+
await cancelResponseBody(response);
|
|
4643
|
+
return {
|
|
4644
|
+
text: "",
|
|
4645
|
+
size,
|
|
4646
|
+
tooLarge: true
|
|
4647
|
+
};
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
if (response.body == null) return {
|
|
4651
|
+
text: "",
|
|
4652
|
+
size: 0,
|
|
4653
|
+
tooLarge: false
|
|
4654
|
+
};
|
|
4655
|
+
const reader = response.body.getReader();
|
|
4656
|
+
const decoder = new TextDecoder();
|
|
4657
|
+
let text = "";
|
|
4658
|
+
let size = 0;
|
|
4659
|
+
try {
|
|
4660
|
+
while (true) {
|
|
4661
|
+
const result = await reader.read();
|
|
4662
|
+
if (result.done) break;
|
|
4663
|
+
const chunkSize = result.value.byteLength;
|
|
4664
|
+
if (size + chunkSize > maxBytes) {
|
|
4665
|
+
size += chunkSize;
|
|
4666
|
+
await reader.cancel();
|
|
4667
|
+
return {
|
|
4668
|
+
text: "",
|
|
4669
|
+
size,
|
|
4670
|
+
tooLarge: true
|
|
4671
|
+
};
|
|
4672
|
+
}
|
|
4673
|
+
size += chunkSize;
|
|
4674
|
+
text += decoder.decode(result.value, { stream: true });
|
|
4675
|
+
}
|
|
4676
|
+
text += decoder.decode();
|
|
4677
|
+
return {
|
|
4678
|
+
text,
|
|
4679
|
+
size,
|
|
4680
|
+
tooLarge: false
|
|
4681
|
+
};
|
|
4682
|
+
} finally {
|
|
4683
|
+
reader.releaseLock();
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4778
4686
|
/**
|
|
4779
4687
|
* Gets a {@link RemoteDocument} from the given response.
|
|
4780
4688
|
* @param url The URL of the document to load.
|
|
@@ -4829,19 +4737,19 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
4829
4737
|
}
|
|
4830
4738
|
let document;
|
|
4831
4739
|
if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
|
|
4832
|
-
const
|
|
4833
|
-
const html = await response
|
|
4834
|
-
if (html.
|
|
4740
|
+
const errorResponse = createResponseMetadata(response);
|
|
4741
|
+
const html = await readBoundedText(response, MAX_HTML_SIZE);
|
|
4742
|
+
if (html.tooLarge) {
|
|
4835
4743
|
logger.warn("HTML response too large, skipping alternate link discovery: {url}", {
|
|
4836
4744
|
url: documentUrl,
|
|
4837
|
-
size: html.
|
|
4745
|
+
size: html.size
|
|
4838
4746
|
});
|
|
4839
|
-
document
|
|
4747
|
+
throw new FetchError(documentUrl, `HTML document is too large to scan for an ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
|
|
4840
4748
|
} else {
|
|
4841
4749
|
const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
|
|
4842
4750
|
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
4843
4751
|
let tagMatch;
|
|
4844
|
-
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
4752
|
+
while ((tagMatch = tagPattern.exec(html.text)) !== null) {
|
|
4845
4753
|
const tagContent = tagMatch[2];
|
|
4846
4754
|
let attrMatch;
|
|
4847
4755
|
const attribs = {};
|
|
@@ -4858,7 +4766,12 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
4858
4766
|
return await fetch(new URL(attribs.href, docUrl).href);
|
|
4859
4767
|
}
|
|
4860
4768
|
}
|
|
4861
|
-
|
|
4769
|
+
try {
|
|
4770
|
+
document = JSON.parse(html.text);
|
|
4771
|
+
} catch (error) {
|
|
4772
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
4773
|
+
throw new FetchError(documentUrl, `HTML document has no ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
|
|
4774
|
+
}
|
|
4862
4775
|
}
|
|
4863
4776
|
} else document = await response.json();
|
|
4864
4777
|
logger.debug("Fetched document: {status} {url} {headers}", {
|
|
@@ -5336,6 +5249,11 @@ const algorithms = {
|
|
|
5336
5249
|
},
|
|
5337
5250
|
"1.3.101.112": "Ed25519"
|
|
5338
5251
|
};
|
|
5252
|
+
const DID_KEY_PREFIX = "did:key:";
|
|
5253
|
+
const ED25519_PUBLIC_KEY_MULTICODEC = 237;
|
|
5254
|
+
const ED25519_PUBLIC_KEY_LENGTH = 32;
|
|
5255
|
+
const DID_KEY_PATTERN = /^did:key:([^/?#]+)$/;
|
|
5256
|
+
const DID_KEY_VERIFICATION_METHOD_PATTERN = /^did:key:([^/?#]+)#([^/?#]+)$/;
|
|
5339
5257
|
/**
|
|
5340
5258
|
* Imports a PEM-SPKI formatted public key.
|
|
5341
5259
|
* @param pem The PEM-SPKI formatted public key.
|
|
@@ -5397,6 +5315,69 @@ const PKCS1_HEADER = /^\s*-----BEGIN\s+RSA\s+PUBLIC\s+KEY-----\s*\n/;
|
|
|
5397
5315
|
function importPem(pem) {
|
|
5398
5316
|
return PKCS1_HEADER.test(pem) ? importPkcs1(pem) : importSpki(pem);
|
|
5399
5317
|
}
|
|
5318
|
+
function decodeEd25519DidKeyMultibase(multibaseKey) {
|
|
5319
|
+
if (!multibaseKey.startsWith("z")) throw new TypeError("did:key must use base58-btc Multibase encoding.");
|
|
5320
|
+
let decoded;
|
|
5321
|
+
try {
|
|
5322
|
+
decoded = decodeMultibase(multibaseKey);
|
|
5323
|
+
} catch (error) {
|
|
5324
|
+
throw new TypeError("Invalid did:key Multibase encoding.", { cause: error });
|
|
5325
|
+
}
|
|
5326
|
+
const { code } = getMulticodecPrefix(decoded);
|
|
5327
|
+
if (code !== ED25519_PUBLIC_KEY_MULTICODEC) throw new TypeError("Unsupported did:key type: 0x" + code.toString(16));
|
|
5328
|
+
const content = removeMulticodecPrefix(decoded);
|
|
5329
|
+
if (content.length !== ED25519_PUBLIC_KEY_LENGTH) throw new TypeError("Invalid Ed25519 did:key length.");
|
|
5330
|
+
return content;
|
|
5331
|
+
}
|
|
5332
|
+
/**
|
|
5333
|
+
* Imports an Ed25519 `did:key` DID.
|
|
5334
|
+
*
|
|
5335
|
+
* @param did The `did:key` DID.
|
|
5336
|
+
* @returns The imported Ed25519 public key.
|
|
5337
|
+
* @throws {TypeError} If the DID is malformed or uses an unsupported key type.
|
|
5338
|
+
* @since 2.4.0
|
|
5339
|
+
*/
|
|
5340
|
+
async function importDidKey(did) {
|
|
5341
|
+
const match = (did instanceof URL ? did.href : did).match(DID_KEY_PATTERN);
|
|
5342
|
+
if (match == null) throw new TypeError("Invalid did:key DID.");
|
|
5343
|
+
const content = decodeEd25519DidKeyMultibase(match[1]);
|
|
5344
|
+
return await crypto.subtle.importKey("raw", content.slice(), "Ed25519", true, ["verify"]);
|
|
5345
|
+
}
|
|
5346
|
+
/**
|
|
5347
|
+
* Exports an Ed25519 public key as a `did:key` DID.
|
|
5348
|
+
*
|
|
5349
|
+
* @param key The Ed25519 public key.
|
|
5350
|
+
* @returns The `did:key` DID.
|
|
5351
|
+
* @throws {TypeError} If the key is invalid or unsupported.
|
|
5352
|
+
* @since 2.4.0
|
|
5353
|
+
*/
|
|
5354
|
+
async function exportDidKey(key) {
|
|
5355
|
+
if (key.algorithm.name !== "Ed25519") throw new TypeError("Unsupported key type: " + JSON.stringify(key.algorithm));
|
|
5356
|
+
return DID_KEY_PREFIX + await exportMultibaseKey(key);
|
|
5357
|
+
}
|
|
5358
|
+
/**
|
|
5359
|
+
* Parses an Ed25519 `did:key` verification method DID URL.
|
|
5360
|
+
*
|
|
5361
|
+
* @param didUrl The `did:key` DID URL.
|
|
5362
|
+
* @returns The parsed verification method.
|
|
5363
|
+
* @throws {TypeError} If the DID URL is malformed, unsupported, or its
|
|
5364
|
+
* fragment does not identify the same key as the DID.
|
|
5365
|
+
* @since 2.4.0
|
|
5366
|
+
*/
|
|
5367
|
+
async function parseDidKeyVerificationMethod(didUrl) {
|
|
5368
|
+
const didUrlString = didUrl instanceof URL ? didUrl.href : didUrl;
|
|
5369
|
+
const match = didUrlString.match(DID_KEY_VERIFICATION_METHOD_PATTERN);
|
|
5370
|
+
if (match == null) throw new TypeError("Invalid did:key verification method.");
|
|
5371
|
+
const [, publicKeyMultibase, fragment] = match;
|
|
5372
|
+
if (publicKeyMultibase !== fragment) throw new TypeError("Invalid did:key verification method fragment.");
|
|
5373
|
+
const publicKey = await importDidKey(DID_KEY_PREFIX + publicKeyMultibase);
|
|
5374
|
+
return {
|
|
5375
|
+
id: new URL(didUrlString),
|
|
5376
|
+
controller: new URL(DID_KEY_PREFIX + publicKeyMultibase),
|
|
5377
|
+
publicKeyMultibase,
|
|
5378
|
+
publicKey
|
|
5379
|
+
};
|
|
5380
|
+
}
|
|
5400
5381
|
/**
|
|
5401
5382
|
* Imports a [Multibase]-encoded public key.
|
|
5402
5383
|
*
|
|
@@ -5563,4 +5544,4 @@ LanguageString.prototype[Symbol.for("nodejs.util.inspect.custom")] = function(_d
|
|
|
5563
5544
|
return `<${this.locale.baseName}> ${inspect(this.toString(), options)}`;
|
|
5564
5545
|
};
|
|
5565
5546
|
//#endregion
|
|
5566
|
-
export { FetchError, LanguageString, UrlError, canParseDecimal, createActivityPubRequest, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportMultibaseKey, exportSpki, getDocumentLoader, getRemoteDocument, getUserAgent, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, preloadedContexts, validatePublicUrl };
|
|
5547
|
+
export { FetchError, LanguageString, UrlError, canParseDecimal, createActivityPubRequest, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, formatIri, getDocumentLoader, getRemoteDocument, getUserAgent, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseIri, parseJsonLdId, preloadedContexts, validatePublicUrl };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require("./chunk-C2EiDwsr.cjs");
|
|
2
|
-
require("./docloader-
|
|
3
|
-
require("./url-
|
|
4
|
-
require("./key-
|
|
2
|
+
require("./docloader-DkiPIIAk.cjs");
|
|
3
|
+
require("./url-2XwVbUS_.cjs");
|
|
4
|
+
require("./key-_wXwomh_.cjs");
|
|
5
5
|
require("./multibase-Bz_UUDtL.cjs");
|
|
6
6
|
require("./langstr-CbAxaeEZ.cjs");
|
|
7
7
|
let node_assert = require("node:assert");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "./docloader-
|
|
2
|
-
import "./url-
|
|
3
|
-
import "./key-
|
|
1
|
+
import "./docloader-DHSAmRW2.mjs";
|
|
2
|
+
import "./url-YWJbnRlf.mjs";
|
|
3
|
+
import "./key-CDGDH_vC.mjs";
|
|
4
4
|
import "./multibase-B4bvakyA.mjs";
|
|
5
5
|
import "./langstr-Di5AvKpB.mjs";
|
|
6
6
|
import { deepStrictEqual, throws } from "node:assert";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-
|
|
1
|
+
import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-CYFeP37O.mjs";
|
|
2
2
|
import { t as HttpHeaderLink } from "./link-NUUWCdnK.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { l as validatePublicUrl, t as UrlError } from "./url-YWJbnRlf.mjs";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
6
6
|
//#endregion
|
|
@@ -4277,6 +4277,28 @@ 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
|
+
} },
|
|
4280
4302
|
"https://join-lemmy.org/context.json": { "@context": ["https://w3id.org/security/v1", {
|
|
4281
4303
|
"as": "https://www.w3.org/ns/activitystreams#",
|
|
4282
4304
|
"lemmy": "https://join-lemmy.org/ns#",
|
|
@@ -4340,6 +4362,66 @@ const logger = getLogger([
|
|
|
4340
4362
|
"docloader"
|
|
4341
4363
|
]);
|
|
4342
4364
|
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
|
+
}
|
|
4343
4425
|
/**
|
|
4344
4426
|
* Gets a {@link RemoteDocument} from the given response.
|
|
4345
4427
|
* @param url The URL of the document to load.
|
|
@@ -4394,19 +4476,19 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
4394
4476
|
}
|
|
4395
4477
|
let document;
|
|
4396
4478
|
if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
|
|
4397
|
-
const
|
|
4398
|
-
const html = await response
|
|
4399
|
-
if (html.
|
|
4479
|
+
const errorResponse = createResponseMetadata(response);
|
|
4480
|
+
const html = await readBoundedText(response, MAX_HTML_SIZE);
|
|
4481
|
+
if (html.tooLarge) {
|
|
4400
4482
|
logger.warn("HTML response too large, skipping alternate link discovery: {url}", {
|
|
4401
4483
|
url: documentUrl,
|
|
4402
|
-
size: html.
|
|
4484
|
+
size: html.size
|
|
4403
4485
|
});
|
|
4404
|
-
document
|
|
4486
|
+
throw new FetchError(documentUrl, `HTML document is too large to scan for an ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
|
|
4405
4487
|
} else {
|
|
4406
4488
|
const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
|
|
4407
4489
|
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
4408
4490
|
let tagMatch;
|
|
4409
|
-
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
4491
|
+
while ((tagMatch = tagPattern.exec(html.text)) !== null) {
|
|
4410
4492
|
const tagContent = tagMatch[2];
|
|
4411
4493
|
let attrMatch;
|
|
4412
4494
|
const attribs = {};
|
|
@@ -4423,7 +4505,12 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
4423
4505
|
return await fetch(new URL(attribs.href, docUrl).href);
|
|
4424
4506
|
}
|
|
4425
4507
|
}
|
|
4426
|
-
|
|
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
|
+
}
|
|
4427
4514
|
}
|
|
4428
4515
|
} else document = await response.json();
|
|
4429
4516
|
logger.debug("Fetched document: {status} {url} {headers}", {
|
|
@@ -4528,4 +4615,4 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
|
|
|
4528
4615
|
return load;
|
|
4529
4616
|
}
|
|
4530
4617
|
//#endregion
|
|
4531
|
-
export {
|
|
4618
|
+
export { getRemoteDocument as n, preloadedContexts as r, getDocumentLoader as t };
|