diff --git a/backend/00-router.go b/backend/00-router.go index 692c87e..8c716b1 100644 --- a/backend/00-router.go +++ b/backend/00-router.go @@ -1167,10 +1167,10 @@ func renderSourceMailboxRead(account Account, folder string, index int, messages msg, err := mail.ReadMessage(bytes.NewReader(raw)) var b strings.Builder if err != nil { - renderReadPane(&b, "Quelle", account.Name, folder, "", index, "(unlesbar)", "", "", string(raw)) + renderMailboxReadPane(&b, "Quelle", account.Name, folder, index, "(unlesbar)", "", "", string(raw)) return b.String() } - renderReadPane(&b, "Quelle", account.Name, folder, "", index, + renderMailboxReadPane(&b, "Quelle", account.Name, folder, index, decodeHeader(msg.Header.Get("Subject")), decodeHeader(msg.Header.Get("From")), decodeHeader(msg.Header.Get("Date")), @@ -1354,10 +1354,10 @@ func renderTargetMailboxRead(account Account, folder string, index int, messages msg, err := mail.ReadMessage(bytes.NewReader(raw)) var b strings.Builder if err != nil { - renderReadPane(&b, "Ziel", account.Name, folder, "", index, "(unlesbar)", "", "", string(raw)) + renderMailboxReadPane(&b, "Ziel", account.Name, folder, index, "(unlesbar)", "", "", string(raw)) return b.String() } - renderReadPane(&b, "Ziel", account.Name, folder, "", index, + renderMailboxReadPane(&b, "Ziel", account.Name, folder, index, decodeHeader(msg.Header.Get("Subject")), decodeHeader(msg.Header.Get("From")), decodeHeader(msg.Header.Get("Date")), @@ -1366,6 +1366,15 @@ func renderTargetMailboxRead(account Account, folder string, index int, messages return b.String() } +func renderMailboxReadPane(w io.Writer, title, account, folder string, index int, subject, from, date, body string) { + if subject == "" { + subject = "(ohne Betreff)" + } + fmt.Fprintf(w, `
%s
`, html.EscapeString(title)) + fmt.Fprintf(w, `

%s

Von: %s
Datum: %s
Postfach: %s / %s / #%d
%s
`, + html.EscapeString(subject), html.EscapeString(from), html.EscapeString(date), html.EscapeString(account), html.EscapeString(folder), index+1, html.EscapeString(body)) +} + func accountsByTarget(accounts []Account) []Account { seen := map[string]bool{} out := make([]Account, 0, len(accounts)) diff --git a/backend/06-mbox.go b/backend/06-mbox.go index 712ed4f..6bbc50e 100644 --- a/backend/06-mbox.go +++ b/backend/06-mbox.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base64" "fmt" + "html" "io" "mime" "mime/multipart" @@ -205,9 +206,127 @@ func extractTextBody(h headerGetter, body []byte) string { } return "" } + if strings.HasPrefix(mediaType, "text/html") || looksLikeHTML(decoded) { + return htmlToText(string(decoded)) + } return string(decoded) } +func looksLikeHTML(body []byte) bool { + s := strings.ToLower(strings.TrimSpace(string(body))) + return strings.HasPrefix(s, "': + inTag = false + default: + if inTag { + continue + } + if r == '\n' { + b.WriteByte('\n') + lastSpace = true + continue + } + if r == '\r' || r == '\t' || r == ' ' { + if !lastSpace { + b.WriteByte(' ') + lastSpace = true + } + continue + } + b.WriteRune(r) + lastSpace = false + } + } + text := html.UnescapeString(b.String()) + lines := strings.Split(text, "\n") + out := make([]string, 0, len(lines)) + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" { + out = append(out, line) + } + } + if len(out) == 0 { + return strings.TrimSpace(text) + } + return strings.Join(out, "\n") +} + +func markHTMLBreaks(s string) string { + repls := []struct { + Needle string + With string + }{ + {"
", "\n"}, + {"
", "\n"}, + {"
", "\n"}, + {"

", "\n"}, + {"", "\n"}, + {"", "\n"}, + {"", " "}, + {"", " "}, + {"", "\n"}, + } + for _, repl := range repls { + s = replaceCaseInsensitive(s, repl.Needle, repl.With) + } + return s +} + +func replaceCaseInsensitive(s, old, new string) string { + var b strings.Builder + lower := strings.ToLower(s) + needle := strings.ToLower(old) + for { + i := strings.Index(lower, needle) + if i < 0 { + b.WriteString(s) + return b.String() + } + b.WriteString(s[:i]) + b.WriteString(new) + cut := i + len(old) + s = s[cut:] + lower = lower[cut:] + } +} + +func stripHTMLBlock(s, tag string) string { + lower := strings.ToLower(s) + open := "<" + tag + close := "" + for { + start := strings.Index(lower, open) + if start < 0 { + return s + } + end := strings.Index(lower[start:], close) + if end < 0 { + return s[:start] + } + end += start + len(close) + s = s[:start] + " " + s[end:] + lower = strings.ToLower(s) + } +} + func decodeTransfer(body []byte, enc string) []byte { switch strings.ToLower(strings.TrimSpace(enc)) { case "quoted-printable":