diff --git a/backend/06-mbox.go b/backend/06-mbox.go index 7349294..712ed4f 100644 --- a/backend/06-mbox.go +++ b/backend/06-mbox.go @@ -2,9 +2,12 @@ package backend import ( "bytes" + "encoding/base64" "fmt" "io" "mime" + "mime/multipart" + "mime/quotedprintable" "net/mail" "os" "path/filepath" @@ -162,7 +165,64 @@ func messageBody(raw []byte) string { if err != nil { return string(raw) } - return string(body) + return extractTextBody(msg.Header, body) +} + +type headerGetter interface { + Get(string) string +} + +func extractTextBody(h headerGetter, body []byte) string { + mediaType, params, _ := mime.ParseMediaType(h.Get("Content-Type")) + decoded := decodeTransfer(body, h.Get("Content-Transfer-Encoding")) + if strings.HasPrefix(mediaType, "multipart/") { + boundary := params["boundary"] + if boundary == "" { + return string(decoded) + } + mr := multipart.NewReader(bytes.NewReader(decoded), boundary) + var htmlBody string + for { + part, err := mr.NextPart() + if err == io.EOF { + break + } + if err != nil { + break + } + partBody, _ := io.ReadAll(part) + text := extractTextBody(part.Header, partBody) + partType, _, _ := mime.ParseMediaType(part.Header.Get("Content-Type")) + if strings.HasPrefix(partType, "text/plain") && strings.TrimSpace(text) != "" { + return text + } + if strings.HasPrefix(partType, "text/html") && htmlBody == "" { + htmlBody = text + } + } + if htmlBody != "" { + return htmlBody + } + return "" + } + return string(decoded) +} + +func decodeTransfer(body []byte, enc string) []byte { + switch strings.ToLower(strings.TrimSpace(enc)) { + case "quoted-printable": + b, err := io.ReadAll(quotedprintable.NewReader(bytes.NewReader(body))) + if err == nil { + return b + } + case "base64": + cleaned := strings.NewReplacer("\r", "", "\n", "", " ", "", "\t", "").Replace(string(body)) + b, err := base64.StdEncoding.DecodeString(cleaned) + if err == nil { + return b + } + } + return body } func safeMboxName(folder string) string {