Skip to content
Closed
36 changes: 36 additions & 0 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,46 @@ import (
// password is incorrect.
var ErrInvalidCredentials = errors.New("Invalid credentials")

type Extension string

const (
ExtUIDPLUS Extension = "UIDPLUS"
)

// Backend is an IMAP server backend. A backend operation always deals with
// users.
type Backend interface {
// Login authenticates a user. If the username or the password is incorrect,
// it returns ErrInvalidCredentials.
Login(connInfo *imap.ConnInfo, username, password string) (User, error)

// SupportedExtensions returns the list of extension identifiers that
// are understood by the backend. Note that values are not capability names
// even though some may match corresponding names. In particular,
// parameters for capability names are not included.
//
// Result should contain an entry for each extension that is implemented
// by go-imap directly. There is no requirement to include extensions
// that are provided by separate libraries (e.g. go-imap-id), though
// it would not hurt - unknown values are silently ignored.
SupportedExtensions() []Extension
}

// ExtensionOption is an optional argument defined by IMAP extension
// that may be passed to the backend.
//
// Backend implementation is supposed to use type assertions to determine
// actual option data and the action needed.
// Backend implementation SHOULD fail the command if it seen
// an unknown ExtensionOption type passed to it.
type ExtensionOption interface {
ExtOption()
}

// ExtensionResult is an optional value that may be returned by
// backend.
//
// Unknown value types are ignored.
type ExtensionResult interface {
ExtResult()
}
38 changes: 38 additions & 0 deletions backend/extensions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package backend

import (
"github.com/emersion/go-imap"
)

// ExpungeSeqSet may be passed to Mailbox.Expunge() to restrict message
// deletion to the specified UIDs sequence set.
//
// See RFC 2359 for details.
type ExpungeSeqSet struct {
*imap.SeqSet
}

func (ExpungeSeqSet) ExtOption() {}

// CopyUIDs must be returned as a result for CopyMessages by backends that
// implement UIDPLUS extension.
//
// See RFC 2359 for value details.
type CopyUIDs struct {
Source *imap.SeqSet
UIDValidity uint32
Dest *imap.SeqSet
}

func (CopyUIDs) ExtResult() {}

// AppendUID must be returned as a result for CreateMessage by backend that
// implement UIDPLUS extension.
//
// See RFC 2359 for value details.
type AppendUID struct {
UIDValidity uint32
UID uint32
}

func (AppendUID) ExtResult() {}
47 changes: 12 additions & 35 deletions backend/mailbox.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package backend

import (
"time"

"github.com/emersion/go-imap"
)

Expand All @@ -15,51 +13,30 @@ type Mailbox interface {
// Closes the mailbox.
Close() error

// Info returns this mailbox info.
Info() (*imap.MailboxInfo, error)

// Status returns this mailbox status. The fields Name, Flags, PermanentFlags
// and UnseenSeqNum in the returned MailboxStatus must be always populated.
// This function does not affect the state of any messages in the mailbox. See
// RFC 3501 section 6.3.10 for a list of items that can be requested.
Status(items []imap.StatusItem) (*imap.MailboxStatus, error)

// SetSubscribed adds or removes the mailbox to the server's set of "active"
// or "subscribed" mailboxes.
SetSubscribed(subscribed bool) error

// Check requests a checkpoint of the currently selected mailbox. A checkpoint
// refers to any implementation-dependent housekeeping associated with the
// mailbox (e.g., resolving the server's in-memory state of the mailbox with
// the state on its disk). A checkpoint MAY take a non-instantaneous amount of
// real time to complete. If a server implementation has no such housekeeping
// considerations, CHECK is equivalent to NOOP.
Check() error
// Poll requests any pending mailbox updates to be sent.
//
// Argument indicates whether EXPUNGE updates are permitted to be
// sent.
Poll(expunge bool) error

// ListMessages returns a list of messages. seqset must be interpreted as UIDs
// if uid is set to true and as message sequence numbers otherwise. See RFC
// 3501 section 6.4.5 for a list of items that can be requested.
//
// Messages must be sent to ch. When the function returns, ch must be closed.
ListMessages(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error
ListMessages(uid bool, seqset *imap.SeqSet, items []imap.FetchItem,
ch chan<- *imap.Message, opts []ExtensionOption) ([]ExtensionResult, error)

// SearchMessages searches messages. The returned list must contain UIDs if
// uid is set to true, or sequence numbers otherwise.
SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error)

// CreateMessage appends a new message to this mailbox. The \Recent flag will
// be added no matter flags is empty or not. If date is nil, the current time
// will be used.
//
// If the Backend implements Updater, it must notify the client immediately
// via a mailbox update.
CreateMessage(flags []string, date time.Time, body imap.Literal) error
SearchMessages(uid bool, criteria *imap.SearchCriteria, opts []ExtensionOption) ([]ExtensionResult, []uint32, error)

// UpdateMessagesFlags alters flags for the specified message(s).
//
// If the Backend implements Updater, it must notify the client immediately
// via a message update.
UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp, flags []string) error
UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp,
silent bool, flags []string, opts []ExtensionOption) ([]ExtensionResult, error)

