161 lines
4.2 KiB
Go
161 lines
4.2 KiB
Go
package backend
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/emersion/go-imap/v2"
|
|
)
|
|
|
|
// TargetMailbox = das Ziel-Postfach (neuer Host). Der Umzug schreibt hier per
|
|
// IMAP APPEND hinein -- NICHT per SMTP. APPEND erhaelt Ordner, Flags und
|
|
// Original-Datum; SMTP wuerde alles als "neu/ungelesen" mit falschem Datum
|
|
// zustellen und die Ordnerstruktur zerstoeren.
|
|
type TargetMailbox interface {
|
|
Folders() ([]TargetFolder, error) // vorhandene Ziel-Ordner (fuer Rollen-Zuordnung)
|
|
Delim() string // Hierarchie-Trenner des Ziels
|
|
EnsureFolder(name string) error // anlegen + subscriben, falls fehlt
|
|
Append(folder string, m RawMessage) error // mit m.Flags und m.InternalDate
|
|
Headers(folder string, limit, offset int) ([]MessageHeader, error)
|
|
FetchOne(folder string, uid uint32) (RawMessage, error)
|
|
DeleteUIDs(folder string, uids []uint32) error
|
|
Close() error
|
|
}
|
|
|
|
func OpenIMAPTarget(a Account) (TargetMailbox, error) {
|
|
c, err := dialIMAPClient(a.DstHost, a.DstPort, a.DstSecurity, a.DstInsecure)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := c.Login(a.DstUser, a.DstPass).Wait(); err != nil {
|
|
_ = c.Close()
|
|
return nil, err
|
|
}
|
|
return &imapTarget{mailbox: &imapClientMailbox{c: c}}, nil
|
|
}
|
|
|
|
type imapTarget struct {
|
|
mailbox *imapClientMailbox
|
|
delim string
|
|
}
|
|
|
|
func (t *imapTarget) Folders() ([]TargetFolder, error) {
|
|
boxes, err := t.mailbox.listMailboxes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
folders := make([]TargetFolder, 0, len(boxes))
|
|
for _, box := range boxes {
|
|
if t.delim == "" && box.Delim != "" {
|
|
t.delim = box.Delim
|
|
}
|
|
if hasIMAPAttr(box.Attrs, `\Noselect`) {
|
|
continue
|
|
}
|
|
folders = append(folders, TargetFolder{Name: box.Name, Attrs: box.Attrs})
|
|
}
|
|
if len(folders) == 0 {
|
|
folders = append(folders, TargetFolder{Name: "INBOX"})
|
|
}
|
|
return folders, nil
|
|
}
|
|
|
|
func (t *imapTarget) Delim() string {
|
|
if t.delim != "" {
|
|
return t.delim
|
|
}
|
|
return "/"
|
|
}
|
|
|
|
func (t *imapTarget) EnsureFolder(name string) error {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" || strings.EqualFold(name, "INBOX") {
|
|
return nil
|
|
}
|
|
if err := t.ensureParentFolders(name); err != nil {
|
|
return err
|
|
}
|
|
if err := t.createFolder(name); err != nil {
|
|
return err
|
|
}
|
|
_ = t.mailbox.c.Subscribe(name).Wait()
|
|
return nil
|
|
}
|
|
|
|
func (t *imapTarget) ensureParentFolders(name string) error {
|
|
delim := t.Delim()
|
|
if delim == "" || !strings.Contains(name, delim) {
|
|
return nil
|
|
}
|
|
parts := strings.Split(name, delim)
|
|
for i := 1; i < len(parts); i++ {
|
|
parent := strings.Join(parts[:i], delim)
|
|
if strings.TrimSpace(parent) == "" || strings.EqualFold(parent, "INBOX") {
|
|
continue
|
|
}
|
|
if err := t.createFolder(parent); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *imapTarget) createFolder(name string) error {
|
|
err := t.mailbox.c.Create(name, nil).Wait()
|
|
if err == nil || isAlreadyExistsError(err) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("imap create %q failed: %w", name, err)
|
|
}
|
|
|
|
func (t *imapTarget) Append(folder string, m RawMessage) error {
|
|
opts := &imap.AppendOptions{Flags: stringsToFlags(m.Flags), Time: m.InternalDate}
|
|
cmd := t.mailbox.c.Append(folder, int64(len(m.Body)), opts)
|
|
if _, err := cmd.Write(m.Body); err != nil {
|
|
_ = cmd.Close()
|
|
return err
|
|
}
|
|
if err := cmd.Close(); err != nil {
|
|
return err
|
|
}
|
|
_, err := cmd.Wait()
|
|
return err
|
|
}
|
|
|
|
func (t *imapTarget) Headers(folder string, limit, offset int) ([]MessageHeader, error) {
|
|
return t.mailbox.fetchHeaders(folder, limit, offset)
|
|
}
|
|
|
|
func (t *imapTarget) FetchOne(folder string, uid uint32) (RawMessage, error) {
|
|
return t.mailbox.fetchOne(folder, uid)
|
|
}
|
|
|
|
func (t *imapTarget) DeleteUIDs(folder string, uids []uint32) error {
|
|
if len(uids) == 0 {
|
|
return nil
|
|
}
|
|
if _, err := t.mailbox.c.Select(folder, nil).Wait(); err != nil {
|
|
return err
|
|
}
|
|
uidSet := imap.UIDSetNum(uint32sToUIDs(uids)...)
|
|
flags := &imap.StoreFlags{Op: imap.StoreFlagsAdd, Silent: true, Flags: []imap.Flag{imap.FlagDeleted}}
|
|
if err := t.mailbox.c.Store(uidSet, flags, nil).Close(); err != nil {
|
|
return err
|
|
}
|
|
_, err := t.mailbox.c.UIDExpunge(uidSet).Collect()
|
|
return err
|
|
}
|
|
|
|
func (t *imapTarget) Close() error {
|
|
return t.mailbox.Close()
|
|
}
|
|
|
|
func uint32sToUIDs(values []uint32) []imap.UID {
|
|
out := make([]imap.UID, 0, len(values))
|
|
for _, value := range values {
|
|
if value != 0 {
|
|
out = append(out, imap.UID(value))
|
|
}
|
|
}
|
|
return out
|
|
}
|