mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-17 12:51:38 +00:00
fix(qqbot): use truncateUtf16Safe for debug log URL/key truncation (#102572)
* fix(qqbot): use truncateUtf16Safe for debug log URL/key truncation Replace naive .slice(0, N) with truncateUtf16Safe() in: - image-size.ts: URL preview in debug logs (2 sites) - upload-cache.ts: cache key preview in debug logs (2 sites) Co-Authored-By: Claude <noreply@anthropic.com> * test(qqbot): cover UTF-16-safe debug previews --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const adapterMocks = vi.hoisted(() => ({
|
||||
fetchMedia: vi.fn(),
|
||||
debugLog: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../adapter/index.js", () => ({
|
||||
@@ -12,6 +13,10 @@ vi.mock("../adapter/index.js", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("./log.js", () => ({
|
||||
debugLog: (...args: unknown[]) => adapterMocks.debugLog(...args),
|
||||
}));
|
||||
|
||||
import { getImageSizeFromUrl, parseImageSize } from "./image-size.js";
|
||||
|
||||
/** Build a minimal valid PNG header with the given dimensions. */
|
||||
@@ -39,6 +44,7 @@ function buildPngHeader(width: number, height: number): Buffer {
|
||||
describe("getImageSizeFromUrl", () => {
|
||||
beforeEach(() => {
|
||||
adapterMocks.fetchMedia.mockReset();
|
||||
adapterMocks.debugLog.mockReset();
|
||||
});
|
||||
|
||||
describe("fetchMedia options contract", () => {
|
||||
@@ -140,6 +146,27 @@ describe("getImageSizeFromUrl", () => {
|
||||
|
||||
expect(size).toBeNull();
|
||||
});
|
||||
|
||||
it("logs fetched URLs without splitting surrogate pairs", async () => {
|
||||
adapterMocks.fetchMedia.mockResolvedValueOnce({
|
||||
buffer: buildPngHeader(800, 600),
|
||||
contentType: "image/png",
|
||||
});
|
||||
const base = "https://cdn.example.com/";
|
||||
const urlPrefix = `${base}${"x".repeat(59 - base.length)}`;
|
||||
|
||||
await getImageSizeFromUrl(`${urlPrefix}😀.png`);
|
||||
|
||||
expect(adapterMocks.debugLog).toHaveBeenCalledWith(
|
||||
`[image-size] Got size from URL: 800x600 - ${urlPrefix}...`,
|
||||
);
|
||||
|
||||
adapterMocks.fetchMedia.mockRejectedValueOnce(new Error("probe failed"));
|
||||
await getImageSizeFromUrl(`${urlPrefix}😀.png`);
|
||||
expect(adapterMocks.debugLog).toHaveBeenLastCalledWith(
|
||||
`[image-size] Error fetching ${urlPrefix}...: probe failed`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { Buffer } from "node:buffer";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { getPlatformAdapter } from "../adapter/index.js";
|
||||
import type { SsrfPolicyConfig } from "../adapter/types.js";
|
||||
import { formatErrorMessage } from "./format.js";
|
||||
@@ -185,7 +186,7 @@ export async function getImageSizeFromUrl(
|
||||
const size = parseImageSize(buffer);
|
||||
if (size) {
|
||||
debugLog(
|
||||
`[image-size] Got size from URL: ${size.width}x${size.height} - ${url.slice(0, 60)}...`,
|
||||
`[image-size] Got size from URL: ${size.width}x${size.height} - ${truncateUtf16Safe(url, 60)}...`,
|
||||
);
|
||||
}
|
||||
return size;
|
||||
@@ -193,7 +194,9 @@ export async function getImageSizeFromUrl(
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
} catch (err) {
|
||||
debugLog(`[image-size] Error fetching ${url.slice(0, 60)}...: ${formatErrorMessage(err)}`);
|
||||
debugLog(
|
||||
`[image-size] Error fetching ${truncateUtf16Safe(url, 60)}...: ${formatErrorMessage(err)}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
// Qqbot tests cover upload cache plugin behavior.
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
debugLog: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./log.js", () => ({
|
||||
debugLog: (...args: unknown[]) => mocks.debugLog(...args),
|
||||
}));
|
||||
|
||||
import { computeFileHash, getCachedFileInfo, setCachedFileInfo } from "./upload-cache.js";
|
||||
|
||||
describe("qqbot upload-cache", () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
mocks.debugLog.mockReset();
|
||||
});
|
||||
|
||||
it("reuses cached file info before expiry", () => {
|
||||
@@ -32,4 +42,21 @@ describe("qqbot upload-cache", () => {
|
||||
|
||||
expect(getCachedFileInfo(hash, "group", "target-overflow", 1)).toBeNull();
|
||||
});
|
||||
|
||||
it("logs cache keys without splitting surrogate pairs", () => {
|
||||
const hash = computeFileHash("qqbot-surrogate-key");
|
||||
const keyPrefix = `${hash}:group:`;
|
||||
|
||||
setCachedFileInfo(hash, "group", "😀target", 1, "file-info", "uuid-safe", 3600);
|
||||
expect(getCachedFileInfo(hash, "group", "😀target", 1)).toBe("file-info");
|
||||
|
||||
expect(mocks.debugLog).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
`[upload-cache] Cache SET: key=${keyPrefix}..., ttl=3540s, uuid=uuid-safe`,
|
||||
);
|
||||
expect(mocks.debugLog).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
`[upload-cache] Cache HIT: key=${keyPrefix}..., fileUuid=uuid-safe`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
isFutureDateTimestampMs,
|
||||
resolveExpiresAtMsFromDurationSeconds,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import type { ChatScope } from "../types.js";
|
||||
import { debugLog } from "./log.js";
|
||||
|
||||
@@ -55,7 +56,9 @@ export function getCachedFileInfo(
|
||||
return null;
|
||||
}
|
||||
|
||||
debugLog(`[upload-cache] Cache HIT: key=${key.slice(0, 40)}..., fileUuid=${entry.fileUuid}`);
|
||||
debugLog(
|
||||
`[upload-cache] Cache HIT: key=${truncateUtf16Safe(key, 40)}..., fileUuid=${entry.fileUuid}`,
|
||||
);
|
||||
return entry.fileInfo;
|
||||
}
|
||||
|
||||
@@ -100,6 +103,6 @@ export function setCachedFileInfo(
|
||||
});
|
||||
|
||||
debugLog(
|
||||
`[upload-cache] Cache SET: key=${key.slice(0, 40)}..., ttl=${effectiveTtl}s, uuid=${fileUuid}`,
|
||||
`[upload-cache] Cache SET: key=${truncateUtf16Safe(key, 40)}..., ttl=${effectiveTtl}s, uuid=${fileUuid}`,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user