New TIdMessage helper

The TIdMessage.LoadFrom…() methods only work reliably with EML files/streams that were created using one of the TIdMessage.SaveTo…() methods. The reason for this is because the SaveTo…() methods generate emails that are encoded with dot-transparency logic applied (a period at the front of a line is doubled, and a single period on a line by itself terminates the email). The LoadFrom…() methods expect this encoding. This is the encoding format used by the POP3 and SMTP protocols when transmitting emails over a socket, but this encoding is not typically used in EML files/streams.

TIdMessage uses an internal parser/generator (TIdMessageClient) that has no concept of where the data is coming from or going to. It just blindly assumes a socket is being used, and thus applies dot-transparency encoding/decoding logic accordingly. This is a known limitation of TIdMessage/Client that I have discussed many times before in various forums, and it will be addressed in Indy 11. Until then, Indy 10 does provide a workaround to load an EML file/stream that is not encoded with dot-transparency enabled:

var
  LStream: TIdReadFileExclusiveStream;
  LMsgClient: TIdMessageClient;
  LIOHandler: TIdIOHandlerStreamMsg;
begin


  IdMessage1.Clear;
  LStream := TIdReadFileExclusiveStream.Create('email.eml');
  try
    LMsgClient := TIdMessageClient.Create;
    try
      LIOHandler := TIdIOHandlerStreamMsg.Create(nil, LStream);
      try
        LIOHandler.FreeStreams := False;
        LIOHandler.EscapeLines := True; 
        LMsgClient.IOHandler := LIOHandler;
        try
          LIOHandler.Open;
          LMsgClient.ProcessMessage(IdMessage1);
        finally
          LMsgClient.IOHandler := nil;
        end;
      finally
        LIOHandler.Free;
      end;
    finally
      LMsgClient.Free;
    end;
  finally
    LStream.Free;
  end;
end;

A similar workaround is available to save an EML file/stream with dot-transparency disabled:

type
  TIdMessageAccess = class(TIdMessage)
  end;
var
  LStream: TIdFileCreateStream;
  LMsgClient: TIdMessageClient;
  LIOHandler: TIdIOHandlerStreamMsg;
begin


  LStream := TIdFileCreateStream.Create('email.eml');
  try
    LMsgClient := TIdMessageClient.Create;
    try
      LIOHandler := TIdIOHandlerStreamMsg.Create(nil, nil, LStream);
      try
        LIOHandler.FreeStreams := False;
        LIOHandler.UnescapeLines := True; 
        LMsgClient.IOHandler := LIOHandler;
        try
          TIdMessageAccess(IdMesssage).FSavingToFile := True; 
          try
            LMsgClient.SendMsg(IdMessage1, False);
          finally
            TIdMessageAccess(IdMesssage).FSavingToFile := False;
          end;
        finally
          LMsgClient.IOHandler := nil;
        end;
      finally
        LIOHandler.Free;
      end;
    finally
      LMsgClient.Free;
    end;
  finally
    LStream.Free;
  end;
end;

To make this easier to use, I have now wrapped these workarounds inside of a class helper in a new TIdMessageHelper.pas unit:

type
  TIdMessageHelper = class helper for TIdMessage
  public
    procedure LoadFromFile(const AFileName: string; const AHeadersOnly: Boolean; const AUsesDotTransparency: Boolean); overload;
    procedure LoadFromStream(AStream: TStream; const AHeadersOnly: Boolean; const AUsesDotTransparency: Boolean); overload;
    procedure SaveToFile(const AFileName: string; const AHeadersOnly: Boolean; const AUseDotTransparency: Boolean); overload;
    procedure SaveToStream(AStream: TStream; const AHeadersOnly: Boolean; const AUseDotTransparency: Boolean); overload;
  end;

This class helper is available in Delphi 2006 and later, and FreePascal.

To disable dot-transparency encoding/decoding logic, simply call TIdMessage.LoadFrom...() or TIdMessage SaveTo...() with the AUsesDotTransparency parameter set to False:

uses
  ..., IdMessage, IdMessageHelper;

IdMessage1.LoadFromFile('email.eml', False, False);
IdMessage1.SaveToFile('email.eml', False, False);

If you are using a older version of Delphi or FreePascal that does not support class helpers, or if you are using C++Builder, there are flat wrapper functions provided as well:

procedure TIdMessageHelper_LoadFromFile(AMsg: TIdMessage; const AFileName: string; const AHeadersOnly: Boolean; const AUsesDotTransparency: Boolean);
procedure TIdMessageHelper_LoadFromStream(AMsg: TIdMessage; AStream: TStream; const AHeadersOnly: Boolean; const AUsesDotTransparency: Boolean);
procedure TIdMessageHelper_SaveToFile(AMsg: TIdMessage; const AFileName: string; const AHeadersOnly: Boolean; const AUseDotTransparency: Boolean);
procedure TIdMessageHelper_SaveToStream(AMsg: TIdMessage; AStream: TStream; const AHeadersOnly: Boolean; const AUseDotTransparency: Boolean);

For example:

uses
  ..., IdMessage, IdMessageHelper;

TIdMessageHelper_LoadFromFile(IdMessage1, 'email.eml', False, False);
TIdMessageHelper_SaveToFile(IdMessage1, 'email.eml', False, False);