diff --git a/rag/services/file_service.py b/rag/services/file_service.py index 521ecd2..1847192 100644 --- a/rag/services/file_service.py +++ b/rag/services/file_service.py @@ -4,6 +4,7 @@ хирургическая замена в DOCX, создание DOCX из текста, транскрипция аудио через SaluteSpeech. Все методы, которые могут быть асинхронными, теперь async. +Добавлено экранирование путей через shlex.quote. """ import asyncio @@ -13,6 +14,7 @@ import logging import os import re import shutil +import shlex import subprocess import tempfile import zipfile @@ -81,8 +83,6 @@ class FileService: # ---------- Синхронные методы извлечения (вызываются через to_thread) ---------- def extract_text_from_docx(self, file_path: str) -> str: """Извлекает текст из DOCX, включая колонтитулы, таблицы и сноски.""" - # ... (код без изменений, полная версия была ранее) - # Я приведу полный код этого метода, чтобы сохранить целостность. try: doc = DocxReader(file_path) full_text = [] @@ -235,6 +235,7 @@ class FileService: """ Асинхронно транскрибирует аудиофайл через SaluteSpeech API. Все блокирующие вызовы (subprocess.run, requests.post) обёрнуты в asyncio.to_thread. + Пути экранируются через shlex.quote. """ import uuid @@ -247,14 +248,17 @@ class FileService: if not shutil.which('ffmpeg') and not os.path.exists(ffmpeg_bin): return "[Ошибка: на сервере не установлен ffmpeg]" + # Экранируем пути + safe_file_path = shlex.quote(file_path) pcm_output = file_path + ".raw" + safe_pcm_output = shlex.quote(pcm_output) try: # Конвертация ffmpeg def _run_ffmpeg(): cmd_ffmpeg = [ - ffmpeg_bin, '-y', '-i', file_path, - '-ac', '1', '-ar', '16000', '-f', 's16le', '-filter:a', 'volume=2.0', pcm_output + ffmpeg_bin, '-y', '-i', safe_file_path, + '-ac', '1', '-ar', '16000', '-f', 's16le', '-filter:a', 'volume=2.0', safe_pcm_output ] return subprocess.run(cmd_ffmpeg, capture_output=True) @@ -276,7 +280,7 @@ class FileService: '-H', f'Authorization: Bearer {token}', '-H', 'Content-Type: audio/x-pcm;bit=16;rate=16000', '-H', f'RqUID: {str(uuid.uuid4())}', - '--data-binary', f'@{pcm_output}' + '--data-binary', f'@{safe_pcm_output}' ] return subprocess.run(curl_cmd, capture_output=True, text=True) @@ -326,7 +330,6 @@ class FileService: try: def _sync_request(): return requests.post(url, headers=headers, data=payload, verify=False) - # ОБЁРТКА: синхронный запрос выполняется в отдельном потоке response = await asyncio.to_thread(_sync_request) token = response.json().get('access_token') logger.info("SaluteSpeech token получен") @@ -479,7 +482,7 @@ class FileService: return text, count # ============================================================ - # ОСНОВНОЙ МЕТОД – теперь асинхронный! + # ОСНОВНОЙ МЕТОД # ============================================================ async def process_any_file(self, file_path: str) -> Tuple[str, int]: