fix(mail): relatorios DMARC nao mostravam o clipe de anexo
has_attachments so era definido para mensagens multipart. Um relatorio agregado DMARC do Google e de parte unica: o corpo E o anexo, um application/zip sem qualquer parte de texto. Ficava guardado como nao tendo anexos, ao lado dos relatorios TLS-RPT do mesmo remetente e na mesma pasta, que trazem uma linha de texto e por isso sao multipart/mixed e apareciam marcados. A regra passa a olhar tambem para a parte de topo: uma disposicao explicita decide (um .txt enviado como a mensagem inteira e anexo, uma parte inline nao e), e sem ela conta como anexo tudo o que nao seja texto nem multipart -- um zip, um PDF, uma mensagem reencaminhada. As mensagens ja guardadas mantinham a marca antiga, e sao justamente aquelas que o utilizador esta a olhar. Ha um backfill que as re-le uma vez, em segundo plano, sem atrasar o arranque. So examina as que estao marcadas como sem anexos: a regra antiga nunca inventou um anexo que nao existisse, portanto uma marca verdadeira nao precisa de segunda opiniao e a varredura fica do tamanho do defeito. A tabela backfills guarda o marcador para nao repetir a cada arranque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7f8d9b7ff1
commit
c06691a056
7 files changed
+279
-6
No files matched your search
@@ -69,18 +69,53 @@ func ExtractMessageHeaders(payload []byte) MessageHeaders {
|
||||
}
|
||||
|
||||
contentType := msg.Header.Get("Content-Type")
|
||||
mediaType, params, err := mime.ParseMediaType(contentType)
|
||||
if err == nil && strings.HasPrefix(mediaType, "multipart/") {
|
||||
// mixed and related carry parts that are not the body; alternative is
|
||||
// just the same text twice, so it does not count as an attachment.
|
||||
out.HasAttachments = mediaType != "multipart/alternative"
|
||||
_ = params
|
||||
mediaType, _, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
mediaType = ""
|
||||
}
|
||||
out.HasAttachments = carriesAttachment(mediaType, msg.Header.Get("Content-Disposition"))
|
||||
|
||||
out.Snippet = buildSnippet(msg, mediaType)
|
||||
return out
|
||||
}
|
||||
|
||||
// carriesAttachment decides whether a message has something worth a paperclip,
|
||||
// from its top-level headers alone.
|
||||
//
|
||||
// Looking only for multipart/* missed the case where the body IS the
|
||||
// attachment: a DMARC aggregate report from Google arrives as a single-part
|
||||
// application/zip with no text part at all, so it showed up unmarked next to
|
||||
// TLS-RPT reports - which do carry a text part, and so are multipart/mixed -
|
||||
// from the same sender in the same folder.
|
||||
func carriesAttachment(mediaType, disposition string) bool {
|
||||
// An explicit disposition is the author saying so, and it settles the
|
||||
// question even for a text type: a .txt sent as the whole message is an
|
||||
// attachment, an inline part is not.
|
||||
if disp, _, err := mime.ParseMediaType(disposition); err == nil {
|
||||
switch strings.ToLower(disp) {
|
||||
case "attachment":
|
||||
return true
|
||||
case "inline":
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(mediaType, "multipart/"):
|
||||
// mixed and related carry parts that are not the body; alternative is
|
||||
// just the same text twice.
|
||||
return mediaType != "multipart/alternative"
|
||||
case mediaType == "":
|
||||
// No Content-Type means text/plain (RFC 2045 section 5.2). An
|
||||
// unparseable one is treated the same way rather than guessed at.
|
||||
return false
|
||||
default:
|
||||
// Anything that is not text and not multipart is a single part that is
|
||||
// not the body: a zip, a PDF, a forwarded message.
|
||||
return !strings.HasPrefix(mediaType, "text/")
|
||||
}
|
||||
}
|
||||
|
||||
// buildSnippet takes the opening of the body as a plain-text preview.
|
||||
func buildSnippet(msg *mail.Message, mediaType string) string {
|
||||
// Only a flat text body is previewed. Walking MIME parts here would mean
|
||||
|
||||
Reference in new issue
Block a user