summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mattermost/rsc/imap/mail.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mattermost/rsc/imap/mail.go')
-rw-r--r--vendor/github.com/mattermost/rsc/imap/mail.go468
1 files changed, 468 insertions, 0 deletions
diff --git a/vendor/github.com/mattermost/rsc/imap/mail.go b/vendor/github.com/mattermost/rsc/imap/mail.go
new file mode 100644
index 000000000..365540f82
--- /dev/null
+++ b/vendor/github.com/mattermost/rsc/imap/mail.go
@@ -0,0 +1,468 @@
+package imap
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "regexp"
+ "sort"
+ "strings"
+ "time"
+)
+
+type Flags uint32
+
+const (
+ FlagJunk Flags = 1 << iota
+ FlagNonJunk
+ FlagReplied
+ FlagFlagged
+ FlagDeleted
+ FlagDraft
+ FlagRecent
+ FlagSeen
+ FlagNoInferiors
+ FlagNoSelect
+ FlagMarked
+ FlagUnMarked
+ FlagHasChildren
+ FlagHasNoChildren
+ FlagInbox // Gmail extension
+ FlagAllMail // Gmail extension
+ FlagDrafts // Gmail extension
+ FlagSent // Gmail extension
+ FlagSpam // Gmail extension
+ FlagStarred // Gmail extension
+ FlagTrash // Gmail extension
+ FlagImportant // Gmail extension
+)
+
+var flagNames = []string{
+ "Junk",
+ "NonJunk",
+ "\\Answered",
+ "\\Flagged",
+ "\\Deleted",
+ "\\Draft",
+ "\\Recent",
+ "\\Seen",
+ "\\NoInferiors",
+ "\\NoSelect",
+ "\\Marked",
+ "\\UnMarked",
+ "\\HasChildren",
+ "\\HasNoChildren",
+ "\\Inbox",
+ "\\AllMail",
+ "\\Drafts",
+ "\\Sent",
+ "\\Spam",
+ "\\Starred",
+ "\\Trash",
+ "\\Important",
+}
+
+// A Box represents an IMAP mailbox.
+type Box struct {
+ Name string // name of mailbox
+ Elem string // last element in name
+ Client *Client
+
+ parent *Box // parent in hierarchy
+ child []*Box // child boxes
+ dead bool // box no longer exists
+ inbox bool // box is inbox
+ flags Flags // allowed flags
+ permFlags Flags // client-modifiable permanent flags
+ readOnly bool // box is read-only
+
+ exists int // number of messages in box (according to server)
+ maxSeen int // maximum message number seen (for polling)
+ unseen int // number of first unseen message
+ validity uint32 // UID validity base number
+ load bool // if false, don't track full set of messages
+ firstNum int // 0 means box not loaded
+ msgByNum []*Msg
+ msgByUID map[uint64]*Msg
+}
+
+func (c *Client) Boxes() []*Box {
+ c.data.lock()
+ defer c.data.unlock()
+
+ box := make([]*Box, len(c.allBox))
+ copy(box, c.allBox)
+ return box
+}
+
+func (c *Client) Box(name string) *Box {
+ c.data.lock()
+ defer c.data.unlock()
+
+ return c.boxByName[name]
+}
+
+func (c *Client) Inbox() *Box {
+ c.data.lock()
+ defer c.data.unlock()
+
+ return c.inbox
+}
+
+func (c *Client) newBox(name, sep string, inbox bool) *Box {
+ c.data.mustBeLocked()
+ if b := c.boxByName[name]; b != nil {
+ return b
+ }
+
+ b := &Box{
+ Name: name,
+ Elem: name,
+ Client: c,
+ inbox: inbox,
+ }
+ if !inbox {
+ b.parent = c.rootBox
+ }
+ if !inbox && sep != "" && name != c.root {
+ if i := strings.LastIndex(name, sep); i >= 0 {
+ b.Elem = name[i+len(sep):]
+ b.parent = c.newBox(name[:i], sep, false)
+ }
+ }
+ c.allBox = append(c.allBox, b)
+ c.boxByName[name] = b
+ if b.parent != nil {
+ b.parent.child = append(b.parent.child, b)
+ }
+ return b
+}
+
+// A Msg represents an IMAP message.
+type Msg struct {
+ Box *Box // box containing message
+ Date time.Time // date
+ Flags Flags // message flags
+ Bytes int64 // size in bytes
+ Lines int64 // number of lines
+ Hdr *MsgHdr // MIME header
+ Root MsgPart // top-level message part
+ GmailID uint64 // Gmail message id
+ GmailThread uint64 // Gmail thread id
+ UID uint64 // unique id for this message
+
+ deleted bool
+ dead bool
+ num int // message number in box (changes)
+}
+
+// TODO: Return os.Error too
+
+type byUID []*Msg
+
+func (x byUID) Len() int { return len(x) }
+func (x byUID) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x byUID) Less(i, j int) bool { return x[i].UID < x[j].UID }
+
+func (b *Box) Msgs() []*Msg {
+ b.Client.data.lock()
+ defer b.Client.data.unlock()
+
+ msgs := make([]*Msg, len(b.msgByUID))
+ n := 0
+ for _, m := range b.msgByUID {
+ msgs[n] = m
+ n++
+ }
+ sort.Sort(byUID(msgs))
+ return msgs
+}
+
+func (b *Box) newMsg(uid uint64, id int) *Msg {
+ b.Client.data.mustBeLocked()
+ if m := b.msgByUID[uid]; m != nil {
+ return m
+ }
+ if b.msgByUID == nil {
+ b.msgByUID = map[uint64]*Msg{}
+ }
+ m := &Msg{
+ UID: uid,
+ Box: b,
+ num: id,
+ }
+ m.Root.Msg = m
+ if b.load {
+ if b.firstNum == 0 {
+ b.firstNum = id
+ }
+ if id < b.firstNum {
+ log.Printf("warning: unexpected id %d < %d", id, b.firstNum)
+ byNum := make([]*Msg, len(b.msgByNum)+b.firstNum-id)
+ copy(byNum[b.firstNum-id:], b.msgByNum)
+ b.msgByNum = byNum
+ b.firstNum = id
+ }
+ if id-b.firstNum < len(b.msgByNum) {
+ b.msgByNum[id-b.firstNum] = m
+ } else {
+ if id-b.firstNum > len(b.msgByNum) {
+ log.Printf("warning: unexpected id %d > %d", id, b.firstNum+len(b.msgByNum))
+ byNum := make([]*Msg, id-b.firstNum)
+ copy(byNum, b.msgByNum)
+ b.msgByNum = byNum
+ }
+ b.msgByNum = append(b.msgByNum, m)
+ }
+ }
+ b.msgByUID[uid] = m
+ return m
+}
+
+func (b *Box) Delete(msgs []*Msg) error {
+ for _, m := range msgs {
+ if m.Box != b {
+ return fmt.Errorf("messages not from this box")
+ }
+ }
+ b.Client.io.lock()
+ defer b.Client.io.unlock()
+ err := b.Client.deleteList(msgs)
+ if err == nil {
+ b.Client.data.lock()
+ defer b.Client.data.unlock()
+ for _, m := range msgs {
+ if m.Flags&FlagDeleted != 0 {
+ delete(b.msgByUID, m.UID)
+ }
+ }
+ }
+ return err
+}
+
+func (b *Box) Copy(msgs []*Msg) error {
+ if len(msgs) == 0 {
+ return nil
+ }
+ src := msgs[0].Box
+ for _, m := range msgs {
+ if m.Box != src {
+ return fmt.Errorf("messages span boxes: %q and %q", src.Name, m.Box.Name)
+ }
+ }
+ b.Client.io.lock()
+ defer b.Client.io.unlock()
+ return b.Client.copyList(b, src, msgs)
+}
+
+func (b *Box) Mute(msgs []*Msg) error {
+ if len(msgs) == 0 {
+ return nil
+ }
+ for _, m := range msgs {
+ if m.Box != b {
+ return fmt.Errorf("messages not from this box")
+ }
+ }
+ b.Client.io.lock()
+ defer b.Client.io.unlock()
+ return b.Client.muteList(b, msgs)
+}
+
+func (b *Box) Check() error {
+ b.Client.io.lock()
+ defer b.Client.io.unlock()
+
+ return b.Client.check(b)
+}
+
+func (m *Msg) Deleted() bool {
+ // Racy but okay. Can add a lock later if it matters.
+ return m.Flags&FlagDeleted != 0
+}
+
+// A Hdr represents a message header.
+type MsgHdr struct {
+ Date string
+ Subject string
+ From []Addr
+ Sender []Addr
+ ReplyTo []Addr
+ To []Addr
+ CC []Addr
+ BCC []Addr
+ InReplyTo string
+ MessageID string
+ Digest string
+}
+
+// An Addr represents a single, named email address.
+// If Name is empty, only the email address is known.
+// If Email is empty, the Addr represents an unspecified (but named) group.
+type Addr struct {
+ Name string
+ Email string
+}
+
+func (a Addr) String() string {
+ if a.Email == "" {
+ return a.Name
+ }
+ if a.Name == "" {
+ return a.Email
+ }
+ return a.Name + " <" + a.Email + ">"
+}
+
+// A MsgPart represents a single part of a MIME-encoded message.
+type MsgPart struct {
+ Msg *Msg // containing message
+ Type string
+ ContentID string
+ Desc string
+ Encoding string
+ Bytes int64
+ Lines int64
+ Charset string
+ Name string
+ Hdr *MsgHdr
+ ID string
+ Child []*MsgPart
+
+ raw []byte // raw message
+ rawHeader []byte // raw RFC-2822 header, for message/rfc822
+ rawBody []byte // raw RFC-2822 body, for message/rfc822
+ mimeHeader []byte // mime header, for attachments
+}
+
+func (p *MsgPart) newPart() *MsgPart {
+ p.Msg.Box.Client.data.mustBeLocked()
+ dot := "."
+ if p.ID == "" { // no dot at root
+ dot = ""
+ }
+ pp := &MsgPart{
+ Msg: p.Msg,
+ ID: fmt.Sprint(p.ID, dot, 1+len(p.Child)),
+ }
+ p.Child = append(p.Child, pp)
+ return pp
+}
+
+func (p *MsgPart) Text() []byte {
+ c := p.Msg.Box.Client
+ var raw []byte
+ c.data.lock()
+ if p == &p.Msg.Root {
+ raw = p.rawBody
+ c.data.unlock()
+ if raw == nil {
+ c.io.lock()
+ if raw = p.rawBody; raw == nil {
+ c.fetch(p, "TEXT")
+ raw = p.rawBody
+ }
+ c.io.unlock()
+ }
+ } else {
+ raw = p.raw
+ c.data.unlock()
+ if raw == nil {
+ c.io.lock()
+ if raw = p.raw; raw == nil {
+ c.fetch(p, "")
+ raw = p.raw
+ }
+ c.io.unlock()
+ }
+ }
+ return decodeText(raw, p.Encoding, p.Charset, false)
+}
+
+func (p *MsgPart) Raw() []byte {
+ c := p.Msg.Box.Client
+ var raw []byte
+ c.data.lock()
+ raw = p.rawBody
+ c.data.unlock()
+ if raw == nil {
+ c.io.lock()
+ if raw = p.rawBody; raw == nil {
+ c.fetch(p, "")
+ raw = p.rawBody
+ }
+ c.io.unlock()
+ }
+ return raw
+}
+
+var sigDash = []byte("\n--\n")
+var quote = []byte("\n> ")
+var nl = []byte("\n")
+
+var onwrote = regexp.MustCompile(`\A\s*On .* wrote:\s*\z`)
+
+func (p *MsgPart) ShortText() []byte {
+ t := p.Text()
+
+ return shortText(t)
+}
+
+func shortText(t []byte) []byte {
+ if t == nil {
+ return nil
+ }
+
+ // Cut signature.
+ i := bytes.LastIndex(t, sigDash)
+ j := bytes.LastIndex(t, quote)
+ if i > j && bytes.Count(t[i+1:], nl) <= 10 {
+ t = t[:i+1]
+ }
+
+ // Cut trailing quoted text.
+ for {
+ rest, last := lastLine(t)
+ trim := bytes.TrimSpace(last)
+ if len(rest) < len(t) && (len(trim) == 0 || trim[0] == '>') {
+ t = rest
+ continue
+ }
+ break
+ }
+
+ // Cut 'On foo.*wrote:' line.
+ rest, last := lastLine(t)
+ if onwrote.Match(last) {
+ t = rest
+ }
+
+ // Cut trailing blank lines.
+ for {
+ rest, last := lastLine(t)
+ trim := bytes.TrimSpace(last)
+ if len(rest) < len(t) && len(trim) == 0 {
+ t = rest
+ continue
+ }
+ break
+ }
+
+ // Cut signature again.
+ i = bytes.LastIndex(t, sigDash)
+ j = bytes.LastIndex(t, quote)
+ if i > j && bytes.Count(t[i+1:], nl) <= 10 {
+ t = t[:i+1]
+ }
+
+ return t
+}
+
+func lastLine(t []byte) (rest, last []byte) {
+ n := len(t)
+ if n > 0 && t[n-1] == '\n' {
+ n--
+ }
+ j := bytes.LastIndex(t[:n], nl)
+ return t[:j+1], t[j+1:]
+}