11package fetcher
22
33import (
4+ "bytes"
45 "encoding/base64"
56 "fmt"
67 "io"
78 "io/ioutil"
8- "log"
99 "mime"
10+ "mime/quotedprintable"
1011 "strings"
1112 "time"
1213
@@ -21,6 +22,7 @@ import (
2122// Attachment holds data for an email attachment.
2223type Attachment struct {
2324 Filename string
25+ PartID string // Keep PartID to fetch on demand
2426 Data []byte
2527}
2628
@@ -141,111 +143,211 @@ func FetchEmails(cfg *config.Config, limit, offset uint32) ([]Email, error) {
141143
142144 messages := make (chan * imap.Message , limit )
143145 done := make (chan error , 1 )
144- fetchItems := []imap.FetchItem {imap .FetchEnvelope , imap .FetchUid , imap . FetchItem ( "BODY[]" ) }
146+ fetchItems := []imap.FetchItem {imap .FetchEnvelope , imap .FetchUid }
145147 go func () {
146148 done <- c .Fetch (seqset , fetchItems , messages )
147149 }()
148150
149- var emails []Email
151+ var msgs []* imap. Message
150152 for msg := range messages {
151- if msg == nil {
152- continue
153- }
153+ msgs = append (msgs , msg )
154+ }
154155
155- bodyLiteral := msg .GetBody (& imap.BodySectionName {})
156- if bodyLiteral == nil {
157- log .Println ("Could not get message body" )
158- continue
159- }
156+ if err := <- done ; err != nil {
157+ return nil , err
158+ }
160159
161- mr , err := mail . CreateReader ( bodyLiteral )
162- if err != nil {
163- log . Printf ( "Error creating mail reader: %v" , err )
160+ var emails [] Email
161+ for _ , msg := range msgs {
162+ if msg == nil || msg . Envelope == nil {
164163 continue
165164 }
166165
167- header := mr .Header
168- fromAddrs , _ := header .AddressList ("From" )
169- toAddrs , _ := header .AddressList ("To" )
170- subject := decodeHeader (header .Get ("Subject" ))
171- date , _ := header .Date ()
172- messageID := header .Get ("Message-ID" )
173- references := header .Get ("References" )
174-
175166 var fromAddr string
176- if len (fromAddrs ) > 0 {
177- fromAddr = fromAddrs [0 ].Address
167+ if len (msg . Envelope . From ) > 0 {
168+ fromAddr = msg . Envelope . From [0 ].Address ()
178169 }
179170
180171 var toAddrList []string
181- for _ , addr := range toAddrs {
182- toAddrList = append (toAddrList , addr .Address )
172+ for _ , addr := range msg . Envelope . To {
173+ toAddrList = append (toAddrList , addr .Address () )
183174 }
184175
185- var body string
186- var attachments []Attachment
187- for {
188- p , err := mr .NextPart ()
189- if err == io .EOF {
190- break
191- } else if err != nil {
192- log .Printf ("Error getting next part: %v" , err )
193- break
176+ emails = append (emails , Email {
177+ UID : msg .Uid ,
178+ From : fromAddr ,
179+ To : toAddrList ,
180+ Subject : decodeHeader (msg .Envelope .Subject ),
181+ Date : msg .Envelope .Date ,
182+ })
183+ }
184+
185+ for i , j := 0 , len (emails )- 1 ; i < j ; i , j = i + 1 , j - 1 {
186+ emails [i ], emails [j ] = emails [j ], emails [i ]
187+ }
188+
189+ return emails , nil
190+ }
191+
192+ func FetchEmailBody (cfg * config.Config , uid uint32 ) (string , []Attachment , error ) {
193+ c , err := connect (cfg )
194+ if err != nil {
195+ return "" , nil , err
196+ }
197+ defer c .Logout ()
198+
199+ if _ , err := c .Select ("INBOX" , false ); err != nil {
200+ return "" , nil , err
201+ }
202+
203+ seqset := new (imap.SeqSet )
204+ seqset .AddNum (uid )
205+
206+ messages := make (chan * imap.Message , 1 )
207+ done := make (chan error , 1 )
208+ fetchItems := []imap.FetchItem {imap .FetchBodyStructure }
209+ go func () {
210+ done <- c .UidFetch (seqset , fetchItems , messages )
211+ }()
212+
213+ if err := <- done ; err != nil {
214+ return "" , nil , err
215+ }
216+
217+ msg := <- messages
218+ if msg == nil || msg .BodyStructure == nil {
219+ return "" , nil , fmt .Errorf ("no message or body structure found with UID %d" , uid )
220+ }
221+
222+ var textPartID string
223+ var attachments []Attachment
224+ var findParts func (* imap.BodyStructure , string )
225+ findParts = func (bs * imap.BodyStructure , prefix string ) {
226+ for i , part := range bs .Parts {
227+ partID := fmt .Sprintf ("%d" , i + 1 )
228+ if prefix != "" {
229+ partID = fmt .Sprintf ("%s.%d" , prefix , i + 1 )
230+ }
231+
232+ if part .MIMEType == "text" && (part .MIMESubType == "plain" || part .MIMESubType == "html" ) && textPartID == "" {
233+ textPartID = partID
194234 }
235+ if part .Disposition == "attachment" || part .Disposition == "inline" {
236+ if filename , ok := part .Params ["filename" ]; ok {
237+ attachments = append (attachments , Attachment {Filename : filename , PartID : partID })
238+ }
239+ }
240+ if len (part .Parts ) > 0 {
241+ findParts (part , partID )
242+ }
243+ }
244+ }
245+ findParts (msg .BodyStructure , "" )
246+
247+ var body string
248+ if textPartID != "" {
249+ partMessages := make (chan * imap.Message , 1 )
250+ partDone := make (chan error , 1 )
251+
252+ fetchItem := imap .FetchItem (fmt .Sprintf ("BODY.PEEK[%s]" , textPartID ))
253+ section , err := imap .ParseBodySectionName (fetchItem )
254+ if err != nil {
255+ return "" , nil , err
256+ }
257+
258+ go func () {
259+ partDone <- c .UidFetch (seqset , []imap.FetchItem {fetchItem }, partMessages )
260+ }()
261+
262+ if err := <- partDone ; err != nil {
263+ return "" , nil , err
264+ }
195265
196- // Correctly parse Content-Disposition
197- cdHeader := p .Header .Get ("Content-Disposition" )
198- if cdHeader != "" {
199- disposition , params , err := mime .ParseMediaType (cdHeader )
200- if err == nil && (disposition == "attachment" || disposition == "inline" ) {
201- filename := params ["filename" ]
202- if filename != "" {
203- partBody , _ := ioutil .ReadAll (p .Body )
266+ partMsg := <- partMessages
267+ if partMsg != nil {
268+ literal := partMsg .GetBody (section )
269+ if literal != nil {
270+ // The new decoding logic starts here
271+ buf , _ := ioutil .ReadAll (literal )
272+ mr , err := mail .CreateReader (bytes .NewReader (buf ))
273+ if err != nil {
274+ body = string (buf )
275+ } else {
276+ p , err := mr .NextPart ()
277+ if err != nil {
278+ body = string (buf )
279+ } else {
204280 encoding := p .Header .Get ("Content-Transfer-Encoding" )
205- if strings .ToLower (encoding ) == "base64" {
206- decoded , decodeErr := base64 .StdEncoding .DecodeString (string (partBody ))
207- if decodeErr == nil {
208- partBody = decoded
281+ bodyBytes , _ := ioutil .ReadAll (p .Body )
282+
283+ switch strings .ToLower (encoding ) {
284+ case "base64" :
285+ decoded , err := base64 .StdEncoding .DecodeString (string (bodyBytes ))
286+ if err == nil {
287+ body = string (decoded )
288+ } else {
289+ body = string (bodyBytes )
290+ }
291+ case "quoted-printable" :
292+ decoded , err := ioutil .ReadAll (quotedprintable .NewReader (strings .NewReader (string (bodyBytes ))))
293+ if err == nil {
294+ body = string (decoded )
295+ } else {
296+ body = string (bodyBytes )
209297 }
298+ default :
299+ body = string (bodyBytes )
210300 }
211- attachments = append (attachments , Attachment {Filename : filename , Data : partBody })
212- continue // Skip to next part
213301 }
214302 }
215303 }
216-
217- // Process body part if not an attachment
218- mediaType , _ , _ := mime .ParseMediaType (p .Header .Get ("Content-Type" ))
219- if (mediaType == "text/plain" || mediaType == "text/html" ) && body == "" {
220- decodedPart , decodeErr := decodePart (p .Body , p .Header )
221- if decodeErr == nil {
222- body = decodedPart
223- }
224- }
225304 }
305+ }
226306
227- emails = append (emails , Email {
228- UID : msg .Uid ,
229- From : fromAddr ,
230- To : toAddrList ,
231- Subject : subject ,
232- Body : body ,
233- Date : date ,
234- MessageID : messageID ,
235- References : strings .Fields (references ),
236- Attachments : attachments ,
237- })
307+ return body , attachments , nil
308+ }
309+
310+ func FetchAttachment (cfg * config.Config , uid uint32 , partID string ) ([]byte , error ) {
311+ c , err := connect (cfg )
312+ if err != nil {
313+ return nil , err
314+ }
315+ defer c .Logout ()
316+
317+ if _ , err := c .Select ("INBOX" , false ); err != nil {
318+ return nil , err
319+ }
320+
321+ seqset := new (imap.SeqSet )
322+ seqset .AddNum (uid )
323+
324+ fetchItem := imap .FetchItem (fmt .Sprintf ("BODY.PEEK[%s]" , partID ))
325+ section , err := imap .ParseBodySectionName (fetchItem )
326+ if err != nil {
327+ return nil , err
238328 }
239329
330+ messages := make (chan * imap.Message , 1 )
331+ done := make (chan error , 1 )
332+ go func () {
333+ done <- c .UidFetch (seqset , []imap.FetchItem {fetchItem }, messages )
334+ }()
335+
240336 if err := <- done ; err != nil {
241337 return nil , err
242338 }
243339
244- for i , j := 0 , len (emails )- 1 ; i < j ; i , j = i + 1 , j - 1 {
245- emails [i ], emails [j ] = emails [j ], emails [i ]
340+ msg := <- messages
341+ if msg == nil {
342+ return nil , fmt .Errorf ("could not fetch attachment" )
246343 }
247344
248- return emails , nil
345+ literal := msg .GetBody (section )
346+ if literal == nil {
347+ return nil , fmt .Errorf ("could not get attachment body" )
348+ }
349+
350+ return ioutil .ReadAll (literal )
249351}
250352
251353func moveEmail (cfg * config.Config , uid uint32 , destMailbox string ) error {
0 commit comments