230 lines
5.4 KiB
Go
230 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"github.com/emersion/go-imap/v2"
|
|
"github.com/emersion/go-imap/v2/imapserver"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// UserSession represents a session tied to a specific user.
|
|
//
|
|
// UserSession implements imapserver.Session. Typically, a UserSession pointer
|
|
// is embedded into a larger struct which overrides Login.
|
|
type UserSession struct {
|
|
// immutable
|
|
*User
|
|
*EntryPoint
|
|
|
|
// may be nil
|
|
*MailboxView
|
|
}
|
|
|
|
var _ imapserver.SessionIMAP4rev2 = (*EntryPoint)(nil)
|
|
var _ imapserver.Session = (*EntryPoint)(nil)
|
|
|
|
type EntryPoint struct {
|
|
*UserSession
|
|
*Server
|
|
}
|
|
|
|
func (sess *EntryPoint) Login(_, token string) (err error) {
|
|
sess.UserSession = &UserSession{
|
|
EntryPoint: sess,
|
|
}
|
|
sess.UserSession.User, err = sess.Server.user(token)
|
|
sess.UserSession.User.sessionMutex.Lock()
|
|
sess.UserSession.User.sessions[sess.UserSession] = struct{}{}
|
|
sess.UserSession.User.sessionMutex.Unlock()
|
|
return err
|
|
}
|
|
|
|
func (sess *EntryPoint) loginRaw(sub uuid.UUID) (err error) {
|
|
sess.UserSession = &UserSession{
|
|
EntryPoint: sess,
|
|
}
|
|
sess.UserSession.User, err = sess.Server.userRaw(sub)
|
|
return err
|
|
}
|
|
|
|
func (sess *UserSession) Append(mboxName string, r imap.LiteralReader, options *imap.AppendOptions) (*imap.AppendData, error) {
|
|
mbox, err := sess.User.mailbox(mboxName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
view := mbox.NewView()
|
|
defer func() {
|
|
err := view.close()
|
|
if err != nil {
|
|
log("Failed to close view: "+err.Error()+", resource leaks may occur", 1)
|
|
}
|
|
}()
|
|
return view.appendBuffer(r, r.Size(), options)
|
|
}
|
|
|
|
func (sess *UserSession) Close() error {
|
|
if sess == nil || sess.MailboxView == nil {
|
|
return nil
|
|
}
|
|
if sess.User != nil {
|
|
err := sess.Unselect()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sess.User.sessionMutex.Lock()
|
|
delete(sess.User.sessions, sess)
|
|
if len(sess.User.sessions) == 0 {
|
|
sess.Server.userMutex.Lock()
|
|
delete(sess.server.users, sess.User.sub)
|
|
sess.Server.userMutex.Unlock()
|
|
}
|
|
sess.User.sessionMutex.Unlock()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sess *UserSession) Select(name string, _ *imap.SelectOptions) (*imap.SelectData, error) {
|
|
mbox, err := sess.User.mailbox(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mbox.sessionMutex.Lock()
|
|
mbox.sessions[sess] = struct{}{}
|
|
mbox.sessionMutex.Unlock()
|
|
sess.MailboxView = mbox.NewView()
|
|
return mbox.selectData()
|
|
}
|
|
|
|
func (sess *UserSession) Unselect() error {
|
|
sess.Mailbox.sessionMutex.Lock()
|
|
defer sess.Mailbox.sessionMutex.Unlock()
|
|
delete(sess.Mailbox.sessions, sess)
|
|
if len(sess.Mailbox.sessions) == 0 {
|
|
sess.User.sessionMutex.Lock()
|
|
delete(sess.mailboxes, sess.name)
|
|
sess.User.sessionMutex.Unlock()
|
|
}
|
|
err := sess.close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sess.MailboxView = nil
|
|
return nil
|
|
}
|
|
|
|
func (sess *UserSession) Copy(numSet imap.NumSet, destName string) (*imap.CopyData, error) {
|
|
dest, err := sess.mailbox(destName)
|
|
if err != nil {
|
|
return nil, &imap.Error{
|
|
Type: imap.StatusResponseTypeNo,
|
|
Code: imap.ResponseCodeTryCreate,
|
|
Text: "No such mailbox",
|
|
}
|
|
} else if sess.MailboxView != nil && dest == sess.Mailbox {
|
|
return nil, &imap.Error{
|
|
Type: imap.StatusResponseTypeNo,
|
|
Text: "Source and destination mailboxes are identical",
|
|
}
|
|
}
|
|
|
|
destView := dest.NewView()
|
|
defer func() {
|
|
err := destView.close()
|
|
if err != nil {
|
|
log("Failed to close view: "+err.Error()+", resource leaks may occur", 1)
|
|
}
|
|
}()
|
|
|
|
var sourceUIDs, destUIDs imap.UIDSet
|
|
err = sess.forEach(numSet, func(seqNum uint32, msg *message) error {
|
|
var appendData *imap.AppendData
|
|
appendData, err = destView.copyMsg(msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sourceUIDs.AddNum(msg.uid)
|
|
destUIDs.AddNum(appendData.UID)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &imap.CopyData{
|
|
UIDValidity: dest.UIDValidity,
|
|
SourceUIDs: sourceUIDs,
|
|
DestUIDs: destUIDs,
|
|
}, nil
|
|
}
|
|
|
|
func (sess *UserSession) Move(w *imapserver.MoveWriter, numSet imap.NumSet, destName string) error {
|
|
dest, err := sess.mailbox(destName)
|
|
if err != nil {
|
|
return &imap.Error{
|
|
Type: imap.StatusResponseTypeNo,
|
|
Code: imap.ResponseCodeTryCreate,
|
|
Text: "No such mailbox",
|
|
}
|
|
} else if sess.MailboxView != nil && dest == sess.Mailbox {
|
|
return &imap.Error{
|
|
Type: imap.StatusResponseTypeNo,
|
|
Text: "Source and destination mailboxes are identical",
|
|
}
|
|
}
|
|
|
|
destView := dest.NewView()
|
|
defer func() {
|
|
err := destView.close()
|
|
if err != nil {
|
|
log("Failed to close view: "+err.Error()+", resource leaks may occur", 1)
|
|
}
|
|
}()
|
|
|
|
var sourceUIDs, destUIDs imap.UIDSet
|
|
err = sess.forEach(numSet, func(seqNum uint32, msg *message) error {
|
|
var appendData *imap.AppendData
|
|
appendData, err = destView.copyMsg(msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sourceUIDs.AddNum(msg.uid)
|
|
destUIDs.AddNum(appendData.UID)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(sourceUIDs) != 0 && len(destUIDs) != 0 {
|
|
err = w.WriteCopyData(&imap.CopyData{
|
|
UIDValidity: dest.UIDValidity,
|
|
SourceUIDs: sourceUIDs,
|
|
DestUIDs: destUIDs,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = sess.Expunge((*imapserver.ExpungeWriter)(w), &sourceUIDs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sess *UserSession) Poll(w *imapserver.UpdateWriter, allowExpunge bool) error {
|
|
if sess.MailboxView == nil {
|
|
return nil
|
|
}
|
|
return sess.MailboxView.Poll(w, allowExpunge)
|
|
}
|
|
|
|
func (sess *UserSession) Idle(w *imapserver.UpdateWriter, stop <-chan struct{}) error {
|
|
if sess.MailboxView == nil {
|
|
return nil // TODO
|
|
}
|
|
return sess.MailboxView.Idle(w, stop)
|
|
}
|