How to find attachments and download them with mxk/go-imap?

Issue

Been trying to figure this one out for a couple of days. I’m trying to download the image attachment of all emails in a mailbox.

Investigating here and there found this mxk/go-imap/issues/17 BODYSTRUCTURE where the author of the library gives some hints, but honestly I cannot wrap my mind around the RFC 3501 neither how I am supposed to use the As* functions of his library, as I see it, navigation wouldn’t be any different from iterating the fields as if they were arrays and doing some recursion when embedded “arrays” are found.

I’ve been able to put this code together that connects to a IMAP server using TLS and fetch all the emails printing the fields of each, excuse the Spanish embedded in the code.

package main

import (
    "crypto/tls"
    "log"
    "time"

    "github.com/mxk/go-imap/imap"
)

func main() {
    client, err := imap.DialTLS("imapserver.com:993", &tls.Config{
        ServerName:         "imapserver.com",
        InsecureSkipVerify: false,
    })

    if client.State() == imap.Login {
        _, err = client.Login("myemail@imapserver.com", "mypassword:)")
        if err != nil {
            log.Fatalf("no se pudo iniciar sesion: %s", err)
        }
    }
    defer client.Logout(30 * time.Second)
    log.Printf("Conectado")
    // lista correo en la bandeja de entrada
    client.Select("INBOX", true)
    log.Printf("Seleccionado: %s", client.Mailbox)
    set, _ := imap.NewSeqSet("")
    set.Add("1:*")

    cmd, err := client.Fetch(set, "FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE", "BODYSTRUCTURE")
    if err != nil {
        log.Fatalf("no se pudo hacer FETCH: %s", err)
    }
    log.Println("Mensajes mas recientes:")
    for cmd.InProgress() {
        // espera por la siguiente respuesta
        client.Recv(-1)

        for _, respuesta := range cmd.Data {
            message := respuesta.MessageInfo()
            log.Printf("= %d\n", message.Seq)
            for _, k := range imap.AsList(message.Attrs["BODYSTRUCTURE"]) {
                log.Printf("== %#v\n", k)
            }
        }
    }
}

this beauty will print the following:

2019/03/16 22:30:45 Conectado
2019/03/16 22:30:45 Seleccionado: --- "INBOX" ---
ReadOnly:     true
Flags:        (\Answered \Deleted \Draft \Flagged \Seen)
PermFlags:    ()
Messages:     2
Recent:       0
Unseen:       1
UIDNext:      85
UIDValidity:  1542111171
UIDNotSticky: false
2019/03/16 22:30:45 Mensajes mas recientes:
2019/03/16 22:30:45 = 1
2019/03/16 22:30:45 == "\"text\""
2019/03/16 22:30:45 == "\"plain\""
2019/03/16 22:30:45 == []imap.Field{"\"charset\"", "\"utf-8\""}
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == "\"base64\""
2019/03/16 22:30:45 == 0x6a
2019/03/16 22:30:45 == 0x3
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 = 1
2019/03/16 22:30:45 == "\"text\""
2019/03/16 22:30:45 == "\"plain\""
2019/03/16 22:30:45 == []imap.Field{"\"charset\"", "\"utf-8\""}
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == "\"base64\""
2019/03/16 22:30:45 == 0x6a
2019/03/16 22:30:45 == 0x3
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 = 2
2019/03/16 22:30:45 == []imap.Field{"\"text\"", "\"plain\"", []imap.Field{"\"charset\"", "\"utf-8\""}, imap.Field(nil), imap.Field(nil), "\"base64\"", 0x66, 0x1, imap.Field(nil), imap.Field(nil), imap.Field(nil), imap.Field(nil)}
2019/03/16 22:30:45 == []imap.Field{"\"image\"", "\"jpeg\"", []imap.Field{"\"name\"", "\"Screenshot_20190315-090210.jpg\""}, imap.Field(nil), imap.Field(nil), "\"base64\"", 0xfd0b2, imap.Field(nil), []imap.Field{"\"attachment\"", []imap.Field{"\"filename\"", "\"Screenshot_20190315-090210.jpg\"", "\"size\"", "\"757415\""}}, imap.Field(nil), imap.Field(nil)}
2019/03/16 22:30:45 == "\"mixed\""
2019/03/16 22:30:45 == []imap.Field{"\"boundary\"", "\"--_com.android.email_70037410718460\""}
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 = 1
2019/03/16 22:30:45 == "\"text\""
2019/03/16 22:30:45 == "\"plain\""
2019/03/16 22:30:45 == []imap.Field{"\"charset\"", "\"utf-8\""}
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == "\"base64\""
2019/03/16 22:30:45 == 0x6a
2019/03/16 22:30:45 == 0x3
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 = 2
2019/03/16 22:30:45 == []imap.Field{"\"text\"", "\"plain\"", []imap.Field{"\"charset\"", "\"utf-8\""}, imap.Field(nil), imap.Field(nil), "\"base64\"", 0x66, 0x1, imap.Field(nil), imap.Field(nil), imap.Field(nil), imap.Field(nil)}
2019/03/16 22:30:45 == []imap.Field{"\"image\"", "\"jpeg\"", []imap.Field{"\"name\"", "\"Screenshot_20190315-090210.jpg\""}, imap.Field(nil), imap.Field(nil), "\"base64\"", 0xfd0b2, imap.Field(nil), []imap.Field{"\"attachment\"", []imap.Field{"\"filename\"", "\"Screenshot_20190315-090210.jpg\"", "\"size\"", "\"757415\""}}, imap.Field(nil), imap.Field(nil)}
2019/03/16 22:30:45 == "\"mixed\""
2019/03/16 22:30:45 == []imap.Field{"\"boundary\"", "\"--_com.android.email_70037410718460\""}
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>
2019/03/16 22:30:45 == <nil>

