Initial commit
This commit is contained in:
246
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
Normal file
246
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// The primary mailbox, as defined in RFC 3501 section 5.1.
|
||||
const InboxName = "INBOX"
|
||||
|
||||
// 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.ToUpper(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"
|
||||
)
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
info.Attributes, _ = ParseStringList(fields[0])
|
||||
|
||||
info.Delimiter, _ = fields[1].(string)
|
||||
|
||||
name, _ := fields[2].(string)
|
||||
info.Name, _ = utf7.Decoder.String(name)
|
||||
info.Name = CanonicalMailboxName(info.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format mailbox info to fields.
|
||||
func (info *MailboxInfo) Format() []interface{} {
|
||||
name, _ := utf7.Encoder.String(info.Name)
|
||||
// Thunderbird doesn't understand delimiters if not quoted
|
||||
return []interface{}{FormatStringList(info.Attributes), Quoted(info.Delimiter), 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 strings.HasPrefix(pattern, info.Delimiter) {
|
||||
reference = ""
|
||||
pattern = strings.TrimPrefix(pattern, info.Delimiter)
|
||||
}
|
||||
if reference != "" {
|
||||
if !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)
|
||||
}
|
||||
|
||||
// Mailbox status items.
|
||||
const (
|
||||
MailboxFlags = "FLAGS"
|
||||
MailboxPermanentFlags = "PERMANENTFLAGS"
|
||||
|
||||
// Defined in RFC 3501 section 6.3.10.
|
||||
MailboxMessages = "MESSAGES"
|
||||
MailboxRecent = "RECENT"
|
||||
MailboxUnseen = "UNSEEN"
|
||||
MailboxUidNext = "UIDNEXT"
|
||||
MailboxUidValidity = "UIDVALIDITY"
|
||||
)
|
||||
|
||||
// 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[string]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 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
|
||||
}
|
||||
|
||||
// Create a new mailbox status that will contain the specified items.
|
||||
func NewMailboxStatus(name string, items []string) *MailboxStatus {
|
||||
status := &MailboxStatus{
|
||||
Name: name,
|
||||
Items: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
for _, k := range items {
|
||||
status.Items[k] = nil
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (status *MailboxStatus) Parse(fields []interface{}) error {
|
||||
status.Items = make(map[string]interface{})
|
||||
|
||||
var k string
|
||||
for i, f := range fields {
|
||||
if i%2 == 0 {
|
||||
var ok bool
|
||||
if k, ok = f.(string); !ok {
|
||||
return errors.New("Key is not a string")
|
||||
}
|
||||
k = strings.ToUpper(k)
|
||||
} else {
|
||||
status.Items[k] = nil
|
||||
|
||||
var err error
|
||||
switch k {
|
||||
case MailboxMessages:
|
||||
status.Messages, err = ParseNumber(f)
|
||||
case MailboxRecent:
|
||||
status.Recent, err = ParseNumber(f)
|
||||
case MailboxUnseen:
|
||||
status.Unseen, err = ParseNumber(f)
|
||||
case MailboxUidNext:
|
||||
status.UidNext, err = ParseNumber(f)
|
||||
case MailboxUidValidity:
|
||||
status.UidValidity, 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 MailboxMessages:
|
||||
v = status.Messages
|
||||
case MailboxRecent:
|
||||
v = status.Recent
|
||||
case MailboxUnseen:
|
||||
v = status.Unseen
|
||||
case MailboxUidNext:
|
||||
v = status.UidNext
|
||||
case MailboxUidValidity:
|
||||
v = status.UidValidity
|
||||
}
|
||||
|
||||
fields = append(fields, k, v)
|
||||
}
|
||||
return fields
|
||||
}
|
Reference in New Issue
Block a user