fix(PROJ-57): UTF-8-Encoding für Mails mit Nicht-UTF-8-Charset korrigieren

Der Mail-Parser ignorierte das charset-Parameter aus Content-Type und
interpretierte Bytes immer als UTF-8, wodurch iso-8859-1/windows-1252
kodierte Mails (z.B. mit Umlauten) als Mojibake gespeichert wurden.
Zusätzlich fehlte das Charset für die Manticore-MySQL-Verbindung und
der charset-Parameter im JSON-Response-Header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-06-24 22:47:00 +02:00
co-authored by Claude Sonnet 4.6
parent 69c120a268
commit 76655f78a2
11 changed files with 34 additions and 10 deletions
+24
View File
@@ -11,6 +11,8 @@ import (
"net/mail"
"strings"
"time"
"golang.org/x/text/encoding/htmlindex"
)
// Attachment represents a MIME attachment in a parsed email.
@@ -184,6 +186,7 @@ func Parse(raw []byte) (*ParsedMail, error) {
} else {
body, _ := io.ReadAll(msg.Body)
decoded := decodeBody(body, msg.Header.Get("Content-Transfer-Encoding"))
decoded = decodeCharset(decoded, params["charset"])
if strings.Contains(mediaType, "html") {
pm.HTMLBody = string(decoded)
} else {
@@ -216,6 +219,9 @@ func parseMultipart(pm *ParsedMail, body io.Reader, boundary string) error {
data, _ := io.ReadAll(part)
cte := part.Header.Get("Content-Transfer-Encoding")
decoded := decodeBody(data, cte)
if strings.Contains(mediaType, "text/") {
decoded = decodeCharset(decoded, params["charset"])
}
// Check disposition for attachment
disp := part.Header.Get("Content-Disposition")
@@ -254,6 +260,24 @@ func parseMultipart(pm *ParsedMail, body io.Reader, boundary string) error {
return nil
}
// decodeCharset converts data from the declared MIME charset to UTF-8.
// Empty charset or "utf-8"/"us-ascii" are passed through unchanged.
func decodeCharset(data []byte, charset string) []byte {
charset = strings.ToLower(strings.TrimSpace(charset))
if charset == "" || charset == "utf-8" || charset == "us-ascii" || charset == "ascii" {
return data
}
enc, err := htmlindex.Get(charset)
if err != nil {
return data
}
decoded, err := enc.NewDecoder().Bytes(data)
if err != nil {
return data
}
return decoded
}
// decodeBody decodes Content-Transfer-Encoding if needed.
func decodeBody(data []byte, cte string) []byte {
switch strings.ToLower(strings.TrimSpace(cte)) {