From 6b17c216a18dc62e189e69488b303f447e4854dd Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 28 Jun 2018 01:10:31 +0100 Subject: [PATCH] imap: ListMessages: handle body viewing, etc --- internal/imap/mailbox.go | 103 ++++++++++++++++++++------------------ internal/store/maildir.go | 10 ++++ internal/store/message.go | 16 +++--- 3 files changed, 74 insertions(+), 55 deletions(-) diff --git a/internal/imap/mailbox.go b/internal/imap/mailbox.go index ddf178a..c8cc107 100644 --- a/internal/imap/mailbox.go +++ b/internal/imap/mailbox.go @@ -6,6 +6,8 @@ import ( "time" "github.com/emersion/go-imap" + "github.com/emersion/go-imap/backend/backendutil" + "github.com/emersion/go-message" "ur.gs/crockery/internal/store" ) @@ -44,8 +46,8 @@ func (m *Mailbox) Info() (*imap.MailboxInfo, error) { // and UnseenSeqNum in the returned MailboxStatus must be always populated. // 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. -func (m *Mailbox) Status(items []string) (*imap.MailboxStatus, error) { - status, err := m.buildStatus(items...) +func (m *Mailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, error) { + status, err := m.buildStatus(items) log.Printf("Mailbox(%v).Status(%#v): %#v %#v", m.stored.Rel(), items, status, err) return status, err } @@ -73,7 +75,7 @@ func (m *Mailbox) Check() error { // 3501 section 6.4.5 for a list of items that can be requested. // // Messages must be sent to ch. When the function returns, ch must be closed. -func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch chan<- *imap.Message) error { +func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error { log.Printf("Mailbox(%v).ListMessages(%#v, %#v, %#v, ch)", m.stored.Rel(), uid, *seqset, items) defer close(ch) @@ -93,35 +95,11 @@ func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch log.Printf(" %v messages: %#v", len(messages), messages) for _, message := range messages { - header, err := message.Header() + imapMsg, err := m.buildMessage(message, items) if err != nil { return err } - envelope := &imap.Envelope{ - // Date: - Subject: header.Get("Subject"), - // From: - // Sender: - // ReplyTo: - // To: - // Cc: - // Bcc: - InReplyTo: header.Get("In-Reply-To"), - MessageId: header.Get("Message-Id"), - } - - body := map[*imap.BodySectionName]imap.Literal{} - - imapMsg := imap.NewMessage(uint32(message.SeqNum), items) - if err := m.fillMessageItems(imapMsg, message); err != nil { - return err - } - - imapMsg.Envelope = envelope - imapMsg.Body = body - imapMsg.Size = uint32(message.Len()) - log.Printf(" Sending message %#v", imapMsg) ch <- imapMsg log.Printf(" Sent message") @@ -130,7 +108,7 @@ func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch return nil } -func (m *Mailbox) buildStatus(items ...string) (*imap.MailboxStatus, error) { +func (m *Mailbox) buildStatus(items []imap.StatusItem) (*imap.MailboxStatus, error) { out := &imap.MailboxStatus{ Name: m.Name(), } @@ -162,32 +140,61 @@ func (m *Mailbox) buildStatus(items ...string) (*imap.MailboxStatus, error) { 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 { - switch k { - case "UID": - imapMsg.Uid = uint32(msg.UID) - imapMsg.Items[k] = uint32(msg.UID) - case "FLAGS": - imapMsg.Flags = []string{} - imapMsg.Items[k] = []string{} - case "RFC822.SIZE": - imapMsg.Size = uint32(msg.Len()) - imapMsg.Items[k] = uint32(msg.Len()) - case "RFC822.HEADER": - str, err := msg.HeaderString() +func (m *Mailbox) buildMessage(msg store.Message, items []imap.FetchItem) (*imap.Message, error) { + out := imap.NewMessage(uint32(msg.SeqNum), items) + + // FIXME: This might not be necessary on some calls, but is needed in 3 + // different cases below. Tidy it up. + data, err := m.stored.MessageData(msg) + if err != nil { + return nil, err + } + defer data.Close() + + entity, err := message.Read(data) + if err != nil { + return nil, err + } + + for _, item := range items { + switch item { + case imap.FetchEnvelope: + env, err := backendutil.FetchEnvelope(entity.Header) if err != nil { - return err + return nil, err } - imapMsg.Items[k] = str + out.Envelope = env + case imap.FetchBody, imap.FetchBodyStructure: + bs, err := backendutil.FetchBodyStructure(entity, item == imap.FetchBodyStructure) + if err != nil { + return nil, err + } + + out.BodyStructure = bs + case imap.FetchInternalDate: + case imap.FetchUid: + out.Uid = uint32(msg.UID) + case imap.FetchFlags: + out.Flags = []string{} + case imap.FetchRFC822Size: + out.Size = uint32(msg.Len()) default: - return fmt.Errorf("Unknown message item: %v", k) + section, err := imap.ParseBodySectionName(item) + if err != nil { + return nil, err + } + + l, err := backendutil.FetchBodySection(entity, section) + if err != nil { + return nil, err + } + + out.Body[section] = l } } - return nil + return out, nil } // SearchMessages searches messages. The returned list must contain UIDs if diff --git a/internal/store/maildir.go b/internal/store/maildir.go index 28b1948..c539613 100644 --- a/internal/store/maildir.go +++ b/internal/store/maildir.go @@ -2,6 +2,7 @@ package store import ( "errors" + "io" "net/mail" "os" "path/filepath" @@ -50,6 +51,15 @@ func (m *Maildir) NextUID() uint64 { return m.uids.NextUID() } +func (m *Maildir) MessageData(message Message) (io.ReadCloser, error) { + filename, err := m.filesystem.Filename(message.Key) + if err != nil { + return nil, err + } + + return os.Open(filename) +} + func (c *concrete) CreateMaildir(m *Maildir) error { if m.directory == "" { m.directory = c.buildMaildirPath(m.Account, m.Name) diff --git a/internal/store/message.go b/internal/store/message.go index 0530a0b..e999872 100644 --- a/internal/store/message.go +++ b/internal/store/message.go @@ -9,12 +9,18 @@ import ( "strings" ) +type MessageInterface interface { + CreateMessage(Message) error +} + type Message struct { Maildir Maildir // maildir.name - UID uint64 - SeqNum uint64 - Key string + UID uint64 + SeqNum uint64 + Key string + + // Data is usually nil, but may be an io.ReadCloser if SetupData is called Data io.ReadCloser hdrCache mail.Header } @@ -54,10 +60,6 @@ func (m *Message) HeaderString() (string, error) { return sb.String(), nil // FIXME } -type MessageInterface interface { - CreateMessage(Message) error -} - // DeliverMessage takes the given message and delivers it to the correct maildir // It does not call Close() on message.Data func (c *concrete) CreateMessage(message Message) error {