mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:30:47 +00:00
fix(ui): fail closed on unreadable theme payloads
This commit is contained in:
@@ -72,13 +72,26 @@ function createImportedTheme() {
|
||||
|
||||
function createResponse(
|
||||
body: string,
|
||||
options: { headers?: HeadersInit; status?: number; url?: string } = {},
|
||||
options: {
|
||||
body?: ReadableStream<Uint8Array> | null;
|
||||
headers?: HeadersInit;
|
||||
status?: number;
|
||||
url?: string;
|
||||
} = {},
|
||||
) {
|
||||
return {
|
||||
ok: (options.status ?? 200) >= 200 && (options.status ?? 200) < 300,
|
||||
status: options.status ?? 200,
|
||||
headers: new Headers(options.headers),
|
||||
body: null,
|
||||
body:
|
||||
options.body === undefined
|
||||
? new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode(body));
|
||||
controller.close();
|
||||
},
|
||||
})
|
||||
: options.body,
|
||||
text: vi.fn(async () => body),
|
||||
url: options.url ?? "",
|
||||
} as unknown as Response;
|
||||
@@ -144,6 +157,16 @@ describe("custom theme import helpers", () => {
|
||||
).rejects.toThrow("too large");
|
||||
});
|
||||
|
||||
it("rejects tweakcn theme responses without a bounded body stream", async () => {
|
||||
const response = createResponse(JSON.stringify(createTweakcnPayload()), { body: null });
|
||||
const fetchImpl = vi.fn(async () => response) as unknown as typeof fetch;
|
||||
|
||||
await expect(
|
||||
importCustomThemeFromUrl("https://tweakcn.com/themes/cmlhfpjhw000004l4f4ax3m7z", fetchImpl),
|
||||
).rejects.toThrow("unreadable theme payload");
|
||||
expect(response.text).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects redirected tweakcn import responses", async () => {
|
||||
const response = createResponse(JSON.stringify(createTweakcnPayload()), {
|
||||
url: "https://example.com/r/themes/cmlhfpjhw000004l4f4ax3m7z",
|
||||
|
||||
@@ -480,36 +480,32 @@ async function readResponseTextWithLimit(response: Response): Promise<string> {
|
||||
throw new Error("tweakcn theme payload is too large.");
|
||||
}
|
||||
|
||||
if (response.body) {
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let bytes = 0;
|
||||
let text = "";
|
||||
try {
|
||||
while (true) {
|
||||
const chunk = await reader.read();
|
||||
if (chunk.done) {
|
||||
break;
|
||||
}
|
||||
bytes += chunk.value.byteLength;
|
||||
if (bytes > MAX_TWEAKCN_THEME_BYTES) {
|
||||
await reader.cancel().catch(() => undefined);
|
||||
throw new Error("tweakcn theme payload is too large.");
|
||||
}
|
||||
text += decoder.decode(chunk.value, { stream: true });
|
||||
}
|
||||
text += decoder.decode();
|
||||
return text;
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
if (!response.body) {
|
||||
throw new Error("tweakcn returned an unreadable theme payload.");
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
if (new TextEncoder().encode(text).byteLength > MAX_TWEAKCN_THEME_BYTES) {
|
||||
throw new Error("tweakcn theme payload is too large.");
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let bytes = 0;
|
||||
let text = "";
|
||||
try {
|
||||
while (true) {
|
||||
const chunk = await reader.read();
|
||||
if (chunk.done) {
|
||||
break;
|
||||
}
|
||||
bytes += chunk.value.byteLength;
|
||||
if (bytes > MAX_TWEAKCN_THEME_BYTES) {
|
||||
await reader.cancel().catch(() => undefined);
|
||||
throw new Error("tweakcn theme payload is too large.");
|
||||
}
|
||||
text += decoder.decode(chunk.value, { stream: true });
|
||||
}
|
||||
text += decoder.decode();
|
||||
return text;
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
async function readJsonResponseWithLimit(response: Response): Promise<unknown> {
|
||||
|
||||
Reference in New Issue
Block a user