IMAP: an attachment's filename is empty for mails sent from Hotmail

0 votes
asked Aug 15, 2012 by Rebex KB (8,190 points)
edited Sep 21, 2012

I'm reading a list of e-mails from an IMAP server using the Rebex IMAP/SSL component:

ImapMessageCollection col = 
    imap.GetMessageList(ImapListFields.Envelope | ImapListFields.MessageStructure);

foreach (ImapMessageInfo item in col)
{
    Console.WriteLine("{0}", item.Subject);
    foreach (ImapMessagePart part in item.GetParts())
    {
        if (part.Kind == ImapMessagePartKind.Attachment)
            Console.WriteLine("  {0} [{1} B]", part.FileName, part.Length);
    }
}

For e-mails sent from various server it works fine. But if an e-mail is sent from Microsoft Hotmail (outlook.com / Windows Live Hotmail), the part.FileName is always empty.

It does not depend on the target IMAP server. I've tried to sent an e-mail from Hotmail to my GMail account or to our company Exchange server. The part.FileName was empty in both cases.

Applies to: Rebex Secure Mail

1 Answer

0 votes
answered Aug 15, 2012 by Jan Sotola (16,920 points)
edited Sep 21, 2012
 
Best answer

Cause of the problem

The problem seems to be caused by a specific way of sending mail attachments from Hotmail.

A code snippet above retrieves the attachment name using the Rebex.Net.ImapMessageInfo object. This object is created from an appropriate message-info structure provided by an IMAP server. And an IMAP server retrieves the information from attachment headers.

An attachment header of an e-mail sent from common mailserver looks like this:

  Content-Type: text/plain; name="file1.txt"
  Content-Description: file1.txt
  Content-Disposition: attachment; filename="file1.txt"; size=6;
                   creation-date="Mon, 10 Oct 2011 12:10:39 GMT";
                   modification-date="Mon, 10 Oct 2011 12:10:42 GMT"
  Content-Transfer-Encoding: base64

While a header from an e-mail sent from Hotmail is following:

  Content-Type: text/plain
  Content-Transfer-Encoding: base64
  Content-Disposition: attachment; filename="file1.txt"

It looks like Hotmail somehow forgets to set the "name" parameter of the Content-Type header and it doesn't even set Content-Description. Because IMAP servers return the Content-Type header and Content-Description headers (but not Content-Desposition header) in response to FETCH (BODY) / FETCH (BODYSTRUCTURE), a properly-behaved IMAP server won't be able to send the filename to the client.

In short, even though it looks hard-to-believe, Hotmail attachments are incompatible with IMAP protocols's message structure retrieval functionality .

Workaround A (slow)

A simple solution would be to load the whole message (using Imap.GetMessage method) instead of working with the ImapMessageInfo only.

ImapMessageCollection col = 
    imap.GetMessageList(ImapListFields.Envelope | ImapListFields.MessageStructure);

foreach (ImapMessageInfo item in col)
{
    Console.WriteLine("{0}", item.Subject);
    var msg = imap.GetMailMessage(item.SequenceNumber);
    foreach (Attachment attachment in item.Attachments)
        Console.WriteLine("  {0}", attachment.FileName);
}

This solution is very slow because the whole mail message (including a content of the attachments) has to be loaded to the IMAP client.

Workaround B (faster)

There is a way to create an IMAP query to download message header only instead of the full message. The Rebex.Mime.MimeMessage class can then retrieve an attachment filename from the Content-Disposition part of the message header.

ImapMessageCollection col =
    imap.GetMessageList(ImapListFields.Envelope | ImapListFields.MessageStructure);

foreach (ImapMessageInfo item in col)
{
    Console.WriteLine("{0}", item.Subject);

    foreach (ImapMessagePart part in item.GetParts())
    {
        if (part.Kind == ImapMessagePartKind.Attachment)
        {
            string partId = part.Id;
            partId = partId.Split('/')[0] + ".MIME";
            byte[] headers = imap.GetMessagePart(item.UniqueId, partId);

            using (MemoryStream ms = new MemoryStream(headers))
            {
                MimeMessage mime = new MimeMessage();
                mime.Options |= MimeOptions.OnlyParseHeaders;
                mime.Load(ms);

                Console.WriteLine("  {0}", mime.Name);
            }
        }
    }
}

Even this workaround is not so fast as the original message retrieval using the ImapMessagePart.FileName property, because a particular attachment headers have to be loaded from IMAP server. But loading just the headers is much faster than loading the whole message and attachments.

Unfortunately, we cannot implement this workaround into the Rebex IMAP/SSL component, since we cannot recognize the Hotmail mails well. However, we're planning to improve the Imap.GetMessagePart method in order it can download the message headers only.

...