refactor(media): share local source path resolution

This commit is contained in:
Vincent Koc
2026-06-23 08:00:46 +08:00
parent e39249100e
commit 2f8ad67a5e
3 changed files with 32 additions and 52 deletions
+1 -26
View File
@@ -4,21 +4,19 @@ import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import type { IncomingMessage, ServerResponse } from "node:http";
import path from "node:path";
import { isPassThroughRemoteMediaSource } from "@openclaw/media-core/media-source-url";
import { resolveDefaultAgentId } from "../agents/agent-scope-config.js";
import { getRuntimeConfig } from "../config/config.js";
import { resolveStateDir } from "../config/paths.js";
import { readLocalFileSafely } from "../infra/fs-safe.js";
import { tryReadJson, writeJson } from "../infra/json-files.js";
import { safeFileURLToPath } from "../infra/local-file-access.js";
import { assertLocalMediaAllowed } from "../media/local-media-access.js";
import { resolveLocalMediaPath } from "../media/local-media-path.js";
import {
createImageProcessor,
getImageMetadata,
readImageProbeFromHeader,
} from "../media/media-services.js";
import { MEDIA_MAX_BYTES, saveMediaBuffer, saveMediaSource } from "../media/store.js";
import { resolveUserPath } from "../utils.js";
import type { AuthRateLimiter } from "./auth-rate-limit.js";
import type { ResolvedGatewayAuth } from "./auth.js";
import { sendJson, sendMethodNotAllowed, sendMissingScopeForbidden } from "./http-common.js";
@@ -35,8 +33,6 @@ const OUTGOING_IMAGE_ROUTE_PREFIX = "/api/chat/media/outgoing";
const DEFAULT_TRANSIENT_OUTGOING_IMAGE_TTL_MS = 15 * 60 * 1000;
const MANAGED_OUTGOING_ATTACHMENT_ID_RE =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const DATA_URL_RE = /^data:/i;
const WINDOWS_DRIVE_RE = /^[A-Za-z]:[\\/]/;
export const DEFAULT_MANAGED_IMAGE_ATTACHMENT_LIMITS = {
maxBytes: 12 * 1024 * 1024,
@@ -274,27 +270,6 @@ function deriveAltText(source: string, index: number) {
return localName || fallback;
}
function resolveLocalMediaPath(source: string): string | undefined {
const trimmed = source.trim();
if (!trimmed || isPassThroughRemoteMediaSource(trimmed) || DATA_URL_RE.test(trimmed)) {
return undefined;
}
if (trimmed.startsWith("file://")) {
try {
return safeFileURLToPath(trimmed);
} catch {
return undefined;
}
}
if (trimmed.startsWith("~")) {
return resolveUserPath(trimmed);
}
if (path.isAbsolute(trimmed) || WINDOWS_DRIVE_RE.test(trimmed)) {
return path.resolve(trimmed);
}
return undefined;
}
function parseImageDataUrl(
source: string,
alt: string,
+29
View File
@@ -0,0 +1,29 @@
import path from "node:path";
import { isPassThroughRemoteMediaSource } from "@openclaw/media-core/media-source-url";
import { safeFileURLToPath } from "../infra/local-file-access.js";
import { resolveUserPath } from "../utils.js";
const DATA_URL_RE = /^data:/i;
const WINDOWS_DRIVE_RE = /^[A-Za-z]:[\\/]/;
/** Resolves a media source to a local path when it is not a remote or data URL. */
export function resolveLocalMediaPath(source: string): string | undefined {
const trimmed = source.trim();
if (!trimmed || isPassThroughRemoteMediaSource(trimmed) || DATA_URL_RE.test(trimmed)) {
return undefined;
}
if (trimmed.startsWith("file://")) {
try {
return safeFileURLToPath(trimmed);
} catch {
return undefined;
}
}
if (trimmed.startsWith("~")) {
return resolveUserPath(trimmed);
}
if (path.isAbsolute(trimmed) || WINDOWS_DRIVE_RE.test(trimmed)) {
return path.resolve(trimmed);
}
return undefined;
}
+2 -26
View File
@@ -1,6 +1,5 @@
// Local media root helpers normalize and match allowed local media roots.
import path from "node:path";
import { isPassThroughRemoteMediaSource } from "@openclaw/media-core/media-source-url";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { uniqueStrings } from "@openclaw/normalization-core/string-normalization";
import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
@@ -10,17 +9,15 @@ import {
} from "../agents/tool-fs-policy.js";
import { resolveStateDir } from "../config/paths.js";
import type { OpenClawConfig } from "../config/types.js";
import { safeFileURLToPath } from "../infra/local-file-access.js";
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
import { resolveConfigDir, resolveUserPath } from "../utils.js";
import { resolveConfigDir } from "../utils.js";
import { resolveLocalMediaPath } from "./local-media-path.js";
type BuildMediaLocalRootsOptions = {
preferredTmpDir?: string;
};
let cachedPreferredTmpDir: string | undefined;
const DATA_URL_RE = /^data:/i;
const WINDOWS_DRIVE_RE = /^[A-Za-z]:[\\/]/;
function resolveCachedPreferredTmpDir(): string {
if (!cachedPreferredTmpDir) {
@@ -78,27 +75,6 @@ export function getAgentScopedMediaLocalRoots(
return roots;
}
function resolveLocalMediaPath(source: string): string | undefined {
const trimmed = source.trim();
if (!trimmed || isPassThroughRemoteMediaSource(trimmed) || DATA_URL_RE.test(trimmed)) {
return undefined;
}
if (trimmed.startsWith("file://")) {
try {
return safeFileURLToPath(trimmed);
} catch {
return undefined;
}
}
if (trimmed.startsWith("~")) {
return resolveUserPath(trimmed);
}
if (path.isAbsolute(trimmed) || WINDOWS_DRIVE_RE.test(trimmed)) {
return path.resolve(trimmed);
}
return undefined;
}
/** Adds only concrete local source parent directories to an existing root allowlist. */
export function appendLocalMediaParentRoots(
roots: readonly string[],