Add attachment download endpoint for DocSys integration
This commit is contained in:
parent
3d2e651a5f
commit
ed28bb8a64
BIN
mail-bridge
BIN
mail-bridge
Binary file not shown.
44
main.go
44
main.go
|
|
@ -490,6 +490,12 @@ func handleMessages(w http.ResponseWriter, r *http.Request, accountName string,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for /messages/{uid}/attachments
|
||||||
|
if len(pathParts) > 1 && pathParts[1] == "attachments" {
|
||||||
|
handleGetAttachments(w, r, client, folder, uid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case "GET":
|
case "GET":
|
||||||
handleGetMessage(w, r, client, folder, uid)
|
handleGetMessage(w, r, client, folder, uid)
|
||||||
|
|
@ -849,3 +855,41 @@ func handleDeleteMessage(w http.ResponseWriter, r *http.Request, client *imapcli
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]string{"status": "deleted"})
|
json.NewEncoder(w).Encode(map[string]string{"status": "deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleGetAttachments(w http.ResponseWriter, r *http.Request, client *imapclient.Client, folder string, uid uint32) {
|
||||||
|
uidSet := imap.UIDSetNum(imap.UID(uid))
|
||||||
|
|
||||||
|
options := &imap.FetchOptions{
|
||||||
|
UID: true,
|
||||||
|
BodySection: []*imap.FetchItemBodySection{{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCmd := client.Fetch(uidSet, options)
|
||||||
|
|
||||||
|
msgData := fetchCmd.Next()
|
||||||
|
if msgData == nil {
|
||||||
|
fetchCmd.Close()
|
||||||
|
http.Error(w, "Message not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := msgData.Collect()
|
||||||
|
if err != nil {
|
||||||
|
fetchCmd.Close()
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fetchCmd.Close()
|
||||||
|
|
||||||
|
if len(buf.BodySection) == 0 {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode([]Attachment{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := buf.BodySection[0].Bytes
|
||||||
|
attachments := ExtractAttachments(raw)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(attachments)
|
||||||
|
}
|
||||||
|
|
|
||||||
89
mime.go
89
mime.go
|
|
@ -120,6 +120,95 @@ func decodeBody(body []byte, encoding string) []byte {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attachment represents an email attachment with content
|
||||||
|
type Attachment struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Content string `json:"content"` // base64 encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractAttachments extracts all attachments from raw email bytes
|
||||||
|
func ExtractAttachments(raw []byte) []Attachment {
|
||||||
|
attachments := make([]Attachment, 0) // Initialize to empty slice (not nil)
|
||||||
|
|
||||||
|
entity, err := message.Read(bytes.NewReader(raw))
|
||||||
|
if err != nil {
|
||||||
|
return attachments
|
||||||
|
}
|
||||||
|
|
||||||
|
extractAttachmentParts(entity, &attachments)
|
||||||
|
return attachments
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractAttachmentParts(entity *message.Entity, attachments *[]Attachment) {
|
||||||
|
mediaType, params, err := mime.ParseMediaType(entity.Header.Get("Content-Type"))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(mediaType, "multipart/") {
|
||||||
|
// Multipart message - recurse into parts
|
||||||
|
mr := entity.MultipartReader()
|
||||||
|
if mr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
part, err := mr.NextPart()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
extractAttachmentParts(part, attachments)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if this is an attachment
|
||||||
|
disp := entity.Header.Get("Content-Disposition")
|
||||||
|
if disp == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dispType, dispParams, err := mime.ParseMediaType(disp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.EqualFold(dispType, "attachment") && !strings.EqualFold(dispType, "inline") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get filename
|
||||||
|
filename := dispParams["filename"]
|
||||||
|
if filename == "" {
|
||||||
|
filename = params["name"]
|
||||||
|
}
|
||||||
|
if filename == "" {
|
||||||
|
// Skip attachments without filename
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read body
|
||||||
|
body, err := io.ReadAll(entity.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode transfer encoding
|
||||||
|
encoding := strings.ToLower(entity.Header.Get("Content-Transfer-Encoding"))
|
||||||
|
decoded := decodeBody(body, encoding)
|
||||||
|
|
||||||
|
// Encode as base64 for JSON transport
|
||||||
|
content := base64.StdEncoding.EncodeToString(decoded)
|
||||||
|
|
||||||
|
*attachments = append(*attachments, Attachment{
|
||||||
|
Filename: filename,
|
||||||
|
ContentType: mediaType,
|
||||||
|
Size: len(decoded),
|
||||||
|
Content: content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// stripHTML removes HTML tags and returns plain text
|
// stripHTML removes HTML tags and returns plain text
|
||||||
func stripHTML(html string) string {
|
func stripHTML(html string) string {
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue