

























11import { spawn } from "node:child_process";
22import crypto from "node:crypto";
3-import fs from "node:fs/promises";
43import path from "node:path";
4+import { root as fsRoot } from "openclaw/plugin-sdk/security-runtime";
55import {
6-FsSafeError,
7-resolveAbsolutePathForRead,
8-root as fsRoot,
9-} from "openclaw/plugin-sdk/security-runtime";
6+classifyFsSafeReadError,
7+readAbsolutePath,
8+resolveCanonicalReadPath,
9+statRequiredDirectory,
10+} from "./path-errors.js";
10111112const DIR_FETCH_HARD_MAX_BYTES = 16 * 1024 * 1024;
1213const DIR_FETCH_DEFAULT_MAX_BYTES = 8 * 1024 * 1024;
@@ -55,16 +56,9 @@ function clampMaxBytes(input: unknown): number {
5556}
56575758function classifyFsError(err: unknown): DirFetchErrCode {
58-if (err instanceof FsSafeError) {
59-if (err.code === "not-found") {
60-return "NOT_FOUND";
61-}
62-if (err.code === "symlink") {
63-return "SYMLINK_REDIRECT";
64-}
65-if (err.code === "invalid-path") {
66-return "INVALID_PATH";
67-}
59+const safeCode = classifyFsSafeReadError(err);
60+if (safeCode) {
61+return safeCode;
6862}
6963const code = (err as { code?: string } | null)?.code;
7064if (code === "ENOENT") {
@@ -184,67 +178,29 @@ async function listTreeEntries(root: string, maxEntries: number): Promise<string
184178}
185179186180export async function handleDirFetch(params: DirFetchParams): Promise<DirFetchResult> {
187-const requestedPath = params.path;
188-if (typeof requestedPath !== "string" || requestedPath.length === 0) {
189-return { ok: false, code: "INVALID_PATH", message: "path required" };
190-}
191-if (requestedPath.includes("\0")) {
192-return { ok: false, code: "INVALID_PATH", message: "path contains NUL byte" };
193-}
194-if (!path.isAbsolute(requestedPath)) {
195-return { ok: false, code: "INVALID_PATH", message: "path must be absolute" };
181+const requestedPath = readAbsolutePath(params.path);
182+if (typeof requestedPath !== "string") {
183+return requestedPath;
196184}
197185198186const maxBytes = clampMaxBytes(params.maxBytes);
199187const includeDotfiles = params.includeDotfiles === true;
200188const followSymlinks = params.followSymlinks === true;
201189const preflightOnly = params.preflightOnly === true;
202190203-let canonical: string;
204-try {
205-canonical = (
206-await resolveAbsolutePathForRead(requestedPath, {
207-symlinks: followSymlinks ? "follow" : "reject",
208-})
209-).canonicalPath;
210-} catch (err) {
211-const code = classifyFsError(err);
212-const canonicalPath =
213-err instanceof FsSafeError &&
214-err.cause &&
215-typeof err.cause === "object" &&
216-"canonicalPath" in err.cause &&
217-typeof err.cause.canonicalPath === "string"
218- ? err.cause.canonicalPath
219- : undefined;
220-return {
221-ok: false,
222- code,
223-message:
224-code === "NOT_FOUND"
225- ? "directory not found"
226- : code === "SYMLINK_REDIRECT"
227- ? "path traverses a symlink; refusing because followSymlinks=false (set plugins.entries.file-transfer.config.nodes.<node>.followSymlinks=true to allow, or update allowReadPaths to the canonical path)"
228- : `realpath failed: ${String(err)}`,
229- ...(canonicalPath ? { canonicalPath } : {}),
230-};
231-}
232-233-let stats: Awaited<ReturnType<typeof fs.stat>>;
234-try {
235-stats = await fs.stat(canonical);
236-} catch (err) {
237-const code = classifyFsError(err);
238-return { ok: false, code, message: `stat failed: ${String(err)}`, canonicalPath: canonical };
191+const canonical = await resolveCanonicalReadPath({
192+ requestedPath,
193+ followSymlinks,
194+classifyError: classifyFsError,
195+notFoundMessage: "directory not found",
196+});
197+if (typeof canonical !== "string") {
198+return canonical;
239199}
240200241-if (!stats.isDirectory()) {
242-return {
243-ok: false,
244-code: "IS_FILE",
245-message: "path is not a directory",
246-canonicalPath: canonical,
247-};
201+const directory = await statRequiredDirectory(canonical, classifyFsError);
202+if (!directory.ok) {
203+return directory;
248204}
249205250206if (preflightOnly) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。