Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ the following common operators:
* `cc`: the mail has the given address as CC destination
* `bcc`: the mail has the given address as BCC destination
* `replyto`: the mail has the given address as Reply-To destination
* `deliveredto`: the mail was delivered to the given address, according to its
Delivered-To header. Useful to match `+` aliases (e.g. `me+tag@gmail.com`)
or mail coming from forwarding services, regardless of the recipients in
`to`. Note that it doesn't match Google Workspace domain aliases.

One more special function is given if you need to use less common operators<sup
id="a1">[1](#f1)</sup>, or want to compose your query manually:
Expand Down Expand Up @@ -645,6 +649,7 @@ as possible. This is the list of fields:
* `cc: [<list>]`: a list of emails in cc.
* `bcc: [<list>]`: a list of emails in bcc.
* `replyto: <string>`: the email listed in the Reply-To field.
* `deliveredto: [<list>]`: a list of emails in the Delivered-To headers.
* `lists: [<list>]`: a list of mailing lists.
* `subject: <string>`: the subject of the email.
* `body: <string>`: the body of the email.
Expand Down
5 changes: 4 additions & 1 deletion internal/engine/cfgtest/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ func (r *evalBuilder) VisitLeaf(n *parser.Leaf) {
var rules []RuleEvaluator

switch n.Function {
case parser.FunctionFrom, parser.FunctionCc, parser.FunctionBcc, parser.FunctionList, parser.FunctionReplyTo:
case parser.FunctionFrom, parser.FunctionCc, parser.FunctionBcc, parser.FunctionList,
parser.FunctionReplyTo, parser.FunctionDeliveredTo:
rules = expandAll(n.Args, func(a string) RuleEvaluator {
return emailField(toMatchField(n.Function), a)
})
Expand Down Expand Up @@ -188,6 +189,8 @@ func toMatchField(f parser.FunctionType) matchField {
return matchFieldBcc
case parser.FunctionReplyTo:
return matchFieldReplyTo
case parser.FunctionDeliveredTo:
return matchFieldDeliveredTo
case parser.FunctionList:
return matchFieldLists
case parser.FunctionSubject:
Expand Down
3 changes: 3 additions & 0 deletions internal/engine/cfgtest/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
matchFieldCc
matchFieldBcc
matchFieldReplyTo
matchFieldDeliveredTo
matchFieldLists
matchFieldSubject
matchFieldBody
Expand Down Expand Up @@ -91,6 +92,8 @@ func (n funcNode) Match(msg cfg.Message) bool {
fields = msg.Bcc
case matchFieldReplyTo:
fields = msg.ReplyTo
case matchFieldDeliveredTo:
fields = msg.DeliveredTo
case matchFieldLists:
fields = msg.Lists
case matchFieldSubject:
Expand Down
1 change: 1 addition & 0 deletions internal/engine/cfgtest/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func TestEmailFieldMatching(t *testing.T) {
{parser.FunctionCc, func(s string) cfg.Message { return cfg.Message{Cc: []string{s}} }},
{parser.FunctionBcc, func(s string) cfg.Message { return cfg.Message{Bcc: []string{s}} }},
{parser.FunctionReplyTo, func(s string) cfg.Message { return cfg.Message{ReplyTo: []string{s}} }},
{parser.FunctionDeliveredTo, func(s string) cfg.Message { return cfg.Message{DeliveredTo: []string{s}} }},
{parser.FunctionList, func(s string) cfg.Message { return cfg.Message{Lists: []string{s}} }},
{parser.FunctionHas, func(s string) cfg.Message { return cfg.Message{From: s} }},
}
Expand Down
24 changes: 24 additions & 0 deletions internal/engine/cfgtest/testdata/pass.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ local filters = {
delete: true,
}
},
{
filter: {
or: [
{deliveredto: 'pippo+shopping@gmail.com'},
{deliveredto: 'pippo+deals@gmail.com'},
]
},
actions: {
labels: ['shopping'],
}
},
],
tests: [
{
Expand Down Expand Up @@ -114,5 +125,18 @@ local filters = {
labels: ['maillist', 'onemorelabel'],
},
},
{
name: 'forwarded aliases',
messages: [
{
to: ['store@simplelogin.com'],
deliveredto: ['pippo+shopping@gmail.com'],
},
{deliveredto: ['pippo+deals@gmail.com']},
],
actions: {
labels: ['shopping'],
},
},
],
}
36 changes: 19 additions & 17 deletions internal/engine/config/v1alpha3/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@ type FilterNode struct {
Or []FilterNode `json:"or,omitempty"`
Not *FilterNode `json:"not,omitempty"`

From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
Cc string `json:"cc,omitempty"`
Bcc string `json:"bcc,omitempty"`
ReplyTo string `json:"replyto,omitempty"`
Subject string `json:"subject,omitempty"`
List string `json:"list,omitempty"`
Has string `json:"has,omitempty"`
Query string `json:"query,omitempty"`
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
Cc string `json:"cc,omitempty"`
Bcc string `json:"bcc,omitempty"`
ReplyTo string `json:"replyto,omitempty"`
DeliveredTo string `json:"deliveredto,omitempty"`
Subject string `json:"subject,omitempty"`
List string `json:"list,omitempty"`
Has string `json:"has,omitempty"`
Query string `json:"query,omitempty"`

// IsEscaped specifies that the given parameters don't need any
// further escaping.
Expand Down Expand Up @@ -147,14 +148,15 @@ type Test struct {

// Message represents the contents and metadata of an email.
type Message struct {
From string `json:"from,omitempty"`
To []string `json:"to,omitempty"`
Cc []string `json:"cc,omitempty"`
Bcc []string `json:"bcc,omitempty"`
ReplyTo []string `json:"replyto,omitempty"`
Lists []string `json:"lists,omitempty"`
Subject string `json:"subject,omitempty"`
Body string `json:"body,omitempty"`
From string `json:"from,omitempty"`
To []string `json:"to,omitempty"`
Cc []string `json:"cc,omitempty"`
Bcc []string `json:"bcc,omitempty"`
ReplyTo []string `json:"replyto,omitempty"`
DeliveredTo []string `json:"deliveredto,omitempty"`
Lists []string `json:"lists,omitempty"`
Subject string `json:"subject,omitempty"`
Body string `json:"body,omitempty"`
}

func jsonTagName(t reflect.StructTag) string {
Expand Down
4 changes: 4 additions & 0 deletions internal/engine/filter/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ func generateLeaf(leaf *parser.Leaf) (Criteria, error) {
return Criteria{
Query: fmt.Sprintf("replyto:%s", query),
}, nil
case parser.FunctionDeliveredTo:
return Criteria{
Query: fmt.Sprintf("deliveredto:%s", query),
}, nil
case parser.FunctionList:
return Criteria{
Query: fmt.Sprintf("list:%s", query),
Expand Down
38 changes: 38 additions & 0 deletions internal/engine/filter/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,44 @@ func TestQuoting(t *testing.T) {
assert.Equal(t, expected, got)
}

func TestDeliveredTo(t *testing.T) {
tests := []struct {
name string
criteria parser.CriteriaAST
want Criteria
}{
{
name: "grouped leaf",
criteria: &parser.Leaf{
Function: parser.FunctionDeliveredTo,
Grouping: parser.OperationOr,
Args: []string{"me+a@gmail.com", "me+b@gmail.com"},
},
want: Criteria{Query: "deliveredto:{me+a@gmail.com me+b@gmail.com}"},
},
{
name: "nested leaf",
criteria: &parser.Node{
Operation: parser.OperationNot,
Children: []parser.CriteriaAST{
&parser.Leaf{
Function: parser.FunctionDeliveredTo,
Args: []string{"me+a@gmail.com"},
},
},
},
want: Criteria{Query: "-deliveredto:me+a@gmail.com"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GenerateCriteria(tt.criteria)
assert.Nil(t, err)
assert.Equal(t, tt.want, got)
})
}
}

func TestSplitLeaf(t *testing.T) {
rule := parser.Rule{
Criteria: &parser.Leaf{
Expand Down
3 changes: 3 additions & 0 deletions internal/engine/parser/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
FunctionCc
FunctionBcc
FunctionReplyTo
FunctionDeliveredTo
FunctionSubject
FunctionList
FunctionHas
Expand All @@ -59,6 +60,8 @@ func (f FunctionType) String() string {
return "bcc"
case FunctionReplyTo:
return "replyto"
case FunctionDeliveredTo:
return "deliveredto"
case FunctionSubject:
return "subject"
case FunctionList:
Expand Down
3 changes: 3 additions & 0 deletions internal/engine/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ func parseFunction(f cfg.FilterNode) (FunctionType, string) {
if f.ReplyTo != "" {
return FunctionReplyTo, f.ReplyTo
}
if f.DeliveredTo != "" {
return FunctionDeliveredTo, f.DeliveredTo
}
if f.Subject != "" {
return FunctionSubject, f.Subject
}
Expand Down
Loading