@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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { t as FetchError } from "./request-89V7ieWl.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 };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require("./chunk-C2EiDwsr.cjs");
|
|
2
|
+
const require_body = require("./body-C0gO8fmt.cjs");
|
|
3
|
+
let node_assert_strict = require("node:assert/strict");
|
|
4
|
+
let node_test = require("node:test");
|
|
5
|
+
let node_zlib = require("node:zlib");
|
|
6
|
+
//#region src/body.test.ts
|
|
7
|
+
const url = "https://example.com/document";
|
|
8
|
+
(0, node_test.test)("readBoundedText handles empty bodies and flushes incomplete UTF-8", async () => {
|
|
9
|
+
(0, node_assert_strict.deepStrictEqual)(await require_body.readBoundedText(new Response(null), 1, url), "");
|
|
10
|
+
(0, node_assert_strict.deepStrictEqual)(await require_body.readBoundedText(new Response(""), 1, url), "");
|
|
11
|
+
(0, node_assert_strict.deepStrictEqual)(await require_body.readBoundedText(new Response(new Uint8Array([226, 130])), 2, url), "�");
|
|
12
|
+
});
|
|
13
|
+
(0, node_test.test)("readBoundedText counts bytes and preserves split UTF-8", async () => {
|
|
14
|
+
const bytes = new TextEncoder().encode("\"안녕\"");
|
|
15
|
+
const stream = new ReadableStream({ start(controller) {
|
|
16
|
+
for (const byte of bytes) controller.enqueue(new Uint8Array([byte]));
|
|
17
|
+
controller.close();
|
|
18
|
+
} });
|
|
19
|
+
(0, node_assert_strict.deepStrictEqual)(await require_body.readBoundedText(new Response(stream), bytes.length, url), "\"안녕\"");
|
|
20
|
+
await (0, node_assert_strict.rejects)(require_body.readBoundedText(new Response(bytes), bytes.length - 1, url), require_body.BodyTooLargeError);
|
|
21
|
+
});
|
|
22
|
+
(0, node_test.test)("readBoundedText rejects a declared oversized body without pulling", async () => {
|
|
23
|
+
let pulled = false;
|
|
24
|
+
let canceled = false;
|
|
25
|
+
const response = new Response(new ReadableStream({
|
|
26
|
+
pull() {
|
|
27
|
+
pulled = true;
|
|
28
|
+
},
|
|
29
|
+
cancel() {
|
|
30
|
+
canceled = true;
|
|
31
|
+
}
|
|
32
|
+
}, { highWaterMark: 0 }), { headers: { "Content-Length": "100" } });
|
|
33
|
+
await (0, node_assert_strict.rejects)(require_body.readBoundedText(response, 10, url), require_body.BodyTooLargeError);
|
|
34
|
+
(0, node_assert_strict.deepStrictEqual)(pulled, false);
|
|
35
|
+
(0, node_assert_strict.deepStrictEqual)(canceled, true);
|
|
36
|
+
(0, node_assert_strict.deepStrictEqual)(response.body?.locked, false);
|
|
37
|
+
});
|
|
38
|
+
(0, node_test.test)("readBoundedText cancels chunked bodies despite absent or false lengths", async () => {
|
|
39
|
+
for (const length of [
|
|
40
|
+
void 0,
|
|
41
|
+
"1",
|
|
42
|
+
"invalid"
|
|
43
|
+
]) {
|
|
44
|
+
let pulls = 0;
|
|
45
|
+
let canceled = false;
|
|
46
|
+
const stream = new ReadableStream({
|
|
47
|
+
pull(controller) {
|
|
48
|
+
pulls++;
|
|
49
|
+
controller.enqueue(new Uint8Array(4));
|
|
50
|
+
},
|
|
51
|
+
cancel() {
|
|
52
|
+
canceled = true;
|
|
53
|
+
}
|
|
54
|
+
}, { highWaterMark: 0 });
|
|
55
|
+
const response = new Response(stream, { headers: length == null ? {} : { "Content-Length": length } });
|
|
56
|
+
await (0, node_assert_strict.rejects)(require_body.readBoundedText(response, 8, url), require_body.BodyTooLargeError);
|
|
57
|
+
(0, node_assert_strict.deepStrictEqual)(pulls, 3);
|
|
58
|
+
(0, node_assert_strict.deepStrictEqual)(canceled, true);
|
|
59
|
+
(0, node_assert_strict.deepStrictEqual)(response.body?.locked, false);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
(0, node_test.test)("readBoundedText limits decoded gzip bytes, not the compressed length", async () => {
|
|
63
|
+
const compressed = (0, node_zlib.gzipSync)("\"" + "a".repeat(4096) + "\"");
|
|
64
|
+
(0, node_assert_strict.ok)(compressed.length < 128);
|
|
65
|
+
const decoded = (0, node_zlib.gunzipSync)(compressed);
|
|
66
|
+
await (0, node_assert_strict.rejects)(require_body.readBoundedText(new Response(decoded, { headers: {
|
|
67
|
+
"Content-Encoding": "gzip",
|
|
68
|
+
"Content-Length": String(compressed.length)
|
|
69
|
+
} }), 128, url), require_body.BodyTooLargeError);
|
|
70
|
+
(0, node_assert_strict.deepStrictEqual)(await require_body.readBoundedText(new Response("{}", { headers: {
|
|
71
|
+
"Content-Encoding": "gzip",
|
|
72
|
+
"Content-Length": "22"
|
|
73
|
+
} }), 2, url), "{}");
|
|
74
|
+
});
|
|
75
|
+
(0, node_test.test)("readBoundedText rejects invalid limits and releases failed streams", async () => {
|
|
76
|
+
for (const limit of [
|
|
77
|
+
0,
|
|
78
|
+
-1,
|
|
79
|
+
NaN,
|
|
80
|
+
Infinity,
|
|
81
|
+
1.5
|
|
82
|
+
]) await (0, node_assert_strict.rejects)(require_body.readBoundedText(new Response("{}"), limit, url), RangeError);
|
|
83
|
+
const failure = /* @__PURE__ */ new Error("broken stream");
|
|
84
|
+
const response = new Response(new ReadableStream({ pull(controller) {
|
|
85
|
+
controller.error(failure);
|
|
86
|
+
} }));
|
|
87
|
+
await (0, node_assert_strict.rejects)(require_body.readBoundedText(response, 10, url), (error) => error === failure);
|
|
88
|
+
(0, node_assert_strict.deepStrictEqual)(response.body?.locked, false);
|
|
89
|
+
});
|
|
90
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { r as readBoundedText, t as BodyTooLargeError } from "./body-DYKjs6_2.mjs";
|
|
2
|
+
import { deepStrictEqual, ok, rejects } from "node:assert/strict";
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
import { gunzipSync, gzipSync } from "node:zlib";
|
|
5
|
+
//#region src/body.test.ts
|
|
6
|
+
const url = "https://example.com/document";
|
|
7
|
+
test("readBoundedText handles empty bodies and flushes incomplete UTF-8", async () => {
|
|
8
|
+
deepStrictEqual(await readBoundedText(new Response(null), 1, url), "");
|
|
9
|
+
deepStrictEqual(await readBoundedText(new Response(""), 1, url), "");
|
|
10
|
+
deepStrictEqual(await readBoundedText(new Response(new Uint8Array([226, 130])), 2, url), "�");
|
|
11
|
+
});
|
|
12
|
+
test("readBoundedText counts bytes and preserves split UTF-8", async () => {
|
|
13
|
+
const bytes = new TextEncoder().encode("\"안녕\"");
|
|
14
|
+
const stream = new ReadableStream({ start(controller) {
|
|
15
|
+
for (const byte of bytes) controller.enqueue(new Uint8Array([byte]));
|
|
16
|
+
controller.close();
|
|
17
|
+
} });
|
|
18
|
+
deepStrictEqual(await readBoundedText(new Response(stream), bytes.length, url), "\"안녕\"");
|
|
19
|
+
await rejects(readBoundedText(new Response(bytes), bytes.length - 1, url), BodyTooLargeError);
|
|
20
|
+
});
|
|
21
|
+
test("readBoundedText rejects a declared oversized body without pulling", async () => {
|
|
22
|
+
let pulled = false;
|
|
23
|
+
let canceled = false;
|
|
24
|
+
const response = new Response(new ReadableStream({
|
|
25
|
+
pull() {
|
|
26
|
+
pulled = true;
|
|
27
|
+
},
|
|
28
|
+
cancel() {
|
|
29
|
+
canceled = true;
|
|
30
|
+
}
|
|
31
|
+
}, { highWaterMark: 0 }), { headers: { "Content-Length": "100" } });
|
|
32
|
+
await rejects(readBoundedText(response, 10, url), BodyTooLargeError);
|
|
33
|
+
deepStrictEqual(pulled, false);
|
|
34
|
+
deepStrictEqual(canceled, true);
|
|
35
|
+
deepStrictEqual(response.body?.locked, false);
|
|
36
|
+
});
|
|
37
|
+
test("readBoundedText cancels chunked bodies despite absent or false lengths", async () => {
|
|
38
|
+
for (const length of [
|
|
39
|
+
void 0,
|
|
40
|
+
"1",
|
|
41
|
+
"invalid"
|
|
42
|
+
]) {
|
|
43
|
+
let pulls = 0;
|
|
44
|
+
let canceled = false;
|
|
45
|
+
const stream = new ReadableStream({
|
|
46
|
+
pull(controller) {
|
|
47
|
+
pulls++;
|
|
48
|
+
controller.enqueue(new Uint8Array(4));
|
|
49
|
+
},
|
|
50
|
+
cancel() {
|
|
51
|
+
canceled = true;
|
|
52
|
+
}
|
|
53
|
+
}, { highWaterMark: 0 });
|
|
54
|
+
const response = new Response(stream, { headers: length == null ? {} : { "Content-Length": length } });
|
|
55
|
+
await rejects(readBoundedText(response, 8, url), BodyTooLargeError);
|
|
56
|
+
deepStrictEqual(pulls, 3);
|
|
57
|
+
deepStrictEqual(canceled, true);
|
|
58
|
+
deepStrictEqual(response.body?.locked, false);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
test("readBoundedText limits decoded gzip bytes, not the compressed length", async () => {
|
|
62
|
+
const compressed = gzipSync("\"" + "a".repeat(4096) + "\"");
|
|
63
|
+
ok(compressed.length < 128);
|
|
64
|
+
const decoded = gunzipSync(compressed);
|
|
65
|
+
await rejects(readBoundedText(new Response(decoded, { headers: {
|
|
66
|
+
"Content-Encoding": "gzip",
|
|
67
|
+
"Content-Length": String(compressed.length)
|
|
68
|
+
} }), 128, url), BodyTooLargeError);
|
|
69
|
+
deepStrictEqual(await readBoundedText(new Response("{}", { headers: {
|
|
70
|
+
"Content-Encoding": "gzip",
|
|
71
|
+
"Content-Length": "22"
|
|
72
|
+
} }), 2, url), "{}");
|
|
73
|
+
});
|
|
74
|
+
test("readBoundedText rejects invalid limits and releases failed streams", async () => {
|
|
75
|
+
for (const limit of [
|
|
76
|
+
0,
|
|
77
|
+
-1,
|
|
78
|
+
NaN,
|
|
79
|
+
Infinity,
|
|
80
|
+
1.5
|
|
81
|
+
]) await rejects(readBoundedText(new Response("{}"), limit, url), RangeError);
|
|
82
|
+
const failure = /* @__PURE__ */ new Error("broken stream");
|
|
83
|
+
const response = new Response(new ReadableStream({ pull(controller) {
|
|
84
|
+
controller.error(failure);
|
|
85
|
+
} }));
|
|
86
|
+
await rejects(readBoundedText(response, 10, url), (error) => error === failure);
|
|
87
|
+
deepStrictEqual(response.body?.locked, false);
|
|
88
|
+
});
|
|
89
|
+
//#endregion
|
|
90
|
+
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
require("./chunk-C2EiDwsr.cjs");
|
|
2
|
-
require("./docloader-
|
|
3
|
-
require("./url-
|
|
4
|
-
require("./key-
|
|
5
|
-
require("./multibase-
|
|
6
|
-
require("./langstr-
|
|
7
|
-
let node_assert = require("node:assert");
|
|
2
|
+
require("./docloader-BqznqaJT.cjs");
|
|
3
|
+
require("./url-BbEC9H_R.cjs");
|
|
4
|
+
require("./key-CWDxavIR.cjs");
|
|
5
|
+
require("./multibase-BL45eko3.cjs");
|
|
6
|
+
require("./langstr-BOQHfJDr.cjs");
|
|
8
7
|
let node_test = require("node:test");
|
|
8
|
+
let node_assert = require("node:assert");
|
|
9
9
|
//#region src/decimal.ts
|
|
10
10
|
const DECIMAL_PATTERN = /^(\+|-)?([0-9]+(\.[0-9]*)?|\.[0-9]+)$/;
|
|
11
11
|
const XML_SCHEMA_WHITESPACE_PATTERN = /[\t\n\r ]+/g;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import "./docloader-
|
|
2
|
-
import "./url-
|
|
3
|
-
import "./key-
|
|
4
|
-
import "./multibase-
|
|
5
|
-
import "./langstr-
|
|
6
|
-
import { deepStrictEqual, throws } from "node:assert";
|
|
1
|
+
import "./docloader-B3cffcK1.mjs";
|
|
2
|
+
import "./url-BBfduh6o.mjs";
|
|
3
|
+
import "./key-Qj7u1qmH.mjs";
|
|
4
|
+
import "./multibase-Da9VJCw8.mjs";
|
|
5
|
+
import "./langstr-DDLc4833.mjs";
|
|
7
6
|
import { test } from "node:test";
|
|
7
|
+
import { deepStrictEqual, throws } from "node:assert";
|
|
8
8
|
//#region src/decimal.ts
|
|
9
9
|
const DECIMAL_PATTERN = /^(\+|-)?([0-9]+(\.[0-9]*)?|\.[0-9]+)$/;
|
|
10
10
|
const XML_SCHEMA_WHITESPACE_PATTERN = /[\t\n\r ]+/g;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-89V7ieWl.mjs";
|
|
2
|
+
import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-DYKjs6_2.mjs";
|
|
3
|
+
import { t as HttpHeaderLink } from "./link-CYszIegt.mjs";
|
|
4
|
+
import { o as validatePublicUrl, t as UrlError } from "./url-BBfduh6o.mjs";
|
|
4
5
|
import { getLogger } from "@logtape/logtape";
|
|
5
6
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
6
7
|
//#region src/contexts/activitystreams.json
|
|
@@ -4529,38 +4530,29 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
4529
4530
|
}
|
|
4530
4531
|
let document;
|
|
4531
4532
|
if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
|
|
4532
|
-
const
|
|
4533
|
-
const
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
4552
|
-
}
|
|
4553
|
-
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) {
|
|
4554
|
-
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
4555
|
-
alternateUrl: attribs.href,
|
|
4556
|
-
url: documentUrl
|
|
4557
|
-
});
|
|
4558
|
-
return await fetch(new URL(attribs.href, docUrl).href);
|
|
4559
|
-
}
|
|
4533
|
+
const html = await readBoundedText(response, 1024 * 1024, documentUrl);
|
|
4534
|
+
const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
|
|
4535
|
+
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
4536
|
+
let tagMatch;
|
|
4537
|
+
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
4538
|
+
const tagContent = tagMatch[2];
|
|
4539
|
+
let attrMatch;
|
|
4540
|
+
const attribs = {};
|
|
4541
|
+
attrPattern.lastIndex = 0;
|
|
4542
|
+
while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
|
|
4543
|
+
const key = attrMatch[1].toLowerCase();
|
|
4544
|
+
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
4545
|
+
}
|
|
4546
|
+
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) {
|
|
4547
|
+
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
4548
|
+
alternateUrl: attribs.href,
|
|
4549
|
+
url: documentUrl
|
|
4550
|
+
});
|
|
4551
|
+
return await fetch(new URL(attribs.href, docUrl).href);
|
|
4560
4552
|
}
|
|
4561
|
-
document = JSON.parse(html);
|
|
4562
4553
|
}
|
|
4563
|
-
|
|
4554
|
+
document = JSON.parse(html);
|
|
4555
|
+
} else document = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, documentUrl));
|
|
4564
4556
|
logger.debug("Fetched document: {status} {url} {headers}", {
|
|
4565
4557
|
status: response.status,
|
|
4566
4558
|
url: documentUrl,
|
|
@@ -4665,4 +4657,4 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
|
|
|
4665
4657
|
return load;
|
|
4666
4658
|
}
|
|
4667
4659
|
//#endregion
|
|
4668
|
-
export {
|
|
4660
|
+
export { cid_v1_default as i, getRemoteDocument as n, preloadedContexts as r, getDocumentLoader as t };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
require("./chunk-C2EiDwsr.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
2
|
+
const require_request = require("./request-4o-Cs0CR.cjs");
|
|
3
|
+
const require_body = require("./body-C0gO8fmt.cjs");
|
|
4
|
+
const require_link = require("./link-qDTmjPw_.cjs");
|
|
5
|
+
const require_url = require("./url-BbEC9H_R.cjs");
|
|
5
6
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
7
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
7
8
|
//#region src/contexts/activitystreams.json
|
|
@@ -4530,38 +4531,29 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
4530
4531
|
}
|
|
4531
4532
|
let document;
|
|
4532
4533
|
if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
|
|
4533
|
-
const
|
|
4534
|
-
const
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
4553
|
-
}
|
|
4554
|
-
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) {
|
|
4555
|
-
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
4556
|
-
alternateUrl: attribs.href,
|
|
4557
|
-
url: documentUrl
|
|
4558
|
-
});
|
|
4559
|
-
return await fetch(new URL(attribs.href, docUrl).href);
|
|
4560
|
-
}
|
|
4534
|
+
const html = await require_body.readBoundedText(response, 1024 * 1024, documentUrl);
|
|
4535
|
+
const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
|
|
4536
|
+
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
4537
|
+
let tagMatch;
|
|
4538
|
+
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
4539
|
+
const tagContent = tagMatch[2];
|
|
4540
|
+
let attrMatch;
|
|
4541
|
+
const attribs = {};
|
|
4542
|
+
attrPattern.lastIndex = 0;
|
|
4543
|
+
while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
|
|
4544
|
+
const key = attrMatch[1].toLowerCase();
|
|
4545
|
+
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
4546
|
+
}
|
|
4547
|
+
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) {
|
|
4548
|
+
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
4549
|
+
alternateUrl: attribs.href,
|
|
4550
|
+
url: documentUrl
|
|
4551
|
+
});
|
|
4552
|
+
return await fetch(new URL(attribs.href, docUrl).href);
|
|
4561
4553
|
}
|
|
4562
|
-
document = JSON.parse(html);
|
|
4563
4554
|
}
|
|
4564
|
-
|
|
4555
|
+
document = JSON.parse(html);
|
|
4556
|
+
} else document = JSON.parse(await require_body.readBoundedText(response, require_body.MAX_BODY_SIZE, documentUrl));
|
|
4565
4557
|
logger.debug("Fetched document: {status} {url} {headers}", {
|
|
4566
4558
|
status: response.status,
|
|
4567
4559
|
url: documentUrl,
|
|
@@ -4678,6 +4670,12 @@ Object.defineProperty(exports, "getDocumentLoader", {
|
|
|
4678
4670
|
return getDocumentLoader;
|
|
4679
4671
|
}
|
|
4680
4672
|
});
|
|
4673
|
+
Object.defineProperty(exports, "getRemoteDocument", {
|
|
4674
|
+
enumerable: true,
|
|
4675
|
+
get: function() {
|
|
4676
|
+
return getRemoteDocument;
|
|
4677
|
+
}
|
|
4678
|
+
});
|
|
4681
4679
|
Object.defineProperty(exports, "preloadedContexts", {
|
|
4682
4680
|
enumerable: true,
|
|
4683
4681
|
get: function() {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-C2EiDwsr.cjs");
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const require_url = require("./url-
|
|
5
|
-
let node_assert = require("node:assert");
|
|
2
|
+
const require_request = require("./request-4o-Cs0CR.cjs");
|
|
3
|
+
const require_docloader = require("./docloader-BqznqaJT.cjs");
|
|
4
|
+
const require_url = require("./url-BbEC9H_R.cjs");
|
|
6
5
|
let node_test = require("node:test");
|
|
6
|
+
let node_zlib = require("node:zlib");
|
|
7
|
+
let node_assert = require("node:assert");
|
|
8
|
+
let node_http = require("node:http");
|
|
7
9
|
//#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
|
|
8
10
|
var require_glob_to_regexp = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, module) => {
|
|
9
11
|
module.exports = function(glob, opts) {
|
|
@@ -1567,4 +1569,81 @@ var esm_default = new class FetchMock {
|
|
|
1567
1569
|
});
|
|
1568
1570
|
esm_default.hardReset();
|
|
1569
1571
|
});
|
|
1572
|
+
(0, node_test.test)("getDocumentLoader() bounds JSON, HTML, and alternate documents", async () => {
|
|
1573
|
+
const url = "https://example.com/bounded";
|
|
1574
|
+
esm_default.mockGlobal();
|
|
1575
|
+
let oversized = true;
|
|
1576
|
+
try {
|
|
1577
|
+
esm_default.get(url, () => new Response("{\"name\":\"hello\"}", { headers: {
|
|
1578
|
+
"Content-Type": "application/activity+json",
|
|
1579
|
+
...oversized ? { "Content-Length": String(16777217) } : {}
|
|
1580
|
+
} }));
|
|
1581
|
+
const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
|
|
1582
|
+
await (0, node_assert.rejects)(loader(url), require_request.FetchError);
|
|
1583
|
+
oversized = false;
|
|
1584
|
+
(0, node_assert.deepStrictEqual)((await loader(url)).document, { name: "hello" });
|
|
1585
|
+
oversized = true;
|
|
1586
|
+
esm_default.get(`${url}/html`, () => new Response(" ".repeat(1048577), { headers: { "Content-Type": "text/html" } }));
|
|
1587
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(`${url}/html`), require_request.FetchError);
|
|
1588
|
+
esm_default.get(`${url}/alternate`, () => new Response("", { headers: {
|
|
1589
|
+
"Content-Type": "text/html",
|
|
1590
|
+
Link: `<${url}>; rel="alternate"; type="application/activity+json"`
|
|
1591
|
+
} }));
|
|
1592
|
+
await (0, node_assert.rejects)(loader(`${url}/alternate`), require_request.FetchError);
|
|
1593
|
+
esm_default.get(`${url}/redirect`, {
|
|
1594
|
+
status: 302,
|
|
1595
|
+
headers: { Location: url }
|
|
1596
|
+
});
|
|
1597
|
+
await (0, node_assert.rejects)(loader(`${url}/redirect`), require_request.FetchError);
|
|
1598
|
+
} finally {
|
|
1599
|
+
esm_default.hardReset();
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
(0, node_test.test)("getDocumentLoader() rejects oversized gzip responses from fetch", async () => {
|
|
1603
|
+
const document = { name: "a".repeat(16 * 1024 * 1024) };
|
|
1604
|
+
const compressed = (0, node_zlib.gzipSync)(JSON.stringify(document));
|
|
1605
|
+
(0, node_assert.ok)(compressed.byteLength < 64 * 1024);
|
|
1606
|
+
const server = (0, node_http.createServer)((request, response) => {
|
|
1607
|
+
const body = request.url === "/small" ? (0, node_zlib.gzipSync)("{\"name\":\"hello\"}") : compressed;
|
|
1608
|
+
response.writeHead(200, {
|
|
1609
|
+
"Content-Type": "application/activity+json",
|
|
1610
|
+
"Content-Encoding": "gzip",
|
|
1611
|
+
"Content-Length": body.byteLength
|
|
1612
|
+
});
|
|
1613
|
+
response.end(body);
|
|
1614
|
+
});
|
|
1615
|
+
await new Promise((resolve, reject) => {
|
|
1616
|
+
server.once("error", reject);
|
|
1617
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
1618
|
+
});
|
|
1619
|
+
try {
|
|
1620
|
+
const address = server.address();
|
|
1621
|
+
(0, node_assert.ok)(address != null && typeof address !== "string");
|
|
1622
|
+
const url = `http://127.0.0.1:${address.port}/document`;
|
|
1623
|
+
await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(url), require_request.FetchError);
|
|
1624
|
+
(0, node_assert.deepStrictEqual)((await require_docloader.getDocumentLoader({ allowPrivateAddress: true })(new URL("/small", url).href)).document, { name: "hello" });
|
|
1625
|
+
} finally {
|
|
1626
|
+
await new Promise((resolve, reject) => {
|
|
1627
|
+
server.close((error) => error == null ? resolve() : reject(error));
|
|
1628
|
+
server.closeAllConnections();
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
(0, node_test.test)("getRemoteDocument() bounds JSON by default", async () => {
|
|
1633
|
+
let canceled = false;
|
|
1634
|
+
let pulls = 0;
|
|
1635
|
+
await (0, node_assert.rejects)(require_docloader.getRemoteDocument("https://example.com/document", new Response(new ReadableStream({
|
|
1636
|
+
pull(controller) {
|
|
1637
|
+
pulls++;
|
|
1638
|
+
controller.enqueue(new Uint8Array(64 * 1024).fill(32));
|
|
1639
|
+
},
|
|
1640
|
+
cancel() {
|
|
1641
|
+
canceled = true;
|
|
1642
|
+
}
|
|
1643
|
+
}, { highWaterMark: 0 })), () => {
|
|
1644
|
+
throw new Error("Unexpected alternate document");
|
|
1645
|
+
}), require_request.FetchError);
|
|
1646
|
+
(0, node_assert.deepStrictEqual)(canceled, true);
|
|
1647
|
+
(0, node_assert.deepStrictEqual)(pulls, 257);
|
|
1648
|
+
});
|
|
1570
1649
|
//#endregion
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
3
|
-
import { t as UrlError } from "./url-
|
|
4
|
-
import { deepStrictEqual, ok, rejects } from "node:assert";
|
|
1
|
+
import { t as FetchError } from "./request-89V7ieWl.mjs";
|
|
2
|
+
import { i as cid_v1_default, n as getRemoteDocument, r as preloadedContexts, t as getDocumentLoader } from "./docloader-B3cffcK1.mjs";
|
|
3
|
+
import { t as UrlError } from "./url-BBfduh6o.mjs";
|
|
5
4
|
import { test } from "node:test";
|
|
5
|
+
import { gzipSync } from "node:zlib";
|
|
6
|
+
import { deepStrictEqual, ok, rejects } from "node:assert";
|
|
7
|
+
import { createServer } from "node:http";
|
|
6
8
|
//#region \0rolldown/runtime.js
|
|
7
9
|
var __create = Object.create;
|
|
8
10
|
var __defProp = Object.defineProperty;
|
|
@@ -1586,5 +1588,82 @@ test("getDocumentLoader()", async (t) => {
|
|
|
1586
1588
|
});
|
|
1587
1589
|
esm_default.hardReset();
|
|
1588
1590
|
});
|
|
1591
|
+
test("getDocumentLoader() bounds JSON, HTML, and alternate documents", async () => {
|
|
1592
|
+
const url = "https://example.com/bounded";
|
|
1593
|
+
esm_default.mockGlobal();
|
|
1594
|
+
let oversized = true;
|
|
1595
|
+
try {
|
|
1596
|
+
esm_default.get(url, () => new Response("{\"name\":\"hello\"}", { headers: {
|
|
1597
|
+
"Content-Type": "application/activity+json",
|
|
1598
|
+
...oversized ? { "Content-Length": String(16777217) } : {}
|
|
1599
|
+
} }));
|
|
1600
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
1601
|
+
await rejects(loader(url), FetchError);
|
|
1602
|
+
oversized = false;
|
|
1603
|
+
deepStrictEqual((await loader(url)).document, { name: "hello" });
|
|
1604
|
+
oversized = true;
|
|
1605
|
+
esm_default.get(`${url}/html`, () => new Response(" ".repeat(1048577), { headers: { "Content-Type": "text/html" } }));
|
|
1606
|
+
await rejects(getDocumentLoader({ allowPrivateAddress: true })(`${url}/html`), FetchError);
|
|
1607
|
+
esm_default.get(`${url}/alternate`, () => new Response("", { headers: {
|
|
1608
|
+
"Content-Type": "text/html",
|
|
1609
|
+
Link: `<${url}>; rel="alternate"; type="application/activity+json"`
|
|
1610
|
+
} }));
|
|
1611
|
+
await rejects(loader(`${url}/alternate`), FetchError);
|
|
1612
|
+
esm_default.get(`${url}/redirect`, {
|
|
1613
|
+
status: 302,
|
|
1614
|
+
headers: { Location: url }
|
|
1615
|
+
});
|
|
1616
|
+
await rejects(loader(`${url}/redirect`), FetchError);
|
|
1617
|
+
} finally {
|
|
1618
|
+
esm_default.hardReset();
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
test("getDocumentLoader() rejects oversized gzip responses from fetch", async () => {
|
|
1622
|
+
const document = { name: "a".repeat(16 * 1024 * 1024) };
|
|
1623
|
+
const compressed = gzipSync(JSON.stringify(document));
|
|
1624
|
+
ok(compressed.byteLength < 64 * 1024);
|
|
1625
|
+
const server = createServer((request, response) => {
|
|
1626
|
+
const body = request.url === "/small" ? gzipSync("{\"name\":\"hello\"}") : compressed;
|
|
1627
|
+
response.writeHead(200, {
|
|
1628
|
+
"Content-Type": "application/activity+json",
|
|
1629
|
+
"Content-Encoding": "gzip",
|
|
1630
|
+
"Content-Length": body.byteLength
|
|
1631
|
+
});
|
|
1632
|
+
response.end(body);
|
|
1633
|
+
});
|
|
1634
|
+
await new Promise((resolve, reject) => {
|
|
1635
|
+
server.once("error", reject);
|
|
1636
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
1637
|
+
});
|
|
1638
|
+
try {
|
|
1639
|
+
const address = server.address();
|
|
1640
|
+
ok(address != null && typeof address !== "string");
|
|
1641
|
+
const url = `http://127.0.0.1:${address.port}/document`;
|
|
1642
|
+
await rejects(getDocumentLoader({ allowPrivateAddress: true })(url), FetchError);
|
|
1643
|
+
deepStrictEqual((await getDocumentLoader({ allowPrivateAddress: true })(new URL("/small", url).href)).document, { name: "hello" });
|
|
1644
|
+
} finally {
|
|
1645
|
+
await new Promise((resolve, reject) => {
|
|
1646
|
+
server.close((error) => error == null ? resolve() : reject(error));
|
|
1647
|
+
server.closeAllConnections();
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
});
|
|
1651
|
+
test("getRemoteDocument() bounds JSON by default", async () => {
|
|
1652
|
+
let canceled = false;
|
|
1653
|
+
let pulls = 0;
|
|
1654
|
+
await rejects(getRemoteDocument("https://example.com/document", new Response(new ReadableStream({
|
|
1655
|
+
pull(controller) {
|
|
1656
|
+
pulls++;
|
|
1657
|
+
controller.enqueue(new Uint8Array(64 * 1024).fill(32));
|
|
1658
|
+
},
|
|
1659
|
+
cancel() {
|
|
1660
|
+
canceled = true;
|
|
1661
|
+
}
|
|
1662
|
+
}, { highWaterMark: 0 })), () => {
|
|
1663
|
+
throw new Error("Unexpected alternate document");
|
|
1664
|
+
}), FetchError);
|
|
1665
|
+
deepStrictEqual(canceled, true);
|
|
1666
|
+
deepStrictEqual(pulls, 257);
|
|
1667
|
+
});
|
|
1589
1668
|
//#endregion
|
|
1590
1669
|
export {};
|