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
|
||||
}
|
||||
|
||||
// Check for /messages/{uid}/attachments
|
||||
if len(pathParts) > 1 && pathParts[1] == "attachments" {
|
||||
handleGetAttachments(w, r, client, folder, uid)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
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")
|
||||
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
|
||||
func stripHTML(html string) string {
|
||||
var result strings.Builder
|
||||
|
|
|
|||
Loading…
Reference in New Issue