mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
Fix/zalo bound api json response reads (#97277)
* fix(zalo): bound Bot API JSON response reads via readProviderJsonResponse
* test(zalo): keep API proof in focused coverage
---------
Co-authored-by: NIO <nocodet@mail.com>
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
(cherry picked from commit 38ddcef78f)
This commit is contained in:
@@ -11,7 +11,36 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||
resolvePinnedHostnameWithPolicyMock(...args),
|
||||
}));
|
||||
|
||||
import { deleteWebhook, getWebhookInfo, sendChatAction, sendPhoto, type ZaloFetch } from "./api.js";
|
||||
import {
|
||||
deleteWebhook,
|
||||
getMe,
|
||||
getWebhookInfo,
|
||||
sendChatAction,
|
||||
sendPhoto,
|
||||
type ZaloFetch,
|
||||
} from "./api.js";
|
||||
|
||||
const ZALO_JSON_CAP_BYTES = 16 * 1024 * 1024;
|
||||
|
||||
function oversizedZaloJsonResponse(onCancel: () => void): Response {
|
||||
const response = new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(new Uint8Array(ZALO_JSON_CAP_BYTES + 1));
|
||||
},
|
||||
cancel() {
|
||||
onCancel();
|
||||
},
|
||||
}),
|
||||
{ headers: { "content-type": "application/json" }, status: 200 },
|
||||
);
|
||||
Object.defineProperty(response, "json", {
|
||||
value: async () => {
|
||||
throw new Error("unbounded json reader was used");
|
||||
},
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
function createOkFetcher() {
|
||||
return vi.fn<ZaloFetch>(async () => new Response(JSON.stringify({ ok: true, result: {} })));
|
||||
@@ -103,10 +132,7 @@ describe("Zalo API request methods", () => {
|
||||
.mockImplementation(() => undefined);
|
||||
try {
|
||||
const fetcher = vi.fn<ZaloFetch>(
|
||||
async () =>
|
||||
({
|
||||
json: async () => ({ ok: true, result: {} }),
|
||||
}) as Response,
|
||||
async () => new Response(JSON.stringify({ ok: true, result: {} })),
|
||||
);
|
||||
|
||||
await sendChatAction(
|
||||
@@ -199,4 +225,18 @@ describe("Zalo API request methods", () => {
|
||||
expect(resolvePinnedHostnameWithPolicyMock).not.toHaveBeenCalled();
|
||||
expect(fetcher).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("bounds oversized getMe JSON responses and cancels the stream", async () => {
|
||||
let cancelCount = 0;
|
||||
const fetcher = vi.fn<ZaloFetch>(async () =>
|
||||
oversizedZaloJsonResponse(() => {
|
||||
cancelCount += 1;
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(getMe("test-token", undefined, fetcher)).rejects.toThrow(
|
||||
"zalo.getMe: JSON response exceeds 16777216 bytes",
|
||||
);
|
||||
expect(cancelCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
||||
import { resolvePinnedHostnameWithPolicy, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
|
||||
const ZALO_API_BASE = "https://bot-api.zaloplatforms.com";
|
||||
@@ -131,7 +132,7 @@ export async function callZaloApi<T = unknown>(
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
const data = (await response.json()) as ZaloApiResponse<T>;
|
||||
const data = await readProviderJsonResponse<ZaloApiResponse<T>>(response, `zalo.${method}`);
|
||||
|
||||
if (!data.ok) {
|
||||
throw new ZaloApiError(
|
||||
|
||||
Reference in New Issue
Block a user