Files
archivmail/internal/api/mail_ownership.go
T

72 lines
1.6 KiB
Go

package api
import (
"context"
"net/mail"
"strings"
"archivmail/pkg/mailparser"
)
// emailsFromHeader is fail-closed: an unparseable (possibly attacker-crafted)
// header yields no addresses and therefore grants no access.
func emailsFromHeader(header string) []string {
addrs, err := mail.ParseAddressList(header)
if err != nil {
return nil
}
out := make([]string, len(addrs))
for i, a := range addrs {
out[i] = strings.ToLower(a.Address)
}
return out
}
// sourceType must be the stored emails.source_type of the mail: delivery
// headers only count for trusted pull sources (see mailparser).
func mailBelongsToUser(pm *mailparser.ParsedMail, userEmail, sourceType string) bool {
target := strings.ToLower(strings.TrimSpace(userEmail))
if pm == nil || target == "" {
return false
}
checkHeader := func(header string) bool {
for _, addr := range emailsFromHeader(header) {
if addr == target {
return true
}
}
return false
}
if checkHeader(pm.From) {
return true
}
for _, to := range pm.To {
if checkHeader(to) {
return true
}
}
for _, cc := range pm.CC {
if checkHeader(cc) {
return true
}
}
for _, addr := range mailparser.TrustedDeliveryAddresses(pm, sourceType) {
if addr == target {
return true
}
}
return false
}
// A failed source lookup only drops the delivery-header path; From/To/CC
// still apply, so the result can never be more permissive than before.
func (s *Server) userOwnsMail(ctx context.Context, id string, pm *mailparser.ParsedMail, userEmail string) bool {
src, err := s.store.GetSourceType(ctx, id)
if err != nil {
src = ""
}
return mailBelongsToUser(pm, userEmail, src)
}