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) }