fix(mailparser): kaputte/truncated Multipart-Mails partiell indexieren statt scheitern

Bei NextPart()-Fehler (EOF, malformed header) wird der bereits geparste Inhalt
zurückgegeben statt der gesamte Parse abgebrochen. parseMultipart() gibt keinen
Fehler mehr zurück — partieller Text/HTML ist besser als gar kein Index-Eintrag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-06-30 20:17:33 +02:00
co-authored by Claude Sonnet 4.6
parent 5e7ae6e056
commit 0ccbd5bafb
+8 -12
View File
@@ -180,9 +180,9 @@ func Parse(raw []byte) (*ParsedMail, error) {
if strings.HasPrefix(mediaType, "multipart/") { if strings.HasPrefix(mediaType, "multipart/") {
boundary := params["boundary"] boundary := params["boundary"]
if err := parseMultipart(pm, msg.Body, boundary); err != nil { // Ignore parse errors — return partial content instead of failing completely.
return nil, fmt.Errorf("mailparser: multipart: %w", err) // Malformed/truncated multipart emails still get metadata + whatever parts parsed.
} parseMultipart(pm, msg.Body, boundary) //nolint:errcheck
} else { } else {
body, _ := io.ReadAll(msg.Body) body, _ := io.ReadAll(msg.Body)
decoded := decodeBody(body, msg.Header.Get("Content-Transfer-Encoding")) decoded := decodeBody(body, msg.Header.Get("Content-Transfer-Encoding"))
@@ -198,15 +198,14 @@ func Parse(raw []byte) (*ParsedMail, error) {
} }
// parseMultipart walks MIME parts and fills text, html, and attachments. // parseMultipart walks MIME parts and fills text, html, and attachments.
func parseMultipart(pm *ParsedMail, body io.Reader, boundary string) error { // Truncated or malformed parts are skipped — partial content is better than nothing.
func parseMultipart(pm *ParsedMail, body io.Reader, boundary string) {
mr := multipart.NewReader(body, boundary) mr := multipart.NewReader(body, boundary)
for { for {
part, err := mr.NextPart() part, err := mr.NextPart()
if err == io.EOF {
break
}
if err != nil { if err != nil {
return err // io.EOF = normal end; any other error = truncated/malformed — stop here.
break
} }
ct := part.Header.Get("Content-Type") ct := part.Header.Get("Content-Type")
@@ -244,9 +243,7 @@ func parseMultipart(pm *ParsedMail, body io.Reader, boundary string) error {
// Nested multipart // Nested multipart
if strings.HasPrefix(mediaType, "multipart/") { if strings.HasPrefix(mediaType, "multipart/") {
if err := parseMultipart(pm, bytes.NewReader(decoded), params["boundary"]); err != nil { parseMultipart(pm, bytes.NewReader(decoded), params["boundary"])
return err
}
continue continue
} }
@@ -257,7 +254,6 @@ func parseMultipart(pm *ParsedMail, body io.Reader, boundary string) error {
pm.HTMLBody += string(decoded) pm.HTMLBody += string(decoded)
} }
} }
return nil
} }
// decodeCharset converts data from the declared MIME charset to UTF-8. // decodeCharset converts data from the declared MIME charset to UTF-8.