Replace hand-rolled IMAP with go-imap

This commit is contained in:
DonVoo 2026-07-14 05:37:49 +02:00
parent 21aa6e39e1
commit 253435d87f
7 changed files with 449 additions and 346 deletions

View file

@ -1,41 +1,44 @@
package backend
import "strings"
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
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
Fetch(folder string) ([]RawMessage, error) // Ziel-Audit: geschriebene Mail wiederfinden
Close() error
}
// OpenIMAPTarget verbindet das Ziel per go-imap/v2 (Security wie Quelle, siehe
// a.DstSecurity/a.DstInsecure; meist "tls").
func OpenIMAPTarget(a Account) (TargetMailbox, error) {
c, err := dialIMAP(a.DstHost, a.DstPort, a.DstSecurity, a.DstInsecure)
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); err != nil {
if err := c.Login(a.DstUser, a.DstPass).Wait(); err != nil {
_ = c.Close()
return nil, err
}
return &imapTarget{c: c}, nil
return &imapTarget{mailbox: &imapClientMailbox{c: c}}, nil
}
type imapTarget struct {
c *simpleIMAP
delim string
mailbox *imapClientMailbox
delim string
}
func (t *imapTarget) Folders() ([]TargetFolder, error) {
boxes, err := t.c.listMailboxes()
boxes, err := t.mailbox.listMailboxes()
if err != nil {
return nil, err
}
@ -63,29 +66,64 @@ func (t *imapTarget) Delim() string {
}
func (t *imapTarget) EnsureFolder(name string) error {
if name == "INBOX" {
name = strings.TrimSpace(name)
if name == "" || strings.EqualFold(name, "INBOX") {
return nil
}
if err := t.c.simple("CREATE " + imapQuote(name)); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "exist") && !strings.Contains(strings.ToLower(err.Error()), "already") {
return err
}
if err := t.ensureParentFolders(name); err != nil {
return err
}
_ = t.c.simple("SUBSCRIBE " + imapQuote(name))
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 {
return t.c.appendMessage(folder, m)
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) Fetch(folder string) ([]RawMessage, error) {
if err := t.c.selectMailbox(folder); err != nil {
return nil, err
}
return t.c.fetchAll()
return t.mailbox.fetchAll(folder)
}
func (t *imapTarget) Close() error {
return t.c.Close()
return t.mailbox.Close()
}