UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

120 lines (119 loc) 3.91 kB
import { a as root } from "./secure-temp-dir-XAWcZnE2.js"; import { n as detectMime } from "./mime-C8mVE2Bw.js"; import "./security-runtime-CQm7DD1u.js"; import "./media-mime-v9DSctG7.js"; import { n as readAbsolutePath, r as resolveCanonicalReadPath, t as classifyFsSafeReadError } from "./path-errors-XUg5zBap.js"; import path from "node:path"; import crypto from "node:crypto"; //#region extensions/file-transfer/src/node-host/file-fetch.ts const FILE_FETCH_HARD_MAX_BYTES = 16 * 1024 * 1024; const FILE_FETCH_DEFAULT_MAX_BYTES = 8 * 1024 * 1024; const TEXT_SNIFF_MAX_BYTES = 8192; function clampMaxBytes(input) { if (typeof input !== "number" || !Number.isFinite(input) || input <= 0) return FILE_FETCH_DEFAULT_MAX_BYTES; return Math.min(Math.floor(input), FILE_FETCH_HARD_MAX_BYTES); } function classifyFsError(err) { const safeCode = classifyFsSafeReadError(err); if (safeCode) return safeCode; const code = err?.code; if (code === "not-file") return "IS_DIRECTORY"; if (code === "ENOENT") return "NOT_FOUND"; if (code === "EACCES" || code === "EPERM") return "PERMISSION_DENIED"; if (code === "EISDIR") return "IS_DIRECTORY"; return "READ_ERROR"; } function isLikelyPlainText(buffer) { if (buffer.byteLength === 0) return true; const sample = buffer.subarray(0, TEXT_SNIFF_MAX_BYTES); if (sample.includes(0)) return false; try { new TextDecoder("utf-8", { fatal: true }).decode(sample); } catch { return false; } let controlBytes = 0; for (const byte of sample) if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) controlBytes += 1; return controlBytes / sample.byteLength < .01; } async function detectFetchedFileMime(params) { const detected = await detectMime(params); if (detected) return detected; return isLikelyPlainText(params.buffer) ? "text/plain" : "application/octet-stream"; } async function handleFileFetch(params) { const requestedPath = readAbsolutePath(params.path); if (typeof requestedPath !== "string") return requestedPath; const maxBytes = clampMaxBytes(params.maxBytes); const followSymlinks = params.followSymlinks === true; const preflightOnly = params.preflightOnly === true; const canonical = await resolveCanonicalReadPath({ requestedPath, followSymlinks, classifyError: classifyFsError, notFoundMessage: "file not found" }); if (typeof canonical !== "string") return canonical; let opened; try { opened = await (await root(path.dirname(canonical))).open(path.basename(canonical)); } catch (err) { const code = classifyFsError(err); return { ok: false, code, message: code === "IS_DIRECTORY" ? "path is a directory" : `open failed: ${String(err)}`, canonicalPath: canonical }; } try { const stats = opened.stat; if (stats.size > maxBytes) return { ok: false, code: "FILE_TOO_LARGE", message: `file size ${stats.size} exceeds limit ${maxBytes}`, canonicalPath: opened.realPath }; if (preflightOnly) return { ok: true, path: opened.realPath, size: stats.size, mimeType: "", base64: "", sha256: "", preflightOnly: true }; const buffer = await opened.handle.readFile(); if (buffer.byteLength > maxBytes) return { ok: false, code: "FILE_TOO_LARGE", message: `read ${buffer.byteLength} bytes exceeds limit ${maxBytes}`, canonicalPath: opened.realPath }; const sha256 = crypto.createHash("sha256").update(buffer).digest("hex"); const base64 = buffer.toString("base64"); const mimeType = await detectFetchedFileMime({ buffer, filePath: opened.realPath }); return { ok: true, path: opened.realPath, size: buffer.byteLength, mimeType, base64, sha256 }; } catch (err) { return { ok: false, code: classifyFsError(err), message: `read failed: ${String(err)}`, canonicalPath: opened.realPath }; } finally { await opened.handle.close().catch(() => void 0); } } //#endregion export { handleFileFetch };