Solution

You should pick a library more up-to-date, the one you choose didn’t have any contributions for 4 years. Here an example with https://github.com/emersion/go-imap library for png and gif.

package main

import (
    "io"
    "io/ioutil"
    "log"

    "github.com/emersion/go-imap"
    "github.com/emersion/go-imap/client"
    "github.com/emersion/go-message"
)

func main() {
    log.Println("Connecting to server...")

    c, err := client.DialTLS("xxxxxxxxx", nil)
    if err != nil {
        log.Fatal(err)
    }

    log.Println("Connected")

    defer c.Logout()

    if err := c.Login("xxxxxxxxxxxxx", "xxxxxxxxxx"); err != nil {
        log.Fatal(err)
    }
    log.Println("Logged in")

    mbox, err := c.Select("INBOX", false)
    if err != nil {
        log.Fatal(err)
    }

    seqset := new(imap.SeqSet)
    seqset.AddRange(1, mbox.Messages)

    messages := make(chan *imap.Message, 10)
    done := make(chan error, 1)
    go func() {
        done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchRFC822}, messages)
    }()

    for msg := range messages {
        for _, r := range msg.Body {
            entity, err := message.Read(r)
            if err != nil {
                log.Fatal(err)
            }

            multiPartReader := entity.MultipartReader()

            for e, err := multiPartReader.NextPart(); err != io.EOF; e, err = multiPartReader.NextPart() {
                kind, params, cErr := e.Header.ContentType()
                if cErr != nil {
                    log.Fatal(cErr)
                }

                if kind != "image/png" && kind != "image/gif" {
                    continue
                }

                c, rErr := ioutil.ReadAll(e.Body)
                if rErr != nil {
                    log.Fatal(rErr)
                }

                log.Printf("Dump file %s", params["name"])

                if fErr := ioutil.WriteFile("/tmp/"+params["name"], c, 0777); fErr != nil {
                    log.Fatal(fErr)
                }
            }
        }
    }

    if err := <-done; err != nil {
        log.Fatal(err)
    }

    log.Println("Done")
}

Answered By – antham

Answer Checked By – Robin (GoLangFix Admin)

Leave a Reply

Your email address will not be published.