delphiimapindy10

Delphi Indy10 IMAP Decoding MIME attachment


Using Delphi 11.

After downloading an email using TIdIMAP4, scanning through the MessageParts, how do I detect if a MessagePart contains a MIME encoded attachment, and how do I decode this into the original format (Image, documents, etc)?

procedure TImapForm.ProcessEmail(MSG: TIdMessage); 
begin
  ContainsAttachement := false;
    
  if Msg.MessageParts.Count > 0 then
  begin
    for i := 0 to Pred(Msg.MessageParts.Count) do
    begin
      if Msg.MessageParts.Items[i] is TIdText then
      begin
        // Process Text-only message here (Don't want HTML)
      end else
      begin
        ContainsAttachement := true;

        // if Msg.MessageParts[i].MessageParts.Encoding = meMIME then
        if MSG.ContentTransferEncoding = 'base64' then //??
        if Msg.MessageParts.Items[i].IsEncoded then //??
        begin
          // How to actually decode the MessagePart to a binary file?
        end;

      end;
    end;
  end;
end;

Email example:

This is a multi-part message in MIME format.
--------------wt6iyRLyQwO4w89MYm2jGb0w
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit

test

--------------wt6iyRLyQwO4w89MYm2jGb0w
Content-Type: image/jpeg; name="Bart.jpg"
Content-Disposition: attachment; filename="Bart.jpg"
Content-Transfer-Encoding: base64

/9j/4AAQSkZJRgABAQEASABIAAD/4ThFRXhpZgAASUkqAAgAAAAMAA4BAgAgAAAAngAAAA8B
AgAUAAAAvgAAABABAgAHAAAA1gAAABIBAwABAAAAAQAAABoBBQABAAAA7gAAABsBBQABAAAA
9gAAACgBAwABAAAAAgAAADEBAgAIAAAA/gAAADIBAgAUAAAAHgEAABMCAwABAAAAAgAAAGmH
etc.


Solution

  • TIdMessage already handles this for you. Look for TIdAttachment message parts, eg:

    procedure TImapForm.ProcessEmail(MSG: TIdMessage); 
    var
      MsgPart: TIdMessagePart;
      i: Integer;
    begin
      ContainsAttachement := false;
        
      for i := 0 to Pred(Msg.MessageParts.Count) do
      begin
        MsgPart := Msg.MessageParts[i];
    
        if MsgPart is TIdText then
        //or: if MsgPart.PartType = mcptText then
        begin
          ...
        end
        else if MsgPart is TIdAttachment then
        //or: if MsgPart.PartType = mcptAttachment then
        begin
          ContainsAttachement := true;
          TIdAttachment(MsgPart).SaveToFile(...);
        end;
      end;
    end;
    

    That being said, MIME-encoded emails can have complex structures (see HTML Messages on Indy's blog, for example). Because of this, message parts can be nested, and have relationships with other message parts. So, the correct way to process standalone attachments (as opposed to HTML-embedded images/media, for instance) in a MIME email is to loop backwards from last part to first part (because MIME parts are ordered from least complex to most complex) looking for attachments whose ParentPart property is -1 (ie, a top-level part).

    In general, if you find a message part whose ContentType property is multipart/... (like multipart/related) that interests you, then you can dig into its nested content by looping again looking for message parts whose ParentPart property refers to that message part that interested you. And so on, and so on, as deep as you need to go.

    For example:

    procedure TImapForm.ProcessEmail(MSG: TIdMessage); 
    var
      MsgPart: TIdMessagePart;
      i: Integer;
    begin
      ContainsAttachement := false;
        
      if Msg.MessageParts.Count = 0 then
      begin
        if IsHeaderMediaType(Msg.ContentType, 'text/plain') then
        begin
          // Process Msg.Body here ...
        end;
      end
      else
      begin
        for i := Pred(Msg.MessageParts.Count) downto 0 do
        begin
          MsgPart := Msg.MessageParts[i];
    
          if MsgPart.ParentPart = -1 then
          begin
            if MsgPart is TIdAttachment then
            begin
              ContainsAttachement := true;
              TIdAttachment(MsgPart).SaveToFile(...);
            end
    
            else if MsgPart is TIdText then
            begin
              if IsHeaderMediaType(MsgPart.ContentType, 'multipart') then
              begin
                // process nested parts whose ParentPart is MsgPart.Index ... 
              end
    
              else if IsHeaderMediaType(MsgPart.ContentType, 'text/plain') then
              begin
                // Process TIdText(MsgPart).Body here ...
              end;
            end;
          end;
    
        end;
      end;
    end;