/* * (Original Copyright (c) 2021 Neil Alexander) * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package imapserver import ( "bufio" "io" "time" "github.com/emersion/go-imap" "github.com/emersion/go-imap/backend/backendutil" "github.com/emersion/go-message/textproto" "github.com/neilalexander/yggmail/internal/storage" "github.com/neilalexander/yggmail/internal/storage/types" "github.com/neilalexander/yggmail/internal/utils/e2ee" ) type Mailbox struct { storage storage.Storage name string user *User } func (mbox *Mailbox) Name() string { return mbox.name } func (mbox *Mailbox) Info() (*imap.MailboxInfo, error) { return &imap.MailboxInfo{ Attributes: []string{}, Delimiter: "/", Name: mbox.name, }, nil } func (mbox *Mailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, error) { pk := mbox.user.username status := imap.NewMailboxStatus(mbox.name, items) status.PermanentFlags = []string{"\\Seen", "\\Answered", "\\Flagged", "\\Deleted"} status.Flags = status.PermanentFlags for _, name := range items { switch name { case imap.StatusMessages: count, _ := mbox.storage.MailCount(pk, mbox.name) status.Messages = uint32(count) case imap.StatusUidNext: id, _ := mbox.storage.MailNextID(pk, mbox.name) status.UidNext = uint32(id) case imap.StatusUidValidity: status.UidValidity = 1 case imap.StatusUnseen: unseen, _ := mbox.storage.MailUnseen(pk, mbox.name) status.Unseen = uint32(unseen) } } return status, nil } func (mbox *Mailbox) ListMessages(uid bool, seqSet *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error { defer close(ch) pk := mbox.user.username ids, err := mbox.getIDsFromSeqSet(uid, seqSet) if err != nil { return err } for _, id := range ids { mseq, m, err := mbox.storage.MailSelect(pk, mbox.name, int(id)) if err != nil { continue } // n, _ := io.ReadAll(m.Body.(io.ReadSeeker)) // mbox.user.log.Printf("Body preview: \n\n %q \n\n", string(n)) // m.Body.(io.ReadSeeker).Seek(0, io.SeekStart) m.Body, err = e2ee.DecryptFilter(m.Body.(io.ReadSeekCloser), mbox.user.session.Decrypt) if err != nil { mbox.user.log.Println("decrypter error: ", err) return err } msg, err := mbox.fetchMessage(mseq, m, items) if err != nil { continue } ch <- msg } return nil } func (mbox *Mailbox) fetchMessage(seqNum int, m *types.Mail, items []imap.FetchItem) (*imap.Message, error) { fetched := imap.NewMessage(uint32(m.ID), items) fetched.SeqNum = uint32(seqNum) fetched.Uid = uint32(m.ID) if !mbox.needsBody(items) || m.Body == nil { return mbox.fillMetadataOnly(fetched, m, items), nil } defer m.Body.Close() br := bufio.NewReader(m.Body) hdr, hdrErr := textproto.ReadHeader(br) if hdrErr != nil { return mbox.fillMetadataOnly(fetched, m, items), nil } for _, item := range items { switch item { case imap.FetchEnvelope: fetched.Envelope, _ = backendutil.FetchEnvelope(hdr) case imap.FetchBody, imap.FetchBodyStructure: fetched.BodyStructure, _ = backendutil.FetchBodyStructure(hdr, br, item == imap.FetchBodyStructure) case imap.FetchRFC822Size: fetched.Size = uint32(m.Size) case imap.FetchInternalDate: fetched.InternalDate = m.Date case imap.FetchFlags: fetched.Flags = mbox.formatFlags(m) default: if section, err := imap.ParseBodySectionName(item); err == nil { l, _ := backendutil.FetchBodySection(hdr, br, section) fetched.Body[section] = l } } } return fetched, nil } func (mbox *Mailbox) needsBody(items []imap.FetchItem) bool { for _, item := range items { switch item { case imap.FetchEnvelope, imap.FetchBody, imap.FetchBodyStructure: return true default: if _, err := imap.ParseBodySectionName(item); err == nil { return true } } } return false } func (mbox *Mailbox) fillMetadataOnly(dest *imap.Message, m *types.Mail, items []imap.FetchItem) *imap.Message { for _, item := range items { switch item { case imap.FetchFlags: dest.Flags = mbox.formatFlags(m) case imap.FetchInternalDate: dest.InternalDate = m.Date case imap.FetchRFC822Size: dest.Size = uint32(m.Size) } } return dest } func (mbox *Mailbox) CreateMessage(flags []string, date time.Time, body imap.Literal) error { id, err := mbox.storage.MailCreate(mbox.user.username, mbox.name, body, body.Len()) if err != nil { return err } seen, answered, flagged, deleted := mbox.parseFlags(flags) return mbox.storage.MailUpdateFlags(mbox.user.username, mbox.name, id, seen, answered, flagged, deleted) } func (mbox *Mailbox) CopyMessages(uid bool, seqSet *imap.SeqSet, destName string) error { pk := mbox.user.username ids, err := mbox.getIDsFromSeqSet(uid, seqSet) if err != nil { return err } for _, id := range ids { _, mail, err := mbox.storage.MailSelect(pk, mbox.name, int(id)) if err != nil { continue } newID, err := mbox.storage.MailCreate(pk, destName, mail.Body, int(mail.Size)) mail.Body.Close() if err == nil { _ = mbox.storage.MailUpdateFlags(pk, destName, newID, mail.Seen, mail.Answered, mail.Flagged, mail.Deleted) } } return nil } func (mbox *Mailbox) UpdateMessagesFlags(uid bool, seqSet *imap.SeqSet, op imap.FlagsOp, flags []string) error { pk := mbox.user.username ids, err := mbox.getIDsFromSeqSet(uid, seqSet) if err != nil { return err } for _, id := range ids { _, mail, err := mbox.storage.MailSelect(pk, mbox.name, int(id)) if err != nil { continue } mail.Body.Close() // Close body. Read Flags seen, ans, flg, del := mbox.calculateNewFlags(mail, op, flags) _ = mbox.storage.MailUpdateFlags(pk, mbox.name, int(id), seen, ans, flg, del) } return nil } // calculateNewFlags выносит логику битовых (флаговых) операций из общего цикла func (mbox *Mailbox) calculateNewFlags(mail *types.Mail, op imap.FlagsOp, flags []string) (s, a, f, d bool) { s, a, f, d = mail.Seen, mail.Answered, mail.Flagged, mail.Deleted ns, na, nf, nd := mbox.parseFlags(flags) switch op { case imap.SetFlags: s, a, f, d = ns, na, nf, nd case imap.AddFlags: if ns { s = true } if na { a = true } if nf { f = true } if nd { d = true } case imap.RemoveFlags: if ns { s = false } if na { a = false } if nf { f = false } if nd { d = false } } return } func (mbox *Mailbox) parseFlags(flags []string) (s, a, f, d bool) { for _, flag := range flags { switch flag { case "\\Seen": s = true case "\\Answered": a = true case "\\Flagged": f = true case "\\Deleted": d = true } } return } func (mbox *Mailbox) formatFlags(mail *types.Mail) []string { f := []string{} if mail.Seen { f = append(f, "\\Seen") } if mail.Answered { f = append(f, "\\Answered") } if mail.Flagged { f = append(f, "\\Flagged") } if mail.Deleted { f = append(f, "\\Deleted") } return f } func (mbox *Mailbox) getIDsFromSeqSet(uid bool, seqSet *imap.SeqSet) ([]int32, error) { var ids []int32 pk := mbox.user.username for _, set := range seqSet.Set { stop := set.Stop if stop == 0 { // In IMAP 0 = All next, _ := mbox.storage.MailNextID(pk, mbox.name) stop = uint32(next - 1) } for i := set.Start; i <= stop; i++ { if !uid { // Convert uid by db pid, err := mbox.storage.MailIDForSeq(pk, mbox.name, int(i)) if err == nil { ids = append(ids, int32(pid)) } } else { ids = append(ids, int32(i)) } } } return ids, nil } func (mbox *Mailbox) SetSubscribed(subscribed bool) error { return mbox.storage.MailboxSubscribe(mbox.user.username, mbox.name, subscribed) } func (mbox *Mailbox) Check() error { return nil } func (mbox *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error) { return mbox.storage.MailSearch(mbox.user.username, mbox.name) } func (mbox *Mailbox) Expunge() error { return mbox.storage.MailExpunge(mbox.user.username, mbox.name) } func (mbox *Mailbox) MoveMessages(uid bool, seqset *imap.SeqSet, dest string) error { pk := mbox.user.username ids, err := mbox.getIDsFromSeqSet(uid, seqset) if err != nil { return err } for _, id := range ids { if nid, err := mbox.storage.MailMove(pk, mbox.name, int(id), dest); err == nil { // Delete mail from Outbox before send in Queue if mbox.name == "Outbox" { _ = mbox.storage.QueueDeleteDestinationForID(mbox.user.Username(), "Outbox", int(nid)) } } } return nil }