@fedify/vocab-runtime 2.1.23 → 2.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/dist/mod.cjs +220 -107
- package/dist/mod.d.cts +10 -0
- package/dist/mod.d.ts +10 -0
- package/dist/mod.js +220 -108
- package/dist/tests/body-D-gC5vdl.cjs +95 -0
- package/dist/tests/body-DBqFojnW.mjs +77 -0
- package/dist/tests/body.test.cjs +90 -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/decimal.test.cjs +6 -6
- package/dist/tests/decimal.test.mjs +6 -6
- package/dist/tests/{docloader-D-CBVlrJ.mjs → docloader-Beoc8qTH.mjs} +66 -37
- package/dist/tests/{docloader-BcWmxXdo.cjs → docloader-fn4ICzFS.cjs} +71 -36
- package/dist/tests/docloader.test.cjs +321 -4
- package/dist/tests/docloader.test.mjs +320 -4
- package/dist/tests/internal/multicodec.test.cjs +2 -2
- package/dist/tests/internal/multicodec.test.mjs +2 -2
- package/dist/tests/{key-BeTHFQJK.mjs → key-BDIsC6hV.mjs} +2 -2
- package/dist/tests/{key-DTTIntwb.cjs → key-BarKGLQz.cjs} +2 -2
- package/dist/tests/key.test.cjs +3 -3
- package/dist/tests/key.test.mjs +3 -3
- package/dist/tests/langstr.test.cjs +2 -2
- package/dist/tests/langstr.test.mjs +2 -2
- package/dist/tests/link.test.cjs +2 -2
- package/dist/tests/link.test.mjs +2 -2
- package/dist/tests/multibase/multibase.test.cjs +2 -2
- package/dist/tests/multibase/multibase.test.mjs +2 -2
- package/dist/tests/{request-CdMLb0ba.cjs → request-DrO5xzev.cjs} +1 -1
- package/dist/tests/{request-DqsgdBUH.mjs → request-DxeGyJVh.mjs} +1 -1
- package/dist/tests/request.test.cjs +2 -2
- package/dist/tests/request.test.mjs +2 -2
- package/dist/tests/temporal.test.cjs +1 -1
- package/dist/tests/temporal.test.mjs +1 -1
- package/dist/tests/{url-o0e-aXQY.mjs → url-3fyJuqC0.mjs} +14 -4
- package/dist/tests/{url-CWU5HT3Y.cjs → url-vZTgkIZg.cjs} +15 -4
- package/dist/tests/url.test.cjs +37 -4
- package/dist/tests/url.test.mjs +37 -4
- package/package.json +1 -1
- package/src/body.test.ts +125 -0
- package/src/body.ts +95 -0
- package/src/contexts/fep-7aa9.json +24 -0
- package/src/contexts.ts +8 -0
- package/src/docloader.test.ts +431 -1
- package/src/docloader.ts +76 -49
- package/src/url.test.ts +29 -3
- package/src/url.ts +21 -5
- /package/dist/tests/{langstr-CbAxaeEZ.cjs → langstr-BOQHfJDr.cjs} +0 -0
- /package/dist/tests/{langstr-Di5AvKpB.mjs → langstr-DDLc4833.mjs} +0 -0
- /package/dist/tests/{link-NUUWCdnK.mjs → link-CYszIegt.mjs} +0 -0
- /package/dist/tests/{link-FguCydMA.cjs → link-qDTmjPw_.cjs} +0 -0
- /package/dist/tests/{multibase-F7LtMMsK.cjs → multibase-8zhYPa7o.cjs} +0 -0
- /package/dist/tests/{multibase-BgU9XRf7.mjs → multibase-Ds75BvXs.mjs} +0 -0
- /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-BBPZjRfg.mjs} +0 -0
- /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-ByS9XLsw.cjs} +0 -0
package/deno.json
CHANGED
package/dist/mod.cjs
CHANGED
|
@@ -6,6 +6,7 @@ let _opentelemetry_api = require("@opentelemetry/api");
|
|
|
6
6
|
let node_process = require("node:process");
|
|
7
7
|
node_process = require_chunk.__toESM(node_process);
|
|
8
8
|
let node_dns_promises = require("node:dns/promises");
|
|
9
|
+
node_dns_promises = require_chunk.__toESM(node_dns_promises);
|
|
9
10
|
let node_net = require("node:net");
|
|
10
11
|
let asn1js = require("asn1js");
|
|
11
12
|
let byte_encodings_base64 = require("byte-encodings/base64");
|
|
@@ -599,6 +600,30 @@ var fep_5711_default = { "@context": {
|
|
|
599
600
|
}
|
|
600
601
|
} };
|
|
601
602
|
//#endregion
|
|
603
|
+
//#region src/contexts/fep-7aa9.json
|
|
604
|
+
var fep_7aa9_default = { "@context": {
|
|
605
|
+
"FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
|
|
606
|
+
"FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
|
|
607
|
+
"FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
|
|
608
|
+
"FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
|
|
609
|
+
"topic": {
|
|
610
|
+
"@id": "https://w3id.org/fep/7aa9#topic",
|
|
611
|
+
"@type": "@id"
|
|
612
|
+
},
|
|
613
|
+
"featuredObject": {
|
|
614
|
+
"@id": "https://w3id.org/fep/7aa9#featuredObject",
|
|
615
|
+
"@type": "@id"
|
|
616
|
+
},
|
|
617
|
+
"canFeature": {
|
|
618
|
+
"@id": "https://w3id.org/fep/7aa9#canFeature",
|
|
619
|
+
"@type": "@id"
|
|
620
|
+
},
|
|
621
|
+
"featureAuthorization": {
|
|
622
|
+
"@id": "https://w3id.org/fep/7aa9#featureAuthorization",
|
|
623
|
+
"@type": "@id"
|
|
624
|
+
}
|
|
625
|
+
} };
|
|
626
|
+
//#endregion
|
|
602
627
|
//#region src/contexts/fep-ef61.json
|
|
603
628
|
var fep_ef61_default = { "@context": {
|
|
604
629
|
"gateways": {
|
|
@@ -4516,6 +4541,7 @@ const preloadedContexts = {
|
|
|
4516
4541
|
"https://w3id.org/fep/5711": fep_5711_default,
|
|
4517
4542
|
"https://www.w3.org/ns/cid/v1": cid_v1_default,
|
|
4518
4543
|
"https://w3id.org/fep/ef61": fep_ef61_default,
|
|
4544
|
+
"https://w3id.org/fep/7aa9": fep_7aa9_default,
|
|
4519
4545
|
"https://join-lemmy.org/context.json": join_lemmy_default,
|
|
4520
4546
|
"http://joinmastodon.org/ns": joinmastodon_default,
|
|
4521
4547
|
"https://purl.archive.org/miscellany": miscellany_default
|
|
@@ -4523,7 +4549,151 @@ const preloadedContexts = {
|
|
|
4523
4549
|
//#endregion
|
|
4524
4550
|
//#region deno.json
|
|
4525
4551
|
var name = "@fedify/vocab-runtime";
|
|
4526
|
-
var version = "2.1.
|
|
4552
|
+
var version = "2.1.25";
|
|
4553
|
+
//#endregion
|
|
4554
|
+
//#region src/request.ts
|
|
4555
|
+
/**
|
|
4556
|
+
* Error thrown when fetching a JSON-LD document failed.
|
|
4557
|
+
*/
|
|
4558
|
+
var FetchError = class extends Error {
|
|
4559
|
+
/**
|
|
4560
|
+
* The URL that failed to fetch.
|
|
4561
|
+
*/
|
|
4562
|
+
url;
|
|
4563
|
+
/**
|
|
4564
|
+
* The HTTP response that failed, if available.
|
|
4565
|
+
*/
|
|
4566
|
+
response;
|
|
4567
|
+
/**
|
|
4568
|
+
* Constructs a new `FetchError`.
|
|
4569
|
+
*
|
|
4570
|
+
* @param url The URL that failed to fetch.
|
|
4571
|
+
* @param message Error message.
|
|
4572
|
+
* @param response The failed HTTP response, if available.
|
|
4573
|
+
*/
|
|
4574
|
+
constructor(url, message, response) {
|
|
4575
|
+
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4576
|
+
this.name = "FetchError";
|
|
4577
|
+
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4578
|
+
this.response = response;
|
|
4579
|
+
}
|
|
4580
|
+
};
|
|
4581
|
+
/**
|
|
4582
|
+
* Creates a request for the given URL.
|
|
4583
|
+
* @param url The URL to create the request for.
|
|
4584
|
+
* @param options The options for the request.
|
|
4585
|
+
* @returns The created request.
|
|
4586
|
+
* @internal
|
|
4587
|
+
*/
|
|
4588
|
+
function createActivityPubRequest(url, options = {}) {
|
|
4589
|
+
return new Request(url, {
|
|
4590
|
+
headers: {
|
|
4591
|
+
Accept: "application/activity+json, application/ld+json",
|
|
4592
|
+
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4593
|
+
},
|
|
4594
|
+
redirect: "manual"
|
|
4595
|
+
});
|
|
4596
|
+
}
|
|
4597
|
+
/**
|
|
4598
|
+
* Gets the user agent string for the given application and URL.
|
|
4599
|
+
* @param options The options for making the user agent string.
|
|
4600
|
+
* @returns The user agent string.
|
|
4601
|
+
* @since 1.3.0
|
|
4602
|
+
*/
|
|
4603
|
+
function getUserAgent({ software, url } = {}) {
|
|
4604
|
+
const fedify = `Fedify/${version}`;
|
|
4605
|
+
const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${node_process.default.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${node_process.default.versions.node}` : null;
|
|
4606
|
+
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4607
|
+
if (runtime != null) userAgent.push(runtime);
|
|
4608
|
+
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4609
|
+
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4610
|
+
}
|
|
4611
|
+
/**
|
|
4612
|
+
* Logs the request.
|
|
4613
|
+
* @param request The request to log.
|
|
4614
|
+
* @internal
|
|
4615
|
+
*/
|
|
4616
|
+
function logRequest(logger, request) {
|
|
4617
|
+
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4618
|
+
method: request.method,
|
|
4619
|
+
url: request.url,
|
|
4620
|
+
headers: Object.fromEntries(request.headers.entries())
|
|
4621
|
+
});
|
|
4622
|
+
}
|
|
4623
|
+
//#endregion
|
|
4624
|
+
//#region src/body.ts
|
|
4625
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
4626
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
4627
|
+
/** A body exceeded the byte limit. @internal */
|
|
4628
|
+
var BodyTooLargeError = class extends FetchError {
|
|
4629
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
4630
|
+
constructor(url, maxBytes) {
|
|
4631
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
4632
|
+
this.name = "BodyTooLargeError";
|
|
4633
|
+
}
|
|
4634
|
+
};
|
|
4635
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
4636
|
+
function validateBodySizeLimit(maxBytes) {
|
|
4637
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
4638
|
+
}
|
|
4639
|
+
/**
|
|
4640
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
4641
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
4642
|
+
* @param message The response or request to read.
|
|
4643
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
4644
|
+
* @param url The URL to include in errors and logs.
|
|
4645
|
+
* @returns The decoded body text.
|
|
4646
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
4647
|
+
* @internal
|
|
4648
|
+
*/
|
|
4649
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
4650
|
+
validateBodySizeLimit(maxBytes);
|
|
4651
|
+
const reader = message.body?.getReader();
|
|
4652
|
+
const tooLarge = () => {
|
|
4653
|
+
(0, _logtape_logtape.getLogger)([
|
|
4654
|
+
"fedify",
|
|
4655
|
+
"runtime",
|
|
4656
|
+
"body"
|
|
4657
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
4658
|
+
url: url.toString(),
|
|
4659
|
+
maxBytes
|
|
4660
|
+
});
|
|
4661
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
4662
|
+
};
|
|
4663
|
+
try {
|
|
4664
|
+
const length = message.headers.get("Content-Length");
|
|
4665
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
4666
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
4667
|
+
if (reader == null) return "";
|
|
4668
|
+
let size = 0;
|
|
4669
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
4670
|
+
const { done, value } = await reader.read();
|
|
4671
|
+
if (done) {
|
|
4672
|
+
controller.close();
|
|
4673
|
+
return;
|
|
4674
|
+
}
|
|
4675
|
+
size += value.byteLength;
|
|
4676
|
+
if (size > maxBytes) tooLarge();
|
|
4677
|
+
controller.enqueue(value);
|
|
4678
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
4679
|
+
try {
|
|
4680
|
+
const chunks = [];
|
|
4681
|
+
while (true) {
|
|
4682
|
+
const { done, value } = await textReader.read();
|
|
4683
|
+
if (done) break;
|
|
4684
|
+
chunks.push(value);
|
|
4685
|
+
}
|
|
4686
|
+
return chunks.join("");
|
|
4687
|
+
} finally {
|
|
4688
|
+
textReader.releaseLock();
|
|
4689
|
+
}
|
|
4690
|
+
} catch (error) {
|
|
4691
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
4692
|
+
throw error;
|
|
4693
|
+
} finally {
|
|
4694
|
+
reader?.releaseLock();
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4527
4697
|
//#endregion
|
|
4528
4698
|
//#region src/link.ts
|
|
4529
4699
|
const parametersNeedLowerCase = ["rel", "type"];
|
|
@@ -4706,81 +4876,18 @@ var HttpHeaderLink = class HttpHeaderLink {
|
|
|
4706
4876
|
}
|
|
4707
4877
|
};
|
|
4708
4878
|
//#endregion
|
|
4709
|
-
//#region src/request.ts
|
|
4710
|
-
/**
|
|
4711
|
-
* Error thrown when fetching a JSON-LD document failed.
|
|
4712
|
-
*/
|
|
4713
|
-
var FetchError = class extends Error {
|
|
4714
|
-
/**
|
|
4715
|
-
* The URL that failed to fetch.
|
|
4716
|
-
*/
|
|
4717
|
-
url;
|
|
4718
|
-
/**
|
|
4719
|
-
* The HTTP response that failed, if available.
|
|
4720
|
-
*/
|
|
4721
|
-
response;
|
|
4722
|
-
/**
|
|
4723
|
-
* Constructs a new `FetchError`.
|
|
4724
|
-
*
|
|
4725
|
-
* @param url The URL that failed to fetch.
|
|
4726
|
-
* @param message Error message.
|
|
4727
|
-
* @param response The failed HTTP response, if available.
|
|
4728
|
-
*/
|
|
4729
|
-
constructor(url, message, response) {
|
|
4730
|
-
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4731
|
-
this.name = "FetchError";
|
|
4732
|
-
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4733
|
-
this.response = response;
|
|
4734
|
-
}
|
|
4735
|
-
};
|
|
4736
|
-
/**
|
|
4737
|
-
* Creates a request for the given URL.
|
|
4738
|
-
* @param url The URL to create the request for.
|
|
4739
|
-
* @param options The options for the request.
|
|
4740
|
-
* @returns The created request.
|
|
4741
|
-
* @internal
|
|
4742
|
-
*/
|
|
4743
|
-
function createActivityPubRequest(url, options = {}) {
|
|
4744
|
-
return new Request(url, {
|
|
4745
|
-
headers: {
|
|
4746
|
-
Accept: "application/activity+json, application/ld+json",
|
|
4747
|
-
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4748
|
-
},
|
|
4749
|
-
redirect: "manual"
|
|
4750
|
-
});
|
|
4751
|
-
}
|
|
4752
|
-
/**
|
|
4753
|
-
* Gets the user agent string for the given application and URL.
|
|
4754
|
-
* @param options The options for making the user agent string.
|
|
4755
|
-
* @returns The user agent string.
|
|
4756
|
-
* @since 1.3.0
|
|
4757
|
-
*/
|
|
4758
|
-
function getUserAgent({ software, url } = {}) {
|
|
4759
|
-
const fedify = `Fedify/${version}`;
|
|
4760
|
-
const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${node_process.default.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${node_process.default.versions.node}` : null;
|
|
4761
|
-
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4762
|
-
if (runtime != null) userAgent.push(runtime);
|
|
4763
|
-
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4764
|
-
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4765
|
-
}
|
|
4766
|
-
/**
|
|
4767
|
-
* Logs the request.
|
|
4768
|
-
* @param request The request to log.
|
|
4769
|
-
* @internal
|
|
4770
|
-
*/
|
|
4771
|
-
function logRequest(logger, request) {
|
|
4772
|
-
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4773
|
-
method: request.method,
|
|
4774
|
-
url: request.url,
|
|
4775
|
-
headers: Object.fromEntries(request.headers.entries())
|
|
4776
|
-
});
|
|
4777
|
-
}
|
|
4778
|
-
//#endregion
|
|
4779
4879
|
//#region src/url.ts
|
|
4780
4880
|
var UrlError = class extends Error {
|
|
4881
|
+
/**
|
|
4882
|
+
* The failure category. Defaults to `"disallowed"`.
|
|
4883
|
+
* `"dns"` includes lookup errors and results with no usable IP addresses.
|
|
4884
|
+
* @since 2.0.29
|
|
4885
|
+
*/
|
|
4886
|
+
reason;
|
|
4781
4887
|
constructor(message, options) {
|
|
4782
4888
|
super(message, options);
|
|
4783
4889
|
this.name = "UrlError";
|
|
4890
|
+
this.reason = options?.reason ?? "disallowed";
|
|
4784
4891
|
}
|
|
4785
4892
|
};
|
|
4786
4893
|
/**
|
|
@@ -4806,9 +4913,12 @@ async function validatePublicUrl(url) {
|
|
|
4806
4913
|
}
|
|
4807
4914
|
let addresses;
|
|
4808
4915
|
try {
|
|
4809
|
-
addresses = await
|
|
4916
|
+
addresses = await node_dns_promises.default.lookup(hostname, { all: true });
|
|
4810
4917
|
} catch (error) {
|
|
4811
|
-
throw new UrlError("DNS lookup failed", {
|
|
4918
|
+
throw new UrlError("DNS lookup failed", {
|
|
4919
|
+
cause: error,
|
|
4920
|
+
reason: "dns"
|
|
4921
|
+
});
|
|
4812
4922
|
}
|
|
4813
4923
|
validateLookupAddresses(addresses);
|
|
4814
4924
|
}
|
|
@@ -4837,7 +4947,7 @@ function validateLookupAddresses(addresses) {
|
|
|
4837
4947
|
ipAddressCount++;
|
|
4838
4948
|
validatePublicIpAddress(address, family);
|
|
4839
4949
|
}
|
|
4840
|
-
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
|
|
4950
|
+
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address", { reason: "dns" });
|
|
4841
4951
|
}
|
|
4842
4952
|
function validatePublicIpAddress(address, family) {
|
|
4843
4953
|
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
|
|
@@ -5037,38 +5147,29 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
5037
5147
|
}
|
|
5038
5148
|
let document;
|
|
5039
5149
|
if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
|
|
5040
|
-
const
|
|
5041
|
-
const
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
5060
|
-
}
|
|
5061
|
-
if (attribs.rel === "alternate" && "type" in attribs && (attribs.type === "application/activity+json" || attribs.type === "application/ld+json" || attribs.type.startsWith("application/ld+json;")) && "href" in attribs && new URL(attribs.href, docUrl).href !== docUrl.href) {
|
|
5062
|
-
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
5063
|
-
alternateUrl: attribs.href,
|
|
5064
|
-
url: documentUrl
|
|
5065
|
-
});
|
|
5066
|
-
return await fetch(new URL(attribs.href, docUrl).href);
|
|
5067
|
-
}
|
|
5150
|
+
const html = await readBoundedText(response, 1024 * 1024, documentUrl);
|
|
5151
|
+
const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
|
|
5152
|
+
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
5153
|
+
let tagMatch;
|
|
5154
|
+
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
5155
|
+
const tagContent = tagMatch[2];
|
|
5156
|
+
let attrMatch;
|
|
5157
|
+
const attribs = {};
|
|
5158
|
+
attrPattern.lastIndex = 0;
|
|
5159
|
+
while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
|
|
5160
|
+
const key = attrMatch[1].toLowerCase();
|
|
5161
|
+
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
5162
|
+
}
|
|
5163
|
+
if (attribs.rel === "alternate" && "type" in attribs && (attribs.type === "application/activity+json" || attribs.type === "application/ld+json" || attribs.type.startsWith("application/ld+json;")) && "href" in attribs && new URL(attribs.href, docUrl).href !== docUrl.href) {
|
|
5164
|
+
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
5165
|
+
alternateUrl: attribs.href,
|
|
5166
|
+
url: documentUrl
|
|
5167
|
+
});
|
|
5168
|
+
return await fetch(new URL(attribs.href, docUrl).href);
|
|
5068
5169
|
}
|
|
5069
|
-
document = JSON.parse(html);
|
|
5070
5170
|
}
|
|
5071
|
-
|
|
5171
|
+
document = JSON.parse(html);
|
|
5172
|
+
} else document = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, documentUrl));
|
|
5072
5173
|
logger.debug("Fetched document: {status} {url} {headers}", {
|
|
5073
5174
|
status: response.status,
|
|
5074
5175
|
url: documentUrl,
|
|
@@ -5082,6 +5183,8 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
5082
5183
|
}
|
|
5083
5184
|
/**
|
|
5084
5185
|
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
5186
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
5187
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
5085
5188
|
*
|
|
5086
5189
|
* The created loader preloads the below frequently used contexts by default
|
|
5087
5190
|
* (unless `options.skipPreloadedContexts` is set to `true`):
|
|
@@ -5093,6 +5196,7 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
5093
5196
|
* - <https://www.w3.org/ns/cid/v1>
|
|
5094
5197
|
* - <https://w3id.org/security/multikey/v1>
|
|
5095
5198
|
* - <https://w3id.org/fep/ef61>
|
|
5199
|
+
* - <https://w3id.org/fep/7aa9>
|
|
5096
5200
|
* - <https://purl.archive.org/socialweb/webfinger>
|
|
5097
5201
|
* - <http://schema.org/>
|
|
5098
5202
|
* @param options Options for the document loader.
|
|
@@ -5115,7 +5219,11 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5115
5219
|
if (!allowPrivateAddress) try {
|
|
5116
5220
|
await validatePublicUrl(currentUrl);
|
|
5117
5221
|
} catch (error) {
|
|
5118
|
-
if (error instanceof UrlError) logger.
|
|
5222
|
+
if (error instanceof UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
5223
|
+
url: currentUrl,
|
|
5224
|
+
error
|
|
5225
|
+
});
|
|
5226
|
+
else logger.error("Disallowed private URL: {url}", {
|
|
5119
5227
|
url: currentUrl,
|
|
5120
5228
|
error
|
|
5121
5229
|
});
|
|
@@ -5153,7 +5261,12 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5153
5261
|
}
|
|
5154
5262
|
return await load(redirectUrl, options, redirected + 1, visited);
|
|
5155
5263
|
}
|
|
5156
|
-
const result = await getRemoteDocument(currentUrl, response,
|
|
5264
|
+
const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
|
|
5265
|
+
options?.signal?.throwIfAborted();
|
|
5266
|
+
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
5267
|
+
if (visited.has(alternateUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
|
|
5268
|
+
return await load(alternateUrl, options, redirected + 1, visited);
|
|
5269
|
+
});
|
|
5157
5270
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
5158
5271
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
5159
5272
|
return result;
|
|
@@ -5169,7 +5282,7 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5169
5282
|
}
|
|
5170
5283
|
});
|
|
5171
5284
|
}
|
|
5172
|
-
return load;
|
|
5285
|
+
return (url, options) => load(url, options);
|
|
5173
5286
|
}
|
|
5174
5287
|
//#endregion
|
|
5175
5288
|
//#region src/internal/multicodec.ts
|
package/dist/mod.d.cts
CHANGED
|
@@ -174,6 +174,8 @@ interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
|
|
|
174
174
|
}
|
|
175
175
|
/**
|
|
176
176
|
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
177
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
178
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
177
179
|
*
|
|
178
180
|
* The created loader preloads the below frequently used contexts by default
|
|
179
181
|
* (unless `options.skipPreloadedContexts` is set to `true`):
|
|
@@ -185,6 +187,7 @@ interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
|
|
|
185
187
|
* - <https://www.w3.org/ns/cid/v1>
|
|
186
188
|
* - <https://w3id.org/security/multikey/v1>
|
|
187
189
|
* - <https://w3id.org/fep/ef61>
|
|
190
|
+
* - <https://w3id.org/fep/7aa9>
|
|
188
191
|
* - <https://purl.archive.org/socialweb/webfinger>
|
|
189
192
|
* - <http://schema.org/>
|
|
190
193
|
* @param options Options for the document loader.
|
|
@@ -419,8 +422,15 @@ declare function encodingFromBaseData(data: string | Uint8Array): Base;
|
|
|
419
422
|
//#endregion
|
|
420
423
|
//#region src/url.d.ts
|
|
421
424
|
declare class UrlError extends Error {
|
|
425
|
+
/**
|
|
426
|
+
* The failure category. Defaults to `"disallowed"`.
|
|
427
|
+
* `"dns"` includes lookup errors and results with no usable IP addresses.
|
|
428
|
+
* @since 2.0.29
|
|
429
|
+
*/
|
|
430
|
+
readonly reason: "dns" | "disallowed";
|
|
422
431
|
constructor(message: string, options?: {
|
|
423
432
|
cause?: unknown;
|
|
433
|
+
reason?: "dns" | "disallowed";
|
|
424
434
|
});
|
|
425
435
|
}
|
|
426
436
|
/**
|
package/dist/mod.d.ts
CHANGED
|
@@ -173,6 +173,8 @@ interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
|
|
|
173
173
|
}
|
|
174
174
|
/**
|
|
175
175
|
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
176
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
177
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
176
178
|
*
|
|
177
179
|
* The created loader preloads the below frequently used contexts by default
|
|
178
180
|
* (unless `options.skipPreloadedContexts` is set to `true`):
|
|
@@ -184,6 +186,7 @@ interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
|
|
|
184
186
|
* - <https://www.w3.org/ns/cid/v1>
|
|
185
187
|
* - <https://w3id.org/security/multikey/v1>
|
|
186
188
|
* - <https://w3id.org/fep/ef61>
|
|
189
|
+
* - <https://w3id.org/fep/7aa9>
|
|
187
190
|
* - <https://purl.archive.org/socialweb/webfinger>
|
|
188
191
|
* - <http://schema.org/>
|
|
189
192
|
* @param options Options for the document loader.
|
|
@@ -418,8 +421,15 @@ declare function encodingFromBaseData(data: string | Uint8Array): Base;
|
|
|
418
421
|
//#endregion
|
|
419
422
|
//#region src/url.d.ts
|
|
420
423
|
declare class UrlError extends Error {
|
|
424
|
+
/**
|
|
425
|
+
* The failure category. Defaults to `"disallowed"`.
|
|
426
|
+
* `"dns"` includes lookup errors and results with no usable IP addresses.
|
|
427
|
+
* @since 2.0.29
|
|
428
|
+
*/
|
|
429
|
+
readonly reason: "dns" | "disallowed";
|
|
421
430
|
constructor(message: string, options?: {
|
|
422
431
|
cause?: unknown;
|
|
432
|
+
reason?: "dns" | "disallowed";
|
|
423
433
|
});
|
|
424
434
|
}
|
|
425
435
|
/**
|