import path from "node:path"; import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; export const DEFAULT_BROWSER_TMP_DIR = resolvePreferredOpenClawTmpDir(); export const DEFAULT_TRACE_DIR = DEFAULT_BROWSER_TMP_DIR; export const DEFAULT_DOWNLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "downloads"); export const DEFAULT_UPLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "uploads"); export function resolvePathWithinRoot(params: { rootDir: string; requestedPath: string; scopeLabel: string; defaultFileName?: string; }): { ok: true; path: string } | { ok: false; error: string } { const root = path.resolve(params.rootDir); const raw = params.requestedPath.trim(); if (!raw) { if (!params.defaultFileName) { return { ok: false, error: "path is required" }; } return { ok: true, path: path.join(root, params.defaultFileName) }; } const resolved = path.resolve(root, raw); const rel = path.relative(root, resolved); if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) { return { ok: false, error: `Invalid path: must stay within ${params.scopeLabel}` }; } return { ok: true, path: resolved }; } export function resolvePathsWithinRoot(params: { rootDir: string; requestedPaths: string[]; scopeLabel: string; }): { ok: true; paths: string[] } | { ok: false; error: string } { const resolvedPaths: string[] = []; for (const raw of params.requestedPaths) { const pathResult = resolvePathWithinRoot({ rootDir: params.rootDir, requestedPath: raw, scopeLabel: params.scopeLabel, }); if (!pathResult.ok) { return { ok: false, error: pathResult.error }; } resolvedPaths.push(pathResult.path); } return { ok: true, paths: resolvedPaths }; }