aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/emersion/go-imap/mailbox.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/emersion/go-imap/mailbox.go')
-rw-r--r--vendor/github.com/emersion/go-imap/mailbox.go314
1 files changed, 314 insertions, 0 deletions
diff --git a/vendor/github.com/emersion/go-imap/mailbox.go b/vendor/github.com/emersion/go-imap/mailbox.go
new file mode 100644
index 0000000..8f12d4d
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/mailbox.go
@@ -0,0 +1,314 @@
+package imap
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "sync"
+
+ "github.com/emersion/go-imap/utf7"
+)
+
+// The primary mailbox, as defined in RFC 3501 section 5.1.
+const InboxName = "INBOX"
+
+// CanonicalMailboxName returns the canonical form of a mailbox name. Mailbox names can be
+// case-sensitive or case-insensitive depending on the backend implementation.
+// The special INBOX mailbox is case-insensitive.
+func CanonicalMailboxName(name string) string {
+ if strings.EqualFold(name, InboxName) {
+ return InboxName
+ }
+ return name
+}
+
+// Mailbox attributes definied in RFC 3501 section 7.2.2.
+const (
+ // It is not possible for any child levels of hierarchy to exist under this\
+ // name; no child levels exist now and none can be created in the future.
+ NoInferiorsAttr = "\\Noinferiors"
+ // It is not possible to use this name as a selectable mailbox.
+ NoSelectAttr = "\\Noselect"
+ // The mailbox has been marked "interesting" by the server; the mailbox
+ // probably contains messages that have been added since the last time the
+ // mailbox was selected.
+ MarkedAttr = "\\Marked"
+ // The mailbox does not contain any additional messages since the last time
+ // the mailbox was selected.
+ UnmarkedAttr = "\\Unmarked"
+)
+
+// Mailbox attributes defined in RFC 6154 section 2 (SPECIAL-USE extension).
+const (
+ // This mailbox presents all messages in the user's message store.
+ AllAttr = "\\All"
+ // This mailbox is used to archive messages.
+ ArchiveAttr = "\\Archive"
+ // This mailbox is used to hold draft messages -- typically, messages that are
+ // being composed but have not yet been sent.
+ DraftsAttr = "\\Drafts"
+ // This mailbox presents all messages marked in some way as "important".
+ FlaggedAttr = "\\Flagged"
+ // This mailbox is where messages deemed to be junk mail are held.
+ JunkAttr = "\\Junk"
+ // This mailbox is used to hold copies of messages that have been sent.
+ SentAttr = "\\Sent"
+ // This mailbox is used to hold messages that have been deleted or marked for
+ // deletion.
+ TrashAttr = "\\Trash"
+)
+
+// Mailbox attributes defined in RFC 3348 (CHILDREN extension)
+const (
+ // The presence of this attribute indicates that the mailbox has child
+ // mailboxes.
+ HasChildrenAttr = "\\HasChildren"
+ // The presence of this attribute indicates that the mailbox has no child
+ // mailboxes.
+ HasNoChildrenAttr = "\\HasNoChildren"
+)
+
+// This mailbox attribute is a signal that the mailbox contains messages that
+// are likely important to the user. This attribute is defined in RFC 8457
+// section 3.
+const ImportantAttr = "\\Important"
+
+// Basic mailbox info.
+type MailboxInfo struct {
+ // The mailbox attributes.
+ Attributes []string
+ // The server's path separator.
+ Delimiter string
+ // The mailbox name.
+ Name string
+}
+
+// Parse mailbox info from fields.
+func (info *MailboxInfo) Parse(fields []interface{}) error {
+ if len(fields) < 3 {
+ return errors.New("Mailbox info needs at least 3 fields")
+ }
+
+ var err error
+ if info.Attributes, err = ParseStringList(fields[0]); err != nil {
+ return err
+ }
+
+ var ok bool
+ if info.Delimiter, ok = fields[1].(string); !ok {
+ // The delimiter may be specified as NIL, which gets converted to a nil interface.
+ if fields[1] != nil {
+ return errors.New("Mailbox delimiter must be a string")
+ }
+ info.Delimiter = ""
+ }
+
+ if name, err := ParseString(fields[2]); err != nil {
+ return err
+ } else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
+ return err
+ } else {
+ info.Name = CanonicalMailboxName(name)
+ }
+
+ return nil
+}
+
+// Format mailbox info to fields.
+func (info *MailboxInfo) Format() []interface{} {
+ name, _ := utf7.Encoding.NewEncoder().String(info.Name)
+ attrs := make([]interface{}, len(info.Attributes))
+ for i, attr := range info.Attributes {
+ attrs[i] = RawString(attr)
+ }
+
+ // If the delimiter is NIL, we need to treat it specially by inserting
+ // a nil field (so that it's later converted to an unquoted NIL atom).
+ var del interface{}
+
+ if info.Delimiter != "" {
+ del = info.Delimiter
+ }
+
+ // Thunderbird doesn't understand delimiters if not quoted
+ return []interface{}{attrs, del, FormatMailboxName(name)}
+}
+
+// TODO: optimize this
+func (info *MailboxInfo) match(name, pattern string) bool {
+ i := strings.IndexAny(pattern, "*%")
+ if i == -1 {
+ // No more wildcards
+ return name == pattern
+ }
+
+ // Get parts before and after wildcard
+ chunk, wildcard, rest := pattern[0:i], pattern[i], pattern[i+1:]
+
+ // Check that name begins with chunk
+ if len(chunk) > 0 && !strings.HasPrefix(name, chunk) {
+ return false
+ }
+ name = strings.TrimPrefix(name, chunk)
+
+ // Expand wildcard
+ var j int
+ for j = 0; j < len(name); j++ {
+ if wildcard == '%' && string(name[j]) == info.Delimiter {
+ break // Stop on delimiter if wildcard is %
+ }
+ // Try to match the rest from here
+ if info.match(name[j:], rest) {
+ return true
+ }
+ }
+
+ return info.match(name[j:], rest)
+}
+
+// Match checks if a reference and a pattern matches this mailbox name, as
+// defined in RFC 3501 section 6.3.8.
+func (info *MailboxInfo) Match(reference, pattern string) bool {
+ name := info.Name
+
+ if info.Delimiter != "" && strings.HasPrefix(pattern, info.Delimiter) {
+ reference = ""
+ pattern = strings.TrimPrefix(pattern, info.Delimiter)
+ }
+ if reference != "" {
+ if info.Delimiter != "" && !strings.HasSuffix(reference, info.Delimiter) {
+ reference += info.Delimiter
+ }
+ if !strings.HasPrefix(name, reference) {
+ return false
+ }
+ name = strings.TrimPrefix(name, reference)
+ }
+
+ return info.match(name, pattern)
+}
+
+// A mailbox status.
+type MailboxStatus struct {
+ // The mailbox name.
+ Name string
+ // True if the mailbox is open in read-only mode.
+ ReadOnly bool
+ // The mailbox items that are currently filled in. This map's values
+ // should not be used directly, they must only be used by libraries
+ // implementing extensions of the IMAP protocol.
+ Items map[StatusItem]interface{}
+
+ // The Items map may be accessed in different goroutines. Protect
+ // concurrent writes.
+ ItemsLocker sync.Mutex
+
+ // The mailbox flags.
+ Flags []string
+ // The mailbox permanent flags.
+ PermanentFlags []string
+ // The sequence number of the first unseen message in the mailbox.
+ UnseenSeqNum uint32
+
+ // The number of messages in this mailbox.
+ Messages uint32
+ // The number of messages not seen since the last time the mailbox was opened.
+ Recent uint32
+ // The number of unread messages.
+ Unseen uint32
+ // The next UID.
+ UidNext uint32
+ // Together with a UID, it is a unique identifier for a message.
+ // Must be greater than or equal to 1.
+ UidValidity uint32
+
+ // Per-mailbox limit of message size. Set only if server supports the
+ // APPENDLIMIT extension.
+ AppendLimit uint32
+}
+
+// Create a new mailbox status that will contain the specified items.
+func NewMailboxStatus(name string, items []StatusItem) *MailboxStatus {
+ status := &MailboxStatus{
+ Name: name,
+ Items: make(map[StatusItem]interface{}),
+ }
+
+ for _, k := range items {
+ status.Items[k] = nil
+ }
+
+ return status
+}
+
+func (status *MailboxStatus) Parse(fields []interface{}) error {
+ status.Items = make(map[StatusItem]interface{})
+
+ var k StatusItem
+ for i, f := range fields {
+ if i%2 == 0 {
+ if kstr, ok := f.(string); !ok {
+ return fmt.Errorf("cannot parse mailbox status: key is not a string, but a %T", f)
+ } else {
+ k = StatusItem(strings.ToUpper(kstr))
+ }
+ } else {
+ status.Items[k] = nil
+
+ var err error
+ switch k {
+ case StatusMessages:
+ status.Messages, err = ParseNumber(f)
+ case StatusRecent:
+ status.Recent, err = ParseNumber(f)
+ case StatusUnseen:
+ status.Unseen, err = ParseNumber(f)
+ case StatusUidNext:
+ status.UidNext, err = ParseNumber(f)
+ case StatusUidValidity:
+ status.UidValidity, err = ParseNumber(f)
+ case StatusAppendLimit:
+ status.AppendLimit, err = ParseNumber(f)
+ default:
+ status.Items[k] = f
+ }
+
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func (status *MailboxStatus) Format() []interface{} {
+ var fields []interface{}
+ for k, v := range status.Items {
+ switch k {
+ case StatusMessages:
+ v = status.Messages
+ case StatusRecent:
+ v = status.Recent
+ case StatusUnseen:
+ v = status.Unseen
+ case StatusUidNext:
+ v = status.UidNext
+ case StatusUidValidity:
+ v = status.UidValidity
+ case StatusAppendLimit:
+ v = status.AppendLimit
+ }
+
+ fields = append(fields, RawString(k), v)
+ }
+ return fields
+}
+
+func FormatMailboxName(name string) interface{} {
+ // Some e-mails servers don't handle quoted INBOX names correctly so we special-case it.
+ if strings.EqualFold(name, "INBOX") {
+ return RawString(name)
+ }
+ return name
+}