kittemail/session.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)
}