

























@@ -14,6 +14,7 @@ type Lane = "normal" | "code";
14141515type FetchJsonOptions = {
1616fetchImpl?: (url: string, init: RequestInit) => Promise<Response>;
17+maxBodyBytes?: number;
1718timeoutMs?: number;
1819};
1920@@ -35,6 +36,10 @@ const DEFAULT_FETCH_TIMEOUT_MS = readPositiveInt(
3536process.env.OPENCLAW_TOOL_SEARCH_GATEWAY_E2E_FETCH_TIMEOUT_MS,
3637180_000,
3738);
39+const DEFAULT_FETCH_BODY_MAX_BYTES = readPositiveInt(
40+process.env.OPENCLAW_TOOL_SEARCH_GATEWAY_E2E_FETCH_BODY_MAX_BYTES,
41+1024 * 1024,
42+);
38433944function assert(condition: unknown, message: string): asserts condition {
4045if (!condition) {
@@ -51,6 +56,52 @@ function timeoutError(message: string) {
5156return Object.assign(new Error(message), { code: "ETIMEDOUT" });
5257}
535859+function bodyTooLargeError(url: string, byteLimit: number) {
60+return Object.assign(new Error(`HTTP response from ${url} exceeded ${byteLimit} bytes`), {
61+code: "ETOOBIG",
62+});
63+}
64+65+async function readBoundedResponseText(
66+response: Response,
67+url: string,
68+byteLimit: number,
69+timeoutPromise: Promise<never>,
70+) {
71+const contentLength = response.headers.get("content-length");
72+if (contentLength) {
73+const parsedLength = Number(contentLength);
74+if (Number.isSafeInteger(parsedLength) && parsedLength > byteLimit) {
75+await response.body?.cancel().catch(() => {});
76+throw bodyTooLargeError(url, byteLimit);
77+}
78+}
79+if (!response.body) {
80+return "";
81+}
82+83+const reader = response.body.getReader();
84+const decoder = new TextDecoder();
85+let byteCount = 0;
86+let text = "";
87+try {
88+while (true) {
89+const { done, value } = await Promise.race([reader.read(), timeoutPromise]);
90+if (done) {
91+return text + decoder.decode();
92+}
93+byteCount += value.byteLength;
94+if (byteCount > byteLimit) {
95+await reader.cancel().catch(() => {});
96+throw bodyTooLargeError(url, byteLimit);
97+}
98+text += decoder.decode(value, { stream: true });
99+}
100+} finally {
101+reader.releaseLock();
102+}
103+}
104+54105async function freePort(): Promise<number> {
55106return await new Promise((resolve, reject) => {
56107const server = net.createServer();
@@ -135,6 +186,7 @@ export async function fetchJson(
135186options: FetchJsonOptions = {},
136187): Promise<unknown> {
137188const timeoutMs = Math.max(1, options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS);
189+const maxBodyBytes = Math.max(1, options.maxBodyBytes ?? DEFAULT_FETCH_BODY_MAX_BYTES);
138190const controller = new AbortController();
139191const error = timeoutError(`HTTP request to ${url} timed out after ${timeoutMs}ms`);
140192let timeout: NodeJS.Timeout | undefined;
@@ -156,7 +208,7 @@ export async function fetchJson(
156208}),
157209timeoutPromise,
158210]);
159-text = await Promise.race([response.text(), timeoutPromise]);
211+text = await readBoundedResponseText(response, url, maxBodyBytes, timeoutPromise);
160212} finally {
161213if (timeout) {
162214clearTimeout(timeout);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。