@fedify/vocab-runtime 2.2.11 → 2.2.13
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 +166 -101
- package/dist/mod.js +166 -101
- package/dist/tests/body-UDpZTF6c.mjs +77 -0
- package/dist/tests/body-Vx0QSf6C.cjs +95 -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-ekzTbc9R.cjs → docloader-B5V6GD-7.cjs} +31 -33
- package/dist/tests/{docloader-Nug7_hIl.mjs → docloader-Cld9tcx4.mjs} +26 -34
- package/dist/tests/docloader.test.cjs +83 -4
- package/dist/tests/docloader.test.mjs +83 -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-CMtb-99N.mjs → request-B2ac0drD.mjs} +1 -1
- package/dist/tests/{request-HcA2nRAs.cjs → request-DqvIx6-9.cjs} +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.test.cjs +2 -2
- package/dist/tests/url.test.mjs +2 -2
- package/package.json +1 -1
- package/src/body.test.ts +125 -0
- package/src/body.ts +95 -0
- package/src/docloader.test.ts +124 -1
- package/src/docloader.ts +41 -43
- /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/dist/tests/{url-o0e-aXQY.mjs → url-BBfduh6o.mjs} +0 -0
- /package/dist/tests/{url-CWU5HT3Y.cjs → url-CnyF489g.cjs} +0 -0
package/deno.json
CHANGED
package/dist/mod.cjs
CHANGED
|
@@ -4523,7 +4523,151 @@ const preloadedContexts = {
|
|
|
4523
4523
|
//#endregion
|
|
4524
4524
|
//#region deno.json
|
|
4525
4525
|
var name = "@fedify/vocab-runtime";
|
|
4526
|
-
var version = "2.2.
|
|
4526
|
+
var version = "2.2.13";
|
|
4527
|
+
//#endregion
|
|
4528
|
+
//#region src/request.ts
|
|
4529
|
+
/**
|
|
4530
|
+
* Error thrown when fetching a JSON-LD document failed.
|
|
4531
|
+
*/
|
|
4532
|
+
var FetchError = class extends Error {
|
|
4533
|
+
/**
|
|
4534
|
+
* The URL that failed to fetch.
|
|
4535
|
+
*/
|
|
4536
|
+
url;
|
|
4537
|
+
/**
|
|
4538
|
+
* The HTTP response that failed, if available.
|
|
4539
|
+
*/
|
|
4540
|
+
response;
|
|
4541
|
+
/**
|
|
4542
|
+
* Constructs a new `FetchError`.
|
|
4543
|
+
*
|
|
4544
|
+
* @param url The URL that failed to fetch.
|
|
4545
|
+
* @param message Error message.
|
|
4546
|
+
* @param response The failed HTTP response, if available.
|
|
4547
|
+
*/
|
|
4548
|
+
constructor(url, message, response) {
|
|
4549
|
+
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4550
|
+
this.name = "FetchError";
|
|
4551
|
+
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4552
|
+
this.response = response;
|
|
4553
|
+
}
|
|
4554
|
+
};
|
|
4555
|
+
/**
|
|
4556
|
+
* Creates a request for the given URL.
|
|
4557
|
+
* @param url The URL to create the request for.
|
|
4558
|
+
* @param options The options for the request.
|
|
4559
|
+
* @returns The created request.
|
|
4560
|
+
* @internal
|
|
4561
|
+
*/
|
|
4562
|
+
function createActivityPubRequest(url, options = {}) {
|
|
4563
|
+
return new Request(url, {
|
|
4564
|
+
headers: {
|
|
4565
|
+
Accept: "application/activity+json, application/ld+json",
|
|
4566
|
+
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4567
|
+
},
|
|
4568
|
+
redirect: "manual"
|
|
4569
|
+
});
|
|
4570
|
+
}
|
|
4571
|
+
/**
|
|
4572
|
+
* Gets the user agent string for the given application and URL.
|
|
4573
|
+
* @param options The options for making the user agent string.
|
|
4574
|
+
* @returns The user agent string.
|
|
4575
|
+
* @since 1.3.0
|
|
4576
|
+
*/
|
|
4577
|
+
function getUserAgent({ software, url } = {}) {
|
|
4578
|
+
const fedify = `Fedify/${version}`;
|
|
4579
|
+
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;
|
|
4580
|
+
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4581
|
+
if (runtime != null) userAgent.push(runtime);
|
|
4582
|
+
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4583
|
+
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4584
|
+
}
|
|
4585
|
+
/**
|
|
4586
|
+
* Logs the request.
|
|
4587
|
+
* @param request The request to log.
|
|
4588
|
+
* @internal
|
|
4589
|
+
*/
|
|
4590
|
+
function logRequest(logger, request) {
|
|
4591
|
+
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4592
|
+
method: request.method,
|
|
4593
|
+
url: request.url,
|
|
4594
|
+
headers: Object.fromEntries(request.headers.entries())
|
|
4595
|
+
});
|
|
4596
|
+
}
|
|
4597
|
+
//#endregion
|
|
4598
|
+
//#region src/body.ts
|
|
4599
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
4600
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
4601
|
+
/** A body exceeded the byte limit. @internal */
|
|
4602
|
+
var BodyTooLargeError = class extends FetchError {
|
|
4603
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
4604
|
+
constructor(url, maxBytes) {
|
|
4605
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
4606
|
+
this.name = "BodyTooLargeError";
|
|
4607
|
+
}
|
|
4608
|
+
};
|
|
4609
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
4610
|
+
function validateBodySizeLimit(maxBytes) {
|
|
4611
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
4612
|
+
}
|
|
4613
|
+
/**
|
|
4614
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
4615
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
4616
|
+
* @param message The response or request to read.
|
|
4617
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
4618
|
+
* @param url The URL to include in errors and logs.
|
|
4619
|
+
* @returns The decoded body text.
|
|
4620
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
4621
|
+
* @internal
|
|
4622
|
+
*/
|
|
4623
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
4624
|
+
validateBodySizeLimit(maxBytes);
|
|
4625
|
+
const reader = message.body?.getReader();
|
|
4626
|
+
const tooLarge = () => {
|
|
4627
|
+
(0, _logtape_logtape.getLogger)([
|
|
4628
|
+
"fedify",
|
|
4629
|
+
"runtime",
|
|
4630
|
+
"body"
|
|
4631
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
4632
|
+
url: url.toString(),
|
|
4633
|
+
maxBytes
|
|
4634
|
+
});
|
|
4635
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
4636
|
+
};
|
|
4637
|
+
try {
|
|
4638
|
+
const length = message.headers.get("Content-Length");
|
|
4639
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
4640
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
4641
|
+
if (reader == null) return "";
|
|
4642
|
+
let size = 0;
|
|
4643
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
4644
|
+
const { done, value } = await reader.read();
|
|
4645
|
+
if (done) {
|
|
4646
|
+
controller.close();
|
|
4647
|
+
return;
|
|
4648
|
+
}
|
|
4649
|
+
size += value.byteLength;
|
|
4650
|
+
if (size > maxBytes) tooLarge();
|
|
4651
|
+
controller.enqueue(value);
|
|
4652
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
4653
|
+
try {
|
|
4654
|
+
const chunks = [];
|
|
4655
|
+
while (true) {
|
|
4656
|
+
const { done, value } = await textReader.read();
|
|
4657
|
+
if (done) break;
|
|
4658
|
+
chunks.push(value);
|
|
4659
|
+
}
|
|
4660
|
+
return chunks.join("");
|
|
4661
|
+
} finally {
|
|
4662
|
+
textReader.releaseLock();
|
|
4663
|
+
}
|
|
4664
|
+
} catch (error) {
|
|
4665
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
4666
|
+
throw error;
|
|
4667
|
+
} finally {
|
|
4668
|
+
reader?.releaseLock();
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4527
4671
|
//#endregion
|
|
4528
4672
|
//#region src/link.ts
|
|
4529
4673
|
const parametersNeedLowerCase = ["rel", "type"];
|
|
@@ -4706,76 +4850,6 @@ var HttpHeaderLink = class HttpHeaderLink {
|
|
|
4706
4850
|
}
|
|
4707
4851
|
};
|
|
4708
4852
|
//#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
4853
|
//#region src/url.ts
|
|
4780
4854
|
var UrlError = class extends Error {
|
|
4781
4855
|
constructor(message, options) {
|
|
@@ -5037,38 +5111,29 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
5037
5111
|
}
|
|
5038
5112
|
let document;
|
|
5039
5113
|
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
|
-
}
|
|
5114
|
+
const html = await readBoundedText(response, 1024 * 1024, documentUrl);
|
|
5115
|
+
const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
|
|
5116
|
+
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
5117
|
+
let tagMatch;
|
|
5118
|
+
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
5119
|
+
const tagContent = tagMatch[2];
|
|
5120
|
+
let attrMatch;
|
|
5121
|
+
const attribs = {};
|
|
5122
|
+
attrPattern.lastIndex = 0;
|
|
5123
|
+
while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
|
|
5124
|
+
const key = attrMatch[1].toLowerCase();
|
|
5125
|
+
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
5126
|
+
}
|
|
5127
|
+
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) {
|
|
5128
|
+
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
5129
|
+
alternateUrl: attribs.href,
|
|
5130
|
+
url: documentUrl
|
|
5131
|
+
});
|
|
5132
|
+
return await fetch(new URL(attribs.href, docUrl).href);
|
|
5068
5133
|
}
|
|
5069
|
-
document = JSON.parse(html);
|
|
5070
5134
|
}
|
|
5071
|
-
|
|
5135
|
+
document = JSON.parse(html);
|
|
5136
|
+
} else document = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, documentUrl));
|
|
5072
5137
|
logger.debug("Fetched document: {status} {url} {headers}", {
|
|
5073
5138
|
status: response.status,
|
|
5074
5139
|
url: documentUrl,
|
package/dist/mod.js
CHANGED
|
@@ -4519,7 +4519,151 @@ const preloadedContexts = {
|
|
|
4519
4519
|
//#endregion
|
|
4520
4520
|
//#region deno.json
|
|
4521
4521
|
var name = "@fedify/vocab-runtime";
|
|
4522
|
-
var version = "2.2.
|
|
4522
|
+
var version = "2.2.13";
|
|
4523
|
+
//#endregion
|
|
4524
|
+
//#region src/request.ts
|
|
4525
|
+
/**
|
|
4526
|
+
* Error thrown when fetching a JSON-LD document failed.
|
|
4527
|
+
*/
|
|
4528
|
+
var FetchError = class extends Error {
|
|
4529
|
+
/**
|
|
4530
|
+
* The URL that failed to fetch.
|
|
4531
|
+
*/
|
|
4532
|
+
url;
|
|
4533
|
+
/**
|
|
4534
|
+
* The HTTP response that failed, if available.
|
|
4535
|
+
*/
|
|
4536
|
+
response;
|
|
4537
|
+
/**
|
|
4538
|
+
* Constructs a new `FetchError`.
|
|
4539
|
+
*
|
|
4540
|
+
* @param url The URL that failed to fetch.
|
|
4541
|
+
* @param message Error message.
|
|
4542
|
+
* @param response The failed HTTP response, if available.
|
|
4543
|
+
*/
|
|
4544
|
+
constructor(url, message, response) {
|
|
4545
|
+
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4546
|
+
this.name = "FetchError";
|
|
4547
|
+
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4548
|
+
this.response = response;
|
|
4549
|
+
}
|
|
4550
|
+
};
|
|
4551
|
+
/**
|
|
4552
|
+
* Creates a request for the given URL.
|
|
4553
|
+
* @param url The URL to create the request for.
|
|
4554
|
+
* @param options The options for the request.
|
|
4555
|
+
* @returns The created request.
|
|
4556
|
+
* @internal
|
|
4557
|
+
*/
|
|
4558
|
+
function createActivityPubRequest(url, options = {}) {
|
|
4559
|
+
return new Request(url, {
|
|
4560
|
+
headers: {
|
|
4561
|
+
Accept: "application/activity+json, application/ld+json",
|
|
4562
|
+
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4563
|
+
},
|
|
4564
|
+
redirect: "manual"
|
|
4565
|
+
});
|
|
4566
|
+
}
|
|
4567
|
+
/**
|
|
4568
|
+
* Gets the user agent string for the given application and URL.
|
|
4569
|
+
* @param options The options for making the user agent string.
|
|
4570
|
+
* @returns The user agent string.
|
|
4571
|
+
* @since 1.3.0
|
|
4572
|
+
*/
|
|
4573
|
+
function getUserAgent({ software, url } = {}) {
|
|
4574
|
+
const fedify = `Fedify/${version}`;
|
|
4575
|
+
const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${process.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${process.versions.node}` : null;
|
|
4576
|
+
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4577
|
+
if (runtime != null) userAgent.push(runtime);
|
|
4578
|
+
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4579
|
+
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4580
|
+
}
|
|
4581
|
+
/**
|
|
4582
|
+
* Logs the request.
|
|
4583
|
+
* @param request The request to log.
|
|
4584
|
+
* @internal
|
|
4585
|
+
*/
|
|
4586
|
+
function logRequest(logger, request) {
|
|
4587
|
+
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4588
|
+
method: request.method,
|
|
4589
|
+
url: request.url,
|
|
4590
|
+
headers: Object.fromEntries(request.headers.entries())
|
|
4591
|
+
});
|
|
4592
|
+
}
|
|
4593
|
+
//#endregion
|
|
4594
|
+
//#region src/body.ts
|
|
4595
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
4596
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
4597
|
+
/** A body exceeded the byte limit. @internal */
|
|
4598
|
+
var BodyTooLargeError = class extends FetchError {
|
|
4599
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
4600
|
+
constructor(url, maxBytes) {
|
|
4601
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
4602
|
+
this.name = "BodyTooLargeError";
|
|
4603
|
+
}
|
|
4604
|
+
};
|
|
4605
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
4606
|
+
function validateBodySizeLimit(maxBytes) {
|
|
4607
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
4608
|
+
}
|
|
4609
|
+
/**
|
|
4610
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
4611
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
4612
|
+
* @param message The response or request to read.
|
|
4613
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
4614
|
+
* @param url The URL to include in errors and logs.
|
|
4615
|
+
* @returns The decoded body text.
|
|
4616
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
4617
|
+
* @internal
|
|
4618
|
+
*/
|
|
4619
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
4620
|
+
validateBodySizeLimit(maxBytes);
|
|
4621
|
+
const reader = message.body?.getReader();
|
|
4622
|
+
const tooLarge = () => {
|
|
4623
|
+
getLogger([
|
|
4624
|
+
"fedify",
|
|
4625
|
+
"runtime",
|
|
4626
|
+
"body"
|
|
4627
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
4628
|
+
url: url.toString(),
|
|
4629
|
+
maxBytes
|
|
4630
|
+
});
|
|
4631
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
4632
|
+
};
|
|
4633
|
+
try {
|
|
4634
|
+
const length = message.headers.get("Content-Length");
|
|
4635
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
4636
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
4637
|
+
if (reader == null) return "";
|
|
4638
|
+
let size = 0;
|
|
4639
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
4640
|
+
const { done, value } = await reader.read();
|
|
4641
|
+
if (done) {
|
|
4642
|
+
controller.close();
|
|
4643
|
+
return;
|
|
4644
|
+
}
|
|
4645
|
+
size += value.byteLength;
|
|
4646
|
+
if (size > maxBytes) tooLarge();
|
|
4647
|
+
controller.enqueue(value);
|
|
4648
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
4649
|
+
try {
|
|
4650
|
+
const chunks = [];
|
|
4651
|
+
while (true) {
|
|
4652
|
+
const { done, value } = await textReader.read();
|
|
4653
|
+
if (done) break;
|
|
4654
|
+
chunks.push(value);
|
|
4655
|
+
}
|
|
4656
|
+
return chunks.join("");
|
|
4657
|
+
} finally {
|
|
4658
|
+
textReader.releaseLock();
|
|
4659
|
+
}
|
|
4660
|
+
} catch (error) {
|
|
4661
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
4662
|
+
throw error;
|
|
4663
|
+
} finally {
|
|
4664
|
+
reader?.releaseLock();
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4523
4667
|
//#endregion
|
|
4524
4668
|
//#region src/link.ts
|
|
4525
4669
|
const parametersNeedLowerCase = ["rel", "type"];
|
|
@@ -4702,76 +4846,6 @@ var HttpHeaderLink = class HttpHeaderLink {
|
|
|
4702
4846
|
}
|
|
4703
4847
|
};
|
|
4704
4848
|
//#endregion
|
|
4705
|
-
//#region src/request.ts
|
|
4706
|
-
/**
|
|
4707
|
-
* Error thrown when fetching a JSON-LD document failed.
|
|
4708
|
-
*/
|
|
4709
|
-
var FetchError = class extends Error {
|
|
4710
|
-
/**
|
|
4711
|
-
* The URL that failed to fetch.
|
|
4712
|
-
*/
|
|
4713
|
-
url;
|
|
4714
|
-
/**
|
|
4715
|
-
* The HTTP response that failed, if available.
|
|
4716
|
-
*/
|
|
4717
|
-
response;
|
|
4718
|
-
/**
|
|
4719
|
-
* Constructs a new `FetchError`.
|
|
4720
|
-
*
|
|
4721
|
-
* @param url The URL that failed to fetch.
|
|
4722
|
-
* @param message Error message.
|
|
4723
|
-
* @param response The failed HTTP response, if available.
|
|
4724
|
-
*/
|
|
4725
|
-
constructor(url, message, response) {
|
|
4726
|
-
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4727
|
-
this.name = "FetchError";
|
|
4728
|
-
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4729
|
-
this.response = response;
|
|
4730
|
-
}
|
|
4731
|
-
};
|
|
4732
|
-
/**
|
|
4733
|
-
* Creates a request for the given URL.
|
|
4734
|
-
* @param url The URL to create the request for.
|
|
4735
|
-
* @param options The options for the request.
|
|
4736
|
-
* @returns The created request.
|
|
4737
|
-
* @internal
|
|
4738
|
-
*/
|
|
4739
|
-
function createActivityPubRequest(url, options = {}) {
|
|
4740
|
-
return new Request(url, {
|
|
4741
|
-
headers: {
|
|
4742
|
-
Accept: "application/activity+json, application/ld+json",
|
|
4743
|
-
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4744
|
-
},
|
|
4745
|
-
redirect: "manual"
|
|
4746
|
-
});
|
|
4747
|
-
}
|
|
4748
|
-
/**
|
|
4749
|
-
* Gets the user agent string for the given application and URL.
|
|
4750
|
-
* @param options The options for making the user agent string.
|
|
4751
|
-
* @returns The user agent string.
|
|
4752
|
-
* @since 1.3.0
|
|
4753
|
-
*/
|
|
4754
|
-
function getUserAgent({ software, url } = {}) {
|
|
4755
|
-
const fedify = `Fedify/${version}`;
|
|
4756
|
-
const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${process.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${process.versions.node}` : null;
|
|
4757
|
-
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4758
|
-
if (runtime != null) userAgent.push(runtime);
|
|
4759
|
-
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4760
|
-
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4761
|
-
}
|
|
4762
|
-
/**
|
|
4763
|
-
* Logs the request.
|
|
4764
|
-
* @param request The request to log.
|
|
4765
|
-
* @internal
|
|
4766
|
-
*/
|
|
4767
|
-
function logRequest(logger, request) {
|
|
4768
|
-
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4769
|
-
method: request.method,
|
|
4770
|
-
url: request.url,
|
|
4771
|
-
headers: Object.fromEntries(request.headers.entries())
|
|
4772
|
-
});
|
|
4773
|
-
}
|
|
4774
|
-
//#endregion
|
|
4775
4849
|
//#region src/url.ts
|
|
4776
4850
|
var UrlError = class extends Error {
|
|
4777
4851
|
constructor(message, options) {
|
|
@@ -5033,38 +5107,29 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
5033
5107
|
}
|
|
5034
5108
|
let document;
|
|
5035
5109
|
if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
|
|
5036
|
-
const
|
|
5037
|
-
const
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
5056
|
-
}
|
|
5057
|
-
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) {
|
|
5058
|
-
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
5059
|
-
alternateUrl: attribs.href,
|
|
5060
|
-
url: documentUrl
|
|
5061
|
-
});
|
|
5062
|
-
return await fetch(new URL(attribs.href, docUrl).href);
|
|
5063
|
-
}
|
|
5110
|
+
const html = await readBoundedText(response, 1024 * 1024, documentUrl);
|
|
5111
|
+
const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
|
|
5112
|
+
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
5113
|
+
let tagMatch;
|
|
5114
|
+
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
5115
|
+
const tagContent = tagMatch[2];
|
|
5116
|
+
let attrMatch;
|
|
5117
|
+
const attribs = {};
|
|
5118
|
+
attrPattern.lastIndex = 0;
|
|
5119
|
+
while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
|
|
5120
|
+
const key = attrMatch[1].toLowerCase();
|
|
5121
|
+
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
5122
|
+
}
|
|
5123
|
+
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) {
|
|
5124
|
+
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
5125
|
+
alternateUrl: attribs.href,
|
|
5126
|
+
url: documentUrl
|
|
5127
|
+
});
|
|
5128
|
+
return await fetch(new URL(attribs.href, docUrl).href);
|
|
5064
5129
|
}
|
|
5065
|
-
document = JSON.parse(html);
|
|
5066
5130
|
}
|
|
5067
|
-
|
|
5131
|
+
document = JSON.parse(html);
|
|
5132
|
+
} else document = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, documentUrl));
|
|
5068
5133
|
logger.debug("Fetched document: {status} {url} {headers}", {
|
|
5069
5134
|
status: response.status,
|
|
5070
5135
|
url: documentUrl,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { t as FetchError } from "./request-B2ac0drD.mjs";
|
|
2
|
+
import { getLogger } from "@logtape/logtape";
|
|
3
|
+
//#region src/body.ts
|
|
4
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
5
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
6
|
+
/** A body exceeded the byte limit. @internal */
|
|
7
|
+
var BodyTooLargeError = class extends FetchError {
|
|
8
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
9
|
+
constructor(url, maxBytes) {
|
|
10
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
11
|
+
this.name = "BodyTooLargeError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
15
|
+
function validateBodySizeLimit(maxBytes) {
|
|
16
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
20
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
21
|
+
* @param message The response or request to read.
|
|
22
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
23
|
+
* @param url The URL to include in errors and logs.
|
|
24
|
+
* @returns The decoded body text.
|
|
25
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
29
|
+
validateBodySizeLimit(maxBytes);
|
|
30
|
+
const reader = message.body?.getReader();
|
|
31
|
+
const tooLarge = () => {
|
|
32
|
+
getLogger([
|
|
33
|
+
"fedify",
|
|
34
|
+
"runtime",
|
|
35
|
+
"body"
|
|
36
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
37
|
+
url: url.toString(),
|
|
38
|
+
maxBytes
|
|
39
|
+
});
|
|
40
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
const length = message.headers.get("Content-Length");
|
|
44
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
45
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
46
|
+
if (reader == null) return "";
|
|
47
|
+
let size = 0;
|
|
48
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
49
|
+
const { done, value } = await reader.read();
|
|
50
|
+
if (done) {
|
|
51
|
+
controller.close();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
size += value.byteLength;
|
|
55
|
+
if (size > maxBytes) tooLarge();
|
|
56
|
+
controller.enqueue(value);
|
|
57
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
58
|
+
try {
|
|
59
|
+
const chunks = [];
|
|
60
|
+
while (true) {
|
|
61
|
+
const { done, value } = await textReader.read();
|
|
62
|
+
if (done) break;
|
|
63
|
+
chunks.push(value);
|
|
64
|
+
}
|
|
65
|
+
return chunks.join("");
|
|
66
|
+
} finally {
|
|
67
|
+
textReader.releaseLock();
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
71
|
+
throw error;
|
|
72
|
+
} finally {
|
|
73
|
+
reader?.releaseLock();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { MAX_BODY_SIZE as n, readBoundedText as r, BodyTooLargeError as t };
|