This commit is contained in:
2026-04-07 11:32:05 +02:00
103 changed files with 14789 additions and 555 deletions
+25 -14
View File
@@ -116,7 +116,7 @@ def parse_date(date_str: str | None) -> datetime | None:
# ─── Parser principale ────────────────────────────────────────────────────────
def parse_eml(raw_bytes: bytes) -> ParsedEmail:
def parse_eml(raw_bytes: bytes, is_receipt: bool = False) -> ParsedEmail:
"""
Parsing completo di un raw EML.
@@ -126,7 +126,11 @@ def parse_eml(raw_bytes: bytes) -> ParsedEmail:
- Allegati: tutti i parti con filename, inclusi message/rfc822
Args:
raw_bytes: byte del messaggio EML grezzo
raw_bytes: byte del messaggio EML grezzo
is_receipt: True se il messaggio e' una ricevuta PEC (accettazione,
avvenuta_consegna, ecc.). In questo caso il body_text/html
esterno (testo della ricevuta) non viene sovrascritto con
il contenuto del messaggio annidato in postacert.eml.
Returns:
ParsedEmail con tutti i campi estratti (fields None/[] se non presenti)
@@ -153,7 +157,7 @@ def parse_eml(raw_bytes: bytes) -> ParsedEmail:
# ── Body e allegati ───────────────────────────────────────────────────────
if msg.is_multipart():
_walk_parts(msg, result)
_walk_parts(msg, result, is_receipt=is_receipt)
else:
_extract_single_part_body(msg, result)
@@ -208,7 +212,7 @@ def _get_filename(part: email.message.Message) -> str | None:
return None
def _walk_parts(msg: email.message.Message, result: ParsedEmail) -> None:
def _walk_parts(msg: email.message.Message, result: ParsedEmail, is_receipt: bool = False) -> None:
"""
Naviga ricorsivamente tutti i part MIME del messaggio.
@@ -230,7 +234,7 @@ def _walk_parts(msg: email.message.Message, result: ParsedEmail) -> None:
# ── EML-in-EML (message/rfc822) ───────────────────────────────────────
if ct == "message/rfc822":
_extract_eml_in_eml(part, filename, result)
_extract_eml_in_eml(part, filename, result, is_receipt=is_receipt)
continue
# ── Allegato esplicito (Content-Disposition: attachment) ──────────────
@@ -292,12 +296,16 @@ def _extract_eml_in_eml(
part: email.message.Message,
filename: str | None,
result: ParsedEmail,
is_receipt: bool = False,
) -> None:
"""
Estrae il messaggio EML annidato in un part message/rfc822.
Per postacert.eml (busta PEC in arrivo): ricorre dentro per estrarre
Per postacert.eml in messaggi posta_certificata: ricorre dentro per estrarre
gli allegati utente e il corpo del messaggio originale del mittente.
Per le ricevute (is_receipt=True): estrae solo gli allegati utente senza
sovrascrivere il body gia' impostato (che e' il testo della ricevuta stessa).
"""
try:
payload = part.get_payload()
@@ -305,7 +313,7 @@ def _extract_eml_in_eml(
inner_bytes: bytes | None = None
if isinstance(payload, list) and payload:
# Forma canonica: payload è lista di Message
# Forma canonica: payload e' lista di Message
inner_msg = payload[0]
if isinstance(inner_msg, email.message.Message):
inner_bytes = inner_msg.as_bytes()
@@ -330,19 +338,22 @@ def _extract_eml_in_eml(
)
result.attachments.append(att)
# Per postacert.eml: ricorre dentro per trovare allegati utente e corpo originale
# Per postacert.eml: ricorre dentro per trovare allegati utente
if is_system and eff_filename.lower() == "postacert.eml":
inner_parsed = parse_eml(inner_bytes)
# Allegati non-sistema del messaggio originale del mittente
for inner_att in inner_parsed.attachments:
if not inner_att.is_pec_system:
result.attachments.append(inner_att)
# Corpo del messaggio originale (più utile del testo della busta PEC)
if inner_parsed.body_html:
result.body_html = inner_parsed.body_html
result.body_text = inner_parsed.body_text
elif inner_parsed.body_text:
result.body_text = inner_parsed.body_text
# Sovrascrive il corpo SOLO per messaggi posta_certificata (non ricevute).
# Per le ricevute il body esterno e' gia' il testo corretto della ricevuta;
# postacert.eml contiene il messaggio originale inviato che non va mostrato.
if not is_receipt:
if inner_parsed.body_html:
result.body_html = inner_parsed.body_html
result.body_text = inner_parsed.body_text
elif inner_parsed.body_text:
result.body_text = inner_parsed.body_text
except Exception as exc:
logger.warning(f"Errore estrazione EML-in-EML: {exc}")