mirror of
https://github.com/neilalexander/yggmail.git
synced 2026-05-20 10:16:29 +03:00
Initial commit
This commit is contained in:
293
internal/imapserver/mailbox.go
Normal file
293
internal/imapserver/mailbox.go
Normal file
@@ -0,0 +1,293 @@
|
||||
package imapserver
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend/backendutil"
|
||||
"github.com/emersion/go-message/textproto"
|
||||
)
|
||||
|
||||
type Mailbox struct {
|
||||
backend *Backend
|
||||
name string
|
||||
user *User
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) getIDsFromSeqSet(uid bool, seqSet *imap.SeqSet) ([]int32, error) {
|
||||
var ids []int32
|
||||
for _, set := range seqSet.Set {
|
||||
for i := set.Start; i <= set.Stop; i++ {
|
||||
if !uid {
|
||||
pid, err := mbox.backend.Storage.MailIDForSeq(mbox.user.username, mbox.name, int(i))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mbox.backend.Storage.MailIDForSeq: %w", err)
|
||||
}
|
||||
ids = append(ids, int32(pid))
|
||||
} else {
|
||||
ids = append(ids, int32(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) Name() string {
|
||||
return mbox.name
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) Info() (*imap.MailboxInfo, error) {
|
||||
info := &imap.MailboxInfo{
|
||||
Attributes: []string{},
|
||||
Delimiter: "/",
|
||||
Name: mbox.name,
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, error) {
|
||||
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, err := mbox.backend.Storage.MailCount(mbox.user.username, mbox.name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mbox.backend.Storage.MailCount: %w", err)
|
||||
}
|
||||
status.Messages = uint32(count)
|
||||
|
||||
case imap.StatusUidNext:
|
||||
id, err := mbox.backend.Storage.MailNextID(mbox.user.username, mbox.name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mbox.backend.Storage.MailNextID: %w", err)
|
||||
}
|
||||
status.UidNext = uint32(id)
|
||||
|
||||
case imap.StatusUidValidity:
|
||||
status.UidValidity = 1
|
||||
|
||||
case imap.StatusRecent:
|
||||
status.Recent = 0 // TODO
|
||||
|
||||
case imap.StatusUnseen:
|
||||
unseen, err := mbox.backend.Storage.MailUnseen(mbox.user.username, mbox.name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mbox.backend.Storage.MailUnseen: %w", err)
|
||||
}
|
||||
status.Unseen = uint32(unseen)
|
||||
}
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) SetSubscribed(subscribed bool) error {
|
||||
return mbox.backend.Storage.MailboxSubscribe(mbox.user.username, mbox.name, subscribed)
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) Check() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) ListMessages(uid bool, seqSet *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error {
|
||||
defer close(ch)
|
||||
|
||||
ids, err := mbox.getIDsFromSeqSet(uid, seqSet)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to get IDs from sequences:", err)
|
||||
return fmt.Errorf("mbox.getIDsFromSeqSet: %w", err)
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
mseq, mid, body, seen, answered, flagged, deleted, datetime, err := mbox.backend.Storage.MailSelect(mbox.user.username, mbox.name, int(id))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fetched := imap.NewMessage(uint32(id)+1, items)
|
||||
fetched.SeqNum = uint32(mseq)
|
||||
fetched.Uid = uint32(mid)
|
||||
|
||||
get := func() (io.Reader, textproto.Header, error) {
|
||||
bodyreader := bufio.NewReader(bytes.NewReader(body))
|
||||
hdr, err := textproto.ReadHeader(bodyreader)
|
||||
if err != nil {
|
||||
return nil, textproto.Header{}, fmt.Errorf("textproto.ReadHeader: %w", err)
|
||||
}
|
||||
return bodyreader, hdr, err
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
switch item {
|
||||
case imap.FetchEnvelope:
|
||||
_, hdr, err := get()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if fetched.Envelope, err = backendutil.FetchEnvelope(hdr); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
case imap.FetchBody, imap.FetchBodyStructure:
|
||||
bodyreader, hdr, err := get()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if fetched.BodyStructure, err = backendutil.FetchBodyStructure(hdr, bodyreader, item == imap.FetchBodyStructure); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
case imap.FetchFlags:
|
||||
fetched.Flags = []string{}
|
||||
if seen {
|
||||
fetched.Flags = append(fetched.Flags, "\\Seen")
|
||||
}
|
||||
if answered {
|
||||
fetched.Flags = append(fetched.Flags, "\\Answered")
|
||||
}
|
||||
if flagged {
|
||||
fetched.Flags = append(fetched.Flags, "\\Flagged")
|
||||
}
|
||||
if deleted {
|
||||
fetched.Flags = append(fetched.Flags, "\\Deleted")
|
||||
}
|
||||
|
||||
case imap.FetchInternalDate:
|
||||
fetched.InternalDate = datetime
|
||||
|
||||
case imap.FetchRFC822Size:
|
||||
fetched.Size = uint32(len(body))
|
||||
|
||||
case imap.FetchUid:
|
||||
fetched.Uid = uint32(id)
|
||||
|
||||
default:
|
||||
section, err := imap.ParseBodySectionName(item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
bodyreader, hdr, err := get()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
l, err := backendutil.FetchBodySection(hdr, bodyreader, section)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fetched.Body[section] = l
|
||||
}
|
||||
}
|
||||
|
||||
ch <- fetched
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error) {
|
||||
return mbox.backend.Storage.MailSearch(mbox.user.username, mbox.name)
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) CreateMessage(flags []string, date time.Time, body imap.Literal) error {
|
||||
b, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b.ReadFrom: %w", err)
|
||||
}
|
||||
id, err := mbox.backend.Storage.MailCreate(mbox.user.username, mbox.name, b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mbox.backend.Storage.MailCreate: %w", err)
|
||||
}
|
||||
for _, flag := range flags {
|
||||
var seen, answered, flagged, deleted bool
|
||||
switch flag {
|
||||
case "\\Seen":
|
||||
seen = true
|
||||
case "\\Answered":
|
||||
answered = true
|
||||
case "\\Flagged":
|
||||
flagged = true
|
||||
case "\\Deleted":
|
||||
deleted = true
|
||||
}
|
||||
if err := mbox.backend.Storage.MailUpdateFlags(
|
||||
mbox.user.username, mbox.name, id, seen, answered, flagged, deleted,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) UpdateMessagesFlags(uid bool, seqSet *imap.SeqSet, op imap.FlagsOp, flags []string) error {
|
||||
ids, err := mbox.getIDsFromSeqSet(uid, seqSet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mbox.getIDsFromSeqSet: %w", err)
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
var seen, answered, flagged, deleted bool
|
||||
var mid int
|
||||
if op != imap.SetFlags {
|
||||
var err error
|
||||
_, mid, _, seen, answered, flagged, deleted, _, err = mbox.backend.Storage.MailSelect(mbox.user.username, mbox.name, int(id))
|
||||
if err != nil {
|
||||
return fmt.Errorf("mbox.backend.Storage.MailSelect: %w", err)
|
||||
}
|
||||
}
|
||||
for _, flag := range flags {
|
||||
switch flag {
|
||||
case "\\Seen":
|
||||
seen = op != imap.RemoveFlags
|
||||
case "\\Answered":
|
||||
answered = op != imap.RemoveFlags
|
||||
case "\\Flagged":
|
||||
flagged = op != imap.RemoveFlags
|
||||
case "\\Deleted":
|
||||
deleted = op != imap.RemoveFlags
|
||||
}
|
||||
}
|
||||
|
||||
if err := mbox.backend.Storage.MailUpdateFlags(
|
||||
mbox.user.username, mbox.name, int(mid), seen, answered, flagged, deleted,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) CopyMessages(uid bool, seqSet *imap.SeqSet, destName string) error {
|
||||
ids, err := mbox.getIDsFromSeqSet(uid, seqSet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mbox.getIDsFromSeqSet: %w", err)
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
_, _, body, seen, answered, flagged, deleted, _, err := mbox.backend.Storage.MailSelect(mbox.user.username, mbox.name, int(id))
|
||||
if err != nil {
|
||||
return fmt.Errorf("mbox.backend.Storage.MailSelect: %w", err)
|
||||
}
|
||||
pid, err := mbox.backend.Storage.MailCreate(mbox.user.username, destName, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mbox.backend.Storage.MailCreate: %w", err)
|
||||
}
|
||||
if err = mbox.backend.Storage.MailUpdateFlags(mbox.user.username, mbox.name, pid, seen, answered, flagged, deleted); err != nil {
|
||||
return fmt.Errorf("mbox.backend.Storage.MailUpdateFlags: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) Expunge() error {
|
||||
return mbox.backend.Storage.MailExpunge(mbox.user.username, mbox.name)
|
||||
}
|
||||
Reference in New Issue
Block a user