import { Da as isPathInside, Ea as isNotFoundPathError, Oa as isSymlinkOpenError, Sa as sameFileIdentity } from "./context-ZGnpCM_T.js";
import { n as assertNoPathAliasEscape } from "./path-alias-guards-M022a3mt.js";
import { constants } from "node:fs";
import path from "node:path";
import fs$1 from "node:fs/promises";

//#region src/infra/fs-safe.ts
var SafeOpenError = class extends Error {
	constructor(code, message, options) {
		super(message, options);
		this.code = code;
		this.name = "SafeOpenError";
	}
};
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in constants;
const NONBLOCK_OPEN_FLAG = "O_NONBLOCK" in constants ? constants.O_NONBLOCK : 0;
const OPEN_READ_FLAGS = constants.O_RDONLY | (SUPPORTS_NOFOLLOW ? constants.O_NOFOLLOW : 0);
const OPEN_READ_NONBLOCK_FLAGS = OPEN_READ_FLAGS | NONBLOCK_OPEN_FLAG;
const OPEN_READ_FOLLOW_FLAGS = constants.O_RDONLY;
const OPEN_READ_FOLLOW_NONBLOCK_FLAGS = OPEN_READ_FOLLOW_FLAGS | NONBLOCK_OPEN_FLAG;
const OPEN_WRITE_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | (SUPPORTS_NOFOLLOW ? constants.O_NOFOLLOW : 0);
const ensureTrailingSep = (value) => value.endsWith(path.sep) ? value : value + path.sep;
async function openVerifiedLocalFile(filePath, options) {
	let handle;
	try {
		const openFlags = options?.allowSymlinkTargetWithinRoot ? options.nonBlockingRead ? OPEN_READ_FOLLOW_NONBLOCK_FLAGS : OPEN_READ_FOLLOW_FLAGS : options?.nonBlockingRead ? OPEN_READ_NONBLOCK_FLAGS : OPEN_READ_FLAGS;
		handle = await fs$1.open(filePath, openFlags);
	} catch (err) {
		if (isNotFoundPathError(err)) throw new SafeOpenError("not-found", "file not found");
		if (isSymlinkOpenError(err)) throw new SafeOpenError("symlink", "symlink open blocked", { cause: err });
		throw err;
	}
	try {
		const [stat, pathStat] = await Promise.all([handle.stat(), options?.allowSymlinkTargetWithinRoot ? fs$1.stat(filePath) : fs$1.lstat(filePath)]);
		if (!options?.allowSymlinkTargetWithinRoot && pathStat.isSymbolicLink()) throw new SafeOpenError("symlink", "symlink not allowed");
		if (!stat.isFile()) throw new SafeOpenError("not-file", "not a file");
		if (options?.rejectHardlinks && stat.nlink > 1) throw new SafeOpenError("invalid-path", "hardlinked path not allowed");
		if (!sameFileIdentity(stat, pathStat)) throw new SafeOpenError("path-mismatch", "path changed during read");
		const realPath = await fs$1.realpath(filePath);
		const realStat = await fs$1.stat(realPath);
		if (options?.rejectHardlinks && realStat.nlink > 1) throw new SafeOpenError("invalid-path", "hardlinked path not allowed");
		if (!sameFileIdentity(stat, realStat)) throw new SafeOpenError("path-mismatch", "path mismatch");
		return {
			handle,
			realPath,
			stat
		};
	} catch (err) {
		await handle.close().catch(() => {});
		if (err instanceof SafeOpenError) throw err;
		if (isNotFoundPathError(err)) throw new SafeOpenError("not-found", "file not found");
		throw err;
	}
}
async function openFileWithinRoot(params) {
	let rootReal;
	try {
		rootReal = await fs$1.realpath(params.rootDir);
	} catch (err) {
		if (isNotFoundPathError(err)) throw new SafeOpenError("not-found", "root dir not found");
		throw err;
	}
	const rootWithSep = ensureTrailingSep(rootReal);
	const resolved = path.resolve(rootWithSep, params.relativePath);
	if (!isPathInside(rootWithSep, resolved)) throw new SafeOpenError("invalid-path", "path escapes root");
	let opened;
	try {
		opened = await openVerifiedLocalFile(resolved, {
			nonBlockingRead: params.nonBlockingRead,
			allowSymlinkTargetWithinRoot: params.allowSymlinkTargetWithinRoot
		});
	} catch (err) {
		if (err instanceof SafeOpenError) {
			if (err.code === "not-found") throw err;
			throw new SafeOpenError("invalid-path", "path is not a regular file under root", { cause: err });
		}
		throw err;
	}
	if (params.rejectHardlinks !== false && opened.stat.nlink > 1) {
		await opened.handle.close().catch(() => {});
		throw new SafeOpenError("invalid-path", "hardlinked path not allowed");
	}
	if (!isPathInside(rootWithSep, opened.realPath)) {
		await opened.handle.close().catch(() => {});
		throw new SafeOpenError("invalid-path", "path escapes root");
	}
	return opened;
}
async function readLocalFileSafely(params) {
	const opened = await openVerifiedLocalFile(params.filePath);
	try {
		if (params.maxBytes !== void 0 && opened.stat.size > params.maxBytes) throw new SafeOpenError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${opened.stat.size})`);
		return {
			buffer: await opened.handle.readFile(),
			realPath: opened.realPath,
			stat: opened.stat
		};
	} finally {
		await opened.handle.close().catch(() => {});
	}
}
async function readFileWithinRoot(params) {
	const opened = await openFileWithinRoot({
		rootDir: params.rootDir,
		relativePath: params.relativePath,
		rejectHardlinks: params.rejectHardlinks,
		nonBlockingRead: params.nonBlockingRead,
		allowSymlinkTargetWithinRoot: params.allowSymlinkTargetWithinRoot
	});
	try {
		if (params.maxBytes !== void 0 && opened.stat.size > params.maxBytes) throw new SafeOpenError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${opened.stat.size})`);
		return {
			buffer: await opened.handle.readFile(),
			realPath: opened.realPath,
			stat: opened.stat
		};
	} finally {
		await opened.handle.close().catch(() => {});
	}
}
async function writeFileWithinRoot(params) {
	let rootReal;
	try {
		rootReal = await fs$1.realpath(params.rootDir);
	} catch (err) {
		if (isNotFoundPathError(err)) throw new SafeOpenError("not-found", "root dir not found");
		throw err;
	}
	const rootWithSep = ensureTrailingSep(rootReal);
	const resolved = path.resolve(rootWithSep, params.relativePath);
	if (!isPathInside(rootWithSep, resolved)) throw new SafeOpenError("invalid-path", "path escapes root");
	try {
		await assertNoPathAliasEscape({
			absolutePath: resolved,
			rootPath: rootReal,
			boundaryLabel: "root"
		});
	} catch (err) {
		throw new SafeOpenError("invalid-path", "path alias escape blocked", { cause: err });
	}
	if (params.mkdir !== false) await fs$1.mkdir(path.dirname(resolved), { recursive: true });
	try {
		await assertNoPathAliasEscape({
			absolutePath: resolved,
			rootPath: rootReal,
			boundaryLabel: "root"
		});
	} catch (err) {
		throw new SafeOpenError("invalid-path", "path alias escape blocked", { cause: err });
	}
	try {
		if ((await fs$1.lstat(resolved)).isSymbolicLink()) throw new SafeOpenError("invalid-path", "symlink open blocked");
	} catch (err) {
		if (err instanceof SafeOpenError) throw err;
		if (!isNotFoundPathError(err)) throw err;
	}
	let ioPath = resolved;
	try {
		const resolvedRealPath = await fs$1.realpath(resolved);
		if (!isPathInside(rootWithSep, resolvedRealPath)) throw new SafeOpenError("invalid-path", "path escapes root");
		ioPath = resolvedRealPath;
	} catch (err) {
		if (err instanceof SafeOpenError) throw err;
		if (!isNotFoundPathError(err)) throw err;
	}
	let handle;
	try {
		handle = await fs$1.open(ioPath, OPEN_WRITE_FLAGS, 384);
	} catch (err) {
		if (isNotFoundPathError(err)) throw new SafeOpenError("not-found", "file not found");
		if (isSymlinkOpenError(err)) throw new SafeOpenError("invalid-path", "symlink open blocked", { cause: err });
		throw err;
	}
	try {
		const [stat, lstat] = await Promise.all([handle.stat(), fs$1.lstat(ioPath)]);
		if (lstat.isSymbolicLink() || !stat.isFile()) throw new SafeOpenError("invalid-path", "path is not a regular file under root");
		if (stat.nlink > 1) throw new SafeOpenError("invalid-path", "hardlinked path not allowed");
		if (!sameFileIdentity(stat, lstat)) throw new SafeOpenError("path-mismatch", "path changed during write");
		const realPath = await fs$1.realpath(ioPath);
		const realStat = await fs$1.stat(realPath);
		if (!sameFileIdentity(stat, realStat)) throw new SafeOpenError("path-mismatch", "path mismatch");
		if (realStat.nlink > 1) throw new SafeOpenError("invalid-path", "hardlinked path not allowed");
		if (!isPathInside(rootWithSep, realPath)) throw new SafeOpenError("invalid-path", "path escapes root");
		if (typeof params.data === "string") await handle.writeFile(params.data, params.encoding ?? "utf8");
		else await handle.writeFile(params.data);
	} finally {
		await handle.close().catch(() => {});
	}
}

//#endregion
export { writeFileWithinRoot as a, readLocalFileSafely as i, openFileWithinRoot as n, readFileWithinRoot as r, SafeOpenError as t };