Files
LambdaMail/services/protocols/internal/domain/sieve/vacation.go
T
Lucas RayanandClaude Opus 5 b9ebf606e1 feat(sieve): motor que avalia as regras na entrega
Primeira metade do que falta para a ausência e os filtros funcionarem:
o motor em si, como domínio puro e testável, ainda sem estar ligado à
entrega.

É um subconjunto deliberado do RFC 5228 mais o vacation do RFC 5230:
os testes e as ações que a interface gera e que um script escrito à
mão provavelmente usa - header com :is, :contains e :matches, anyof,
allof, not, if/elsif/else, fileinto, discard, addflag, keep, stop e
vacation. O que não entende é recusado em vez de ignorado: uma regra
que o utilizador escreveu e que silenciosamente não faz nada é pior do
que uma que diz que não consegue correr.

:matches é correspondência com curingas, não expressão regular. Em
Sieve todos os outros caracteres são literais, portanto "a.b@x.test"
tem de casar apenas com um ponto verdadeiro; compilá-lo como regex
faria casar também "axb@x.test" e entregaria a um autor de scripts toda
a gramática das expressões regulares, incluindo padrões que levam tempo
exponencial numa linha de assunto construída para isso. O algoritmo faz
backtracking iterativo, linear no caso comum, e há um teste com limite
de tempo que falha se voltar a ser exponencial.

A parte da ausência que realmente importa é quando não responder. O
RFC 3834 é a regra: responde-se à mensagem de uma pessoa e a mais nada.
Remetente de envelope vazio, auto-submitted, precedence bulk, qualquer
cabeçalho de lista, x-auto-response-suppress, endereços reservados como
mailer-daemon, e a própria caixa - nenhum recebe resposta. É assim que
uma caixa evita responder a uma lista à frente de toda a gente, ou
entrar num ciclo com outro respondedor automático.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 13:24:20 -03:00

61 lines
1.9 KiB
Go

package sieve
import "strings"
// shouldAutoReply decides whether a message may be answered automatically.
//
// This is the part of a vacation responder that matters. Replying to the wrong
// thing is how a mailbox answers a mailing list in front of everyone, or ends
// up in a loop with another autoresponder that replies to the reply. RFC 3834
// is the rule: answer a message a person actually sent, and nothing else.
func shouldAutoReply(msg Message) bool {
// An empty envelope sender is a bounce or a notification. Replying to one
// sends mail to nobody, or worse, back into a loop.
if strings.TrimSpace(msg.Sender) == "" {
return false
}
// Never answer yourself: the Sent copy and any self-addressed message.
if msg.Recipient != "" && strings.EqualFold(msg.Sender, msg.Recipient) {
return false
}
local := strings.ToLower(msg.Sender)
if at := strings.Index(local, "@"); at > 0 {
local = local[:at]
}
// Addresses that exist to carry automated mail. Answering one is at best
// noise and at worst a loop.
for _, reserved := range []string{"mailer-daemon", "postmaster", "no-reply", "noreply", "bounce", "bounces"} {
if local == reserved || strings.HasPrefix(local, reserved+"+") {
return false
}
}
// Headers that say, in one way or another, "this was not typed by a
// person" or "do not answer this".
if len(msg.header("auto-submitted")) > 0 {
for _, v := range msg.header("auto-submitted") {
if !strings.EqualFold(strings.TrimSpace(v), "no") {
return false
}
}
}
for _, header := range []string{
"list-id", "list-help", "list-unsubscribe", "list-post",
"x-auto-response-suppress", "x-autoreply", "x-autorespond",
} {
if len(msg.header(header)) > 0 {
return false
}
}
for _, v := range msg.header("precedence") {
switch strings.ToLower(strings.TrimSpace(v)) {
case "bulk", "list", "junk":
return false
}
}
return true
}