mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix(gateway): parse image data URLs linearly (#102825)
Co-authored-by: WangYan <wang.yan29@xydigit.com>
This commit is contained in:
parent
8985598302
commit
79d3daa09b
2 changed files with 76 additions and 5 deletions
|
|
@ -586,6 +586,59 @@ describe("createManagedOutgoingImageBlocks", () => {
|
|||
await expectPathMissing(path.join(stateDir, "media", "outgoing", "records"));
|
||||
});
|
||||
|
||||
it("rejects semicolon-heavy malformed data urls without backtracking", async () => {
|
||||
const semicolons = ";".repeat(64);
|
||||
const malformed = `data:image/png${semicolons}`;
|
||||
|
||||
await expect(
|
||||
createManagedOutgoingImageBlocks({
|
||||
sessionKey: "agent:main:main",
|
||||
mediaUrls: [malformed],
|
||||
stateDir,
|
||||
}),
|
||||
).rejects.toThrow("Invalid image data URL");
|
||||
});
|
||||
|
||||
it("rejects malformed data urls with a later ;base64, marker after a payload comma", async () => {
|
||||
await expect(
|
||||
createManagedOutgoingImageBlocks({
|
||||
sessionKey: "agent:main:main",
|
||||
mediaUrls: ["data:image/png;base64,garbage;base64,iVBORw0KGgo="],
|
||||
stateDir,
|
||||
}),
|
||||
).rejects.toThrow("Invalid image data URL");
|
||||
});
|
||||
|
||||
it("requires the base64 marker to be adjacent to the payload comma", async () => {
|
||||
await expect(
|
||||
createManagedOutgoingImageBlocks({
|
||||
sessionKey: "agent:main:main",
|
||||
mediaUrls: [`data:image/png;base64\n,${TINY_PNG_BASE64}`],
|
||||
stateDir,
|
||||
}),
|
||||
).rejects.toThrow("Invalid image data URL");
|
||||
});
|
||||
|
||||
it("preserves parameterized image data urls", async () => {
|
||||
const blocks = await createManagedOutgoingImageBlocks({
|
||||
sessionKey: "agent:main:main",
|
||||
mediaUrls: [`data:image/png;charset=utf-8;base64,${TINY_PNG_BASE64}`],
|
||||
stateDir,
|
||||
});
|
||||
|
||||
expect(requireBlock(blocks).mimeType).toBe("image/png");
|
||||
});
|
||||
|
||||
it("rejects data urls without a media type", async () => {
|
||||
await expect(
|
||||
createManagedOutgoingImageBlocks({
|
||||
sessionKey: "agent:main:main",
|
||||
mediaUrls: [`data:;base64,${TINY_PNG_BASE64}`],
|
||||
stateDir,
|
||||
}),
|
||||
).rejects.toThrow("Invalid image data URL");
|
||||
});
|
||||
|
||||
it("rewrites local image sources into managed display blocks without leaking the source path", async () => {
|
||||
const sourcePath = path.join(stateDir, "workspace", "fixtures", "dot.png");
|
||||
await fs.mkdir(path.dirname(sourcePath), { recursive: true });
|
||||
|
|
|
|||
|
|
@ -279,22 +279,40 @@ function parseImageDataUrl(
|
|||
if (!trimmed.startsWith("data:")) {
|
||||
return { kind: "not-data-url" };
|
||||
}
|
||||
const match = /^data:([^;,]+)(?:;[^,]*)*;base64,([A-Za-z0-9+/=\s]+)$/i.exec(trimmed);
|
||||
if (!match) {
|
||||
|
||||
const afterPrefix = trimmed.slice("data:".length);
|
||||
const commaIdx = afterPrefix.indexOf(",");
|
||||
const mimeAndParams = commaIdx < 0 ? "" : afterPrefix.slice(0, commaIdx);
|
||||
const base64Marker = ";base64";
|
||||
if (mimeAndParams.slice(-base64Marker.length).toLowerCase() !== base64Marker) {
|
||||
throw new Error("Invalid image data URL");
|
||||
}
|
||||
const contentType = match[1]?.trim().toLowerCase() ?? "";
|
||||
const semicolonIdx = mimeAndParams.indexOf(";");
|
||||
const contentType = (semicolonIdx < 0 ? mimeAndParams : mimeAndParams.slice(0, semicolonIdx))
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (!contentType) {
|
||||
throw new Error("Invalid image data URL");
|
||||
}
|
||||
|
||||
const base64Part = afterPrefix.slice(commaIdx + 1);
|
||||
if (!/^[A-Za-z0-9+/=\s]+$/.test(base64Part)) {
|
||||
throw new Error("Invalid image data URL");
|
||||
}
|
||||
|
||||
if (!contentType.startsWith("image/")) {
|
||||
return { kind: "non-image-data-url" };
|
||||
}
|
||||
if (estimateBase64DecodedByteLength(match[2]) > limits.maxBytes) {
|
||||
|
||||
if (estimateBase64DecodedByteLength(base64Part) > limits.maxBytes) {
|
||||
throw createManagedImageAttachmentError(
|
||||
`Managed image attachment ${JSON.stringify(alt)} exceeds the ${formatLimitMiB(limits.maxBytes)} byte limit`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "image-data-url",
|
||||
buffer: Buffer.from(match[2].replace(/\s+/g, ""), "base64"),
|
||||
buffer: Buffer.from(base64Part.replace(/\s+/g, ""), "base64"),
|
||||
contentType,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue