This commit is contained in:
2026-03-18 17:43:03 +01:00
parent d80d912fb3
commit c89c08c397
10 changed files with 2352 additions and 123 deletions
+75
View File
@@ -59,6 +59,81 @@ async def upload_eml(
raise
async def upload_attachment(
tenant_id: str,
mailbox_id: str,
message_id: str,
filename: str,
content: bytes,
content_type: str = "application/octet-stream",
) -> str:
"""
Carica un allegato su MinIO e restituisce il percorso oggetto.
Percorso: tenants/{tenant_id}/mailboxes/{mailbox_id}/attachments/{message_id}/{filename}
Args:
tenant_id: UUID del tenant
mailbox_id: UUID della casella
message_id: UUID del messaggio a cui appartiene l'allegato
filename: nome file dell'allegato (usato nel path)
content: byte del file
content_type: MIME type del file
Returns:
Percorso oggetto su MinIO (senza il nome bucket)
"""
client = get_minio_client()
bucket = settings.minio_bucket
# Sanitizza il filename per evitare path traversal
safe_filename = _sanitize_filename(filename)
object_path = (
f"tenants/{tenant_id}/mailboxes/{mailbox_id}"
f"/attachments/{message_id}/{safe_filename}"
)
try:
data_stream = io.BytesIO(content)
await client.put_object(
bucket_name=bucket,
object_name=object_path,
data=data_stream,
length=len(content),
content_type=content_type,
)
logger.debug(
f"Allegato caricato: s3://{bucket}/{object_path} "
f"({len(content)} bytes, {content_type})"
)
return object_path
except Exception as e:
logger.error(f"Errore upload allegato {object_path}: {e}")
raise
def _sanitize_filename(filename: str) -> str:
"""
Sanitizza il nome file per uso sicuro come path MinIO.
Rimuove caratteri pericolosi mantenendo l'estensione originale.
"""
import re
# Rimuovi path separators e null bytes
safe = filename.replace("/", "_").replace("\\", "_").replace("\x00", "")
# Rimuovi caratteri non ASCII e di controllo
safe = re.sub(r"[^\w.\-() ]", "_", safe, flags=re.UNICODE)
# Limita la lunghezza (MinIO ha limite 1024 chars per object name)
if len(safe) > 200:
# Mantieni estensione
parts = safe.rsplit(".", 1)
if len(parts) == 2:
safe = parts[0][:196] + "." + parts[1]
else:
safe = safe[:200]
return safe or "attachment"
async def ensure_bucket_exists() -> None:
"""Verifica che il bucket MinIO esista, altrimenti lo crea."""
client = get_minio_client()