- 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.
109 lines
2.2 KiB
Go
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)
|
|
}
|