feat(policy): case-insensitive address and domain matching

This commit is contained in:
2026-06-21 23:47:39 +01:00
parent 5fb022bbaf
commit 4d6ac3e7c6
2 changed files with 74 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
package policy
import "testing"
func TestMatchAddress(t *testing.T) {
wl := []string{"bob@example.com", "@trusted.com"}
cases := []struct {
addr string
want bool
}{
{"bob@example.com", true},
{"BOB@Example.com", true},
{`"Bob" <bob@example.com>`, true},
{"alice@trusted.com", true},
{"alice@untrusted.com", false},
{"eve@example.com", false},
{"", false},
}
for _, c := range cases {
if got := MatchAddress(wl, c.addr); got != c.want {
t.Fatalf("MatchAddress(%q)=%v want %v", c.addr, got, c.want)
}
}
}
func TestNormalizeAddr(t *testing.T) {
if got := NormalizeAddr(`"Bob Smith" <Bob@Example.COM>`); got != "bob@example.com" {
t.Fatalf("got %q", got)
}
}
+44
View File
@@ -0,0 +1,44 @@
// Package policy holds pure enforcement functions: address matching,
// inbound filtering, and (in a later phase) outbound checks.
package policy
import (
"net/mail"
"strings"
)
// NormalizeAddr lower-cases an address and strips any display name/brackets.
func NormalizeAddr(addr string) string {
addr = strings.TrimSpace(addr)
if a, err := mail.ParseAddress(addr); err == nil {
return strings.ToLower(a.Address)
}
return strings.ToLower(strings.Trim(addr, "<> "))
}
// MatchAddress reports whether addr matches any whitelist entry.
// Entries beginning with '@' match a whole domain; others match exactly.
func MatchAddress(entries []string, addr string) bool {
norm := NormalizeAddr(addr)
if norm == "" {
return false
}
at := strings.LastIndex(norm, "@")
domain := ""
if at >= 0 {
domain = norm[at:] // includes '@'
}
for _, e := range entries {
e = strings.ToLower(strings.TrimSpace(e))
if strings.HasPrefix(e, "@") {
if e == domain {
return true
}
continue
}
if e == norm {
return true
}
}
return false
}