Fix plain mbox reader buffer aliasing

This commit is contained in:
DonVoo 2026-07-15 00:57:16 +02:00
parent e63a513cff
commit 6c4073a01c
3 changed files with 134 additions and 2 deletions

90
bug-mbox-reader.md Normal file
View file

@ -0,0 +1,90 @@
# 🔴 KRITISCH — mbox-Leser liefert falsche Mails (Buffer-Aliasing)
**Schweregrad: hoch.** Der Leser gibt fuer fast jeden Index dieselbe (falsche)
Mail zurueck. Betrifft Vorschau, manuelles Verschieben, Archiv-Export und
Archiv-Dedup.
**Entwarnung vorweg:** Die **Archiv-Dateien auf der Platte sind korrekt.** Der
Schreiber ist ein anderer Pfad und haengt die Rohbytes richtig an. Geprueft:
In `colak@dr-gold.de/INBOX.mbox` steht an Position 537 exakt das, was
`mbox_index` sagt. **Kaputt ist ausschliesslich der Leser.** Es sind keine Daten
verloren.
## Die Ursache — eine Zeile
[`backend/06-mbox.go:441`](backend/06-mbox.go), `readMboxMessagesBytes`:
```go
if inMsg && cur.Len() > 0 {
msgs = append(msgs, bytes.TrimRight(cur.Bytes(), "\n")) // Slice ZEIGT IN cur's Puffer
cur.Reset() // Puffer wird wiederverwendet
}
```
`bytes.Buffer.Bytes()` liefert **keine Kopie**, sondern einen Slice **in den
internen Puffer**. Direkt danach `cur.Reset()` — der Puffer wird fuer die
naechste Mail ueberschrieben. **Alle zuvor angehaengten Slices zeigen auf
denselben Speicher** und tragen am Ende den zuletzt geschriebenen Inhalt.
Der Lehrbuchfehler „`Buffer.Bytes()` + `Reset()` ohne Kopie".
## Der Fix
```go
if inMsg && cur.Len() > 0 {
trimmed := bytes.TrimRight(cur.Bytes(), "\n")
msg := make([]byte, len(trimmed))
copy(msg, trimmed) // <-- ECHTE Kopie
msgs = append(msgs, msg)
cur.Reset()
}
```
(Dasselbe fuer den Abschluss nach der Schleife, falls dort ebenfalls
`cur.Bytes()` ohne Kopie angehaengt wird — bitte pruefen.)
**Besser noch:** `bytes.Buffer` ganz vermeiden und die Nachrichten ueber
Byte-Offsets aus dem Original-Slice schneiden (`b[start:end]`) — dann gibt es
kein Aliasing-Risiko und keine Kopie zu viel.
## Reproduktion (gemessen an `colak@dr-gold.de` / INBOX, 678 Mails)
```
idx | mbox_index sagt | Vorschau liefert
0 | Katalogseite | Angebot: CARRYMATE Transportgriffe FALSCH
1 | Angebot: CARRYMATE Transportgriffe | Baustoff + Metall FALSCH
2 | Schoenes Wochenende | Baustoff + Metall FALSCH
3 | Bestellung '150129' | Baustoff + Metall FALSCH
...
15 | Per E-Mail senden: Bestellschein.doc| Baustoff + Metall FALSCH
```
Ab Index 1 kommt **immer dieselbe** Mail zurueck. Die **Liste** und
`mbox_index` stimmen ueberein — nur der Leser luegt.
## Blast Radius — bitte alle pruefen
`ReadMboxMessage` / `readMboxMessages` haengen an:
| Stelle | Wirkung des Bugs |
|---|---|
| `08-viewer.go:51` | **Vorschau** zeigt die falsche Mail |
| `08-viewer.go:83`, `:335` | dito (Ziel-/Transfer-Vorschau) |
| **`08-viewer.go:489`** | **manuelles Verschieben** → schreibt die **falsche Mail** in ein echtes Ziel-Postfach |
| `08-viewer.go:599` | Suche liefert falsche Treffer |
| `00-router.go:1320` | Transfer-Vorschau |
| **`12-archive-tools.go:291`, `:332`** | **Archiv-Export** (ZIP) und **Archiv-Dedup** → Export koennte falsche Mails enthalten, Dedup koennte **die falschen loeschen** |
**Wichtig:** Falls `/archives/dedup` oder das manuelle Verschieben schon benutzt
wurden, muss geprueft werden, ob dabei Schaden entstanden ist.
## Abnahme
1. Unit-Test, der genau das faengt: mbox mit **3 unterschiedlich langen** Mails
schreiben, dann `readMboxMessagesBytes` aufrufen und pruefen, dass
`msgs[0] != msgs[1] != msgs[2]` und jede den **erwarteten Betreff** hat.
(Ein Test mit gleich langen Mails wuerde den Bug NICHT finden — deshalb
unterschiedliche Laengen, damit der Puffer waechst.)
2. Live: `/view?account=colak@dr-gold.de&folder=INBOX`, dann fuer die Indizes
0, 1, 2, 300, 677 die Vorschau abrufen → Betreff und Datum muessen **exakt**
dem entsprechen, was `mbox_index` fuer diese `seq` sagt.
3. Ich (Claude) fahre den Sweep ueber alle 678 Indizes von colak gegen
`mbox_index`**0 Abweichungen** ist die Latte.