mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:50:42 +00:00
fix(media): load inbound media store URIs
This commit is contained in:
@@ -431,4 +431,23 @@ describe("loadWebMedia", () => {
|
||||
code: "path-not-allowed",
|
||||
});
|
||||
});
|
||||
|
||||
it("hydrates inbound media store URIs before allowed-root checks", async () => {
|
||||
const id = `signal-${Date.now()}-${Math.random().toString(36).slice(2)}.png`;
|
||||
const filePath = path.join(stateDir, "media", "inbound", id);
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, Buffer.from(TINY_PNG_BASE64, "base64"));
|
||||
|
||||
try {
|
||||
const result = await loadWebMedia(`media://inbound/${id}`, {
|
||||
maxBytes: 1024 * 1024,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("image");
|
||||
expect(result.buffer.length).toBeGreaterThan(0);
|
||||
expect(result.fileName).toBe(id);
|
||||
} finally {
|
||||
await fs.rm(filePath, { force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
mimeTypeFromFilePath,
|
||||
normalizeMimeType,
|
||||
} from "./mime.js";
|
||||
import { resolveMediaBufferPath } from "./store.js";
|
||||
|
||||
export { getDefaultLocalRoots, LocalMediaAccessError };
|
||||
export type { LocalMediaAccessErrorCode };
|
||||
@@ -56,6 +57,46 @@ type WebMediaOptions = {
|
||||
hostReadCapability?: boolean;
|
||||
};
|
||||
|
||||
async function resolveMediaStoreUriToPath(mediaUrl: string): Promise<string | null> {
|
||||
if (!mediaUrl.startsWith("media://")) {
|
||||
return null;
|
||||
}
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(mediaUrl);
|
||||
} catch (err) {
|
||||
throw new LocalMediaAccessError("invalid-path", `Invalid media URI: ${mediaUrl}`, {
|
||||
cause: err,
|
||||
});
|
||||
}
|
||||
if (parsed.hostname !== "inbound") {
|
||||
throw new LocalMediaAccessError(
|
||||
"path-not-allowed",
|
||||
`Unsupported media URI location: ${parsed.hostname || "(missing)"}`,
|
||||
);
|
||||
}
|
||||
let id: string;
|
||||
try {
|
||||
id = decodeURIComponent(parsed.pathname.replace(/^\/+/, ""));
|
||||
} catch (err) {
|
||||
throw new LocalMediaAccessError("invalid-path", `Invalid media URI: ${mediaUrl}`, {
|
||||
cause: err,
|
||||
});
|
||||
}
|
||||
if (!id || id.includes("/")) {
|
||||
throw new LocalMediaAccessError("invalid-path", `Invalid media URI: ${mediaUrl}`);
|
||||
}
|
||||
try {
|
||||
return await resolveMediaBufferPath(id, "inbound");
|
||||
} catch (err) {
|
||||
throw new LocalMediaAccessError(
|
||||
"invalid-path",
|
||||
err instanceof Error ? err.message : `Invalid media URI: ${mediaUrl}`,
|
||||
{ cause: err },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveWebMediaOptions(params: {
|
||||
maxBytesOrOptions?: number | WebMediaOptions;
|
||||
options?: { ssrfPolicy?: SsrFPolicy; localRoots?: readonly string[] | "any" };
|
||||
@@ -356,7 +397,10 @@ async function loadWebMediaInternal(
|
||||
} = options;
|
||||
// Strip MEDIA: prefix used by agent tools (e.g. TTS) to tag media paths.
|
||||
// Be lenient: LLM output may add extra whitespace (e.g. " MEDIA : /tmp/x.png").
|
||||
mediaUrl = mediaUrl.replace(/^\s*MEDIA\s*:\s*/i, "");
|
||||
if (!/^\s*media:\/\//i.test(mediaUrl)) {
|
||||
mediaUrl = mediaUrl.replace(/^\s*MEDIA\s*:\s*/i, "");
|
||||
}
|
||||
mediaUrl = (await resolveMediaStoreUriToPath(mediaUrl)) ?? mediaUrl;
|
||||
// Use fileURLToPath for proper handling of file:// URLs (handles file://localhost/path, etc.)
|
||||
if (mediaUrl.startsWith("file://")) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user