// CopyMessages copies the specified message(s) to the end of the specified
// destination mailbox. The flags and internal date of the message(s) SHOULD
Expand All @@ -70,12 +47,12 @@ type Mailbox interface {
//
// If the Backend implements Updater, it must notify the client immediately
// via a mailbox update.
CopyMessages(uid bool, seqset *imap.SeqSet, dest string) error
CopyMessages(uid bool, seqset *imap.SeqSet, dest string, opts []ExtensionOption) ([]ExtensionResult, error)

// Expunge permanently removes all messages that have the \Deleted flag set
// from the currently selected mailbox.
//
// If the Backend implements Updater, it must notify the client immediately
// via an expunge update.
Expunge() error
Expunge(opts []ExtensionOption) ([]ExtensionResult, error)
}
6 changes: 6 additions & 0 deletions backend/memory/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ func (be *Backend) Login(_ *imap.ConnInfo, username, password string) (backend.U
return nil, errors.New("Bad username or password")
}

func (be *Backend) SupportedExtensions() []backend.Extension {
return []backend.Extension{
backend.ExtUIDPLUS,
}
}

func New() *Backend {
user := &User{username: "username", password: "password"}

Expand Down
120 changes: 55 additions & 65 deletions backend/memory/mailbox.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package memory

import (
"io/ioutil"
"time"

"github.com/emersion/go-imap"
"github.com/emersion/go-imap/backend"
"github.com/emersion/go-imap/backend/backendutil"
Expand All @@ -19,11 +16,17 @@ type Mailbox struct {
user *User
}

type SelectedMailbox struct {
*Mailbox
conn backend.Conn
readOnly bool
}

func (mbox *Mailbox) Name() string {
return mbox.name
}

func (mbox *Mailbox) Info() (*imap.MailboxInfo, error) {
func (mbox *Mailbox) info() (*imap.MailboxInfo, error) {
info := &imap.MailboxInfo{
Delimiter: Delimiter,
Name: mbox.name,
Expand Down Expand Up @@ -78,40 +81,11 @@ func (mbox *Mailbox) unseenSeqNum() uint32 {
return 0
}

func (mbox *Mailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, error) {
status := imap.NewMailboxStatus(mbox.name, items)
status.Flags = mbox.flags()
status.PermanentFlags = []string{"\\*"}
status.UnseenSeqNum = mbox.unseenSeqNum()

for _, name := range items {
switch name {
case imap.StatusMessages:
status.Messages = uint32(len(mbox.Messages))
case imap.StatusUidNext:
status.UidNext = mbox.uidNext()
case imap.StatusUidValidity:
status.UidValidity = 1
case imap.StatusRecent:
status.Recent = 0 // TODO
case imap.StatusUnseen:
status.Unseen = 0 // TODO
}
}

return status, nil
}

func (mbox *Mailbox) SetSubscribed(subscribed bool) error {
mbox.Subscribed = subscribed
func (mbox *Mailbox) Poll(_ bool) error {
return nil
}

func (mbox *Mailbox) Check() error {
return nil
}

func (mbox *Mailbox) ListMessages(uid bool, seqSet *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error {
func (mbox *Mailbox) ListMessages(uid bool, seqSet *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message, _ []backend.ExtensionOption) ([]backend.ExtensionResult, error) {
defer close(ch)

for i, msg := range mbox.Messages {
Expand All @@ -135,10 +109,10 @@ func (mbox *Mailbox) ListMessages(uid bool, seqSet *imap.SeqSet, items []imap.Fe
ch <- m
}

return nil
return nil, nil
}

func (mbox *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error) {
func (mbox *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria, _ []backend.ExtensionOption) ([]backend.ExtensionResult, []uint32, error) {
var ids []uint32
for i, msg := range mbox.Messages {
seqNum := uint32(i + 1)
Expand All @@ -156,30 +130,11 @@ func (mbox *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]
}
ids = append(ids, id)
}
return ids, nil
}

func (mbox *Mailbox) CreateMessage(flags []string, date time.Time, body imap.Literal) error {
if date.IsZero() {
date = time.Now()
}

b, err := ioutil.ReadAll(body)
if err != nil {
return err
}

mbox.Messages = append(mbox.Messages, &Message{
Uid: mbox.uidNext(),
Date: date,
Size: uint32(len(b)),
Flags: flags,
Body: b,
})
return nil
return nil, ids, nil
}

func (mbox *Mailbox) UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, op imap.FlagsOp, flags []string) error {
func (mbox *SelectedMailbox) UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, op imap.FlagsOp,
silent bool, flags []string, _ []backend.ExtensionOption) ([]backend.ExtensionResult, error) {
for i, msg := range mbox.Messages {
var id uint32
if uid {
Expand All @@ -192,17 +147,29 @@ func (mbox *Mailbox) UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, op imap.
}

msg.Flags = backendutil.UpdateFlags(msg.Flags, op, flags)

if !silent {
updMsg := imap.NewMessage(uint32(i+1), []imap.FetchItem{imap.FetchFlags})
updMsg.Flags = msg.Flags
if uid {
updMsg.Items[imap.FetchUid] = nil
updMsg.Uid = msg.Uid
}
mbox.conn.SendUpdate(&backend.MessageUpdate{Message: updMsg})
}
}

return nil
return nil, nil
}

func (mbox *Mailbox) CopyMessages(uid bool, seqset *imap.SeqSet, destName string) error {
func (mbox *Mailbox) CopyMessages(uid bool, seqset *imap.SeqSet, destName string, _ []backend.ExtensionOption) ([]backend.ExtensionResult, error) {
dest, ok := mbox.user.mailboxes[destName]
if !ok {
return backend.ErrNoSuchMailbox
return nil, backend.ErrNoSuchMailbox
}

var srcSet, destSet imap.SeqSet

for i, msg := range mbox.Messages {
var id uint32
if uid {
Expand All @@ -214,15 +181,32 @@ func (mbox *Mailbox) CopyMessages(uid bool, seqset *imap.SeqSet, destName string
continue
}

srcSet.AddNum(msg.Uid)

msgCopy := *msg
msgCopy.Uid = dest.uidNext()
dest.Messages = append(dest.Messages, &msgCopy)
destSet.AddNum(msgCopy.Uid)
}

return nil
return []backend.ExtensionResult{
backend.CopyUIDs{
Source: &srcSet,
UIDValidity: 1,
Dest: &destSet,
},
}, nil
}

func (mbox *Mailbox) Expunge() error {
func (mbox *SelectedMailbox) Expunge(opts []backend.ExtensionOption) ([]backend.ExtensionResult, error) {
var allowedUIDs *imap.SeqSet
for _, opt := range opts {
switch opt := opt.(type) {
case backend.ExpungeSeqSet:
allowedUIDs = opt.SeqSet
}
}

for i := len(mbox.Messages) - 1; i >= 0; i-- {
msg := mbox.Messages[i]

Expand All @@ -234,12 +218,18 @@ func (mbox *Mailbox) Expunge() error {
}
}

if allowedUIDs != nil && !allowedUIDs.Contains(msg.Uid) {
continue
}

if deleted {
mbox.Messages = append(mbox.Messages[:i], mbox.Messages[i+1:]...)

mbox.conn.SendUpdate(&backend.ExpungeUpdate{SeqNum: uint32(i + 1)})
}
}

return nil
return nil, nil
}

func (mbox *Mailbox) Close() error {
Expand Down
Loading