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
@@ -86,3 +86,59 @@ func TestExtractMessageHeaders_TruncatesWithoutBreakingUTF8(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A DMARC aggregate report from Google is a single-part message whose body IS
|
||||
// the attachment: Content-Type application/zip, no text part at all. The flag
|
||||
// was only ever set for multipart/*, so these arrived without the paperclip
|
||||
// while TLS-RPT reports - which do carry a text part, so multipart/mixed - got
|
||||
// one. Same folder, same sender, one marked and one not.
|
||||
func TestExtractMessageHeaders_SinglePartAttachmentCounts(t *testing.T) {
|
||||
msg := "From: <noreply-dmarc-support@google.com>\r\n" +
|
||||
"To: <dmarc@example.test>\r\n" +
|
||||
"Subject: Report domain: example.test\r\n" +
|
||||
"Content-Type: application/zip; name=\"google.com!example.test!1.zip\"\r\n" +
|
||||
"Content-Disposition: attachment; filename=\"google.com!example.test!1.zip\"\r\n" +
|
||||
"Content-Transfer-Encoding: base64\r\n" +
|
||||
"\r\n" +
|
||||
"UEsDBBQAAAAIAA==\r\n"
|
||||
|
||||
h := ExtractMessageHeaders([]byte(msg))
|
||||
|
||||
if !h.HasAttachments {
|
||||
t.Error("a message whose whole body is a zip was reported as having no attachments")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractMessageHeaders_AttachmentDetection(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
headers string
|
||||
want bool
|
||||
}{
|
||||
{"plain text", "Content-Type: text/plain; charset=utf-8\r\n", false},
|
||||
{"html only", "Content-Type: text/html; charset=utf-8\r\n", false},
|
||||
// The same text twice is not an attachment.
|
||||
{"alternative", "Content-Type: multipart/alternative; boundary=b\r\n", false},
|
||||
{"mixed", "Content-Type: multipart/mixed; boundary=b\r\n", true},
|
||||
{"related", "Content-Type: multipart/related; boundary=b\r\n", true},
|
||||
{"bare zip", "Content-Type: application/zip\r\n", true},
|
||||
{"bare pdf", "Content-Type: application/pdf\r\n", true},
|
||||
// A forwarded message carried as the whole body.
|
||||
{"attached message", "Content-Type: message/rfc822\r\n", true},
|
||||
// An explicit disposition wins even over a text type: someone sent a
|
||||
// .txt file as the entire message.
|
||||
{"text as attachment", "Content-Type: text/plain\r\nContent-Disposition: attachment; filename=\"notes.txt\"\r\n", true},
|
||||
{"inline text", "Content-Type: text/plain\r\nContent-Disposition: inline\r\n", false},
|
||||
// No Content-Type at all means text/plain (RFC 2045 section 5.2).
|
||||
{"absent content type", "", false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
msg := "From: <a@b.test>\r\nSubject: x\r\n" + tc.headers + "\r\nbody\r\n"
|
||||
if got := ExtractMessageHeaders([]byte(msg)).HasAttachments; got != tc.want {
|
||||
t.Errorf("HasAttachments = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user