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.
type StoreManager struct {
AddrPolicy *policy.Addressing
Store storage.Store
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.
func (s *StoreManager) MailboxForAddress(mailbox string) (string, error) {
return policy.ParseMailboxName(mailbox)
return s.AddrPolicy.ExtractMailbox(mailbox)
}
// makeMetadata populates Metadata from a storage.Message.

View File

@@ -15,13 +15,18 @@ type Addressing struct {
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.
func (a *Addressing) NewRecipient(address string) (*Recipient, error) {
local, domain, err := ParseEmailAddress(address)
if err != nil {
return nil, err
}
mailbox, err := ParseMailboxName(local)
mailbox, err := a.ExtractMailbox(local)
if err != nil {
return nil, err
}
@@ -66,35 +71,6 @@ func (a *Addressing) ShouldStoreDomain(domain string) bool {
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.
// An error is returned if the local or domain parts fail validation following the guidelines
// in RFC3696.
@@ -263,3 +239,32 @@ func ValidateDomainPart(domain string) bool {
}
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 {
input string
expect string
@@ -139,7 +141,7 @@ func TestParseMailboxName(t *testing.T) {
{"chars|}~", "chars|}~"},
}
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)
} else {
if result != tt.expect {
@@ -157,7 +159,7 @@ func TestParseMailboxName(t *testing.T) {
{"first\nlast", "Control chars not permitted"},
}
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)
}
}

View File

@@ -10,7 +10,6 @@ import (
"time"
"github.com/jhillyerd/inbucket/pkg/config"
"github.com/jhillyerd/inbucket/pkg/policy"
"github.com/jhillyerd/inbucket/pkg/storage"
"github.com/jhillyerd/inbucket/pkg/stringutil"
"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.
func (fs *Store) AddMessage(m storage.Message) (id string, err error) {
mb, err := fs.mbox(m.Mailbox())
if err != nil {
return "", err
}
mb := fs.mbox(m.Mailbox())
mb.Lock()
defer mb.Unlock()
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.
func (fs *Store) GetMessage(mailbox, id string) (storage.Message, error) {
mb, err := fs.mbox(mailbox)
if err != nil {
return nil, err
}
mb := fs.mbox(mailbox)
mb.RLock()
defer mb.RUnlock()
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.
func (fs *Store) GetMessages(mailbox string) ([]storage.Message, error) {
mb, err := fs.mbox(mailbox)
if err != nil {
return nil, err
}
mb := fs.mbox(mailbox)
mb.RLock()
defer mb.RUnlock()
return mb.getMessages()
@@ -149,10 +139,7 @@ func (fs *Store) GetMessages(mailbox string) ([]storage.Message, error) {
// MarkSeen flags the message as having been read.
func (fs *Store) MarkSeen(mailbox, id string) error {
mb, err := fs.mbox(mailbox)
if err != nil {
return err
}
mb := fs.mbox(mailbox)
mb.Lock()
defer mb.Unlock()
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.
func (fs *Store) RemoveMessage(mailbox, id string) error {
mb, err := fs.mbox(mailbox)
if err != nil {
return err
}
mb := fs.mbox(mailbox)
mb.Lock()
defer mb.Unlock()
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.
func (fs *Store) PurgeMessages(mailbox string) error {
mb, err := fs.mbox(mailbox)
if err != nil {
return err
}
mb := fs.mbox(mailbox)
mb.Lock()
defer mb.Unlock()
return mb.purge()
@@ -241,12 +222,8 @@ func (fs *Store) VisitMailboxes(f func([]storage.Message) (cont bool)) error {
}
// mbox returns the named mailbox.
func (fs *Store) mbox(mailbox string) (*mbox, error) {
name, err := policy.ParseMailboxName(mailbox)
if err != nil {
return nil, err
}
hash := stringutil.HashMailboxName(name)
func (fs *Store) mbox(mailbox string) *mbox {
hash := stringutil.HashMailboxName(mailbox)
s1 := hash[0:3]
s2 := hash[0:6]
path := filepath.Join(fs.mailPath, s1, s2, hash)
@@ -254,11 +231,11 @@ func (fs *Store) mbox(mailbox string) (*mbox, error) {
return &mbox{
RWMutex: fs.hashLock.Get(hash),
store: fs,
name: name,
name: mailbox,
dirName: hash,
path: path,
indexPath: indexPath,
}, nil
}
}
// mboxFromPath constructs a mailbox based on name hash.

View File

@@ -3,6 +3,7 @@ package test
import (
"errors"
"github.com/jhillyerd/inbucket/pkg/config"
"github.com/jhillyerd/inbucket/pkg/message"
"github.com/jhillyerd/inbucket/pkg/policy"
"github.com/jhillyerd/inbucket/pkg/storage"
@@ -55,7 +56,8 @@ func (m *ManagerStub) GetMetadata(mailbox string) ([]*message.Metadata, error) {
// MailboxForAddress invokes policy.ParseMailboxName.
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.