2021-07-18 23:03:28 +01:00
|
|
|
/*
|
2026-02-14 02:34:09 +05:00
|
|
|
* (Original Copyright (c) 2021 Neil Alexander)
|
2021-07-18 23:03:28 +01:00
|
|
|
*
|
2026-02-14 02:34:09 +05:00
|
|
|
* 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/.
|
2021-07-18 23:03:28 +01:00
|
|
|
*/
|
|
|
|
|
|
2021-07-07 18:15:07 +01:00
|
|
|
package imapserver
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
|
|
|
|
"io"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/emersion/go-imap"
|
|
|
|
|
"github.com/emersion/go-imap/backend/backendutil"
|
|
|
|
|
"github.com/emersion/go-message/textproto"
|
2026-02-14 02:34:09 +05:00
|
|
|
"github.com/neilalexander/yggmail/internal/storage"
|
2021-07-09 22:25:10 +01:00
|
|
|
"github.com/neilalexander/yggmail/internal/storage/types"
|
2026-02-14 02:34:09 +05:00
|
|
|
"github.com/neilalexander/yggmail/internal/utils/e2ee"
|
2021-07-07 18:15:07 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Mailbox struct {
|
2026-02-14 02:34:09 +05:00
|
|
|
storage storage.Storage
|
2021-07-07 18:15:07 +01:00
|
|
|
name string
|
|
|
|
|
user *User
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (mbox *Mailbox) Name() string {
|
|
|
|
|
return mbox.name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (mbox *Mailbox) Info() (*imap.MailboxInfo, error) {
|
2026-02-14 02:34:09 +05:00
|
|
|
return &imap.MailboxInfo{
|
2021-07-07 18:15:07 +01:00
|
|
|
Attributes: []string{},
|
|
|
|
|
Delimiter: "/",
|
|
|
|
|
Name: mbox.name,
|
2026-02-14 02:34:09 +05:00
|
|
|
}, nil
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (mbox *Mailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, error) {
|
2026-02-14 02:34:09 +05:00
|
|
|
pk := mbox.user.username
|
2021-07-07 18:15:07 +01:00
|
|
|
status := imap.NewMailboxStatus(mbox.name, items)
|
2026-02-14 02:34:09 +05:00
|
|
|
status.PermanentFlags = []string{"\\Seen", "\\Answered", "\\Flagged", "\\Deleted"}
|
2021-07-07 18:15:07 +01:00
|
|
|
status.Flags = status.PermanentFlags
|
|
|
|
|
|
|
|
|
|
for _, name := range items {
|
|
|
|
|
switch name {
|
|
|
|
|
case imap.StatusMessages:
|
2026-02-14 02:34:09 +05:00
|
|
|
count, _ := mbox.storage.MailCount(pk, mbox.name)
|
2021-07-07 18:15:07 +01:00
|
|
|
status.Messages = uint32(count)
|
|
|
|
|
case imap.StatusUidNext:
|
2026-02-14 02:34:09 +05:00
|
|
|
id, _ := mbox.storage.MailNextID(pk, mbox.name)
|
2021-07-07 18:15:07 +01:00
|
|
|
status.UidNext = uint32(id)
|
|
|
|
|
case imap.StatusUidValidity:
|
|
|
|
|
status.UidValidity = 1
|
|
|
|
|
case imap.StatusUnseen:
|
2026-02-14 02:34:09 +05:00
|
|
|
unseen, _ := mbox.storage.MailUnseen(pk, mbox.name)
|
2021-07-07 18:15:07 +01:00
|
|
|
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)
|
2026-02-14 02:34:09 +05:00
|
|
|
pk := mbox.user.username
|
2021-07-07 18:15:07 +01:00
|
|
|
|
|
|
|
|
ids, err := mbox.getIDsFromSeqSet(uid, seqSet)
|
|
|
|
|
if err != nil {
|
2026-02-14 02:34:09 +05:00
|
|
|
return err
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, id := range ids {
|
2026-02-14 02:34:09 +05:00
|
|
|
mseq, m, err := mbox.storage.MailSelect(pk, mbox.name, int(id))
|
2021-07-07 18:15:07 +01:00
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
// 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)
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
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
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
msg, err := mbox.fetchMessage(mseq, m, items)
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
ch <- msg
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
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)
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
if !mbox.needsBody(items) || m.Body == nil {
|
|
|
|
|
return mbox.fillMetadataOnly(fetched, m, items), nil
|
|
|
|
|
}
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
defer m.Body.Close()
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
br := bufio.NewReader(m.Body)
|
|
|
|
|
hdr, hdrErr := textproto.ReadHeader(br)
|
|
|
|
|
if hdrErr != nil {
|
|
|
|
|
return mbox.fillMetadataOnly(fetched, m, items), nil
|
|
|
|
|
}
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
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)
|
2021-07-07 18:15:07 +01:00
|
|
|
fetched.Body[section] = l
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
return fetched, nil
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
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
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (mbox *Mailbox) CreateMessage(flags []string, date time.Time, body imap.Literal) error {
|
2026-02-14 02:34:09 +05:00
|
|
|
id, err := mbox.storage.MailCreate(mbox.user.username, mbox.name, body, body.Len())
|
2021-07-07 18:15:07 +01:00
|
|
|
if err != nil {
|
2026-02-14 02:34:09 +05:00
|
|
|
return err
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
2026-02-14 02:34:09 +05:00
|
|
|
|
|
|
|
|
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)
|
2021-07-07 18:15:07 +01:00
|
|
|
if err != nil {
|
2026-02-14 02:34:09 +05:00
|
|
|
return err
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
2026-02-14 02:34:09 +05:00
|
|
|
|
|
|
|
|
for _, id := range ids {
|
|
|
|
|
_, mail, err := mbox.storage.MailSelect(pk, mbox.name, int(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
2026-02-14 02:34:09 +05:00
|
|
|
|
|
|
|
|
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)
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (mbox *Mailbox) UpdateMessagesFlags(uid bool, seqSet *imap.SeqSet, op imap.FlagsOp, flags []string) error {
|
2026-02-14 02:34:09 +05:00
|
|
|
pk := mbox.user.username
|
2021-07-07 18:15:07 +01:00
|
|
|
ids, err := mbox.getIDsFromSeqSet(uid, seqSet)
|
|
|
|
|
if err != nil {
|
2026-02-14 02:34:09 +05:00
|
|
|
return err
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, id := range ids {
|
2026-02-14 02:34:09 +05:00
|
|
|
_, mail, err := mbox.storage.MailSelect(pk, mbox.name, int(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
2026-02-14 02:34:09 +05:00
|
|
|
mail.Body.Close() // Close body. Read Flags
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
seen, ans, flg, del := mbox.calculateNewFlags(mail, op, flags)
|
|
|
|
|
_ = mbox.storage.MailUpdateFlags(pk, mbox.name, int(id), seen, ans, flg, del)
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
// 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
|
|
|
|
|
}
|
2021-07-09 19:38:05 +01:00
|
|
|
}
|
2026-02-14 02:34:09 +05:00
|
|
|
return
|
|
|
|
|
}
|
2021-07-09 19:38:05 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
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
|
|
|
|
|
}
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
2026-02-14 02:34:09 +05:00
|
|
|
return
|
|
|
|
|
}
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
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)
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
2026-02-14 02:34:09 +05:00
|
|
|
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))
|
|
|
|
|
}
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 02:34:09 +05:00
|
|
|
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)
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (mbox *Mailbox) Expunge() error {
|
2026-02-14 02:34:09 +05:00
|
|
|
return mbox.storage.MailExpunge(mbox.user.username, mbox.name)
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
2025-12-02 19:23:49 +00:00
|
|
|
|
|
|
|
|
func (mbox *Mailbox) MoveMessages(uid bool, seqset *imap.SeqSet, dest string) error {
|
2026-02-14 02:34:09 +05:00
|
|
|
pk := mbox.user.username
|
2025-12-02 19:23:49 +00:00
|
|
|
ids, err := mbox.getIDsFromSeqSet(uid, seqset)
|
|
|
|
|
if err != nil {
|
2026-02-14 02:34:09 +05:00
|
|
|
return err
|
2025-12-02 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
for _, id := range ids {
|
2026-02-14 02:34:09 +05:00
|
|
|
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))
|
2025-12-20 14:06:14 +00:00
|
|
|
}
|
2025-12-02 22:34:08 +00:00
|
|
|
}
|
2025-12-02 19:23:49 +00:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|