# PROJ-85: Fix undeklarierte 8-Bit-Zeichen in Header/Body ohne Encoded-Word ## Status: Deployed **Created:** 2026-08-06 **Last Updated:** 2026-08-06 ## Kontext Anlass war eine gemeldete Mojibake-Mail ("Grü?e" statt "Grüße"). Diagnose ergab: diese konkrete Mail ist korrekt archiviert — der Fehler (`=3F` statt `=DC` im Original-Encoded-Word) kam bereits so vom Absender (Amazon, 2018, DKIM-signiert) und darf laut GoBD nicht nachträglich verändert werden. Beim Scan wurde daneben aber ein echter archivmail-Bug gefunden, verschieden von PROJ-84: Header (insbesondere Subject) mit rohen 8-Bit-Bytes **ohne** RFC-2047-Encoded-Word-Syntax (technisch RFC-5322-widrig, in freier Wildbahn bei Marketing-/Newsletter-Systemen üblich) wurden nicht auf ihr tatsächliches Charset geprüft. `decodeCharset()` hatte dieselbe Lücke für den Body bei fehlendem/leerem Content-Type. Ergebnis: ungültige UTF-8-Bytes landeten unverändert in `emails.subject` (Postgres, DB-Encoding `SQL_ASCII`, akzeptiert das klaglos) und im Manticore-Index. ## Dependencies - Ergänzt PROJ-84 (Fix MIME-Header-Charset-Dekodierung) — andere Ursache (kein Encoded-Word-Marker vorhanden), daher eigenes Ticket statt Erweiterung - Nutzt dieselbe Backfill-Infrastruktur (`fix-subjects`-Kommando, Audit-Log) ## User Stories - Als User will ich, dass Betreffs mit undeklarierten 8-Bit-Sonderzeichen (ä/ö/ü/ß/„–"/„®" etc.) korrekt lesbar sind, wenn der Absender kein valides Encoded-Word genutzt hat. - Als User will ich, dass auch der Mail-Body korrekt dargestellt/durchsuchbar ist, wenn das deklarierte oder fehlende Charset nicht zu den tatsächlichen Bytes passt. - Als Auditor will ich, dass eine Reparatur klar von echter Absenderseiten-Korruption unterschieden wird — Mails, die der Absender bereits fehlerhaft verschickt hat (wie das Encoded-Word-Beispiel), werden NICHT verändert. ## Acceptance Criteria - [x] `RepairUTF8`/`RepairUTF8Bytes` erkennt Bytes außerhalb gültiger UTF-8-Sequenzen und versucht Windows-1252-Fallback-Dekodierung, bytegenau (nicht bufferweise) — gültiges UTF-8 bleibt unverändert - [x] `decodeMIMEHeader()` wendet die Reparatur auf das Ergebnis an (inkl. Fehlerpfad) - [x] Body-Decoding (Single-Part und Multipart, alle Content-Type-Fehlerpfade) wendet dieselbe Reparatur an, außer bei binären Attachments (`Attachment.Data` bleibt byte-exakt) - [x] `fix-subjects`-Kommando erkennt zusätzlich zu Encoded-Word-Fällen (PROJ-84) auch rohe 8-Bit-Subjects ohne Encoded-Word-Marker (`NeedsCharsetRepair`) - [x] Tests decken ab: gültiges UTF-8 bleibt unangetastet, Windows-1252-Sonderzeichen (ä/ö/ü/ß/–/®) werden korrekt repariert - [x] `--apply` auf 132 durchgeführt (54/54 aktualisiert, 0 Fehler), danach `archivmail reindex` für Body-Korrektur (User-Gegenprobe bestätigt, 2026-08-06) - [x] `--apply` + `reindex` auf 131 (Produktiv) durchgeführt, ohne Fehler (2026-08-06) ## Edge Cases - Encoded-Word mit RFC-5322-widriger Syntax (Leerzeichen im codierten Teil) → bleibt unverändert, als „undecodable" gezählt, nicht Teil dieses Fixes (schon in PROJ-84 als Grenzfall dokumentiert) - Mail, bei der der Absender selbst bereits fehlerhaft kodiert hat (z.B. `=3F` statt korrektem Byte im Encoded-Word) → wird NICHT repariert, da das die archivierte Originalnachricht selbst verfälschen würde (GoBD-Grundsatz: Archiv bildet ab, was ankam, nicht was gemeint war) - Binäre Attachments (z.B. CSV mit Windows-1252) → `Attachment.Data` bleibt byte-exakt, keine Reparatur, sonst wäre der heruntergeladene Anhang verändert - Text, der zufällig wie gültiges UTF-8 aussieht, aber eigentlich Windows-1252 ist → nicht erkennbar/nicht behandelt (Kollisionsfall, in der Praxis selten und nicht zuverlässig unterscheidbar) ## Technical Requirements (optional) - Kein neues Datenbankschema - Postgres-DB-Encoding bleibt `SQL_ASCII` (Bestand, nicht Teil dieses Fixes) — Nebenbefund, der erklärt, warum ungültige Bytes bisher klaglos angenommen wurden - Body-Korrektur wirkt erst nach zusätzlichem `archivmail reindex`-Lauf (Body liegt nur in Manticore, nicht in Postgres) --- ## Tech Design (Solution Architect) Direkt umgesetzt ohne vorgelagerte Architektur-Phase — Bugfix + Backfill-Erweiterung auf bestehender Infrastruktur (PROJ-84), kein neues UI, kein neues Datenmodell. ## Implementation Notes (Backend, 2026-08-06) Neue/geänderte Dateien: - `pkg/mailparser/charset_repair.go` (neu) — `RepairUTF8()`, `RepairUTF8Bytes()`, `NeedsCharsetRepair()`. Strategie: erst UTF-8-Validität prüfen, nur für Bytes, die nicht Teil einer gültigen UTF-8-Sequenz sein können, Windows-1252-Fallback (`golang.org/x/text/encoding/charmap`) - `pkg/mailparser/charset_repair_test.go` (neu) — 12 Testfälle - `pkg/mailparser/parser.go` — `decodeMIMEHeader()`-Ergebnis, Single-Part-Body, Multipart-Text-/HTML-Buffer, Content-Type-Fehlerpfad laufen durch die Reparatur; `Attachment.Data` bewusst ausgenommen - `internal/storage/subject_charset_backfill.go` (neu) — `ListNonASCIISubjects()` - `cmd/archivmail/cmd_fix_subjects_candidates.go` (neu) — merged/dedupliziert Encoded-Word- und Charset-Repair-Kandidaten - `cmd/archivmail/cmd_fix_subjects.go` — Filter erweitert auf `HasEncodedWord || NeedsCharsetRepair` Stolperstein auf dem Server: Prefilter `octet_length(subject) <> length(subject)` liefert in einer `SQL_ASCII`-DB immer 0 Treffer, weil `length()` dort Bytes zählt. Ersetzt durch `subject ~ '[^[:ascii:]]'`. Messung auf 192.168.1.132 (Dry-Run, read-only): 21.004 geprüfte Kandidaten (Encoded-Word- + Charset-Repair-Muster kombiniert), 54 zusätzliche Betreffs durch Charset-Repair betroffen (alle Tenant 3), 0 Fehlklassifikationen. Body-Verifikation über alle 52.961 Mails mit gepatchtem Parser: `parsedSubjectInvalidUTF8: 54 → 0`, `bodyInvalidUTF8: 219 → 0`. Build, `go vet`, `go test ./pkg/mailparser/` auf 132 grün. `--apply` noch nicht ausgeführt. ## QA Test Results _To be added by /qa_ ## Deployment _To be added by /deploy_