Correctly handle LISTMESSAGES and STATUS commands using SeqNum instead of UID

This commit is contained in:
2018-06-28 00:00:44 +01:00
parent 22f6eeacd7
commit 3e5ab5bb0a
5 changed files with 106 additions and 23 deletions

View File

@@ -45,15 +45,9 @@ func (m *Mailbox) Info() (*imap.MailboxInfo, error) {
// This function does not affect the state of any messages in the mailbox. See // This function does not affect the state of any messages in the mailbox. See
// RFC 3501 section 6.3.10 for a list of items that can be requested. // RFC 3501 section 6.3.10 for a list of items that can be requested.
func (m *Mailbox) Status(items []string) (*imap.MailboxStatus, error) { func (m *Mailbox) Status(items []string) (*imap.MailboxStatus, error) {
log.Printf("Mailbox(%v).Status(%#v)", m.stored.Rel(), items) status, err := m.buildStatus(items...)
log.Printf("Mailbox(%v).Status(%#v): %#v %#v", m.stored.Rel(), items, status, err)
return &imap.MailboxStatus{ return status, err
Name: m.Name(),
Flags: []string{}, // FIXME: what do we need here?
PermanentFlags: []string{}, // FIXME: what do we need here?
//UnseenSeqNum: 0, // FIXME: what do we need here? What even is a seqnum?
Messages: 1, // FIXME: hardcoded
}, nil
} }
// SetSubscribed adds or removes the mailbox to the server's set of "active" // SetSubscribed adds or removes the mailbox to the server's set of "active"
@@ -85,7 +79,7 @@ func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch
var messages []store.Message var messages []store.Message
for _, set := range seqset.Set { for _, set := range seqset.Set {
msgs, err := m.session.store.FindMessagesInRange( msgs, err := m.session.store.FindMessagesInRangeBySeqNum(
m.stored, uint64(set.Start), uint64(set.Stop), m.stored, uint64(set.Start), uint64(set.Stop),
) )
@@ -119,14 +113,13 @@ func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch
body := map[*imap.BodySectionName]imap.Literal{} body := map[*imap.BodySectionName]imap.Literal{}
imapMsg := imap.NewMessage(uint32(message.UID), items) imapMsg := imap.NewMessage(uint32(message.SeqNum), items)
if err := fillItems(imapMsg, message); err != nil { if err := m.fillMessageItems(imapMsg, message); err != nil {
return err return err
} }
imapMsg.Envelope = envelope imapMsg.Envelope = envelope
imapMsg.Body = body imapMsg.Body = body
imapMsg.Uid = uint32(message.UID)
imapMsg.Size = uint32(message.Len()) imapMsg.Size = uint32(message.Len())
log.Printf(" Sending message %#v", imapMsg) log.Printf(" Sending message %#v", imapMsg)
@@ -137,14 +130,50 @@ func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch
return nil return nil
} }
func fillItems(imapMsg *imap.Message, msg store.Message) error { func (m *Mailbox) buildStatus(items ...string) (*imap.MailboxStatus, error) {
out := &imap.MailboxStatus{
Name: m.Name(),
}
for _, item := range items {
switch item {
case "FLAGS": // TODO: ???
out.Flags = []string{}
case "PERMANENTFLAGS": // TODO: ???
out.PermanentFlags = []string{}
// The number of messages in the mailbox.
case "MESSAGES":
out.Messages = uint32(m.session.store.CountMessages(m.stored))
// The number of messages with the \Recent flag set. TODO
case "RECENT":
// The next unique identifier value of the mailbox.
case "UIDNEXT":
out.UidNext = uint32(m.stored.NextUID())
// The unique identifier validity value of the mailbox
case "UIDVALIDITY":
out.UidValidity = uint32(m.stored.UIDValidity())
// The number of messages which do not have the \Seen flag set. TODO
case "UNSEEN":
default:
return nil, fmt.Errorf("Unknown status item: %v", item)
}
}
return out, nil
}
func (m *Mailbox) fillMessageItems(imapMsg *imap.Message, msg store.Message) error {
// FIXME: do I have to fill Items as well as the individual fields? Unclear
for k, _ := range imapMsg.Items { for k, _ := range imapMsg.Items {
switch k { switch k {
case "UID": case "UID":
imapMsg.Items[k] = msg.UID imapMsg.Uid = uint32(msg.UID)
imapMsg.Items[k] = uint32(msg.UID)
case "FLAGS": case "FLAGS":
imapMsg.Flags = []string{}
imapMsg.Items[k] = []string{} imapMsg.Items[k] = []string{}
case "RFC822.SIZE": case "RFC822.SIZE":
imapMsg.Size = uint32(msg.Len())
imapMsg.Items[k] = uint32(msg.Len()) imapMsg.Items[k] = uint32(msg.Len())
case "RFC822.HEADER": case "RFC822.HEADER":
str, err := msg.HeaderString() str, err := msg.HeaderString()
@@ -154,7 +183,7 @@ func fillItems(imapMsg *imap.Message, msg store.Message) error {
imapMsg.Items[k] = str imapMsg.Items[k] = str
default: default:
return fmt.Errorf("Unknown item: %v", k) return fmt.Errorf("Unknown message item: %v", k)
} }
} }

View File

@@ -7,8 +7,10 @@ import (
) )
type Entry struct { type Entry struct {
UID uint64 UID uint64
Extra map[string][]string SeqNum uint64
Extra map[string][]string
LastKnownFilename string LastKnownFilename string
} }

View File

@@ -83,6 +83,12 @@ func handleFile(filename string, flags int) (*List, error) {
}, nil }, nil
} }
func (l *List) NextUID() uint64 {
l.mutex.Lock()
defer l.mutex.Unlock()
return l.header.NextUID
}
func (l *List) UIDValidity() uint64 { func (l *List) UIDValidity() uint64 {
return l.header.UIDValidity return l.header.UIDValidity
} }
@@ -95,11 +101,35 @@ func (l *List) Entries() []Entry {
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()
// FIXME: use copy() ?
return l.entries return l.entries
} }
func (l *List) EntriesInRange(start, end uint64) []Entry { func (l *List) EntriesInRangeBySeqNum(start, end uint64) []Entry {
l.mutex.Lock()
defer l.mutex.Unlock()
// IMAP SeqNums are indexed from 1. So 0 should be invalid, right?
if start == 0 {
return nil
}
start--
if int(start) > len(l.entries) {
return nil
}
if end == 0 {
return l.entries[start:]
}
if int(end) > len(l.entries) {
end = uint64(len(l.entries))
}
return l.entries[start:end]
}
func (l *List) EntriesInRangeByUID(start, end uint64) []Entry {
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()
@@ -124,6 +154,13 @@ func (l *List) Entry(key string) *Entry {
return nil return nil
} }
func (l *List) Count() uint64 {
l.mutex.Lock()
defer l.mutex.Unlock()
return uint64(len(l.entries))
}
func (l *List) Append(filenames ...string) error { func (l *List) Append(filenames ...string) error {
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()

View File

@@ -16,7 +16,8 @@ type MaildirInterface interface {
CreateMaildir(*Maildir) error CreateMaildir(*Maildir) error
FindMaildir(account Account, name string) (Maildir, error) FindMaildir(account Account, name string) (Maildir, error)
FindMaildirs(account Account) ([]Maildir, error) FindMaildirs(account Account) ([]Maildir, error)
FindMessagesInRange(m Maildir, start, end uint64) ([]Message, error) FindMessagesInRangeBySeqNum(m Maildir, start, end uint64) ([]Message, error)
CountMessages(m Maildir) uint64
} }
type Maildir struct { type Maildir struct {
@@ -41,6 +42,14 @@ func (m *Maildir) Header(key string) (mail.Header, error) {
return m.filesystem.Header(key) return m.filesystem.Header(key)
} }
func (m *Maildir) UIDValidity() uint64 {
return m.uids.UIDValidity()
}
func (m *Maildir) NextUID() uint64 {
return m.uids.NextUID()
}
func (c *concrete) CreateMaildir(m *Maildir) error { func (c *concrete) CreateMaildir(m *Maildir) error {
if m.directory == "" { if m.directory == "" {
m.directory = c.buildMaildirPath(m.Account, m.Name) m.directory = c.buildMaildirPath(m.Account, m.Name)
@@ -130,13 +139,14 @@ func (c *concrete) buildMaildirPath(account Account, parts ...string) string {
return filepath.Join(bits...) return filepath.Join(bits...)
} }
func (c *concrete) FindMessagesInRange(m Maildir, start, end uint64) ([]Message, error) { func (c *concrete) FindMessagesInRangeBySeqNum(m Maildir, start, end uint64) ([]Message, error) {
var out []Message var out []Message
entries := m.uids.EntriesInRange(start, end) entries := m.uids.EntriesInRangeBySeqNum(start, end)
for _, entry := range entries { for _, entry := range entries {
msg := Message{ msg := Message{
UID: entry.UID, UID: entry.UID,
SeqNum: entry.SeqNum,
Maildir: m, Maildir: m,
Key: entry.Key(), Key: entry.Key(),
} }
@@ -146,3 +156,7 @@ func (c *concrete) FindMessagesInRange(m Maildir, start, end uint64) ([]Message,
return out, nil return out, nil
} }
func (c *concrete) CountMessages(m Maildir) uint64 {
return m.uids.Count()
}

View File

@@ -13,6 +13,7 @@ type Message struct {
Maildir Maildir // maildir.name Maildir Maildir // maildir.name
UID uint64 UID uint64
SeqNum uint64
Key string Key string
Data io.ReadCloser Data io.ReadCloser
hdrCache mail.Header hdrCache mail.Header