264 lines
4.9 KiB
Go
264 lines
4.9 KiB
Go
package server
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/emersion/go-imap"
|
|
"github.com/emersion/go-imap/backend"
|
|
"github.com/emersion/go-imap/commands"
|
|
"github.com/emersion/go-imap/responses"
|
|
)
|
|
|
|
// imap errors in Authenticated state.
|
|
var (
|
|
ErrNotAuthenticated = errors.New("Not authenticated")
|
|
)
|
|
|
|
type Select struct {
|
|
commands.Select
|
|
}
|
|
|
|
func (cmd *Select) Handle(conn Conn) error {
|
|
ctx := conn.Context()
|
|
if ctx.User == nil {
|
|
return ErrNotAuthenticated
|
|
}
|
|
|
|
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
items := []imap.StatusItem{
|
|
imap.StatusMessages, imap.StatusRecent, imap.StatusUnseen,
|
|
imap.StatusUidNext, imap.StatusUidValidity,
|
|
}
|
|
|
|
status, err := mbox.Status(items)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx.Mailbox = mbox
|
|
ctx.MailboxReadOnly = cmd.ReadOnly || status.ReadOnly
|
|
|
|
res := &responses.Select{Mailbox: status}
|
|
if err := conn.WriteResp(res); err != nil {
|
|
return err
|
|
}
|
|
|
|
var code imap.StatusRespCode = imap.CodeReadWrite
|
|
if ctx.MailboxReadOnly {
|
|
code = imap.CodeReadOnly
|
|
}
|
|
return ErrStatusResp(&imap.StatusResp{
|
|
Type: imap.StatusRespOk,
|
|
Code: code,
|
|
})
|
|
}
|
|
|
|
type Create struct {
|
|
commands.Create
|
|
}
|
|
|
|
func (cmd *Create) Handle(conn Conn) error {
|
|
ctx := conn.Context()
|
|
if ctx.User == nil {
|
|
return ErrNotAuthenticated
|
|
}
|
|
|
|
return ctx.User.CreateMailbox(cmd.Mailbox)
|
|
}
|
|
|
|
type Delete struct {
|
|
commands.Delete
|
|
}
|
|
|
|
func (cmd *Delete) Handle(conn Conn) error {
|
|
ctx := conn.Context()
|
|
if ctx.User == nil {
|
|
return ErrNotAuthenticated
|
|
}
|
|
|
|
return ctx.User.DeleteMailbox(cmd.Mailbox)
|
|
}
|
|
|
|
type Rename struct {
|
|
commands.Rename
|
|
}
|
|
|
|
func (cmd *Rename) Handle(conn Conn) error {
|
|
ctx := conn.Context()
|
|
if ctx.User == nil {
|
|
return ErrNotAuthenticated
|
|
}
|
|
|
|
return ctx.User.RenameMailbox(cmd.Existing, cmd.New)
|
|
}
|
|
|
|
type Subscribe struct {
|
|
commands.Subscribe
|
|
}
|
|
|
|
func (cmd *Subscribe) Handle(conn Conn) error {
|
|
ctx := conn.Context()
|
|
if ctx.User == nil {
|
|
return ErrNotAuthenticated
|
|
}
|
|
|
|
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return mbox.SetSubscribed(true)
|
|
}
|
|
|
|
type Unsubscribe struct {
|
|
commands.Unsubscribe
|
|
}
|
|
|
|
func (cmd *Unsubscribe) Handle(conn Conn) error {
|
|
ctx := conn.Context()
|
|
if ctx.User == nil {
|
|
return ErrNotAuthenticated
|
|
}
|
|
|
|
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return mbox.SetSubscribed(false)
|
|
}
|
|
|
|
type List struct {
|
|
commands.List
|
|
}
|
|
|
|
func (cmd *List) Handle(conn Conn) error {
|
|
ctx := conn.Context()
|
|
if ctx.User == nil {
|
|
return ErrNotAuthenticated
|
|
}
|
|
|
|
ch := make(chan *imap.MailboxInfo)
|
|
res := &responses.List{Mailboxes: ch, Subscribed: cmd.Subscribed}
|
|
|
|
done := make(chan error, 1)
|
|
go (func() {
|
|
done <- conn.WriteResp(res)
|
|
close(done)
|
|
})()
|
|
|
|
mailboxes, err := ctx.User.ListMailboxes(cmd.Subscribed)
|
|
if err != nil {
|
|
close(ch)
|
|
return err
|
|
}
|
|
|
|
for _, mbox := range mailboxes {
|
|
info, err := mbox.Info()
|
|
if err != nil {
|
|
close(ch)
|
|
return err
|
|
}
|
|
|
|
// An empty ("" string) mailbox name argument is a special request to return
|
|
// the hierarchy delimiter and the root name of the name given in the
|
|
// reference.
|
|
if cmd.Mailbox == "" {
|
|
ch <- &imap.MailboxInfo{
|
|
Attributes: []string{imap.NoSelectAttr},
|
|
Delimiter: info.Delimiter,
|
|
Name: info.Delimiter,
|
|
}
|
|
break
|
|
}
|
|
|
|
if info.Match(cmd.Reference, cmd.Mailbox) {
|
|
ch <- info
|
|
}
|
|
}
|
|
|
|
close(ch)
|
|
|
|
return <-done
|
|
}
|
|
|
|
type Status struct {
|
|
commands.Status
|
|
}
|
|
|
|
func (cmd *Status) Handle(conn Conn) error {
|
|
ctx := conn.Context()
|
|
if ctx.User == nil {
|
|
return ErrNotAuthenticated
|
|
}
|
|
|
|
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
status, err := mbox.Status(cmd.Items)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Only keep items thqat have been requested
|
|
items := make(map[imap.StatusItem]interface{})
|
|
for _, k := range cmd.Items {
|
|
items[k] = status.Items[k]
|
|
}
|
|
status.Items = items
|
|
|
|
res := &responses.Status{Mailbox: status}
|
|
return conn.WriteResp(res)
|
|
}
|
|
|
|
type Append struct {
|
|
commands.Append
|
|
}
|
|
|
|
func (cmd *Append) Handle(conn Conn) error {
|
|
ctx := conn.Context()
|
|
if ctx.User == nil {
|
|
return ErrNotAuthenticated
|
|
}
|
|
|
|
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
|
if err == backend.ErrNoSuchMailbox {
|
|
return ErrStatusResp(&imap.StatusResp{
|
|
Type: imap.StatusRespNo,
|
|
Code: imap.CodeTryCreate,
|
|
Info: err.Error(),
|
|
})
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := mbox.CreateMessage(cmd.Flags, cmd.Date, cmd.Message); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If APPEND targets the currently selected mailbox, send an untagged EXISTS
|
|
// Do this only if the backend doesn't send updates itself
|
|
if conn.Server().Updates == nil && ctx.Mailbox != nil && ctx.Mailbox.Name() == mbox.Name() {
|
|
status, err := mbox.Status([]imap.StatusItem{imap.StatusMessages})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
status.Flags = nil
|
|
status.PermanentFlags = nil
|
|
status.UnseenSeqNum = 0
|
|
|
|
res := &responses.Select{Mailbox: status}
|
|
if err := conn.WriteResp(res); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|