package imap import ( "bytes" "encoding/json" "io" "mime" "mime/multipart" "net/mail" "strings" imapTypes "github.com/emersion/go-imap/v2" "github.com/ultisuite/ulti-backend/internal/mail/threading" ) type EmailAddress struct { Name string `json:"name"` Address string `json:"address"` } func addressesToJSON(addrs []imapTypes.Address) []byte { result := make([]EmailAddress, 0, len(addrs)) for _, a := range addrs { result = append(result, EmailAddress{ Name: a.Name, Address: a.Addr(), }) } b, _ := json.Marshal(result) return b } func parseBody(raw []byte) (text string, html string) { if len(raw) == 0 { return "", "" } msg, err := mail.ReadMessage(bytes.NewReader(raw)) if err != nil { return string(raw), "" } contentType := msg.Header.Get("Content-Type") if contentType == "" { contentType = "text/plain" } mediaType, params, err := mime.ParseMediaType(contentType) if err != nil { body, _ := io.ReadAll(msg.Body) return string(body), "" } if strings.HasPrefix(mediaType, "multipart/") { return parseMultipart(msg.Body, params["boundary"]) } body, _ := io.ReadAll(msg.Body) if mediaType == "text/html" { return "", string(body) } return string(body), "" } func parseMultipart(r io.Reader, boundary string) (text string, html string) { mr := multipart.NewReader(r, boundary) for { part, err := mr.NextPart() if err != nil { break } partType := part.Header.Get("Content-Type") mediaType, params, _ := mime.ParseMediaType(partType) switch { case mediaType == "text/plain": body, _ := io.ReadAll(part) text = string(body) case mediaType == "text/html": body, _ := io.ReadAll(part) html = string(body) case strings.HasPrefix(mediaType, "multipart/"): t, h := parseMultipart(part, params["boundary"]) if text == "" { text = t } if html == "" { html = h } } } return text, html } func parseThreadHeaders(raw []byte) (references []string, inReplyTo string) { if len(raw) == 0 { return nil, "" } msg, err := mail.ReadMessage(bytes.NewReader(raw)) if err != nil { return nil, "" } refs := msg.Header.Get("References") irt := strings.TrimSpace(msg.Header.Get("In-Reply-To")) return threading.ParseMessageIDs(refs), threading.NormalizeMessageID(irt) }