Search code examples
delphiindyindy10

How can a message client read an attachment downloaded by indy?


I have a message client, written in delphi using Indy libraries, that receives email messages. I am having difficulties decoding an MMS text message email.

These messages come as multipart/mixed emails with one message part (an attachment) that of text/plain (that is base64 encoded) with a filename like text0.txt.

My TIdMessageClient calls ProcessMessage (using the stream-based version) to populate a TidMessage that I'm going to display on the screen. But as I go through the message parts and try to unravel them, that attached file is a thorn in my side. Currently, I have it printing out the name of the attachment into a string which works fine (see code snippet below, FBody is a string type), but can't get the text file's contents.

Here's the bit that does work:

FBody := 'Attachment: ['+TidAttachment(Msg.MessageParts.Items[0]).FileName+']';

(Edited:) Originally when I wrote this question I wasn't sure if the attachment was stored in a TidAttachmentFile or TidAttachmentMemory object. But with the right debugger commands, I've determined it's a TidAttachmentFile. I suppose it would be possible to use TidAttachmentFile.SaveToFile() to save the attachment to a file on disk and then read the file back from disk, but that seems wasteful and slow (especially for a 200 character text message). I would really prefer to do this all "in memory" without temp files if possible.

What do I need to do (a) make TidMessageClient return a TidAttachmentMemory object rather than a TidAttachmentObject (in ProcessMessage), and (b) read the attached text file into a string?

Based on the indy documentation, the start I have at how this code would look is roughly like this:

TidAttachmentMemory(Msg.MessageParts.Items[0]).PrepareTempStream();
FBody := FBody + TidAttachmentMemory(Msg.MessageParts.Items[0]).DataString;
TidAttachmentMemory(Msg.MessageParts.Items[0]).FinishTempStream;

Please feel free to point me in the right direction if this is not the right way to go or use TidAttachment(s).


Solution

  • I suppose it would be possible to use TidAttachmentFile.SaveToFile() to save the attachment to a file on disk and then read the file back from disk, but that seems wasteful and slow (especially for a 200 character text message).

    When using TIdAttachmentFile, the file is always on disk. The TIdAttachmentFile.StoredPathName property specifies the path to the actual file. The TIdAttachmentFile.SaveToFile() method merely copies the file to the specified location.

    I would really prefer to do this all "in memory" without temp files if possible.

    It is possible.

    What do I need to do (a) make TidMessageClient return a TidAttachmentMemory object rather than a TidAttachmentObject (in ProcessMessage)

    In the TIdMessage.OnCreateAttachment event, return a TIdAttachmentMemory object, eg:

    procedure TMyForm.IdMessage1CreateAttachment(const AMsg: TIdMessage; const AHeaders: TStrings; var AAttachment: TIdAttachment);
    begin
      AAttachment := TIdAttachmentMemory.Create(AMsg.MessageParts);
    end;
    

    If no handler is assigned to the TIdMessage.OnCreateAttachment event, or if it does not assign anything to AAttachment, then a TIdAttachmentFile is created by default.

    You could optionally implement your own custom TIdAttachment-derived class instead, say one that uses TStringStream internally if you know the attachment contains textual data (which the AHeaders parameter will tell you).

    and (b) read the attached text file into a string?

    Based on the indy documentation, the start I have at how this code would look is roughly like this:

    You are close. You need to use the TIdAttachment.OpenLoadStream() method instead of TIdAttachment.PrepareTempStream(), and you need to read the data from the TStream that TIdAttachment.OpenLoadStream() returns. In your example, you could use Indy's ReadStringFromStream() function for that, eg:

    // if using Indy 10.6 or later...
    var
      Attachment: TIdAttachment;
      Strm: TStream;
     begin
      ...
      Attachment := TIdAttachment(Msg.MessageParts.Items[0]);
      Strm := Attachment.OpenLoadStream;
      try
        FBody := FBody + ReadStringFromStream(Strm, -1, CharsetToEncoding(Attachment.Charset));
      finally
        Attachment.CloseLoadStream;
      end;
      ...
    end;
    

    Or:

    // if using Indy 10.5.x or earlier...
    var
      Attachment: TIdAttachment;
      Strm: TStream;
      Enc: TIdTextEncoding;
     begin
      ...
      Attachment := TIdAttachment(Msg.MessageParts.Items[0]);
      Strm := Attachment.OpenLoadStream;
      try
        Enc := CharsetToEncoding(Attachment.Charset);
        try
          FBody := FBody + ReadStringFromStream(Strm, -1, Enc);
        finally
          Enc.Free;
        end;
      finally
        Attachment.CloseLoadStream;
      end;
      ...
    end;