package backend import ( "bytes" "fmt" "html" "net/http" "net/mail" "os" "path/filepath" "strconv" "strings" ) // Der Viewer betrachtet die LOKALEN mbox-Dateien im Browser (Outlook-2013- // Dreispalter): links Konto/Ordner-Baum, Mitte Nachrichtenliste, rechts // Lesebereich. Optional Weiterleiten einzelner Mails per SMTP (09-smtp.go). func viewerHandler(w http.ResponseWriter, r *http.Request) { account := r.URL.Query().Get("account") folder := r.URL.Query().Get("folder") if account != "" && folder != "" { renderMessageList(w, account, folder) return } if r.Header.Get("HX-Request") == "true" { renderViewerTree(w) return } renderShell(w, "", `
Backup-Postfach links waehlen.
`) } func messageHandler(w http.ResponseWriter, r *http.Request) { account := r.URL.Query().Get("account") folder := r.URL.Query().Get("folder") index, err := strconv.Atoi(r.URL.Query().Get("index")) if err != nil { http.Error(w, "bad index", http.StatusBadRequest) return } path, err := mboxPath(account, folder) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } raw, err := ReadMboxMessage(path, index) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } msg, err := mail.ReadMessage(bytes.NewReader(raw)) if err != nil { renderReadPane(w, "(unlesbar)", "", "", string(raw)) return } renderReadPane(w, decodeHeader(msg.Header.Get("Subject")), decodeHeader(msg.Header.Get("From")), decodeHeader(msg.Header.Get("Date")), messageBody(raw), ) } func forwardHandler(w http.ResponseWriter, r *http.Request) { // TODO Codex: GET = Weiterleiten-Formular (An/Betreff/Text), POST = // ForwardMessage() aufrufen. NUR hier kommt SMTP zum Einsatz -- der Umzug // selbst nutzt IMAP APPEND. } func renderViewerTree(w http.ResponseWriter) { w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprint(w, `
Backups
`) entries, err := os.ReadDir(Cfg.MboxRoot) if err != nil { fmt.Fprintf(w, `
%s
`, html.EscapeString(err.Error())) return } found := false for _, entry := range entries { if !entry.IsDir() { continue } account := entry.Name() mboxes, _ := filepath.Glob(filepath.Join(Cfg.MboxRoot, account, "*.mbox")) if len(mboxes) == 0 { continue } found = true fmt.Fprintf(w, `
%s
`, html.EscapeString(account)) for _, path := range mboxes { folder := strings.TrimSuffix(filepath.Base(path), ".mbox") fmt.Fprintf(w, `%s`, urlEsc(account), urlEsc(folder), urlEsc(account), urlEsc(folder), html.EscapeString(folder)) } } if !found { fmt.Fprint(w, `
Noch keine mbox-Backups.
`) } } func renderMessageList(w http.ResponseWriter, account, folder string) { w.Header().Set("Content-Type", "text/html; charset=utf-8") path, err := mboxPath(account, folder) if err != nil { fmt.Fprintf(w, `
Nachrichten
%s
`, html.EscapeString(err.Error())) return } entries, err := ReadMboxList(path) if err != nil { fmt.Fprintf(w, `
Nachrichten
%s
`, html.EscapeString(err.Error())) return } fmt.Fprintf(w, `
%s / %s (%d)
`, html.EscapeString(account), html.EscapeString(folder), len(entries)) for _, entry := range entries { subject := entry.Subject if subject == "" { subject = "(ohne Betreff)" } from := entry.From if from == "" { from = "(ohne Absender)" } fmt.Fprintf(w, `%s%s%s`, urlEsc(account), urlEsc(folder), entry.Index, urlEsc(account), urlEsc(folder), entry.Index, html.EscapeString(from), html.EscapeString(subject), html.EscapeString(entry.Date)) } if len(entries) == 0 { fmt.Fprint(w, `
Keine Nachrichten in dieser mbox.
`) } } func renderReadPane(w http.ResponseWriter, subject, from, date, body string) { w.Header().Set("Content-Type", "text/html; charset=utf-8") if subject == "" { subject = "(ohne Betreff)" } fmt.Fprintf(w, `
Lesebereich

%s

Von: %s
Datum: %s
%s
`, html.EscapeString(subject), html.EscapeString(from), html.EscapeString(date), html.EscapeString(body)) } func mboxPath(account, folder string) (string, error) { if account == "" || folder == "" || strings.Contains(account, "..") || strings.Contains(folder, "..") { return "", fmt.Errorf("ungueltiges Postfach") } root, err := filepath.Abs(Cfg.MboxRoot) if err != nil { return "", err } path, err := filepath.Abs(filepath.Join(root, account, safeMboxName(folder)+".mbox")) if err != nil { return "", err } rel, err := filepath.Rel(root, path) if err != nil || strings.HasPrefix(rel, "..") { return "", fmt.Errorf("ungueltiger mbox-Pfad") } return path, nil } func urlEsc(s string) string { r := strings.NewReplacer("%", "%25", " ", "%20", "&", "%26", "?", "%3F", "#", "%23", "+", "%2B", "/", "%2F") return r.Replace(s) }