/
raiden
/
obsidian-git-encrypt
Обзор
Документация
Войти
/
raiden
/
obsidian-git-encrypt
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/sync/localStateFile.ts
99 строк
4 KB
Robert Kuzhin
feat: add Obsidian session and desktop UX
09 авг 2026, 13:07
09 авг 2026, 13:07
14fe125
Код
Авторство
О чём код?
import { lstat, mkdir, readFile, realpath, unlink } from "node:fs/promises"; import path from "node:path"; import { writeFileAtomic } from "../mirror/atomicWriter.js"; import { PathSafetyError } from "../mirror/errors.js"; import { assertSeparatedRoots, canonicalDirectory, isWithin } from "../mirror/pathSafety.js"; export async function openLocalStateRoot( stateRoot: string, encryptedRepositoryRoot: string, ): Promise<string> { if (!path.isAbsolute(stateRoot)) { throw new PathSafetyError("Local state root must be absolute"); } const canonicalRepositoryRoot = await canonicalDirectory(encryptedRepositoryRoot); const resolvedStateRoot = await resolveProspectiveLocalStatePath(stateRoot); if ( isWithin(canonicalRepositoryRoot, resolvedStateRoot) || isWithin(resolvedStateRoot, canonicalRepositoryRoot) ) { throw new PathSafetyError("Local state and encrypted repository roots must be separate"); } await mkdir(stateRoot, { recursive: true, mode: 0o700 }); const canonicalStateRoot = await canonicalDirectory(stateRoot); await assertSeparatedRoots(canonicalStateRoot, canonicalRepositoryRoot); return canonicalStateRoot; } export async function resolveProspectiveLocalStatePath(candidate: string): Promise<string> { let existingAncestor = path.resolve(candidate); const missingSegments: string[] = []; while (true) { const metadata = await lstat(existingAncestor).catch((error: unknown) => { if (isNodeError(error) && error.code === "ENOENT") return undefined; throw error; }); if (metadata !== undefined) break; const parent = path.dirname(existingAncestor); if (parent === existingAncestor) { throw new PathSafetyError("Unable to resolve a parent for local state root"); } missingSegments.unshift(path.basename(existingAncestor)); existingAncestor = parent; } return path.join(await realpath(existingAncestor), ...missingSegments); } export async function readLocalStateFile( root: string, filename: string, maximumBytes: number, ): Promise<Uint8Array | undefined> { const target = path.join(root, filename); const metadata = await lstat(target).catch((error: unknown) => { if (isNodeError(error) && error.code === "ENOENT") return undefined; throw error; }); if (metadata === undefined) return undefined; if (!metadata.isFile() || metadata.isSymbolicLink()) { throw new PathSafetyError("Local encrypted state must be a regular file"); } if (metadata.size > maximumBytes) { throw new PathSafetyError("Local encrypted state exceeds its size limit"); } return readFile(target); } export async function writeLocalStateFile( root: string, filename: string, data: Uint8Array, ): Promise<void> { const target = path.join(root, filename); const metadata = await lstat(target).catch((error: unknown) => { if (isNodeError(error) && error.code === "ENOENT") return undefined; throw error; }); if (metadata !== undefined && (!metadata.isFile() || metadata.isSymbolicLink())) { throw new PathSafetyError("Local encrypted state target must be a regular file or absent"); } await writeFileAtomic(root, target, data); } export async function deleteLocalStateFile(root: string, filename: string): Promise<void> { const target = path.join(root, filename); const metadata = await lstat(target).catch((error: unknown) => { if (isNodeError(error) && error.code === "ENOENT") return undefined; throw error; }); if (metadata === undefined) return; if (!metadata.isFile() || metadata.isSymbolicLink()) { throw new PathSafetyError("Local encrypted state target must be a regular file"); } await unlink(target); } function isNodeError(error: unknown): error is NodeJS.ErrnoException { return error instanceof Error && "code" in error; }