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>
61 lines
1.9 KiB
Go
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
|
|
}
|