3

I have sent myself a test email which has an image in the body of the email. This image is not an attachment, just pasted into the body of the email.

I am using MailKit to read this incoming email, but cannot find how to access that image.

I'm using:

MimeMessage message = client.Inbox.GetMessage(uid);

If I use message.ToString(), I can see that it is there:

...
Content-Disposition: inline; filename="image001.png"; size=4570;
        creation-date="Mon, 16 Sep 2019 09:21:07 GMT";
        modification-date="Mon, 16 Sep 2019 09:21:07 GMT"
Content-ID: <image001.png@01D56A4B.7CD234E0>
Content-Transfer-Encoding: base64

iVBORw0KGgoAAAANSUhEUgAAAJUAAAAlCAY...

And I assume the base64 encoded lines are the actual image, but how do I get at it?

EDIT

Here is my test code:

    static void Main(string[] args)
    {
        ImapClient client = new ImapClient();
        client.Connect("...
        client.Authenticate("...
        client.Inbox.Open(FolderAccess.ReadWrite);
        IList<UniqueId> uids = client.Inbox.Search(SearchQuery.All);
        foreach (UniqueId uid in uids)
        {
            MimeMessage message = client.Inbox.GetMessage(uid);

            IList<IMessageSummary> info = client.Inbox.Fetch(new[] { uid }, MessageSummaryItems.All);

            foreach (MimeEntity me in message.Attachments)
                HandleMimeEntity(me, 1);
        }
        client.Disconnect(true);
    }

    static void HandleMimeEntity(MimeEntity entity)
    {
        int i, j;

        Multipart multipart = entity as Multipart;

        if (multipart != null)
        {
            Console.WriteLine("multipart");
            for (i = 0; i < multipart.Count; i++)
            {
                Console.WriteLine(i + " - " + multipart[i].ContentType.MimeType + " (" + multipart[i].IsAttachment + ")");
                HandleMimeEntity(multipart[i]);
            }
            return;
        }

        MessagePart rfc822 = entity as MessagePart;

        if (rfc822 != null)
        {
            MimeMessage message = rfc822.Message;
            Console.WriteLine("mimemessage - " + message.Subject);

            HandleMimeEntity(message.Body, lvl + 1);
            return;
        }

        MimePart part = (MimePart)entity;
        Console.WriteLine("mimepart - " + part.FileName);

        // do something with the MimePart, such as save content to disk
    }
Graham
  • 7,807
  • 20
  • 69
  • 114
  • What do you get in `message.Body`? – Chetan Sep 16 '19 at 10:04
  • I would say to search in the html for the tag "img" and read his value, then convert to base64 or if it is link download the image – Pedro Brito Sep 16 '19 at 10:16
  • I was hoping for something simpler than parsing the raw email body myself (which is why I chose MailKit). Maybe I'll see what else is out there. – Graham Sep 16 '19 at 10:36
  • Why would you need to parse the email body yourself? You aren't making any sense... MailKit already parsed it for you. – jstedfast Sep 16 '19 at 12:14
  • That's what I was hoping for. I just need to know how to get at this inline image. There is a htmlbody property, but that only contains an img tag pointing at the filename, but how do I get the actual "file". It isn't an attachment so not in `message.Attachments`. I would also still have to parse the img tag to find the file name, but I could live with that. – Graham Sep 16 '19 at 12:39

1 Answers1

7

I would really, really, really recommend reading the FAQ and/or the README to understand how MIME is structured so that answers to questions like this become obvious, but in the meantime...

Let's start with the understanding that MIME is a tree structure, which means there's a root node (which may or may not be a leaf-node), there can be branch nodes (ex. multipart/mixed, multipart/alternative, multipart/related, etc.) and there are leaf-nodes (ex. text/plain, text/html, image/jpeg, application/octet-stream, etc.).

The root node of the MIME structure is the MimeMessage.Body property.

If the message contains only a text/plain MIME entity, then the MimeMessage.Body node will be that text/plain MIME entity.

In your case, it sounds like you have at least a text/html entity and an image/png entity.

Knowing nothing else about the structure of your message, we can conclude that MimeMessage.Body isn't the text/html entity nor the image/png entity because you can only have 1 root node and text/* and image/* parts are leaf-node entities, not branch node entities.

That means that MimeMessage.Body will be a multipart/* which will be represented by the Multipart (or a subclass thereof).

Since it's clear that the MimeMessage.Body in your case is a Multipart, we can cast it:

var multipart = (Multipart) message.Body;

Once we have the multipart, we can iterate over its children:

foreach (var child in multipart) {
    // ...
}

At this point we will need to figure out if the child is another Multipart, a MimePart (which will represent text or image data), or an embedded message (aka MessagePart).

You can use an as cast to quickly deduce what it is.

And then you just continue walking the tree of MIME entities until you find what you are looking for which will likely require some recursion using the above approach.

MimeKit has a few alternative ways to do this, however:

  1. MimeMessage.BodyParts
  2. MimeIterator
  3. and MimeVisitor

Here's a quick solution to your problem using the BodyParts property:

var myImage = message.BodyParts.OfType<MimePart> ().FirstOrDefault (x => x.IsMimeType ("image", "png"));
jstedfast
  • 35,744
  • 5
  • 97
  • 110
  • 1
    Yes, already read those bits. The one bit of your answer that solved my problem was `The root node of the MIME structure is the MimeMessage.Body property.` That's the bit I needed, so thank you for that. The fact that it is called `Body` is what threw me off. – Graham Sep 17 '19 at 08:18
  • FWIW, TextBody and HtmlBody are convenience properties that scan the tree to find the most likely text part that would represent the message body – jstedfast Sep 17 '19 at 10:47