Files
archivmail/pkg/mailparser/charset_repair_test.go
sysopsandClaude Sonnet 5 8f46d688a8 feat(PROJ-85): Fix undeklarierte 8-Bit-Zeichen in Header/Body ohne Encoded-Word
Getrennt von PROJ-84: Header (v.a. Subject) mit rohen 8-Bit-Bytes ohne
RFC-2047-Encoded-Word-Syntax wurden nicht auf tatsächliches Charset
geprüft, landeten als ungültiges UTF-8 in emails.subject und Manticore.
Gleiche Lücke bei decodeCharset() für den Body ohne verwertbaren
Content-Type.

RepairUTF8/RepairUTF8Bytes (charset_repair.go): bytegenaue Reparatur,
gültiges UTF-8 bleibt Identität, nur ungültige Byte-Sequenzen fallen
auf Windows-1252 zurück. Attachment.Data bewusst ausgenommen (bleibt
byte-exakt für Downloads). fix-subjects-Kommando erkennt jetzt beide
Fälle (HasEncodedWord || NeedsCharsetRepair).

Verifiziert auf 192.168.1.132: 54 zusätzliche Subject-Fälle, 219
Body-Fälle behoben (bodyInvalidUTF8 219 -> 0). --apply noch nicht
ausgeführt, Body-Korrektur braucht zusätzlich reindex.

Die ursprünglich gemeldete Amazon-Mail bleibt bewusst unverändert:
Encoding-Fehler kam bereits so vom Absender (=3F statt =DC im
Original-Encoded-Word), GoBD verbietet nachträgliche Korrektur
archivierter Originalinhalte.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WapWkrQusDuBMhaN8WyuXB
2026-08-06 20:46:58 +02:00

79 lines
2.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package mailparser
import (
"strings"
"testing"
)
func TestRepairUTF8(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{"pure ascii untouched", "Passwort geaendert", "Passwort geaendert"},
{"valid utf8 untouched", "Passwort geändert ok", "Passwort geändert ok"},
{"windows1252 umlauts", "Passwort ge\xe4ndert", "Passwort geändert"},
{"windows1252 sharp s", "Mini-Fu\xdfball", "Mini-Fußball"},
{"cp1252 en dash 0x96", "Netbook f\xfcr 207 Euro \x96 jetzt", "Netbook für 207 Euro jetzt"},
{"cp1252 registered 0xae", "NVIDIA\xae Karten", "NVIDIA® Karten"},
{"mixed valid utf8 and latin1", "Gr\xfc\xdfe ünd", "Grüße ünd"},
{"empty", "", ""},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := RepairUTF8(tc.in); got != tc.want {
t.Errorf("RepairUTF8(%q) = %q, want %q", tc.in, got, tc.want)
}
})
}
}
func TestNeedsCharsetRepair(t *testing.T) {
if NeedsCharsetRepair("Passwort geändert") {
t.Error("valid UTF-8 must not be flagged for repair")
}
if !NeedsCharsetRepair("Passwort ge\xe4ndert") {
t.Error("raw Latin-1 byte must be flagged for repair")
}
}
// A Subject header with raw 8-bit bytes and no RFC 2047 encoded-word must be
// decoded via the Windows-1252 fallback instead of ending up as invalid UTF-8.
func TestParseRawEightBitSubject(t *testing.T) {
raw := "From: a@example.com\r\n" +
"Subject: Passwort f\xfcr WoltLab ge\xe4ndert\r\n" +
"Content-Type: text/plain\r\n\r\n" +
"Gr\xfc\xdfe\r\n"
pm, err := Parse([]byte(raw))
if err != nil {
t.Fatalf("Parse: %v", err)
}
if pm.Subject != "Passwort für WoltLab geändert" {
t.Errorf("Subject = %q", pm.Subject)
}
if !strings.Contains(pm.TextBody, "Grüße") {
t.Errorf("TextBody = %q", pm.TextBody)
}
}
// A correctly encoded UTF-8 mail must survive the repair unchanged.
func TestParseValidUTF8NotMangled(t *testing.T) {
raw := "From: a@example.com\r\n" +
"Subject: =?UTF-8?Q?Gr=C3=BC=C3=9Fe?=\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n\r\n" +
"Schöne Grüße äöüß\r\n"
pm, err := Parse([]byte(raw))
if err != nil {
t.Fatalf("Parse: %v", err)
}
if pm.Subject != "Grüße" {
t.Errorf("Subject = %q", pm.Subject)
}
if !strings.Contains(pm.TextBody, "Schöne Grüße äöüß") {
t.Errorf("TextBody = %q", pm.TextBody)
}
}