UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

230 lines (229 loc) 9.32 kB
import { m as FsSafeError, r as isNotFoundPathError } from "./path-BlG8lhgR.js"; import { t as sameFileIdentity } from "./file-identity-BKNyWMFA.js"; import { n as assertNoSymlinkParentsSync, t as assertNoSymlinkParents } from "./symlink-parents-LGlX4F0x.js"; import fs from "node:fs"; import path from "node:path"; import fs$1 from "node:fs/promises"; //#region node_modules/@openclaw/fs-safe/dist/regular-file.js function resolveRegularFileAppendFlags(constants = fs.constants) { const noFollow = constants.O_NOFOLLOW; return constants.O_CREAT | constants.O_APPEND | constants.O_WRONLY | (typeof noFollow === "number" ? noFollow : 0); } function resolveRegularFileReadFlags() { return fs.constants.O_RDONLY | (typeof fs.constants.O_NOFOLLOW === "number" && process.platform !== "win32" ? fs.constants.O_NOFOLLOW : 0); } async function readFileHandleBounded(params) { if (params.maxBytes === void 0) return await params.handle.readFile(); const chunks = []; const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, Math.max(1, params.maxBytes + 1))); let total = 0; while (true) { const { bytesRead } = await params.handle.read(scratch, 0, scratch.length, null); if (bytesRead === 0) return Buffer.concat(chunks, total); total += bytesRead; if (total > params.maxBytes) throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`); chunks.push(Buffer.from(scratch.subarray(0, bytesRead))); } } function readFileDescriptorBounded(params) { if (params.maxBytes === void 0) return fs.readFileSync(params.fd); const chunks = []; const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, Math.max(1, params.maxBytes + 1))); let total = 0; while (true) { const bytesRead = fs.readSync(params.fd, scratch, 0, scratch.length, null); if (bytesRead === 0) return Buffer.concat(chunks, total); total += bytesRead; if (total > params.maxBytes) throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`); chunks.push(Buffer.from(scratch.subarray(0, bytesRead))); } } async function statRegularFile(filePath) { let stat; try { stat = await fs$1.lstat(filePath); } catch (err) { if (isNotFoundPathError(err)) return { missing: true }; throw err; } if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("path must be a regular file"); return { missing: false, stat }; } function statRegularFileSync(filePath) { let stat; try { stat = fs.lstatSync(filePath); } catch (err) { if (isNotFoundPathError(err)) return { missing: true }; throw err; } if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("path must be a regular file"); return { missing: false, stat }; } async function readRegularFile(params) { const result = await statRegularFile(params.filePath); if (result.missing) throw Object.assign(/* @__PURE__ */ new Error(`File not found: ${params.filePath}`), { code: "ENOENT" }); if (params.maxBytes !== void 0 && result.stat.size > params.maxBytes) throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`); let handle; try { handle = await fs$1.open(params.filePath, resolveRegularFileReadFlags()); } catch (err) { if (isNotFoundPathError(err)) throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`); throw err; } try { const stat = await handle.stat(); let pathStat; try { pathStat = await fs$1.lstat(params.filePath); } catch (err) { if (isNotFoundPathError(err)) throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`); throw err; } verifyStableReadTarget({ filePath: params.filePath, pathStat, postOpenStat: stat, preOpenStat: result.stat }); if (params.maxBytes !== void 0 && stat.size > params.maxBytes) throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`); return { buffer: await readFileHandleBounded({ handle, filePath: params.filePath, maxBytes: params.maxBytes }), stat }; } finally { await handle.close(); } } function verifyStableReadTarget(params) { if (!params.postOpenStat.isFile() || params.pathStat.isSymbolicLink() || !params.pathStat.isFile()) throw new Error(`File is not a regular file: ${params.filePath}`); if (!sameFileIdentity(params.preOpenStat, params.postOpenStat) || !sameFileIdentity(params.pathStat, params.postOpenStat)) throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`); } function readOpenedRegularFileSync(params) { const stat = fs.fstatSync(params.fd); verifyStableReadTarget({ filePath: params.filePath, pathStat: fs.lstatSync(params.filePath), postOpenStat: stat, preOpenStat: params.preOpenStat }); if (params.maxBytes !== void 0 && stat.size > params.maxBytes) throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`); return { buffer: readFileDescriptorBounded({ fd: params.fd, filePath: params.filePath, maxBytes: params.maxBytes }), stat }; } function readRegularFileSync(params) { const result = statRegularFileSync(params.filePath); if (result.missing) throw Object.assign(/* @__PURE__ */ new Error(`File not found: ${params.filePath}`), { code: "ENOENT" }); if (params.maxBytes !== void 0 && result.stat.size > params.maxBytes) throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`); const fd = fs.openSync(params.filePath, resolveRegularFileReadFlags()); try { return readOpenedRegularFileSync({ fd, filePath: params.filePath, preOpenStat: result.stat, maxBytes: params.maxBytes }); } finally { fs.closeSync(fd); } } function verifyStableAppendTarget(params) { if (!params.postOpenStat.isFile()) throw new Error(`Refusing to append to non-file: ${params.filePath}`); if (params.postOpenStat.nlink > 1) throw new Error(`Refusing to append to hardlinked file: ${params.filePath}`); const pre = params.preOpenStat; if (pre && (pre.dev !== params.postOpenStat.dev || pre.ino !== params.postOpenStat.ino)) throw new Error(`Refusing to append after file changed: ${params.filePath}`); } async function appendRegularFile(options) { if (options.rejectSymlinkParents === true) { const resolvedDir = path.resolve(path.dirname(options.filePath)); await assertNoSymlinkParents({ rootDir: path.parse(resolvedDir).root, targetPath: resolvedDir, allowMissing: false, allowRootChildSymlink: true, requireDirectories: true, messagePrefix: "Refusing to append under" }); } let preOpenStat; try { const stat = await fs$1.lstat(options.filePath); if (stat.isSymbolicLink()) throw new Error(`Refusing to append through symlink: ${options.filePath}`); if (!stat.isFile()) throw new Error(`Refusing to append to non-file: ${options.filePath}`); preOpenStat = stat; } catch (err) { if (!isNotFoundPathError(err)) throw err; } const contentBytes = Buffer.isBuffer(options.content) ? options.content.byteLength : Buffer.byteLength(options.content, options.encoding ?? "utf8"); if (options.maxFileBytes !== void 0 && (preOpenStat?.size ?? 0) + contentBytes > options.maxFileBytes) return; const handle = await fs$1.open(options.filePath, resolveRegularFileAppendFlags(), options.mode ?? 384); try { const stat = await handle.stat(); verifyStableAppendTarget({ preOpenStat, postOpenStat: stat, filePath: options.filePath }); if (options.maxFileBytes !== void 0 && stat.size + contentBytes > options.maxFileBytes) return; await handle.chmod(options.mode ?? 384); await handle.appendFile(options.content, options.encoding ?? "utf8"); } finally { await handle.close(); } } function appendRegularFileSync(options) { if (options.rejectSymlinkParents === true) { const resolvedDir = path.resolve(path.dirname(options.filePath)); assertNoSymlinkParentsSync({ rootDir: path.parse(resolvedDir).root, targetPath: resolvedDir, allowMissing: false, allowRootChildSymlink: true, requireDirectories: true, messagePrefix: "Refusing to append under" }); } let preOpenStat; try { const stat = fs.lstatSync(options.filePath); if (stat.isSymbolicLink()) throw new Error(`Refusing to append through symlink: ${options.filePath}`); if (!stat.isFile()) throw new Error(`Refusing to append to non-file: ${options.filePath}`); preOpenStat = stat; } catch (err) { if (!isNotFoundPathError(err)) throw err; } const contentBuffer = typeof options.content === "string" ? Buffer.from(options.content, options.encoding ?? "utf8") : Buffer.from(options.content); if (options.maxFileBytes !== void 0 && (preOpenStat?.size ?? 0) + contentBuffer.byteLength > options.maxFileBytes) return; const fd = fs.openSync(options.filePath, resolveRegularFileAppendFlags(), options.mode ?? 384); try { const stat = fs.fstatSync(fd); verifyStableAppendTarget({ preOpenStat, postOpenStat: stat, filePath: options.filePath }); if (options.maxFileBytes !== void 0 && stat.size + contentBuffer.byteLength > options.maxFileBytes) return; fs.fchmodSync(fd, options.mode ?? 384); fs.writeSync(fd, contentBuffer, 0, contentBuffer.byteLength); } finally { fs.closeSync(fd); } } //#endregion export { resolveRegularFileAppendFlags as a, readRegularFileSync as i, appendRegularFileSync as n, statRegularFile as o, readRegularFile as r, statRegularFileSync as s, appendRegularFile as t };