1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 09:37:02 +00:00

policy: Tie mailbox extraction to Addressing for #33

This commit is contained in:
James Hillyerd
2018-04-02 20:15:50 -07:00
parent c2e1d58b90
commit 939ff19991
5 changed files with 57 additions and 70 deletions

View File

@@ -35,6 +35,7 @@ type Manager interface {
// StoreManager is a message Manager backed by the storage.Store. // StoreManager is a message Manager backed by the storage.Store.
type StoreManager struct { type StoreManager struct {
AddrPolicy *policy.Addressing
Store storage.Store Store storage.Store
Hub *msghub.Hub Hub *msghub.Hub
} }
@@ -154,7 +155,7 @@ func (s *StoreManager) SourceReader(mailbox, id string) (io.ReadCloser, error) {
// MailboxForAddress parses an email address to return the canonical mailbox name. // MailboxForAddress parses an email address to return the canonical mailbox name.
func (s *StoreManager) MailboxForAddress(mailbox string) (string, error) { func (s *StoreManager) MailboxForAddress(mailbox string) (string, error) {
return policy.ParseMailboxName(mailbox) return s.AddrPolicy.ExtractMailbox(mailbox)
} }
// makeMetadata populates Metadata from a storage.Message. // makeMetadata populates Metadata from a storage.Message.

View File

@@ -15,13 +15,18 @@ type Addressing struct {
Config *config.Root Config *config.Root
} }
// ExtractMailbox extracts the mailbox name from a partial email address.
func (a *Addressing) ExtractMailbox(address string) (string, error) {
return parseMailboxName(address)
}
// NewRecipient parses an address into a Recipient. // NewRecipient parses an address into a Recipient.
func (a *Addressing) NewRecipient(address string) (*Recipient, error) { func (a *Addressing) NewRecipient(address string) (*Recipient, error) {
local, domain, err := ParseEmailAddress(address) local, domain, err := ParseEmailAddress(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
mailbox, err := ParseMailboxName(local) mailbox, err := a.ExtractMailbox(local)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -66,35 +71,6 @@ func (a *Addressing) ShouldStoreDomain(domain string) bool {
return false return false
} }
// ParseMailboxName takes a localPart string (ex: "user+ext" without "@domain")
// and returns just the mailbox name (ex: "user"). Returns an error if
// localPart contains invalid characters; it won't accept any that must be
// quoted according to RFC3696.
func ParseMailboxName(localPart string) (result string, err error) {
if localPart == "" {
return "", fmt.Errorf("Mailbox name cannot be empty")
}
result = strings.ToLower(localPart)
invalid := make([]byte, 0, 10)
for i := 0; i < len(result); i++ {
c := result[i]
switch {
case 'a' <= c && c <= 'z':
case '0' <= c && c <= '9':
case bytes.IndexByte([]byte("!#$%&'*+-=/?^_`.{|}~"), c) >= 0:
default:
invalid = append(invalid, c)
}
}
if len(invalid) > 0 {
return "", fmt.Errorf("Mailbox name contained invalid character(s): %q", invalid)
}
if idx := strings.Index(result, "+"); idx > -1 {
result = result[0:idx]
}
return result, nil
}
// ParseEmailAddress unescapes an email address, and splits the local part from the domain part. // ParseEmailAddress unescapes an email address, and splits the local part from the domain part.
// An error is returned if the local or domain parts fail validation following the guidelines // An error is returned if the local or domain parts fail validation following the guidelines
// in RFC3696. // in RFC3696.
@@ -263,3 +239,32 @@ func ValidateDomainPart(domain string) bool {
} }
return true return true
} }
// ParseMailboxName takes a localPart string (ex: "user+ext" without "@domain")
// and returns just the mailbox name (ex: "user"). Returns an error if
// localPart contains invalid characters; it won't accept any that must be
// quoted according to RFC3696.
func parseMailboxName(localPart string) (result string, err error) {
if localPart == "" {
return "", fmt.Errorf("Mailbox name cannot be empty")
}
result = strings.ToLower(localPart)
invalid := make([]byte, 0, 10)
for i := 0; i < len(result); i++ {
c := result[i]
switch {
case 'a' <= c && c <= 'z':
case '0' <= c && c <= '9':
case bytes.IndexByte([]byte("!#$%&'*+-=/?^_`.{|}~"), c) >= 0:
default:
invalid = append(invalid, c)
}
}
if len(invalid) > 0 {
return "", fmt.Errorf("Mailbox name contained invalid character(s): %q", invalid)
}
if idx := strings.Index(result, "+"); idx > -1 {
result = result[0:idx]
}
return result, nil
}

