import { promises as fs } from 'fs';
import path from 'path';
export class FileService {
private notesDir: string;
private allowedExtensions: string[] = ['.md', '.txt', '.json', '.yaml', '.yml'];
constructor() {
this.notesDir = process.env.NOTES_DIR || './notes';
}
async readFile(filePath: string): Promise<string> {
try {
// Проверяем безопасность пути
const safePath = this.sanitizePath(filePath);
console.log(`📖 Чтение файла: ${safePath}`);
const content = await fs.readFile(safePath, 'utf-8');
console.log(`✅ Файл прочитан: ${safePath} (${content.length} символов)`);
return content;
} catch (error) {
console.error('Ошибка чтения файла:', error);
throw new Error(`Ошибка чтения файла: ${error}`);
}
}
async writeFile(filePath: string, content: string): Promise<void> {
try {
// Проверяем безопасность пути
const safePath = this.sanitizePath(filePath);
console.log(`✏️ Запись в файл: ${safePath}`);
// Создаем директорию если её нет
const dir = path.dirname(safePath);
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(safePath, content, 'utf-8');
console.log(`✅ Файл записан: ${safePath} (${content.length} символов)`);
} catch (error) {
console.error('Ошибка записи файла:', error);
throw new Error(`Ошибка записи файла: ${error}`);
}
}
async listFiles(dirPath: string = '.'): Promise<string[]> {
try {
const safePath = this.sanitizePath(dirPath);
console.log(`📁 Список файлов в: ${safePath}`);
const files = await fs.readdir(safePath, { withFileTypes: true });
const fileNames = files
.filter(file => file.isFile() && this.isAllowedExtension(file.name))
.map(file => file.name);
console.log(`✅ Найдено файлов: ${fileNames.length}`);
return fileNames;
} catch (error) {
console.error('Ошибка получения списка файлов:', error);
throw new Error(`Ошибка получения списка файлов: ${error}`);
}
}
async fileExists(filePath: string): Promise<boolean> {
try {
const safePath = this.sanitizePath(filePath);
await fs.access(safePath);
return true;
} catch {
return false;
}
}
private sanitizePath(filePath: string): string {
// Убираем потенциально опасные символы
const cleanPath = filePath.replace(/[<>:"|?*]/g, '');
// Разрешаем только относительные пути
if (path.isAbsolute(cleanPath)) {
throw new Error('Абсолютные пути не разрешены');
}
// Разрешаем только файлы в notes директории
const resolvedPath = path.resolve(this.notesDir, cleanPath);
const notesDirResolved = path.resolve(this.notesDir);
if (!resolvedPath.startsWith(notesDirResolved)) {
throw new Error('Доступ к файлу вне notes директории запрещен');
}
return resolvedPath;
}
private isAllowedExtension(fileName: string): boolean {
const ext = path.extname(fileName).toLowerCase();
return this.allowedExtensions.includes(ext);
}
}