feat: durcit l'exécution SQL IA, borne les uploads et ajoute des tests
- sql_executor: remplace le filtre regex fragile par une autorisation SQLite (set_authorizer) en complément de mode=ro ; rejette les instructions multiples - uploads: lecture bornée des PDF (helper read_upload_limited, limite 20 Mo, HTTP 413) branchée sur /extract et /save-with-pdf - tests: suite pytest (54 tests) couvrant amounts, dates, storage, sql_executor, uploads et DatabaseService.save_document ; pytest ajouté en dépendance dev Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
48
src/plesna_gerance/utils/uploads.py
Normal file
48
src/plesna_gerance/utils/uploads.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""Lecture bornée des fichiers uploadés (protection mémoire / DoS)."""
|
||||
|
||||
from fastapi import UploadFile
|
||||
|
||||
# Taille maximale acceptée pour un PDF uploadé (20 Mo)
|
||||
MAX_UPLOAD_SIZE = 20 * 1024 * 1024
|
||||
|
||||
_CHUNK_SIZE = 1024 * 1024 # 1 Mo
|
||||
|
||||
|
||||
class UploadTooLargeError(Exception):
|
||||
"""Levée quand un fichier uploadé dépasse la taille maximale autorisée."""
|
||||
|
||||
def __init__(self, max_size: int):
|
||||
self.max_size = max_size
|
||||
super().__init__(
|
||||
f"Fichier trop volumineux (maximum {max_size // (1024 * 1024)} Mo)"
|
||||
)
|
||||
|
||||
|
||||
async def read_upload_limited(
|
||||
file: UploadFile, max_size: int = MAX_UPLOAD_SIZE
|
||||
) -> bytes:
|
||||
"""Lit un fichier uploadé par morceaux en plafonnant la taille totale.
|
||||
|
||||
Évite de charger en mémoire un fichier arbitrairement gros.
|
||||
|
||||
Args:
|
||||
file: Fichier uploadé (FastAPI UploadFile).
|
||||
max_size: Taille maximale en octets.
|
||||
|
||||
Returns:
|
||||
Contenu binaire du fichier.
|
||||
|
||||
Raises:
|
||||
UploadTooLargeError: si le fichier dépasse ``max_size``.
|
||||
"""
|
||||
chunks: list[bytes] = []
|
||||
total = 0
|
||||
while True:
|
||||
chunk = await file.read(_CHUNK_SIZE)
|
||||
if not chunk:
|
||||
break
|
||||
total += len(chunk)
|
||||
if total > max_size:
|
||||
raise UploadTooLargeError(max_size)
|
||||
chunks.append(chunk)
|
||||
return b"".join(chunks)
|
||||
Reference in New Issue
Block a user