























@@ -13,6 +13,7 @@ const controlTimeoutMs = readPositiveInt(
1313Math.min(fetchTimeoutMs, 30000),
1414);
1515const chatTimeoutMs = readPositiveInt("OPENWEBUI_CHAT_TIMEOUT_MS", fetchTimeoutMs);
16+const responseBodyMaxBytes = readPositiveInt("OPENWEBUI_RESPONSE_BODY_MAX_BYTES", 1024 * 1024);
1617const smokeMode =
1718process.env.OPENWEBUI_SMOKE_MODE ?? process.env.OPENCLAW_OPENWEBUI_SMOKE_MODE ?? "chat";
1819@@ -68,6 +69,12 @@ function createTimeoutError(label, timeoutMs) {
6869return error;
6970}
707172+function createBodyTooLargeError(label, byteLimit) {
73+const error = new Error(`${label} response body exceeded ${byteLimit} bytes`);
74+error.code = "ETOOBIG";
75+return error;
76+}
77+7178async function withRequestTimeout(label, timeoutMs, run) {
7279const controller = new AbortController();
7380const timeoutError = createTimeoutError(label, timeoutMs);
@@ -87,6 +94,51 @@ async function withRequestTimeout(label, timeoutMs, run) {
8794}
8895}
899697+async function readBoundedResponseText(response, label, byteLimit = responseBodyMaxBytes) {
98+const contentLength = response.headers.get("content-length");
99+if (contentLength) {
100+const parsedLength = Number(contentLength);
101+if (Number.isSafeInteger(parsedLength) && parsedLength > byteLimit) {
102+await response.body?.cancel().catch(() => {});
103+throw createBodyTooLargeError(label, byteLimit);
104+}
105+}
106+if (!response.body) {
107+return "";
108+}
109+110+const reader = response.body.getReader();
111+const decoder = new TextDecoder();
112+let byteCount = 0;
113+let text = "";
114+try {
115+while (true) {
116+const { done, value } = await reader.read();
117+if (done) {
118+return text + decoder.decode();
119+}
120+byteCount += value.byteLength;
121+if (byteCount > byteLimit) {
122+await reader.cancel().catch(() => {});
123+throw createBodyTooLargeError(label, byteLimit);
124+}
125+text += decoder.decode(value, { stream: true });
126+}
127+} finally {
128+reader.releaseLock();
129+}
130+}
131+132+async function readBoundedResponseJson(response, label) {
133+const body = await readBoundedResponseText(response, label);
134+try {
135+return JSON.parse(body);
136+} catch (error) {
137+const message = error instanceof Error ? error.message : String(error);
138+throw new Error(`${label} returned invalid JSON: ${message}`, { cause: error });
139+}
140+}
141+90142function getCookieHeader(res) {
91143const raw = res.headers.get("set-cookie");
92144if (!raw) {
@@ -125,12 +177,12 @@ async function fetchSignin() {
125177 signal,
126178});
127179if (!response.ok) {
128-const body = await response.text();
180+const body = await readBoundedResponseText(response, "Open WebUI signin");
129181throw new Error(`signin failed: HTTP ${response.status} ${body}`);
130182}
131183return {
132184cookie: getCookieHeader(response),
133-json: await response.json(),
185+json: await readBoundedResponseJson(response, "Open WebUI signin"),
134186};
135187});
136188}
@@ -145,11 +197,14 @@ async function fetchModels(authHeaders, attempt) {
145197return {
146198ok: false,
147199status: response.status,
148-text: await response.text(),
200+text: await readBoundedResponseText(
201+response,
202+`Open WebUI models attempt ${attempt}`,
203+),
149204};
150205}
151206return {
152-json: await response.json(),
207+json: await readBoundedResponseJson(response, `Open WebUI models attempt ${attempt}`),
153208ok: true,
154209};
155210},
@@ -171,11 +226,12 @@ async function fetchChatCompletion(authHeaders, targetModel) {
171226 signal,
172227});
173228if (!response.ok) {
229+const body = await readBoundedResponseText(response, "Open WebUI chat completion");
174230throw new Error(
175-`/api/chat/completions failed: HTTP ${response.status} ${await response.text()}`,
231+`/api/chat/completions failed: HTTP ${response.status} ${body}`,
176232);
177233}
178-return await response.json();
234+return await readBoundedResponseJson(response, "Open WebUI chat completion");
179235});
180236}
181237此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。