View File

@@ -122,7 +122,9 @@ func TestShouldStoreDomain(t *testing.T) {
} }
} }
func TestParseMailboxName(t *testing.T) { func TestExtractMailbox(t *testing.T) {
addrPolicy := policy.Addressing{Config: &config.Root{}}
var validTable = []struct { var validTable = []struct {
input string input string
expect string expect string
@@ -139,7 +141,7 @@ func TestParseMailboxName(t *testing.T) {
{"chars|}~", "chars|}~"}, {"chars|}~", "chars|}~"},
} }
for _, tt := range validTable { for _, tt := range validTable {
if result, err := policy.ParseMailboxName(tt.input); err != nil { if result, err := addrPolicy.ExtractMailbox(tt.input); err != nil {
t.Errorf("Error while parsing %q: %v", tt.input, err) t.Errorf("Error while parsing %q: %v", tt.input, err)
} else { } else {
if result != tt.expect { if result != tt.expect {
@@ -157,7 +159,7 @@ func TestParseMailboxName(t *testing.T) {
{"first\nlast", "Control chars not permitted"}, {"first\nlast", "Control chars not permitted"},
} }
for _, tt := range invalidTable { for _, tt := range invalidTable {
if _, err := policy.ParseMailboxName(tt.input); err == nil { if _, err := addrPolicy.ExtractMailbox(tt.input); err == nil {
t.Errorf("Didn't get an error while parsing %q: %v", tt.input, tt.msg) t.Errorf("Didn't get an error while parsing %q: %v", tt.input, tt.msg)
} }
} }

View File

@@ -10,7 +10,6 @@ import (
"time" "time"
"github.com/jhillyerd/inbucket/pkg/config" "github.com/jhillyerd/inbucket/pkg/config"
"github.com/jhillyerd/inbucket/pkg/policy"
"github.com/jhillyerd/inbucket/pkg/storage" "github.com/jhillyerd/inbucket/pkg/storage"
"github.com/jhillyerd/inbucket/pkg/stringutil" "github.com/jhillyerd/inbucket/pkg/stringutil"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@@ -66,10 +65,7 @@ func New(cfg config.Storage) (storage.Store, error) {
// AddMessage adds a message to the specified mailbox. // AddMessage adds a message to the specified mailbox.
func (fs *Store) AddMessage(m storage.Message) (id string, err error) { func (fs *Store) AddMessage(m storage.Message) (id string, err error) {
mb, err := fs.mbox(m.Mailbox()) mb := fs.mbox(m.Mailbox())
if err != nil {
return "", err
}
mb.Lock() mb.Lock()
defer mb.Unlock() defer mb.Unlock()
r, err := m.Source() r, err := m.Source()
@@ -127,10 +123,7 @@ func (fs *Store) AddMessage(m storage.Message) (id string, err error) {
// GetMessage returns the messages in the named mailbox, or an error. // GetMessage returns the messages in the named mailbox, or an error.
func (fs *Store) GetMessage(mailbox, id string) (storage.Message, error) { func (fs *Store) GetMessage(mailbox, id string) (storage.Message, error) {
mb, err := fs.mbox(mailbox) mb := fs.mbox(mailbox)
if err != nil {
return nil, err
}
mb.RLock() mb.RLock()
defer mb.RUnlock() defer mb.RUnlock()
return mb.getMessage(id) return mb.getMessage(id)
@@ -138,10 +131,7 @@ func (fs *Store) GetMessage(mailbox, id string) (storage.Message, error) {
// GetMessages returns the messages in the named mailbox, or an error. // GetMessages returns the messages in the named mailbox, or an error.
func (fs *Store) GetMessages(mailbox string) ([]storage.Message, error) { func (fs *Store) GetMessages(mailbox string) ([]storage.Message, error) {
mb, err := fs.mbox(mailbox) mb := fs.mbox(mailbox)
if err != nil {
return nil, err
}
mb.RLock() mb.RLock()
defer mb.RUnlock() defer mb.RUnlock()
return mb.getMessages() return mb.getMessages()
@@ -149,10 +139,7 @@ func (fs *Store) GetMessages(mailbox string) ([]storage.Message, error) {
// MarkSeen flags the message as having been read. // MarkSeen flags the message as having been read.
func (fs *Store) MarkSeen(mailbox, id string) error { func (fs *Store) MarkSeen(mailbox, id string) error {
mb, err := fs.mbox(mailbox) mb := fs.mbox(mailbox)
if err != nil {
return err
}
mb.Lock() mb.Lock()
defer mb.Unlock() defer mb.Unlock()
if !mb.indexLoaded { if !mb.indexLoaded {
@@ -175,10 +162,7 @@ func (fs *Store) MarkSeen(mailbox, id string) error {
// RemoveMessage deletes a message by ID from the specified mailbox. // RemoveMessage deletes a message by ID from the specified mailbox.
func (fs *Store) RemoveMessage(mailbox, id string) error { func (fs *Store) RemoveMessage(mailbox, id string) error {
mb, err := fs.mbox(mailbox) mb := fs.mbox(mailbox)
if err != nil {
return err
}
mb.Lock() mb.Lock()
defer mb.Unlock() defer mb.Unlock()
return mb.removeMessage(id) return mb.removeMessage(id)
@@ -186,10 +170,7 @@ func (fs *Store) RemoveMessage(mailbox, id string) error {
// PurgeMessages deletes all messages in the named mailbox, or returns an error. // PurgeMessages deletes all messages in the named mailbox, or returns an error.
func (fs *Store) PurgeMessages(mailbox string) error { func (fs *Store) PurgeMessages(mailbox string) error {
mb, err := fs.mbox(mailbox) mb := fs.mbox(mailbox)
if err != nil {
return err
}
mb.Lock() mb.Lock()
defer mb.Unlock() defer mb.Unlock()
return mb.purge() return mb.purge()
@@ -241,12 +222,8 @@ func (fs *Store) VisitMailboxes(f func([]storage.Message) (cont bool)) error {
} }
// mbox returns the named mailbox. // mbox returns the named mailbox.
func (fs *Store) mbox(mailbox string) (*mbox, error) { func (fs *Store) mbox(mailbox string) *mbox {
name, err := policy.ParseMailboxName(mailbox) hash := stringutil.HashMailboxName(mailbox)
if err != nil {
return nil, err
}
hash := stringutil.HashMailboxName(name)
s1 := hash[0:3] s1 := hash[0:3]
s2 := hash[0:6] s2 := hash[0:6]
path := filepath.Join(fs.mailPath, s1, s2, hash) path := filepath.Join(fs.mailPath, s1, s2, hash)
@@ -254,11 +231,11 @@ func (fs *Store) mbox(mailbox string) (*mbox, error) {
return &mbox{ return &mbox{
RWMutex: fs.hashLock.Get(hash), RWMutex: fs.hashLock.Get(hash),
store: fs, store: fs,
name: name, name: mailbox,
dirName: hash, dirName: hash,
path: path, path: path,
indexPath: indexPath, indexPath: indexPath,
}, nil }
} }
// mboxFromPath constructs a mailbox based on name hash. // mboxFromPath constructs a mailbox based on name hash.

View File

@@ -3,6 +3,7 @@ package test
import ( import (
"errors" "errors"
"github.com/jhillyerd/inbucket/pkg/config"
"github.com/jhillyerd/inbucket/pkg/message" "github.com/jhillyerd/inbucket/pkg/message"
"github.com/jhillyerd/inbucket/pkg/policy" "github.com/jhillyerd/inbucket/pkg/policy"
"github.com/jhillyerd/inbucket/pkg/storage" "github.com/jhillyerd/inbucket/pkg/storage"
@@ -55,7 +56,8 @@ func (m *ManagerStub) GetMetadata(mailbox string) ([]*message.Metadata, error) {
// MailboxForAddress invokes policy.ParseMailboxName. // MailboxForAddress invokes policy.ParseMailboxName.
func (m *ManagerStub) MailboxForAddress(address string) (string, error) { func (m *ManagerStub) MailboxForAddress(address string) (string, error) {
return policy.ParseMailboxName(address) addrPolicy := &policy.Addressing{Config: &config.Root{}}
return addrPolicy.ExtractMailbox(address)
} }
// MarkSeen marks a message as having been read. // MarkSeen marks a message as having been read.