diff --git a/internal/policy/inbound.go b/internal/policy/inbound.go new file mode 100644 index 0000000..3dfaa6b --- /dev/null +++ b/internal/policy/inbound.go @@ -0,0 +1,29 @@ +package policy + +import "regexp" + +// InboundRule captures one account's read-side filtering. +type InboundRule struct { + WhitelistInEnabled bool + WhitelistIn []string + SubjectRegex *regexp.Regexp // nil = no subject filter +} + +// CompileSubject compiles a subject filter; empty pattern => (nil, nil). +func CompileSubject(pattern string) (*regexp.Regexp, error) { + if pattern == "" { + return nil, nil + } + return regexp.Compile(pattern) +} + +// Allows reports whether a message with the given sender and subject is visible. +func (r InboundRule) Allows(from, subject string) bool { + if r.WhitelistInEnabled && !MatchAddress(r.WhitelistIn, from) { + return false + } + if r.SubjectRegex != nil && !r.SubjectRegex.MatchString(subject) { + return false + } + return true +} diff --git a/internal/policy/inbound_test.go b/internal/policy/inbound_test.go new file mode 100644 index 0000000..954baa8 --- /dev/null +++ b/internal/policy/inbound_test.go @@ -0,0 +1,43 @@ +package policy + +import "testing" + +func TestInboundAllows(t *testing.T) { + re, err := CompileSubject("(?i)invoice") + if err != nil { + t.Fatalf("compile: %v", err) + } + rule := InboundRule{ + WhitelistInEnabled: true, + WhitelistIn: []string{"@trusted.com"}, + SubjectRegex: re, + } + cases := []struct { + from, subject string + want bool + }{ + {"bob@trusted.com", "Your Invoice #5", true}, + {"bob@trusted.com", "lunch?", false}, // subject fails + {"eve@evil.com", "Invoice", false}, // sender fails + {"eve@evil.com", "lunch?", false}, // both fail + } + for _, c := range cases { + if got := rule.Allows(c.from, c.subject); got != c.want { + t.Fatalf("Allows(%q,%q)=%v want %v", c.from, c.subject, got, c.want) + } + } +} + +func TestInboundNoFiltersAllowsAll(t *testing.T) { + rule := InboundRule{} // whitelist disabled, no regex + if !rule.Allows("anyone@anywhere.com", "anything") { + t.Fatal("empty rule must allow everything") + } +} + +func TestCompileSubjectEmpty(t *testing.T) { + re, err := CompileSubject("") + if err != nil || re != nil { + t.Fatalf("empty pattern: re=%v err=%v", re, err) + } +}