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
72 lines
2.2 KiB
Go
72 lines
2.2 KiB
Go
package mailparser
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
|
|
var (
|
|
mboxSeparator = []byte("From ")
|
|
mboxHeaderTag = []byte("From: ")
|
|
mboxQuoted = []byte(">From ")
|
|
)
|
|
|
|
// SplitMbox splits a raw mbox file into individual RFC 2822 message bytes.
|
|
// Each message starts with a "From " separator line which is skipped.
|
|
//
|
|
// Scan errors (e.g. a single line larger than the 10 MB scanner limit) are
|
|
// ignored here for backwards compatibility — use SplitMboxErr to detect a
|
|
// truncated result instead of silently importing partial data.
|
|
func SplitMbox(data []byte) [][]byte {
|
|
messages, _ := SplitMboxErr(data)
|
|
return messages
|
|
}
|
|
|
|
// SplitMboxErr behaves like SplitMbox but also reports scanner failures.
|
|
// On error the already-collected messages are still returned so the caller can
|
|
// decide between aborting and importing a partial file.
|
|
func SplitMboxErr(data []byte) ([][]byte, error) {
|
|
var messages [][]byte
|
|
var current bytes.Buffer
|
|
|
|
scanner := bufio.NewScanner(bytes.NewReader(data))
|
|
scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
|
|
|
|
inMessage := false
|
|
for scanner.Scan() {
|
|
// Bytes() avoids a string allocation per line — mbox files routinely
|
|
// have millions of lines.
|
|
line := scanner.Bytes()
|
|
// mbox separator: line starts with "From " but not "From:" header
|
|
if bytes.HasPrefix(line, mboxSeparator) && !bytes.HasPrefix(line, mboxHeaderTag) {
|
|
if inMessage && current.Len() > 0 {
|
|
// Copy: current.Bytes() aliases the buffer, which is reused
|
|
// after Reset() and would corrupt already-collected messages.
|
|
msg := bytes.TrimSpace(current.Bytes())
|
|
messages = append(messages, append([]byte(nil), msg...))
|
|
current.Reset()
|
|
}
|
|
inMessage = true
|
|
continue
|
|
}
|
|
if inMessage {
|
|
// unescape ">From " lines (mbox quoting)
|
|
if bytes.HasPrefix(line, mboxQuoted) {
|
|
line = line[1:]
|
|
}
|
|
current.Write(line)
|
|
current.WriteByte('\n')
|
|
}
|
|
}
|
|
if inMessage && current.Len() > 0 {
|
|
msg := bytes.TrimSpace(current.Bytes())
|
|
messages = append(messages, append([]byte(nil), msg...))
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return messages, fmt.Errorf("mailparser: mbox scan aborted after %d messages: %w", len(messages), err)
|
|
}
|
|
return messages, nil
|
|
}
|