Initial Email-Forwarder scaffold
This commit is contained in:
commit
91e52aecd0
22 changed files with 1007 additions and 0 deletions
112
backend/11-folders.go
Normal file
112
backend/11-folders.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package backend
|
||||
|
||||
import "strings"
|
||||
|
||||
// Dieses Modul löst zwei Umzugs-Fallen:
|
||||
//
|
||||
// 1. Standardordner NICHT doppeln. Quelle "Gesendete Objekte" darf am Ziel
|
||||
// nicht neben "Gesendete Elemente" als Zweitordner landen. Wir bilden jeden
|
||||
// Ordner auf eine kanonische ROLLE ab und schreiben in den vorhandenen
|
||||
// Rollen-Ordner des Ziels — statt einen neuen anzulegen.
|
||||
// 2. Hierarchie-Trenner übersetzen. Quelle nutzt evtl. "." (INBOX.Sent.2023),
|
||||
// Ziel "/" — Pfad muss umgehängt werden. Sonderzeichen (Umlaute) sind auf
|
||||
// der Leitung modified-UTF-7; go-imap dekodiert/kodiert das — wir arbeiten
|
||||
// hier IMMER mit dem dekodierten Klartext-Namen, nie doppelt kodieren.
|
||||
|
||||
type FolderRole string
|
||||
|
||||
const (
|
||||
RoleNone FolderRole = ""
|
||||
RoleInbox FolderRole = "inbox"
|
||||
RoleSent FolderRole = "sent"
|
||||
RoleDrafts FolderRole = "drafts"
|
||||
RoleJunk FolderRole = "junk"
|
||||
RoleTrash FolderRole = "trash"
|
||||
RoleArchive FolderRole = "archive"
|
||||
RoleOutbox FolderRole = "outbox"
|
||||
)
|
||||
|
||||
// specialUseAttr = IMAP SPECIAL-USE (RFC 6154) -> Rolle. Erste Wahl, weil
|
||||
// sprachunabhängig. Outbox/Postausgang hat kein Standard-Flag (Client-lokal).
|
||||
var specialUseAttr = map[string]FolderRole{
|
||||
"\\Sent": RoleSent,
|
||||
"\\Drafts": RoleDrafts,
|
||||
"\\Junk": RoleJunk,
|
||||
"\\Trash": RoleTrash,
|
||||
"\\Archive": RoleArchive,
|
||||
}
|
||||
|
||||
// nameAliases = Fallback, wenn der Alt-Server kein SPECIAL-USE meldet. Vergleich
|
||||
// case-insensitiv gegen das LETZTE Pfadsegment (ohne "INBOX."-Präfix).
|
||||
// Mehrsprachig, weil Provider lokalisiert benennen.
|
||||
var nameAliases = map[FolderRole][]string{
|
||||
RoleInbox: {"inbox", "posteingang"},
|
||||
RoleSent: {"sent", "sent items", "sent messages", "gesendet", "gesendete", "gesendete objekte", "gesendete elemente", "gesendete e-mails"},
|
||||
RoleDrafts: {"drafts", "draft", "entwurf", "entwürfe", "entwuerfe"},
|
||||
RoleJunk: {"junk", "junk e-mail", "junk-e-mail", "spam", "bulk mail", "werbung"},
|
||||
RoleTrash: {"trash", "deleted", "deleted items", "deleted messages", "papierkorb", "gelöscht", "gelöschte", "gelöschte objekte", "gelöschte elemente", "müll"},
|
||||
RoleArchive: {"archive", "archiv", "archived", "archiviert"},
|
||||
RoleOutbox: {"outbox", "postausgang"},
|
||||
}
|
||||
|
||||
// RoleFromAttrs bestimmt die Rolle aus den SPECIAL-USE-Attributen (bevorzugt).
|
||||
func RoleFromAttrs(attrs []string) FolderRole {
|
||||
for _, a := range attrs {
|
||||
if role, ok := specialUseAttr[a]; ok {
|
||||
return role
|
||||
}
|
||||
}
|
||||
return RoleNone
|
||||
}
|
||||
|
||||
// RoleFromName ist der Fallback über den (dekodierten) Ordnernamen. delim ist
|
||||
// der Hierarchie-Trenner des jeweiligen Servers.
|
||||
func RoleFromName(name, delim string) FolderRole {
|
||||
leaf := name
|
||||
if delim != "" {
|
||||
parts := strings.Split(name, delim)
|
||||
leaf = parts[len(parts)-1]
|
||||
}
|
||||
if strings.EqualFold(strings.TrimPrefix(name, "INBOX"+delim), "") || strings.EqualFold(name, "INBOX") {
|
||||
return RoleInbox
|
||||
}
|
||||
lc := strings.ToLower(strings.TrimSpace(leaf))
|
||||
for role, aliases := range nameAliases {
|
||||
for _, alias := range aliases {
|
||||
if lc == alias {
|
||||
return role
|
||||
}
|
||||
}
|
||||
}
|
||||
return RoleNone
|
||||
}
|
||||
|
||||
// Role kombiniert beide: erst SPECIAL-USE, dann Namensliste.
|
||||
func Role(attrs []string, name, delim string) FolderRole {
|
||||
if r := RoleFromAttrs(attrs); r != RoleNone {
|
||||
return r
|
||||
}
|
||||
return RoleFromName(name, delim)
|
||||
}
|
||||
|
||||
// TargetFolder beschreibt einen am Ziel existierenden Ordner (für die Rollen-
|
||||
// Zuordnung beim Umzug).
|
||||
type TargetFolder struct {
|
||||
Name string
|
||||
Attrs []string
|
||||
}
|
||||
|
||||
// MapSourceToTarget entscheidet, in welchen ZIEL-Ordner eine Quell-Mailbox
|
||||
// wandert:
|
||||
// - manuelles folder_map hat Vorrang (übergibt override != "");
|
||||
// - hat die Quelle eine Rolle und existiert am Ziel ein Ordner gleicher Rolle
|
||||
// -> dessen Name (kein Neuanlegen, keine Dublette);
|
||||
// - sonst Pfad vom Quell- auf den Ziel-Trenner umhängen und (ggf. neu) anlegen.
|
||||
//
|
||||
// TODO Codex: Implementieren; srcDelim/dstDelim aus dem jeweiligen Server (LIST-
|
||||
// Antwort). override kommt aus folder_map.
|
||||
func MapSourceToTarget(srcName string, srcAttrs []string, srcDelim, dstDelim string,
|
||||
targets []TargetFolder, override string) string {
|
||||
// TODO Codex.
|
||||
return ""
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue