fix: Crash-Robustheit + Performance in Backend und Frontend härten

Backend: recover() in allen langlebigen Goroutinen (neues internal/safego-
Paket), MIME-Multipart-Tiefenlimit gegen Stack-Overflow, IMAP-Zeilenlängen-
und FETCH-Result-Limits gegen OOM, MBOX-Buffer-Aliasing-Bug (Datenkorruption
beim Import), ungeprüfte Type Assertions abgesichert, Data Race im
API-Key-Rate-Limiter behoben, SMTP-Session-Panic führt jetzt zu 451-Retry
statt Prozessabsturz. Performance: O(n²)-String-Concat in Mailparser und
IMAP-Parser durch strings.Builder ersetzt.

Frontend: Error Boundaries für Root und Mail-Detailansicht ergänzt (gab es
vorher nicht), zahlreiche Guards gegen nil-Slices aus dem Backend-JSON die
sonst .map()/.length-Crashes/White-Screens auslösten, defekte JSON-Antworten
in api/core.ts abgefangen, zwei React-Key-Bugs bei löschbaren Listen
korrigiert.

Verifiziert auf 192.168.1.132: Build und Tests der geänderten Pakete
fehlerfrei, keine Regressionen gegenüber vorbestehendem Stand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
This commit is contained in:
sysops
2026-08-05 13:39:00 +02:00
co-authored by Claude Sonnet 5
parent 0fa4fad49d
commit 798cb2817c
35 changed files with 502 additions and 112 deletions
+17 -1
View File
@@ -365,7 +365,23 @@ func (s *session) Rcpt(to string, _ *smtp.RcptOptions) error {
return nil
}
func (s *session) Data(r io.Reader) error {
func (s *session) Data(r io.Reader) (err error) {
// go-smtp does not guard session handlers: a panic while processing a
// malformed mail would take the whole archivmail process down. Convert it
// into a temporary failure so the sending MTA retries.
defer func() {
if rec := recover(); rec != nil {
s.daemon.stats.Rejected.Add(1)
s.daemon.logger.Error("SMTP: recovered from panic while processing message",
"from", s.from, "ip", s.remoteIP, "panic", fmt.Sprintf("%v", rec))
err = &smtp.SMTPError{
Code: 451,
EnhancedCode: smtp.EnhancedCode{4, 3, 0},
Message: "Temporary processing failure, please retry",
}
}
}()
var buf bytes.Buffer
if _, err := io.Copy(&buf, r); err != nil {
s.daemon.stats.Rejected.Add(1)