236 lines
4.7 KiB
Go
236 lines
4.7 KiB
Go
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"
|
|
)
|
|
|
|
func (s *Server) connectIMAP() (*imapclient.Client, error) {
|
|
var c *imapclient.Client
|
|
var err error
|
|
if s.imap.tls {
|
|
c, err = imapclient.DialTLS(s.imap.host, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
c, err = imapclient.Dial(s.imap.host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !s.imap.insecure {
|
|
if err := c.StartTLS(nil); err != nil {
|
|
c.Close()
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return c, err
|
|
}
|
|
|
|
func listMailboxes(conn *imapclient.Client) ([]*imap.MailboxInfo, error) {
|
|
ch := make(chan *imap.MailboxInfo, 10)
|
|
done := make(chan error, 1)
|
|
go func () {
|
|
done <- conn.List("", "*", ch)
|
|
}()
|
|
|
|
var mailboxes []*imap.MailboxInfo
|
|
for mbox := range ch {
|
|
mailboxes = append(mailboxes, mbox)
|
|
}
|
|
|
|
if err := <-done; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sort.Slice(mailboxes, func(i, j int) bool {
|
|
return mailboxes[i].Name < mailboxes[j].Name
|
|
})
|
|
return mailboxes, nil
|
|
}
|
|
|
|
func ensureMailboxSelected(conn *imapclient.Client, mboxName string) error {
|
|
mbox := conn.Mailbox()
|
|
if mbox == nil || mbox.Name != mboxName {
|
|
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 {
|
|
from = mbox.Messages - n
|
|
}
|
|
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, fetch, ch)
|
|
}()
|
|
|
|
msgs := make([]imapMessage, 0, n)
|
|
for msg := range ch {
|
|
msgs = append(msgs, imapMessage{msg})
|
|
}
|
|
|
|
if err := <-done; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Reverse list of messages
|
|
for i := len(msgs)/2-1; i >= 0; i-- {
|
|
opp := len(msgs)-1-i
|
|
msgs[i], msgs[opp] = msgs[opp], msgs[i]
|
|
}
|
|
|
|
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
|
|
}
|