231 lines
4.0 KiB
Go
231 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"github.com/emersion/go-imap/v2"
|
|
"github.com/emersion/go-imap/v2/imapserver"
|
|
"github.com/google/uuid"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
const mailboxDelim rune = '/'
|
|
|
|
type User struct {
|
|
server *Server // may be nil if sub is not set
|
|
sub uuid.UUID
|
|
}
|
|
|
|
func (u *User) Login(_, token string) error {
|
|
sub, err := Authenticate(token, u.server.config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
u.sub = sub
|
|
|
|
_, err = u.mailbox("INBOX")
|
|
if err != nil {
|
|
if errors.Is(err, ErrNoSuchMailbox) {
|
|
err := u.Create("INBOX", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *User) mailbox(name string) (*Mailbox, error) {
|
|
return loadMbox(name, u)
|
|
}
|
|
|
|
func (u *User) Status(name string, options *imap.StatusOptions) (*imap.StatusData, error) {
|
|
mbox, err := u.mailbox(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return mbox.StatusData(options), nil
|
|
}
|
|
|
|
func (u *User) List(w *imapserver.ListWriter, ref string, patterns []string, options *imap.ListOptions) error {
|
|
// TODO: fail if ref doesn't exist
|
|
|
|
if len(patterns) == 0 {
|
|
return w.WriteList(&imap.ListData{
|
|
Attrs: []imap.MailboxAttr{imap.MailboxAttrNoSelect},
|
|
Delim: mailboxDelim,
|
|
})
|
|
}
|
|
|
|
mailboxQuery, err := Database.DB.Query("SELECT id, mailbox, subscribed FROM mailboxes WHERE owner = $1", u.sub[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
err := mailboxQuery.Close()
|
|
if err != nil {
|
|
log("Failed to close rows: "+err.Error()+", resource leaks may occur", 1)
|
|
}
|
|
}()
|
|
|
|
var l []imap.ListData
|
|
for mailboxQuery.Next() {
|
|
var id []byte
|
|
var name string
|
|
var subscribed bool
|
|
err := mailboxQuery.Scan(&id, &name, &subscribed)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mbox, err := loadMboxRaw(subscribed, id, name, u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
match := false
|
|
for _, pattern := range patterns {
|
|
match = imapserver.MatchList(name, mailboxDelim, ref, pattern)
|
|
if match {
|
|
break
|
|
}
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
|
|
data, err := mbox.list(options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if data != nil {
|
|
l = append(l, *data)
|
|
}
|
|
}
|
|
|
|
sort.Slice(l, func(i, j int) bool {
|
|
return l[i].Mailbox < l[j].Mailbox
|
|
})
|
|
|
|
for _, data := range l {
|
|
err := w.WriteList(&data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *User) Create(name string, _ *imap.CreateOptions) error {
|
|
name = strings.TrimRight(name, string(mailboxDelim))
|
|
|
|
err := u.NameNotExists(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newUUID := uuid.New()
|
|
|
|
err = CreateMailbox(newUUID, name, u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *User) Delete(name string) error {
|
|
mbox, err := u.mailbox(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = mbox.Delete()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *User) NameNotExists(name string) error {
|
|
rows, err := Database.DB.Query("SELECT 1 FROM mailboxes WHERE mailbox = $1", name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
err := rows.Close()
|
|
if err != nil {
|
|
log("Failed to close rows: "+err.Error()+", resource leaks may occur", 1)
|
|
}
|
|
}()
|
|
|
|
if !rows.Next() {
|
|
return nil
|
|
} else {
|
|
return &imap.Error{
|
|
Type: imap.StatusResponseTypeNo,
|
|
Code: imap.ResponseCodeAlreadyExists,
|
|
Text: "Mailbox already exists",
|
|
}
|
|
}
|
|
}
|
|
|
|
func (u *User) Rename(oldName, newName string) error {
|
|
newName = strings.TrimRight(newName, string(mailboxDelim))
|
|
|
|
mbox, err := u.mailbox(oldName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = u.NameNotExists(newName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = mbox.rename(newName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *User) Subscribe(name string) error {
|
|
mbox, err := u.mailbox(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = mbox.SetSubscribed(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *User) Unsubscribe(name string) error {
|
|
mbox, err := u.mailbox(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = mbox.SetSubscribed(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *User) Namespace() (*imap.NamespaceData, error) {
|
|
return &imap.NamespaceData{
|
|
Personal: []imap.NamespaceDescriptor{{Delim: mailboxDelim}},
|
|
}, nil
|
|
}
|