

























@@ -195,6 +195,142 @@ describe("performMatrixRequest", () => {
195195"MockAgent",
196196);
197197});
198+199+it("rejects oversized JSON responses via content-length before buffering the body", async () => {
200+const arrayBuffer = vi.fn(async () => new ArrayBuffer(0));
201+stubRuntimeFetch(
202+vi.fn(
203+async () =>
204+({
205+ok: true,
206+status: 200,
207+headers: new Headers({
208+"content-type": "application/json",
209+"content-length": String(16 * 1024 * 1024),
210+}),
211+ arrayBuffer,
212+}) as unknown as Response,
213+),
214+);
215+216+await expect(
217+performMatrixRequest({
218+homeserver: "http://127.0.0.1:8008",
219+accessToken: "token",
220+method: "GET",
221+endpoint: "/_matrix/client/v3/account/whoami",
222+timeoutMs: 5000,
223+maxBytes: 1024,
224+ssrfPolicy: { allowPrivateNetwork: true },
225+}),
226+).rejects.toThrow("Matrix JSON response exceeds configured size limit");
227+expect(arrayBuffer).not.toHaveBeenCalled();
228+});
229+230+it("applies streaming byte limits when JSON responses omit content-length", async () => {
231+const chunk = new Uint8Array(768);
232+const stream = new ReadableStream<Uint8Array>({
233+start(controller) {
234+controller.enqueue(chunk);
235+controller.enqueue(chunk);
236+controller.close();
237+},
238+});
239+stubRuntimeFetch(
240+vi.fn(
241+async () =>
242+new Response(stream, {
243+status: 200,
244+headers: { "content-type": "application/json" },
245+}),
246+),
247+);
248+249+await expect(
250+performMatrixRequest({
251+homeserver: "http://127.0.0.1:8008",
252+accessToken: "token",
253+method: "GET",
254+endpoint: "/_matrix/client/v3/account/whoami",
255+timeoutMs: 5000,
256+maxBytes: 1024,
257+ssrfPolicy: { allowPrivateNetwork: true },
258+}),
259+).rejects.toThrow(
260+"Matrix JSON response exceeds configured size limit (1536 bytes > 1024 bytes)",
261+);
262+});
263+264+it("uses the configured idle-timeout error for stalled JSON downloads", async () => {
265+vi.useFakeTimers();
266+try {
267+const stream = new ReadableStream<Uint8Array>({
268+start(controller) {
269+controller.enqueue(new Uint8Array([1, 2, 3]));
270+},
271+});
272+stubRuntimeFetch(
273+vi.fn(
274+async () =>
275+new Response(stream, {
276+status: 200,
277+headers: { "content-type": "application/json" },
278+}),
279+),
280+);
281+282+const requestPromise = performMatrixRequest({
283+homeserver: "http://127.0.0.1:8008",
284+accessToken: "token",
285+method: "GET",
286+endpoint: "/_matrix/client/v3/account/whoami",
287+timeoutMs: 5000,
288+maxBytes: 1024,
289+readIdleTimeoutMs: 50,
290+ssrfPolicy: { allowPrivateNetwork: true },
291+});
292+293+const rejection = expect(requestPromise).rejects.toThrow(
294+"Matrix media download stalled: no data received for 50ms",
295+);
296+await vi.advanceTimersByTimeAsync(60);
297+await rejection;
298+} finally {
299+vi.useRealTimers();
300+}
301+}, 5_000);
302+303+it("returns full JSON bodies that stay under the byte limit", async () => {
304+const payload = JSON.stringify({ ok: true, items: [1, 2, 3] });
305+const stream = new ReadableStream<Uint8Array>({
306+start(controller) {
307+controller.enqueue(new TextEncoder().encode(payload));
308+controller.close();
309+},
310+});
311+stubRuntimeFetch(
312+vi.fn(
313+async () =>
314+new Response(stream, {
315+status: 200,
316+headers: { "content-type": "application/json" },
317+}),
318+),
319+);
320+321+const result = await performMatrixRequest({
322+homeserver: "http://127.0.0.1:8008",
323+accessToken: "token",
324+method: "GET",
325+endpoint: "/_matrix/client/v3/account/whoami",
326+timeoutMs: 5000,
327+maxBytes: 1024,
328+ssrfPolicy: { allowPrivateNetwork: true },
329+});
330+331+expect(result.text).toBe(payload);
332+expect(result.buffer.toString("utf8")).toBe(payload);
333+});
198334});
199335200336describe("createMatrixGuardedFetch", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。