mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 22:17:00 +00:00
fix(qqbot): add timeout to remote media URL fetches (#103018)
* fix(qqbot): add timeout to remote media URL fetches * fix(qqbot): use response header media timeout --------- Co-authored-by: llagy009 <0668001470@xydigit.com>
This commit is contained in:
@@ -6,9 +6,14 @@ import { ensurePlatformAdapter } from "./bootstrap.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getRuntimeConfig: vi.fn(),
|
||||
readRemoteMediaBuffer: vi.fn(),
|
||||
resolveApprovalOverGateway: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/media-runtime", () => ({
|
||||
readRemoteMediaBuffer: (...args: unknown[]) => mocks.readRemoteMediaBuffer(...args),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", () => ({
|
||||
getRuntimeConfig: mocks.getRuntimeConfig,
|
||||
}));
|
||||
@@ -46,6 +51,36 @@ describe("QQBot built-in platform adapter", () => {
|
||||
ensurePlatformAdapter();
|
||||
});
|
||||
|
||||
it("forwards response header deadlines to the media runtime", async () => {
|
||||
mocks.readRemoteMediaBuffer.mockResolvedValueOnce({
|
||||
buffer: Buffer.from("image"),
|
||||
fileName: "remote.png",
|
||||
});
|
||||
|
||||
const result = await getPlatformAdapter().fetchMedia({
|
||||
url: "https://media.qq.com/assets/photo.png",
|
||||
filePathHint: "photo.png",
|
||||
maxBytes: 1024,
|
||||
maxRedirects: 2,
|
||||
timeoutMs: 5_000,
|
||||
responseHeaderTimeoutMs: 120_000,
|
||||
ssrfPolicy: { hostnameAllowlist: ["*.qq.com"] },
|
||||
requestInit: { headers: { accept: "image/png" } },
|
||||
});
|
||||
|
||||
expect(result).toEqual({ buffer: Buffer.from("image"), fileName: "remote.png" });
|
||||
expect(mocks.readRemoteMediaBuffer).toHaveBeenCalledWith({
|
||||
url: "https://media.qq.com/assets/photo.png",
|
||||
filePathHint: "photo.png",
|
||||
maxBytes: 1024,
|
||||
maxRedirects: 2,
|
||||
timeoutMs: 5_000,
|
||||
responseHeaderTimeoutMs: 120_000,
|
||||
ssrfPolicy: { hostnameAllowlist: ["*.qq.com"] },
|
||||
requestInit: { headers: { accept: "image/png" } },
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves plugin ownership and the canonical first-answer result", async () => {
|
||||
const adapter = getPlatformAdapter();
|
||||
|
||||
|
||||
@@ -77,6 +77,8 @@ function createBuiltinAdapter(): PlatformAdapter {
|
||||
filePathHint: options.filePathHint,
|
||||
maxBytes: options.maxBytes,
|
||||
maxRedirects: options.maxRedirects,
|
||||
timeoutMs: options.timeoutMs,
|
||||
responseHeaderTimeoutMs: options.responseHeaderTimeoutMs,
|
||||
ssrfPolicy: options.ssrfPolicy,
|
||||
requestInit: options.requestInit,
|
||||
});
|
||||
|
||||
@@ -17,6 +17,10 @@ export interface FetchMediaOptions {
|
||||
maxBytes?: number;
|
||||
/** Maximum redirects to follow. */
|
||||
maxRedirects?: number;
|
||||
/** Abort the complete remote media request after this many milliseconds. */
|
||||
timeoutMs?: number;
|
||||
/** Abort if final response headers have not arrived after this many milliseconds. */
|
||||
responseHeaderTimeoutMs?: number;
|
||||
/** SSRF policy configuration. */
|
||||
ssrfPolicy?: SsrfPolicyConfig;
|
||||
/** Extra fetch() RequestInit options. */
|
||||
|
||||
@@ -28,6 +28,7 @@ vi.mock("../adapter/index.js", () => ({
|
||||
|
||||
import {
|
||||
QQBOT_MEDIA_SSRF_POLICY,
|
||||
QQBOT_REMOTE_MEDIA_RESPONSE_HEADER_TIMEOUT_MS,
|
||||
checkFileSize,
|
||||
downloadFile,
|
||||
fileExistsAsync,
|
||||
@@ -95,6 +96,7 @@ describe("qqbot file-utils downloadFile", () => {
|
||||
url: "https://media.qq.com/assets/photo.png",
|
||||
filePathHint: "photo.png",
|
||||
ssrfPolicy: QQBOT_MEDIA_SSRF_POLICY,
|
||||
responseHeaderTimeoutMs: QQBOT_REMOTE_MEDIA_RESPONSE_HEADER_TIMEOUT_MS,
|
||||
});
|
||||
expect(QQBOT_MEDIA_SSRF_POLICY).toEqual({
|
||||
hostnameAllowlist: [
|
||||
|
||||
@@ -70,6 +70,8 @@ export const QQBOT_MEDIA_SSRF_POLICY: SsrfPolicyConfig = {
|
||||
allowRfc2544BenchmarkRange: true,
|
||||
};
|
||||
|
||||
export const QQBOT_REMOTE_MEDIA_RESPONSE_HEADER_TIMEOUT_MS = 120_000;
|
||||
|
||||
/** Result of local file-size validation. */
|
||||
interface FileSizeCheckResult {
|
||||
ok: boolean;
|
||||
@@ -183,6 +185,7 @@ export async function downloadFile(
|
||||
url: parsedUrl.toString(),
|
||||
filePathHint: originalFilename,
|
||||
ssrfPolicy: QQBOT_MEDIA_SSRF_POLICY,
|
||||
responseHeaderTimeoutMs: QQBOT_REMOTE_MEDIA_RESPONSE_HEADER_TIMEOUT_MS,
|
||||
});
|
||||
|
||||
let filename = normalizeOptionalString(originalFilename) ?? "";
|
||||
|
||||
Reference in New Issue
Block a user