fix!: decode attatchments

drew created

Change summary

fetcher/fetcher.go | 32 +++++++++++++++++++++++++++++---
main.go            | 21 +++++++++++++++++++--
tui/messages.go    |  1 +
3 files changed, 49 insertions(+), 5 deletions(-)

Detailed changes

fetcher/fetcher.go 🔗

@@ -24,6 +24,7 @@ type Attachment struct {
 	Filename string
 	PartID   string // Keep PartID to fetch on demand
 	Data     []byte
+	Encoding string // Store encoding for proper decoding
 }
 
 type Email struct {
@@ -250,7 +251,11 @@ func FetchEmailBody(account *config.Account, uid uint32) (string, []Attachment,
 
 		// Add as attachment if it has a disposition or a filename (and not just plain text)
 		if filename != "" && (part.Disposition == "attachment" || part.Disposition == "inline" || part.MIMEType != "text") {
-			attachments = append(attachments, Attachment{Filename: filename, PartID: partID})
+			attachments = append(attachments, Attachment{
+				Filename: filename,
+				PartID:   partID,
+				Encoding: part.Encoding, // Store encoding for proper decoding
+			})
 		}
 	}
 
@@ -345,7 +350,7 @@ func FetchEmailBody(account *config.Account, uid uint32) (string, []Attachment,
 	return body, attachments, nil
 }
 
-func FetchAttachment(account *config.Account, uid uint32, partID string) ([]byte, error) {
+func FetchAttachment(account *config.Account, uid uint32, partID string, encoding string) ([]byte, error) {
 	c, err := connect(account)
 	if err != nil {
 		return nil, err
@@ -385,7 +390,28 @@ func FetchAttachment(account *config.Account, uid uint32, partID string) ([]byte
 		return nil, fmt.Errorf("could not get attachment body")
 	}
 
-	return ioutil.ReadAll(literal)
+	rawBytes, err := ioutil.ReadAll(literal)
+	if err != nil {
+		return nil, err
+	}
+
+	switch strings.ToLower(encoding) {
+	case "base64":
+		decoder := base64.NewDecoder(base64.StdEncoding, bytes.NewReader(rawBytes))
+		decoded, err := ioutil.ReadAll(decoder)
+		if err == nil {
+			return decoded, nil
+		}
+		return rawBytes, nil
+	case "quoted-printable":
+		decoded, err := ioutil.ReadAll(quotedprintable.NewReader(bytes.NewReader(rawBytes)))
+		if err == nil {
+			return decoded, nil
+		}
+		return rawBytes, nil
+	default:
+		return rawBytes, nil
+	}
 }
 
 func moveEmail(account *config.Account, uid uint32, destMailbox string) error {

main.go 🔗

@@ -551,7 +551,23 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			return m, nil
 		}
 
-		return m, tea.Batch(m.current.Init(), downloadAttachmentCmd(account, email.UID, msg))
+		// Find the correct attachment to get encoding
+		var encoding string
+		for _, att := range email.Attachments {
+			if att.PartID == msg.PartID {
+				encoding = att.Encoding
+				break
+			}
+		}
+		newMsg := tui.DownloadAttachmentMsg{
+			Index:     msg.Index,
+			Filename:  msg.Filename,
+			PartID:    msg.PartID,
+			Data:      msg.Data,
+			AccountID: msg.AccountID,
+			Encoding:  encoding,
+		}
+		return m, tea.Batch(m.current.Init(), downloadAttachmentCmd(account, email.UID, newMsg))
 
 	case tui.AttachmentDownloadedMsg:
 		var statusMsg string
@@ -861,7 +877,8 @@ func archiveEmailCmd(account *config.Account, uid uint32, accountID string) tea.
 
 func downloadAttachmentCmd(account *config.Account, uid uint32, msg tui.DownloadAttachmentMsg) tea.Cmd {
 	return func() tea.Msg {
-		data, err := fetcher.FetchAttachment(account, uid, msg.PartID)
+		// Download and decode the attachment using encoding provided in msg.Encoding.
+		data, err := fetcher.FetchAttachment(account, uid, msg.PartID, msg.Encoding)
 		if err != nil {
 			return tui.AttachmentDownloadedMsg{Err: err}
 		}

tui/messages.go 🔗

@@ -109,6 +109,7 @@ type DownloadAttachmentMsg struct {
 	PartID    string
 	Data      []byte
 	AccountID string
+	Encoding  string
 }
 
 type AttachmentDownloadedMsg struct {