@fedify/vocab-runtime 2.3.7 → 2.3.9-dev.2048
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-C0gO8fmt.cjs +95 -0
- package/dist/tests/body-DYKjs6_2.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-DI-3ZVtd.mjs → docloader-B3cffcK1.mjs} +26 -34
- package/dist/tests/{docloader-hPeotBri.cjs → docloader-BqznqaJT.cjs} +31 -33
- 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-pMmqUKuo.cjs → key-CWDxavIR.cjs} +2 -2
- package/dist/tests/{key-CrrK9mYh.mjs → key-Qj7u1qmH.mjs} +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-Cj1FlU8G.cjs → request-4o-Cs0CR.cjs} +1 -1
- package/dist/tests/{request-BMbeYb8u.mjs → request-89V7ieWl.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.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-Bz_UUDtL.cjs → multibase-BL45eko3.cjs} +0 -0
- /package/dist/tests/{multibase-B4bvakyA.mjs → multibase-Da9VJCw8.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-BF_Nb_Cu.cjs → url-BbEC9H_R.cjs} +0 -0
package/deno.json
CHANGED
package/dist/mod.cjs
CHANGED
|
@@ -4476,7 +4476,151 @@ const preloadedContexts = {
|
|
|
4476
4476
|
//#endregion
|
|
4477
4477
|
//#region deno.json
|
|
4478
4478
|
var name = "@fedify/vocab-runtime";
|
|
4479
|
-
var version = "2.3.
|
|
4479
|
+
var version = "2.3.9-dev.2048+c88fcc8c";
|
|
4480
|
+
//#endregion
|
|
4481
|
+
//#region src/request.ts
|
|
4482
|
+
/**
|
|
4483
|
+
* Error thrown when fetching a JSON-LD document failed.
|
|
4484
|
+
*/
|
|
4485
|
+
var FetchError = class extends Error {
|
|
4486
|
+
/**
|
|
4487
|
+
* The URL that failed to fetch.
|
|
4488
|
+
*/
|
|
4489
|
+
url;
|
|
4490
|
+
/**
|
|
4491
|
+
* The HTTP response that failed, if available.
|
|
4492
|
+
*/
|
|
4493
|
+
response;
|
|
4494
|
+
/**
|
|
4495
|
+
* Constructs a new `FetchError`.
|
|
4496
|
+
*
|
|
4497
|
+
* @param url The URL that failed to fetch.
|
|
4498
|
+
* @param message Error message.
|
|
4499
|
+
* @param response The failed HTTP response, if available.
|
|
4500
|
+
*/
|
|
4501
|
+
constructor(url, message, response) {
|
|
4502
|
+
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4503
|
+
this.name = "FetchError";
|
|
4504
|
+
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4505
|
+
this.response = response;
|
|
4506
|
+
}
|
|
4507
|
+
};
|
|
4508
|
+
/**
|
|
4509
|
+
* Creates a request for the given URL.
|
|
4510
|
+
* @param url The URL to create the request for.
|
|
4511
|
+
* @param options The options for the request.
|
|
4512
|
+
* @returns The created request.
|
|
4513
|
+
* @internal
|
|
4514
|
+
*/
|
|
4515
|
+
function createActivityPubRequest(url, options = {}) {
|
|
4516
|
+
return new Request(url, {
|
|
4517
|
+
headers: {
|
|
4518
|
+
Accept: "application/activity+json, application/ld+json",
|
|
4519
|
+
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4520
|
+
},
|
|
4521
|
+
redirect: "manual"
|
|
4522
|
+
});
|
|
4523
|
+
}
|
|
4524
|
+
/**
|
|
4525
|
+
* Gets the user agent string for the given application and URL.
|
|
4526
|
+
* @param options The options for making the user agent string.
|
|
4527
|
+
* @returns The user agent string.
|
|
4528
|
+
* @since 1.3.0
|
|
4529
|
+
*/
|
|
4530
|
+
function getUserAgent({ software, url } = {}) {
|
|
4531
|
+
const fedify = `Fedify/${version}`;
|
|
4532
|
+
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;
|
|
4533
|
+
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4534
|
+
if (runtime != null) userAgent.push(runtime);
|
|
4535
|
+
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4536
|
+
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4537
|
+
}
|
|
4538
|
+
/**
|
|
4539
|
+
* Logs the request.
|
|
4540
|
+
* @param request The request to log.
|
|
4541
|
+
* @internal
|
|
4542
|
+
*/
|
|
4543
|
+
function logRequest(logger, request) {
|
|
4544
|
+
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4545
|
+
method: request.method,
|
|
4546
|
+
url: request.url,
|
|
4547
|
+
headers: Object.fromEntries(request.headers.entries())
|
|
4548
|
+
});
|
|
4549
|
+
}
|
|
4550
|
+
//#endregion
|
|
4551
|
+
//#region src/body.ts
|
|
4552
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
4553
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
4554
|
+
/** A body exceeded the byte limit. @internal */
|
|
4555
|
+
var BodyTooLargeError = class extends FetchError {
|
|
4556
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
4557
|
+
constructor(url, maxBytes) {
|
|
4558
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
4559
|
+
this.name = "BodyTooLargeError";
|
|
4560
|
+
}
|
|
4561
|
+
};
|
|
4562
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
4563
|
+
function validateBodySizeLimit(maxBytes) {
|
|
4564
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
4565
|
+
}
|
|
4566
|
+
/**
|
|
4567
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
4568
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
4569
|
+
* @param message The response or request to read.
|
|
4570
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
4571
|
+
* @param url The URL to include in errors and logs.
|
|
4572
|
+
* @returns The decoded body text.
|
|
4573
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
4574
|
+
* @internal
|
|
4575
|
+
*/
|
|
4576
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
4577
|
+
validateBodySizeLimit(maxBytes);
|
|
4578
|
+
const reader = message.body?.getReader();
|
|
4579
|
+
const tooLarge = () => {
|
|
4580
|
+
(0, _logtape_logtape.getLogger)([
|
|
4581
|
+
"fedify",
|
|
4582
|
+
"runtime",
|
|
4583
|
+
"body"
|
|
4584
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
4585
|
+
url: url.toString(),
|
|
4586
|
+
maxBytes
|
|
4587
|
+
});
|
|
4588
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
4589
|
+
};
|
|
4590
|
+
try {
|
|
4591
|
+
const length = message.headers.get("Content-Length");
|
|
4592
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
4593
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
4594
|
+
if (reader == null) return "";
|
|
4595
|
+
let size = 0;
|
|
4596
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
4597
|
+
const { done, value } = await reader.read();
|
|
4598
|
+
if (done) {
|
|
4599
|
+
controller.close();
|
|
4600
|
+
return;
|
|
4601
|
+
}
|
|
4602
|
+
size += value.byteLength;
|
|
4603
|
+
if (size > maxBytes) tooLarge();
|
|
4604
|
+
controller.enqueue(value);
|
|
4605
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
4606
|
+
try {
|
|
4607
|
+
const chunks = [];
|
|
4608
|
+
while (true) {
|
|
4609
|
+
const { done, value } = await textReader.read();
|
|
4610
|
+
if (done) break;
|
|
4611
|
+
chunks.push(value);
|
|
4612
|
+
}
|
|
4613
|
+
return chunks.join("");
|
|
4614
|
+
} finally {
|
|
4615
|
+
textReader.releaseLock();
|
|
4616
|
+
}
|
|
4617
|
+
} catch (error) {
|
|
4618
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
4619
|
+
throw error;
|
|
4620
|
+
} finally {
|
|
4621
|
+
reader?.releaseLock();
|
|
4622
|
+
}
|
|
4623
|
+
}
|
|
4480
4624
|
//#endregion
|
|
4481
4625
|
//#region src/link.ts
|
|
4482
4626
|
const parametersNeedLowerCase = ["rel", "type"];
|
|
@@ -4659,76 +4803,6 @@ var HttpHeaderLink = class HttpHeaderLink {
|
|
|
4659
4803
|
}
|
|
4660
4804
|
};
|
|
4661
4805
|
//#endregion
|
|
4662
|
-
//#region src/request.ts
|
|
4663
|
-
/**
|
|
4664
|
-
* Error thrown when fetching a JSON-LD document failed.
|
|
4665
|
-
*/
|
|
4666
|
-
var FetchError = class extends Error {
|
|
4667
|
-
/**
|
|
4668
|
-
* The URL that failed to fetch.
|
|
4669
|
-
*/
|
|
4670
|
-
url;
|
|
4671
|
-
/**
|
|
4672
|
-
* The HTTP response that failed, if available.
|
|
4673
|
-
*/
|
|
4674
|
-
response;
|
|
4675
|
-
/**
|
|
4676
|
-
* Constructs a new `FetchError`.
|
|
4677
|
-
*
|
|
4678
|
-
* @param url The URL that failed to fetch.
|
|
4679
|
-
* @param message Error message.
|
|
4680
|
-
* @param response The failed HTTP response, if available.
|
|
4681
|
-
*/
|
|
4682
|
-
constructor(url, message, response) {
|
|
4683
|
-
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4684
|
-
this.name = "FetchError";
|
|
4685
|
-
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4686
|
-
this.response = response;
|
|
4687
|
-
}
|
|
4688
|
-
};
|
|
4689
|
-
/**
|
|
4690
|
-
* Creates a request for the given URL.
|
|
4691
|
-
* @param url The URL to create the request for.
|
|
4692
|
-
* @param options The options for the request.
|
|
4693
|
-
* @returns The created request.
|
|
4694
|
-
* @internal
|
|
4695
|
-
*/
|
|
4696
|
-
function createActivityPubRequest(url, options = {}) {
|
|
4697
|
-
return new Request(url, {
|
|
4698
|
-
headers: {
|
|
4699
|
-
Accept: "application/activity+json, application/ld+json",
|
|
4700
|
-
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4701
|
-
},
|
|
4702
|
-
redirect: "manual"
|
|
4703
|
-
});
|
|
4704
|
-
}
|
|
4705
|
-
/**
|
|
4706
|
-
* Gets the user agent string for the given application and URL.
|
|
4707
|
-
* @param options The options for making the user agent string.
|
|
4708
|
-
* @returns The user agent string.
|
|
4709
|
-
* @since 1.3.0
|
|
4710
|
-
*/
|
|
4711
|
-
function getUserAgent({ software, url } = {}) {
|
|
4712
|
-
const fedify = `Fedify/${version}`;
|
|
4713
|
-
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;
|
|
4714
|
-
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4715
|
-
if (runtime != null) userAgent.push(runtime);
|
|
4716
|
-
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4717
|
-
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4718
|
-
}
|
|
4719
|
-
/**
|
|
4720
|
-
* Logs the request.
|
|
4721
|
-
* @param request The request to log.
|
|
4722
|
-
* @internal
|
|
4723
|
-
*/
|
|
4724
|
-
function logRequest(logger, request) {
|
|
4725
|
-
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4726
|
-
method: request.method,
|
|
4727
|
-
url: request.url,
|
|
4728
|
-
headers: Object.fromEntries(request.headers.entries())
|
|
4729
|
-
});
|
|
4730
|
-
}
|
|
4731
|
-
//#endregion
|
|
4732
4806
|
//#region src/url.ts
|
|
4733
4807
|
var UrlError = class extends Error {
|
|
4734
4808
|
constructor(message, options) {
|
|
@@ -4990,38 +5064,29 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
4990
5064
|
}
|
|
4991
5065
|
let document;
|
|
4992
5066
|
if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
|
|
4993
|
-
const
|
|
4994
|
-
const
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
5013
|
-
}
|
|
5014
|
-
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) {
|
|
5015
|
-
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
5016
|
-
alternateUrl: attribs.href,
|
|
5017
|
-
url: documentUrl
|
|
5018
|
-
});
|
|
5019
|
-
return await fetch(new URL(attribs.href, docUrl).href);
|
|
5020
|
-
}
|
|
5067
|
+
const html = await readBoundedText(response, 1024 * 1024, documentUrl);
|
|
5068
|
+
const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
|
|
5069
|
+
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
5070
|
+
let tagMatch;
|
|
5071
|
+
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
5072
|
+
const tagContent = tagMatch[2];
|
|
5073
|
+
let attrMatch;
|
|
5074
|
+
const attribs = {};
|
|
5075
|
+
attrPattern.lastIndex = 0;
|
|
5076
|
+
while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
|
|
5077
|
+
const key = attrMatch[1].toLowerCase();
|
|
5078
|
+
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
5079
|
+
}
|
|
5080
|
+
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) {
|
|
5081
|
+
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
5082
|
+
alternateUrl: attribs.href,
|
|
5083
|
+
url: documentUrl
|
|
5084
|
+
});
|
|
5085
|
+
return await fetch(new URL(attribs.href, docUrl).href);
|
|
5021
5086
|
}
|
|
5022
|
-
document = JSON.parse(html);
|
|
5023
5087
|
}
|
|
5024
|
-
|
|
5088
|
+
document = JSON.parse(html);
|
|
5089
|
+
} else document = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, documentUrl));
|
|
5025
5090
|
logger.debug("Fetched document: {status} {url} {headers}", {
|
|
5026
5091
|
status: response.status,
|
|
5027
5092
|
url: documentUrl,
|
package/dist/mod.js
CHANGED
|
@@ -4472,7 +4472,151 @@ const preloadedContexts = {
|
|
|
4472
4472
|
//#endregion
|
|
4473
4473
|
//#region deno.json
|
|
4474
4474
|
var name = "@fedify/vocab-runtime";
|
|
4475
|
-
var version = "2.3.
|
|
4475
|
+
var version = "2.3.9-dev.2048+c88fcc8c";
|
|
4476
|
+
//#endregion
|
|
4477
|
+
//#region src/request.ts
|
|
4478
|
+
/**
|
|
4479
|
+
* Error thrown when fetching a JSON-LD document failed.
|
|
4480
|
+
*/
|
|
4481
|
+
var FetchError = class extends Error {
|
|
4482
|
+
/**
|
|
4483
|
+
* The URL that failed to fetch.
|
|
4484
|
+
*/
|
|
4485
|
+
url;
|
|
4486
|
+
/**
|
|
4487
|
+
* The HTTP response that failed, if available.
|
|
4488
|
+
*/
|
|
4489
|
+
response;
|
|
4490
|
+
/**
|
|
4491
|
+
* Constructs a new `FetchError`.
|
|
4492
|
+
*
|
|
4493
|
+
* @param url The URL that failed to fetch.
|
|
4494
|
+
* @param message Error message.
|
|
4495
|
+
* @param response The failed HTTP response, if available.
|
|
4496
|
+
*/
|
|
4497
|
+
constructor(url, message, response) {
|
|
4498
|
+
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4499
|
+
this.name = "FetchError";
|
|
4500
|
+
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4501
|
+
this.response = response;
|
|
4502
|
+
}
|
|
4503
|
+
};
|
|
4504
|
+
/**
|
|
4505
|
+
* Creates a request for the given URL.
|
|
4506
|
+
* @param url The URL to create the request for.
|
|
4507
|
+
* @param options The options for the request.
|
|
4508
|
+
* @returns The created request.
|
|
4509
|
+
* @internal
|
|
4510
|
+
*/
|
|
4511
|
+
function createActivityPubRequest(url, options = {}) {
|
|
4512
|
+
return new Request(url, {
|
|
4513
|
+
headers: {
|
|
4514
|
+
Accept: "application/activity+json, application/ld+json",
|
|
4515
|
+
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4516
|
+
},
|
|
4517
|
+
redirect: "manual"
|
|
4518
|
+
});
|
|
4519
|
+
}
|
|
4520
|
+
/**
|
|
4521
|
+
* Gets the user agent string for the given application and URL.
|
|
4522
|
+
* @param options The options for making the user agent string.
|
|
4523
|
+
* @returns The user agent string.
|
|
4524
|
+
* @since 1.3.0
|
|
4525
|
+
*/
|
|
4526
|
+
function getUserAgent({ software, url } = {}) {
|
|
4527
|
+
const fedify = `Fedify/${version}`;
|
|
4528
|
+
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;
|
|
4529
|
+
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4530
|
+
if (runtime != null) userAgent.push(runtime);
|
|
4531
|
+
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4532
|
+
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4533
|
+
}
|
|
4534
|
+
/**
|
|
4535
|
+
* Logs the request.
|
|
4536
|
+
* @param request The request to log.
|
|
4537
|
+
* @internal
|
|
4538
|
+
*/
|
|
4539
|
+
function logRequest(logger, request) {
|
|
4540
|
+
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4541
|
+
method: request.method,
|
|
4542
|
+
url: request.url,
|
|
4543
|
+
headers: Object.fromEntries(request.headers.entries())
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
//#endregion
|
|
4547
|
+
//#region src/body.ts
|
|
4548
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
4549
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
4550
|
+
/** A body exceeded the byte limit. @internal */
|
|
4551
|
+
var BodyTooLargeError = class extends FetchError {
|
|
4552
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
4553
|
+
constructor(url, maxBytes) {
|
|
4554
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
4555
|
+
this.name = "BodyTooLargeError";
|
|
4556
|
+
}
|
|
4557
|
+
};
|
|
4558
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
4559
|
+
function validateBodySizeLimit(maxBytes) {
|
|
4560
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
4561
|
+
}
|
|
4562
|
+
/**
|
|
4563
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
4564
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
4565
|
+
* @param message The response or request to read.
|
|
4566
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
4567
|
+
* @param url The URL to include in errors and logs.
|
|
4568
|
+
* @returns The decoded body text.
|
|
4569
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
4570
|
+
* @internal
|
|
4571
|
+
*/
|
|
4572
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
4573
|
+
validateBodySizeLimit(maxBytes);
|
|
4574
|
+
const reader = message.body?.getReader();
|
|
4575
|
+
const tooLarge = () => {
|
|
4576
|
+
getLogger([
|
|
4577
|
+
"fedify",
|
|
4578
|
+
"runtime",
|
|
4579
|
+
"body"
|
|
4580
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
4581
|
+
url: url.toString(),
|
|
4582
|
+
maxBytes
|
|
4583
|
+
});
|
|
4584
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
4585
|
+
};
|
|
4586
|
+
try {
|
|
4587
|
+
const length = message.headers.get("Content-Length");
|
|
4588
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
4589
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
4590
|
+
if (reader == null) return "";
|
|
4591
|
+
let size = 0;
|
|
4592
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
4593
|
+
const { done, value } = await reader.read();
|
|
4594
|
+
if (done) {
|
|
4595
|
+
controller.close();
|
|
4596
|
+
return;
|
|
4597
|
+
}
|
|
4598
|
+
size += value.byteLength;
|
|
4599
|
+
if (size > maxBytes) tooLarge();
|
|
4600
|
+
controller.enqueue(value);
|
|
4601
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
4602
|
+
try {
|
|
4603
|
+
const chunks = [];
|
|
4604
|
+
while (true) {
|
|
4605
|
+
const { done, value } = await textReader.read();
|
|
4606
|
+
if (done) break;
|
|
4607
|
+
chunks.push(value);
|
|
4608
|
+
}
|
|
4609
|
+
return chunks.join("");
|
|
4610
|
+
} finally {
|
|
4611
|
+
textReader.releaseLock();
|
|
4612
|
+
}
|
|
4613
|
+
} catch (error) {
|
|
4614
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
4615
|
+
throw error;
|
|
4616
|
+
} finally {
|
|
4617
|
+
reader?.releaseLock();
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4476
4620
|
//#endregion
|
|
4477
4621
|
//#region src/link.ts
|
|
4478
4622
|
const parametersNeedLowerCase = ["rel", "type"];
|
|
@@ -4655,76 +4799,6 @@ var HttpHeaderLink = class HttpHeaderLink {
|
|
|
4655
4799
|
}
|
|
4656
4800
|
};
|
|
4657
4801
|
//#endregion
|
|
4658
|
-
//#region src/request.ts
|
|
4659
|
-
/**
|
|
4660
|
-
* Error thrown when fetching a JSON-LD document failed.
|
|
4661
|
-
*/
|
|
4662
|
-
var FetchError = class extends Error {
|
|
4663
|
-
/**
|
|
4664
|
-
* The URL that failed to fetch.
|
|
4665
|
-
*/
|
|
4666
|
-
url;
|
|
4667
|
-
/**
|
|
4668
|
-
* The HTTP response that failed, if available.
|
|
4669
|
-
*/
|
|
4670
|
-
response;
|
|
4671
|
-
/**
|
|
4672
|
-
* Constructs a new `FetchError`.
|
|
4673
|
-
*
|
|
4674
|
-
* @param url The URL that failed to fetch.
|
|
4675
|
-
* @param message Error message.
|
|
4676
|
-
* @param response The failed HTTP response, if available.
|
|
4677
|
-
*/
|
|
4678
|
-
constructor(url, message, response) {
|
|
4679
|
-
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4680
|
-
this.name = "FetchError";
|
|
4681
|
-
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4682
|
-
this.response = response;
|
|
4683
|
-
}
|
|
4684
|
-
};
|
|
4685
|
-
/**
|
|
4686
|
-
* Creates a request for the given URL.
|
|
4687
|
-
* @param url The URL to create the request for.
|
|
4688
|
-
* @param options The options for the request.
|
|
4689
|
-
* @returns The created request.
|
|
4690
|
-
* @internal
|
|
4691
|
-
*/
|
|
4692
|
-
function createActivityPubRequest(url, options = {}) {
|
|
4693
|
-
return new Request(url, {
|
|
4694
|
-
headers: {
|
|
4695
|
-
Accept: "application/activity+json, application/ld+json",
|
|
4696
|
-
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4697
|
-
},
|
|
4698
|
-
redirect: "manual"
|
|
4699
|
-
});
|
|
4700
|
-
}
|
|
4701
|
-
/**
|
|
4702
|
-
* Gets the user agent string for the given application and URL.
|
|
4703
|
-
* @param options The options for making the user agent string.
|
|
4704
|
-
* @returns The user agent string.
|
|
4705
|
-
* @since 1.3.0
|
|
4706
|
-
*/
|
|
4707
|
-
function getUserAgent({ software, url } = {}) {
|
|
4708
|
-
const fedify = `Fedify/${version}`;
|
|
4709
|
-
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;
|
|
4710
|
-
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4711
|
-
if (runtime != null) userAgent.push(runtime);
|
|
4712
|
-
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4713
|
-
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4714
|
-
}
|
|
4715
|
-
/**
|
|
4716
|
-
* Logs the request.
|
|
4717
|
-
* @param request The request to log.
|
|
4718
|
-
* @internal
|
|
4719
|
-
*/
|
|
4720
|
-
function logRequest(logger, request) {
|
|
4721
|
-
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4722
|
-
method: request.method,
|
|
4723
|
-
url: request.url,
|
|
4724
|
-
headers: Object.fromEntries(request.headers.entries())
|
|
4725
|
-
});
|
|
4726
|
-
}
|
|
4727
|
-
//#endregion
|
|
4728
4802
|
//#region src/url.ts
|
|
4729
4803
|
var UrlError = class extends Error {
|
|
4730
4804
|
constructor(message, options) {
|
|
@@ -4986,38 +5060,29 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
4986
5060
|
}
|
|
4987
5061
|
let document;
|
|
4988
5062
|
if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
|
|
4989
|
-
const
|
|
4990
|
-
const
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
5009
|
-
}
|
|
5010
|
-
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) {
|
|
5011
|
-
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
5012
|
-
alternateUrl: attribs.href,
|
|
5013
|
-
url: documentUrl
|
|
5014
|
-
});
|
|
5015
|
-
return await fetch(new URL(attribs.href, docUrl).href);
|
|
5016
|
-
}
|
|
5063
|
+
const html = await readBoundedText(response, 1024 * 1024, documentUrl);
|
|
5064
|
+
const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
|
|
5065
|
+
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
5066
|
+
let tagMatch;
|
|
5067
|
+
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
5068
|
+
const tagContent = tagMatch[2];
|
|
5069
|
+
let attrMatch;
|
|
5070
|
+
const attribs = {};
|
|
5071
|
+
attrPattern.lastIndex = 0;
|
|
5072
|
+
while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
|
|
5073
|
+
const key = attrMatch[1].toLowerCase();
|
|
5074
|
+
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
5075
|
+
}
|
|
5076
|
+
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) {
|
|
5077
|
+
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
5078
|
+
alternateUrl: attribs.href,
|
|
5079
|
+
url: documentUrl
|
|
5080
|
+
});
|
|
5081
|
+
return await fetch(new URL(attribs.href, docUrl).href);
|
|
5017
5082
|
}
|
|
5018
|
-
document = JSON.parse(html);
|
|
5019
5083
|
}
|
|
5020
|
-
|
|
5084
|
+
document = JSON.parse(html);
|
|
5085
|
+
} else document = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, documentUrl));
|
|
5021
5086
|
logger.debug("Fetched document: {status} {url} {headers}", {
|
|
5022
5087
|
status: response.status,
|
|
5023
5088
|
url: documentUrl,
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require("./chunk-C2EiDwsr.cjs");
|
|
2
|
+
const require_request = require("./request-4o-Cs0CR.cjs");
|
|
3
|
+
let _logtape_logtape = require("@logtape/logtape");
|
|
4
|
+
//#region src/body.ts
|
|
5
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
6
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
7
|
+
/** A body exceeded the byte limit. @internal */
|
|
8
|
+
var BodyTooLargeError = class extends require_request.FetchError {
|
|
9
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
10
|
+
constructor(url, maxBytes) {
|
|
11
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
12
|
+
this.name = "BodyTooLargeError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
16
|
+
function validateBodySizeLimit(maxBytes) {
|
|
17
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
21
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
22
|
+
* @param message The response or request to read.
|
|
23
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
24
|
+
* @param url The URL to include in errors and logs.
|
|
25
|
+
* @returns The decoded body text.
|
|
26
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
30
|
+
validateBodySizeLimit(maxBytes);
|
|
31
|
+
const reader = message.body?.getReader();
|
|
32
|
+
const tooLarge = () => {
|
|
33
|
+
(0, _logtape_logtape.getLogger)([
|
|
34
|
+
"fedify",
|
|
35
|
+
"runtime",
|
|
36
|
+
"body"
|
|
37
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
38
|
+
url: url.toString(),
|
|
39
|
+
maxBytes
|
|
40
|
+
});
|
|
41
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
42
|
+
};
|
|
43
|
+
try {
|
|
44
|
+
const length = message.headers.get("Content-Length");
|
|
45
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
46
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
47
|
+
if (reader == null) return "";
|
|
48
|
+
let size = 0;
|
|
49
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
50
|
+
const { done, value } = await reader.read();
|
|
51
|
+
if (done) {
|
|
52
|
+
controller.close();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
size += value.byteLength;
|
|
56
|
+
if (size > maxBytes) tooLarge();
|
|
57
|
+
controller.enqueue(value);
|
|
58
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
59
|
+
try {
|
|
60
|
+
const chunks = [];
|
|
61
|
+
while (true) {
|
|
62
|
+
const { done, value } = await textReader.read();
|
|
63
|
+
if (done) break;
|
|
64
|
+
chunks.push(value);
|
|
65
|
+
}
|
|
66
|
+
return chunks.join("");
|
|
67
|
+
} finally {
|
|
68
|
+
textReader.releaseLock();
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
72
|
+
throw error;
|
|
73
|
+
} finally {
|
|
74
|
+
reader?.releaseLock();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
Object.defineProperty(exports, "BodyTooLargeError", {
|
|
79
|
+
enumerable: true,
|
|
80
|
+
get: function() {
|
|
81
|
+
return BodyTooLargeError;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
Object.defineProperty(exports, "MAX_BODY_SIZE", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
get: function() {
|
|
87
|
+
return MAX_BODY_SIZE;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
Object.defineProperty(exports, "readBoundedText", {
|
|
91
|
+
enumerable: true,
|
|
92
|
+
get: function() {
|
|
93
|
+
return readBoundedText;
|
|
94
|
+
}
|
|
95
|
+
});
|