From 25c63d05302fef11f79c30270a6e911da9010a38 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 2 Dec 2019 19:53:09 +0100 Subject: [PATCH] Add basic message view --- go.mod | 1 + go.sum | 2 + imap.go | 155 +++++++++++++++++++++++++++++++++++++++++--- public/mailbox.html | 6 +- server.go | 56 ++++++++++++++++ 5 files changed, 211 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 07d3dac..669cb67 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,6 @@ go 1.13 require ( github.com/emersion/go-imap v1.0.1 + github.com/emersion/go-message v0.10.4-0.20190609165112-592ace5bc1ca github.com/labstack/echo/v4 v4.1.11 ) diff --git a/go.sum b/go.sum index bd3e888..0e963f9 100644 --- a/go.sum +++ b/go.sum @@ -3,9 +3,11 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/emersion/go-imap v1.0.1 h1:J3duplefIrglQtE63hCGYdGLgMjYWqHvkUUEbimbXY8= github.com/emersion/go-imap v1.0.1/go.mod h1:MEiDDwwQFcZ+L45Pa68jNGv0qU9kbW+SJzwDpvSfX1s= +github.com/emersion/go-message v0.10.4-0.20190609165112-592ace5bc1ca h1:OYhqtJI4eOLvGtRIsUfP87VMJ1J/o6ks1tah9DlYkn4= github.com/emersion/go-message v0.10.4-0.20190609165112-592ace5bc1ca/go.mod h1:3h+HsGTCFHmk4ngJ2IV/YPhdlaOcR6hcgqM3yca9v7c= github.com/emersion/go-sasl v0.0.0-20190520160400-47d427600317 h1:tYZxAY8nu3JJQKios9f27Sbvbkfm4XHXT476gVtszu0= github.com/emersion/go-sasl v0.0.0-20190520160400-47d427600317/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k= +github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg= github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww= diff --git a/imap.go b/imap.go index 869d457..3da0447 100644 --- a/imap.go +++ b/imap.go @@ -1,9 +1,16 @@ package koushin import ( + "bufio" + "fmt" + "io/ioutil" "sort" + "strconv" + "strings" "github.com/emersion/go-imap" + "github.com/emersion/go-message" + "github.com/emersion/go-message/textproto" imapclient "github.com/emersion/go-imap/client" ) @@ -53,17 +60,91 @@ func listMailboxes(conn *imapclient.Client) ([]*imap.MailboxInfo, error) { return mailboxes, nil } -func listMessages(conn *imapclient.Client, mboxName string) ([]*imap.Message, error) { +func ensureMailboxSelected(conn *imapclient.Client, mboxName string) error { mbox := conn.Mailbox() if mbox == nil || mbox.Name != mboxName { - var err error - mbox, err = conn.Select(mboxName, false) - if err != nil { - return nil, err + if _, err := conn.Select(mboxName, false); err != nil { + return err + } + } + return nil +} + +type imapMessage struct { + *imap.Message +} + +func textPartPath(bs *imap.BodyStructure) ([]int, bool) { + if bs.Disposition != "" && !strings.EqualFold(bs.Disposition, "inline") { + return nil, false + } + + if strings.EqualFold(bs.MIMEType, "text") { + return []int{1}, true + } + + if !strings.EqualFold(bs.MIMEType, "multipart") { + return nil, false + } + + textPartNum := -1 + for i, part := range bs.Parts { + num := i + 1 + + if strings.EqualFold(part.MIMEType, "multipart") { + if subpath, ok := textPartPath(part); ok { + return append([]int{num}, subpath...), true + } + } + if !strings.EqualFold(part.MIMEType, "text") { + continue + } + + var pick bool + switch strings.ToLower(part.MIMESubType) { + case "plain": + pick = true + case "html": + pick = textPartNum < 0 + } + + if pick { + textPartNum = num } } + if textPartNum > 0 { + return []int{textPartNum}, true + } + return nil, false +} + +func (msg *imapMessage) TextPartName() string { + if msg.BodyStructure == nil { + return "" + } + + path, ok := textPartPath(msg.BodyStructure) + if !ok { + return "" + } + + l := make([]string, len(path)) + for i, partNum := range path { + l[i] = strconv.Itoa(partNum) + } + + return strings.Join(l, ".") +} + +func listMessages(conn *imapclient.Client, mboxName string) ([]imapMessage, error) { + if err := ensureMailboxSelected(conn, mboxName); err != nil { + return nil, err + } + n := uint32(10) + + mbox := conn.Mailbox() from := uint32(1) to := mbox.Messages if mbox.Messages > n { @@ -72,15 +153,17 @@ func listMessages(conn *imapclient.Client, mboxName string) ([]*imap.Message, er seqSet := new(imap.SeqSet) seqSet.AddRange(from, to) + fetch := []imap.FetchItem{imap.FetchEnvelope, imap.FetchUid, imap.FetchBodyStructure} + ch := make(chan *imap.Message, 10) done := make(chan error, 1) go func() { - done <- conn.Fetch(seqSet, []imap.FetchItem{imap.FetchEnvelope}, ch) + done <- conn.Fetch(seqSet, fetch, ch) }() - msgs := make([]*imap.Message, 0, n) + msgs := make([]imapMessage, 0, n) for msg := range ch { - msgs = append(msgs, msg) + msgs = append(msgs, imapMessage{msg}) } if err := <-done; err != nil { @@ -95,3 +178,59 @@ func listMessages(conn *imapclient.Client, mboxName string) ([]*imap.Message, er return msgs, nil } + +var _ = message.Read + +func getMessage(conn *imapclient.Client, mboxName string, uid uint32, partPath []int) (*imap.Message, string, error) { + if err := ensureMailboxSelected(conn, mboxName); err != nil { + return nil, "", err + } + + seqSet := new(imap.SeqSet) + seqSet.AddNum(uid) + + var textHeaderSection imap.BodySectionName + textHeaderSection.Peek = true + textHeaderSection.Specifier = imap.HeaderSpecifier + textHeaderSection.Path = partPath + + var textBodySection imap.BodySectionName + textBodySection.Peek = true + textBodySection.Path = partPath + + fetch := []imap.FetchItem{ + imap.FetchEnvelope, + imap.FetchUid, + imap.FetchBodyStructure, + textHeaderSection.FetchItem(), + textBodySection.FetchItem(), + } + + ch := make(chan *imap.Message, 1) + if err := conn.UidFetch(seqSet, fetch, ch); err != nil { + return nil, "", err + } + + msg := <-ch + if msg == nil { + return nil, "", fmt.Errorf("server didn't return message") + } + + headerReader := bufio.NewReader(msg.GetBody(&textHeaderSection)) + h, err := textproto.ReadHeader(headerReader) + if err != nil { + return nil, "", err + } + + text, err := message.New(message.Header{h}, msg.GetBody(&textBodySection)) + if err != nil { + return nil, "", err + } + + b, err := ioutil.ReadAll(text.Body) + if err != nil { + return nil, "", err + } + + return msg, string(b), nil +} diff --git a/public/mailbox.html b/public/mailbox.html index efae0d9..282f462 100644 --- a/public/mailbox.html +++ b/public/mailbox.html @@ -2,6 +2,8 @@

koushin

+

{{.Mailbox.Name}}

+

Mailboxes: