ultisuite-backend/internal/mail/imap/parse.go
R3D347HR4Y 95196f7777 Add mail attachment and draft management features
- Introduced new functionality for managing email attachments and drafts in the mail API.
- Added handlers for listing, uploading, and downloading message attachments in `internal/api/mail/handlers_attachments.go`.
- Implemented draft management endpoints for creating, updating, and deleting drafts in `internal/api/mail/handlers_drafts.go`.
- Created new service methods for handling draft and attachment operations in `internal/api/mail/drafts.go` and `internal/api/mail/storage.go`.
- Added validation and error handling for draft and attachment operations.
- Included unit tests for draft and folder functionalities in `internal/api/mail/drafts_test.go` and `internal/api/mail/folders_test.go`.
- Updated API routes to support new draft and attachment features, enhancing overall mail management capabilities.
2026-05-22 17:14:36 +02:00

109 lines
2.2 KiB
Go

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)
}