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
68 lines
5.9 KiB
Markdown
68 lines
5.9 KiB
Markdown
# PROJ-85: Fix undeklarierte 8-Bit-Zeichen in Header/Body ohne Encoded-Word
|
||
|
||
## Status: In Review
|
||
**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
|
||
- [ ] `--apply` auf 132 durchgeführt, danach `archivmail reindex` für Body-Korrektur
|
||
- [ ] `--apply` + `reindex` auf 131 (Produktiv), Umfang dort separat messen (131 ≠ 132, siehe PROJ-84-Erfahrung: 0 vs. 543 Kandidaten)
|
||
|
||
## 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)
|
||
|
||
---
|
||
<!-- Sections below are added by subsequent skills -->
|
||
|
||
## 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_
|