UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

137 lines (136 loc) 5.84 kB
import { S as safeFileURLToPath } from "./fs-safe-aqmM_n6V.js"; import { p as resolveUserPath } from "./utils-CCC-BEJH.js"; import "./local-file-access-CBe_wA_B.js"; import { l as resolveMediaBufferPath, s as getMediaDir } from "./store-C9M_0A1p.js"; import path from "node:path"; import fs from "node:fs/promises"; //#region src/media/media-reference.ts /** Error raised when a media reference cannot be mapped to an allowed local media file. */ var MediaReferenceError = class extends Error { constructor(code, message, options) { super(message, options); this.code = code; this.name = "MediaReferenceError"; } }; /** Strips legacy MEDIA: prefixes while preserving canonical media:// references. */ function normalizeMediaReferenceSource(source) { const trimmed = source.trim(); if (/^media:\/\//i.test(trimmed)) return trimmed; return trimmed.replace(/^\s*MEDIA\s*:\s*/i, "").trim(); } /** Classifies media reference schemes before local resolution or sandbox rewriting. */ function classifyMediaReferenceSource(source, options) { const allowDataUrl = options?.allowDataUrl ?? true; const looksLikeWindowsDrivePath = /^[a-zA-Z]:[\\/]/.test(source); const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(source); const isFileUrl = /^file:/i.test(source); const isHttpUrl = /^https?:\/\//i.test(source); const isDataUrl = /^data:/i.test(source); const isMediaStoreUrl = /^media:\/\//i.test(source); return { hasScheme, hasUnsupportedScheme: hasScheme && !looksLikeWindowsDrivePath && !isFileUrl && !isHttpUrl && !isMediaStoreUrl && !(allowDataUrl && isDataUrl), isDataUrl, isFileUrl, isHttpUrl, isMediaStoreUrl, looksLikeWindowsDrivePath }; } function maybeLocalPathFromSource(source) { if (/^file:/i.test(source)) try { return safeFileURLToPath(source); } catch { return null; } if (source.startsWith("~")) return resolveUserPath(source); if (path.isAbsolute(source)) return source; return null; } function relativePathEscapesBase(relativePath) { return relativePath === ".." || relativePath.startsWith("../") || relativePath.startsWith("..\\") || path.isAbsolute(relativePath); } async function resolvePathForContainment(candidate) { try { return await fs.realpath(candidate); } catch { return path.resolve(candidate); } } /** Parses canonical inbound media-store URIs and rejects nested or cross-bucket references. */ function parseInboundMediaUri(source) { const normalizedSource = normalizeMediaReferenceSource(source); if (!/^media:\/\//i.test(normalizedSource)) return null; let parsed; try { parsed = new URL(normalizedSource); } catch (err) { throw new MediaReferenceError("invalid-path", `Invalid media URI: ${normalizedSource}`, { cause: err }); } if (parsed.hostname !== "inbound") throw new MediaReferenceError("path-not-allowed", `Unsupported media URI location: ${parsed.hostname || "(missing)"}`); let id; try { id = decodeURIComponent(parsed.pathname.replace(/^\/+/, "")); } catch (err) { throw new MediaReferenceError("invalid-path", `Invalid media URI: ${normalizedSource}`, { cause: err }); } if (!id || id.includes("/") || id.includes("\\") || id.includes("\0")) throw new MediaReferenceError("invalid-path", `Invalid media URI: ${normalizedSource}`); return { id, normalizedSource }; } async function resolveInboundMediaUri(normalizedSource) { const uri = parseInboundMediaUri(normalizedSource); if (!uri) return null; return { ...uri, physicalPath: await resolveInboundMediaPath(uri.id, uri.normalizedSource), sourceType: "uri" }; } /** Rewrites inbound media-store URIs to sandbox-relative paths for staged agent inputs. */ function resolveMediaReferenceSandboxPath(source, inboundDir = "media/inbound") { const normalizedSource = normalizeMediaReferenceSource(source); const uri = parseInboundMediaUri(normalizedSource); if (!uri) return { resolved: normalizedSource }; return { resolved: path.posix.join(inboundDir.replace(/\\/g, "/"), uri.id), rewrittenFrom: uri.normalizedSource }; } /** Resolves inbound media:// URIs or first-level inbound file paths to concrete store files. */ async function resolveInboundMediaReference(source) { const normalizedSource = normalizeMediaReferenceSource(source); if (!normalizedSource) return null; const uriSource = await resolveInboundMediaUri(normalizedSource); if (uriSource) return uriSource; const localPath = maybeLocalPathFromSource(normalizedSource); if (!localPath) return null; const rawInboundDir = path.resolve(getMediaDir(), "inbound"); const rawResolvedPath = path.resolve(localPath); const rawRel = path.relative(rawInboundDir, rawResolvedPath); const rel = rawRel && !relativePathEscapesBase(rawRel) ? rawRel : path.relative(await resolvePathForContainment(rawInboundDir), await resolvePathForContainment(localPath)); if (!rel || relativePathEscapesBase(rel) || rel.includes(path.sep)) return null; return { id: rel, normalizedSource, physicalPath: await resolveInboundMediaPath(rel, normalizedSource), sourceType: "path" }; } /** Converts inbound media references for callers that need a direct local file path. */ async function resolveMediaReferenceLocalPath(source) { const normalizedSource = normalizeMediaReferenceSource(source); return (await resolveInboundMediaReference(normalizedSource))?.physicalPath ?? normalizedSource; } async function resolveInboundMediaPath(id, source) { try { return await resolveMediaBufferPath(id, "inbound"); } catch (err) { throw new MediaReferenceError("invalid-path", err instanceof Error ? err.message : `Invalid media reference: ${source}`, { cause: err }); } } //#endregion export { resolveMediaReferenceLocalPath as a, resolveInboundMediaReference as i, classifyMediaReferenceSource as n, resolveMediaReferenceSandboxPath as o, normalizeMediaReferenceSource as r, MediaReferenceError as t };