{ $HDR$}
{**********************************************************************}
{ Unit archived using Team Coherence                                   }
{ Team Coherence is Copyright 2002 by Quality Software Components      }
{                                                                      }
{ For further information / comments, visit our WEB site at            }
{ http://www.TeamCoherence.com                                         }
{**********************************************************************}
{}
{ $Log:  11669: IdMessageClient.pas
{
{   Rev 1.37    11/11/2003 12:06:26 AM  BGooijen
{ Did all todo's ( TStream to TIdStream mainly )
}
{
{   Rev 1.36    2003.10.24 10:43:10 AM  czhower
{ TIdSTream to dos
}
{
    Rev 1.35    10/17/2003 12:37:36 AM  DSiders
  Added localization comments.
  Added resource string for exception message.
}
{
{   Rev 1.34    2003.10.14 9:57:12 PM  czhower
{ Compile todos
}
{
{   Rev 1.33    10/12/2003 1:49:56 PM  BGooijen
{ Changed comment of last checkin
}
{
{   Rev 1.32    10/12/2003 1:43:40 PM  BGooijen
{ Changed IdCompilerDefines.inc to Core\IdCompilerDefines.inc
}
{
{   Rev 1.30    10/11/2003 4:21:14 PM  BGooijen
{ Compiles in D7 again
}
{
{   Rev 1.29    10/10/2003 10:42:28 PM  BGooijen
{ DotNet
}
{
{   Rev 1.28    9/10/2003 1:50:52 PM  SGrobety
{ DotNet
}
{
{   Rev 1.27    10/8/2003 9:53:42 PM  GGrieve
{ Remove $IFDEFs
}
{
{   Rev 1.26    05/10/2003 16:39:52  CCostelloe
{ Set default ContentType
}
{
{   Rev 1.25    03/10/2003 21:03:40  CCostelloe
{ Bug fixes
}
{
{   Rev 1.24    2003.10.02 9:27:52 PM  czhower
{ DotNet Excludes
}
{
{   Rev 1.23    01/10/2003 17:58:56  HHariri
{ More fixes for Multipart Messages and also fixes for incorrect transfer
{ encoding settings
}
{
{   Rev 1.20    01/10/2003 10:57:56  CCostelloe
{ Fixed GenerateTextPartContentType (was ignoring ContentType)
}
{
{   Rev 1.19    26/09/2003 01:03:48  CCostelloe
{ Modified ProcessAttachment in ReceiveBody to update message's Encoding if
{ attachment was XX-encoded.  Added decoding of message bodies encoded as
{ base64 or quoted-printable.  Added support for nested MIME parts
{ (ParentPart).  Added support for TIdText in UU and XX encoding.  Added
{ missing base64 and QP support where needed. Rewrote/rearranged most of code.
}
{
{   Rev 1.18    04/09/2003 20:44:56  CCostelloe
{ In SendBody, removed blank line between boundaries and Text part header;
{ recoded wDoublePoint
}
{
{   Rev 1.17    30/08/2003 18:40:44  CCostelloe
{ Updated to use IdMessageCoderMIME's new random boundaries
}
{
{   Rev 1.16    8/8/2003 12:27:18 PM  JPMugaas
{ Should now compile.
}
{
{   Rev 1.15    07/08/2003 00:39:06  CCostelloe
{ Modified SendBody to deal with unencoded attachments (otherwise 7bit
{ attachments had the attachment header written out as 7bit but was encoded as
{ base64)
}
{
{   Rev 1.14    11/07/2003 01:14:20  CCostelloe
{ SendHeader changed to support new IdMessage.GenerateHeader putting generated
{ headers in IdMessage.LastGeneratedHeaders.
}
{
{   Rev 1.13    6/15/2003 01:13:10 PM  JPMugaas
{ Minor fixes and cleanups.
}
{
{   Rev 1.12    5/18/2003 02:31:44 PM  JPMugaas
{ Reworked some things so IdSMTP and IdDirectSMTP can share code including
{ stuff for pipelining.
}
{
{   Rev 1.11    5/8/2003 03:18:06 PM  JPMugaas
{ Flattened ou the SASL authentication API, made a custom descendant of SASL
{ enabled TIdMessageClient classes.
}
{
{   Rev 1.10    5/8/2003 11:28:02 AM  JPMugaas
{ Moved feature negoation properties down to the ExplicitTLSClient level as
{ feature negotiation goes hand in hand with explicit TLS support.
}
{
{   Rev 1.9    5/8/2003 02:17:58 AM  JPMugaas
{ Fixed an AV in IdPOP3 with SASL list on forms.  Made exceptions for SASL
{ mechanisms missing more consistant, made IdPOP3 support feature feature
{ negotiation, and consolidated some duplicate code.
}
{
{   Rev 1.8    3/17/2003 02:16:06 PM  JPMugaas
{ Now descends from ExplicitTLS base class.
}
{
{   Rev 1.7    2/24/2003 07:25:18 PM  JPMugaas
{ Now compiles with new code.
}
{
{   Rev 1.6    12-8-2002 21:12:36  BGooijen
{ Changed calls to Writeln to  IOHandler.WriteLn, because the parent classes
{ don't provide Writeln, System.Writeln was assumed by the compiler
}
{
{   Rev 1.5    12-8-2002 21:08:58  BGooijen
{ The TIdIOHandlerStream was not Opened before used, fixed that.
}
{
{   Rev 1.4    12/6/2002 05:30:22 PM  JPMugaas
{ Now decend from TIdTCPClientCustom instead of TIdTCPClient.
}
{
{   Rev 1.3    12/5/2002 02:54:06 PM  JPMugaas
{ Updated for new API definitions.
}
{
{   Rev 1.2    11/23/2002 03:33:44 AM  JPMugaas
{ Reverted changes because they were problematic.  Kudzu didn't explain why.
}
{
{   Rev 1.1    11/19/2002 05:35:30 PM  JPMugaas
{ Fixed problem with a line starting with a ".".  A double period should only
{ be used if the line is really just one "." and no other cases.
}
{
{   Rev 1.0    11/13/2002 07:56:58 AM  JPMugaas
}
unit IdMessageClient;

{
  2003-10-04 Ciaran Costelloe (see comments starting CC4)
    If attachment not base64 encoded and has no ContentType, set to text/plain
  2003-Sep-20 Ciaran Costelloe
    Modified ProcessAttachment in ReceiveBody to update message's Encoding
    if attachment was XX-encoded.  Added decoding of message bodies
    encoded as base64 or quoted-printable.  Added support for nested MIME parts
    (ParentPart).  Added support for TIdText in UU and XX encoding.  Added
    missing base64 and QP support where needed.
    Rewrote/rearranged most of code.

  2001-Oct-29 Don Siders
    Modified TIdMessageClient.SendMsg to use AHeadersOnly argument.

  2001-Dec-1  Don Siders
    Save ContentDisposition in TIdMessageClient.ProcessAttachment

  2003-Sep-04 Ciaran Costelloe (CC comments)
    Commented-out IOHandler.WriteLn(''); in SendBody which used to insert a blank line
    between boundary and text attachment header, causing the attachment header to
    be parsed as part of the attachment text (the blank line is the delimiter for
    the end of the header).

  2003-Sep-11 Ciaran Costelloe (CC2 comments)
    Added support in decoding for message body (as distinct from message parts) being
    encoded.
    Added support for generating encoded message body.
}

{$I Core\IdCompilerDefines.inc}

interface

uses
  Classes,
  {$IFNDEF DotNetExclude}
  IdExplicitTLSClientServerBase,
  {$ENDIF}
  IdCoreGlobal,
  IdMessage, IdTCPClient, IdHeaderList,
  IdCoderMIME;

type
  {$IFNDEF DotNetExclude}
  TIdMessageClient = class(TIdExplicitTLSClient)
  {$ELSE}
  TIdMessageClient = class(TIdTCPClientCustom)
  {$ENDIF}
  protected
    // The length of the folded line
    FMsgLineLength: integer;
    // The string to be pre-pended to the next line
    FMsgLineFold: string;

    procedure ReceiveBody(AMsg: TIdMessage; const ADelim: string = '.'); virtual;
    function  ReceiveHeader(AMsg: TIdMessage; const AAltTerm: string = ''): string; virtual;
    procedure SendBody(AMsg: TIdMessage); virtual;
    procedure SendHeader(AMsg: TIdMessage); virtual;
    procedure WriteBodyText(AMsg: TIdMessage); virtual;
    procedure WriteFoldedLine(const ALine : string);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure ProcessMessage(AMsg: TIdMessage; AHeaderOnly: Boolean = False); overload;
    {$IFNDEF DotNetExclude}
    procedure ProcessMessage(AMsg: TIdMessage; const AStream: TStream; AHeaderOnly: Boolean = False); overload;
    procedure ProcessMessage(AMsg: TIdMessage; const AFilename: string; AHeaderOnly: Boolean = False); overload;
    {$ENDIF}
    procedure SendMsg(AMsg: TIdMessage; const AHeadersOnly: Boolean = False); overload; virtual;
    //
    {$IFNDEF DotNetExclude}
    property Capabilities;
    {$ENDIF}
    property MsgLineLength: integer read FMsgLineLength write FMsgLineLength;
    property MsgLineFold: string read FMsgLineFold write FMsgLineFold;
  end;

implementation
uses
                                                                                                     
  IdCoderQuotedPrintable, IdMessageCoderQuotedPrintable, IdMessageCoderMIME,
  IdMessageCoderUUE, IdMessageCoderXXE,
  //
  {$IFNDEF DotNetExclude}
  IdIOHandlerStream,
  {$ENDIF}
  IdCoder, IdCoder3to4,
  IdCoderHeader, IdGlobal, IdMessageCoder, IdComponent, IdException, IdResourceStrings, IdTCPConnection,
  IdStream, IdTCPStream,
  IdIOHandler, IdAttachmentFile,
  SysUtils, IdText, IdAttachment;

const
  SContentType = 'Content-Type'; {do not localize}
  SContentTransferEncoding = 'Content-Transfer-Encoding'; {do not localize}
  SThisIsMultiPartMessageInMIMEFormat = 'This is a multi-part message in MIME format'; {do not localize}

function  GenerateTextPartContentType(AContentType, ACharSet: String): String;
Begin
  Result := SContentType + {': text/plain'} ': '+AContentType; {do not localize}
  if ACharSet>'' then begin
    Result := Result + ' ; CHARSET="'+ACharSet+'"'; {do not localize}
  end
End;//

function GetLongestLine(var ALine : String; ADelim : String) : String;
var
  i, fnd, lineLen, delimLen : Integer;
begin
  i := 0;
  fnd := -1;
  delimLen := length(ADelim);
  lineLen := length(ALine);

  while i < lineLen do
  begin
    if ALine[i] = ADelim[1] then
    begin
      if Copy(ALine, i, delimLen) = ADelim then
      begin
        fnd := i;
      end;
    end;
    Inc(i);
  end;

  if fnd = -1 then
  begin
    result := '';
  end

  else begin
    result := Copy(ALine, 1, fnd - 1);
    ALine := Copy(ALine, fnd + delimLen, lineLen);
  end;
end;

///////////////////
// TIdMessageClient
///////////////////

constructor TIdMessageClient.Create(AOwner: TComponent);
begin
  inherited;

  FMsgLineLength := 79;
  FMsgLineFold := TAB;
end;

procedure TIdMessageClient.WriteFoldedLine(const ALine : string);
var
  ins, s, line, spare : String;
  msgLen, insLen : Word;
begin
  s := ALine;

  // To give an amount of thread-safety
  ins := FMsgLineFold;
  insLen := Length(ins);
  msgLen := FMsgLineLength;

  // Do first line
  if length(s) > FMsgLineLength then
  begin
    spare := Copy(s, 1, msgLen);
    line := GetLongestLine(spare, ' ');
    s := spare + Copy(s, msgLen + 1, length(s));
    IOHandler.WriteLn(line);

    // continue with the folded lines
    while length(s) > (msgLen - insLen) do
    begin
      spare := Copy(s, 1, (msgLen - insLen));
      line := GetLongestLine(spare, ' ');
      s := ins + spare + Copy(s, (msgLen - insLen) + 1, length(s));
      IOHandler.WriteLn(line);
    end;

    // complete the output with what's left
    if Trim(s) <> '' then
    begin
      IOHandler.WriteLn(ins + s);
    end;
  end

  else begin
    IOHandler.WriteLn(s);
  end;
end;

procedure TIdMessageClient.ReceiveBody(AMsg: TIdMessage; const ADelim: string = '.');
var
  LMsgEnd: Boolean;
  LActiveDecoder: TIdMessageDecoder;
  LLine: string;
  LBodyDestStream: TStringStream;
  LParentPart: integer;
  LPreviousParentPart: integer;

  function ProcessTextPart(ADecoder: TIdMessageDecoder): TIdMessageDecoder;
  var
    LDestStream: TStringStream;
    i: integer;
    LTxt : TIdText;
  begin
    LDestStream := TStringStream.Create('');
    try
      LParentPart := AMsg.MIMEBoundary.ParentPart;
      Result := ADecoder.ReadBody(LDestStream, LMsgEnd);
      LTxt := TIdText.Create(AMsg.MessageParts);
      LTxt.ContentType := ADecoder.Headers.Values[SContentType];
      LTxt.ContentTransfer := ADecoder.Headers.Values['Content-Transfer-Encoding']; {do not localize}
      LTxt.Body.Text := LDestStream.DataString;
      LTxt.ContentID := ADecoder.Headers.Values['Content-ID'];  {do not localize}
      LTxt.ContentLocation := ADecoder.Headers.Values['Content-Location'];  {do not localize}
      LTxt.ExtraHeaders.NameValueSeparator := '=';
      for i := 0 to ADecoder.Headers.Count-1 do begin
        if LTxt.Headers.IndexOfName(ADecoder.Headers.Names[i]) < 0 then begin
          LTxt.ExtraHeaders.Add(ADecoder.Headers.Strings[i]);
        end;
      end;
      if AnsiSameText(Copy(LTxt.ContentType, 1, 10), 'multipart/') then begin {do not localize}
        LTxt.ParentPart := LPreviousParentPart;
      end else begin
        LTxt.ParentPart := LParentPart;
      end;
      ADecoder.Free;
    finally
      FreeAndNil(LDestStream);
    end;
  end;

  function ProcessAttachment(ADecoder: TIdMessageDecoder): TIdMessageDecoder;
  var
    LDestStream: TStream;
    i: integer;
    Attachment: TIdAttachment;
  begin
    Result := nil; // supress warnings
    LParentPart := AMsg.MIMEBoundary.ParentPart;
    AMsg.DoCreateAttachment(ADecoder.Headers, Attachment);
    Assert(Assigned(Attachment), 'Attachment must not be unassigned here!'); {Do not localize}
    with Attachment do begin
      try
        LDestStream := PrepareTempStream;
        try
          Result := ADecoder.ReadBody(LDestStream, LMsgEnd);

          {CC: See if it is XXE...}
          if ADecoder is TIdMessageDecoderUUE then begin
            if TIdMessageDecoderUUE(ADecoder).CodingType = 'XXE' then begin {do not localize}
              AMsg.Encoding := meXX;
            end;
          end;

          ContentType := ADecoder.Headers.Values[SContentType];
          ContentTransfer := ADecoder.Headers.Values['Content-Transfer-Encoding'];  {do not localize}

          ContentDisposition := ADecoder.Headers.Values['Content-Disposition']; {do not localize}
          ContentID := ADecoder.Headers.Values['Content-ID'];                   {do not localize}
          ContentLocation := ADecoder.Headers.Values['Content-Location'];       {do not localize}

          Filename := ADecoder.Filename;
          ExtraHeaders.NameValueSeparator := '=';
          for i := 0 to ADecoder.Headers.Count-1 do begin
            if Headers.IndexOfName(ADecoder.Headers.Names[i]) < 0 then begin
              ExtraHeaders.Add(ADecoder.Headers.Strings[i]);
            end;
          end;
          if AnsiSameText(Copy(ContentType, 1, 10), 'multipart/') then begin  {do not localize}
            ParentPart := LPreviousParentPart;
          end else begin
            ParentPart := LParentPart;
          end;
          ADecoder.Free;
        finally
          FinishTempStream;
        end;
      except
        //this should also remove the Item from the TCollection.
        //Note that Delete does not exist in the TCollection.
        AMsg.MessageParts[Index].Free;
        Free;
      end;
    end;
  end;

Begin
  LMsgEnd := False;
  if AMsg.NoDecode then begin
    IOHandler.Capture(AMsg.Body, ADelim);
  end else begin
    BeginWork(wmRead);
    try
      if ((AMsg.Encoding <> meMIME) and
       ((AMsg.ContentTransferEncoding = 'base64') or  {do not localize}
       (AMsg.ContentTransferEncoding = 'quoted-printable'))) then begin {do not localize}
        {CC: All the message body is encoded as base64 or QP.}
        LBodyDestStream := TStringStream.Create('');
        LActiveDecoder := TIdMessageDecoderMime.Create(AMsg);
        LActiveDecoder.SourceStream := TIdTCPStream.Create(Self);
        LActiveDecoder.ReadBody(LBodyDestStream, LMsgEnd);
        AMsg.Body.Text := LBodyDestStream.DataString;
        LActiveDecoder.Free;
        LBodyDestStream.Free;
      end else begin
        LActiveDecoder := nil;
        repeat
          {CC: This code assumes the preamble text (before the first boundary) is
          plain text.  I cannot imagine it not being, but if it arises, lines
          will have to be decoded.}
          LLine := IOHandler.ReadLn;
          if LLine = ADelim then begin
            Break;
          end;
          if LActiveDecoder = nil then begin
            LActiveDecoder := TIdMessageDecoderList.CheckForStart(AMsg, LLine);
          end;
          if LActiveDecoder = nil then begin
            {CC: Recoded non-compliant wDoublePoint}
            if ((Length(LLine) > 0) and (LLine[1] = '.')) then begin
              Delete(LLine,1,1);
            end;//if '..'
            AMsg.Body.Add(LLine);
          end else begin
            while LActiveDecoder <> nil do begin
              LActiveDecoder.SourceStream := TIdTCPStream.Create(Self);
              LPreviousParentPart := AMsg.MIMEBoundary.ParentPart;
              LActiveDecoder.ReadHeader;
              case LActiveDecoder.PartType of
               mcptUnknown:
                begin
                  raise EIdException.Create(RSMsgClientUnkownMessagePartType);
                end;
               mcptText:
                begin
                  LActiveDecoder := ProcessTextPart(LActiveDecoder);
                end;
               mcptAttachment:
                begin
                  LActiveDecoder := ProcessAttachment(LActiveDecoder);
                end;
              end;
            end;
          end;
        until LMsgEnd;
      end;
    finally
      EndWork(wmRead);
    end;
  end;
end;

procedure TIdMessageClient.SendHeader(AMsg: TIdMessage);
begin
  AMsg.GenerateHeader;
  IOHandler.Write(AMsg.LastGeneratedHeaders);
end;

procedure TIdMessageClient.SendBody(AMsg: TIdMEssage);
var
  i: Integer;
  LAttachment: TIdAttachment;
  LBoundary: string;
  LDestIdStream: TIdStream;
  LSrcStream: TStream;
  LStrStream: TStringStream;
  ISOCharset: string;
  HeaderEncoding: Char;  { B | Q }
  TransferEncoding: TTransfer;
  LEncoder: TIdMessageEncoder;
  LLine: string;
  LX: integer;

  function GetLine(ASrcStream: TStream; var ALine: string): Boolean;
  {Gets the next character, adding an extra '.' if line starts with a '.'}
  var
    LChar: Char;
    LGotAChar: Boolean;
  begin
    LGotAChar := False;
    Result := True;
    ALine := '';
    while ASrcStream.Read(LChar, 1) > 0 do begin
      if ((LGotAChar = False) and (LChar = '.')) then begin
        {Lines that start with a '.' are required to have an extra '.'
        inserted per RFC 821.}
        ALine := ALine + LChar;
      end;
      LGotAChar := True;
      if LChar = #13 then begin
        {Get the LF after the CR...}
        ASrcStream.Read(LChar, 1);
        ALine := ALine+EOL;
        Exit;
      end;
      ALine := ALine + LChar;
    end;
    if LGotAChar = False then begin
      Result := False;
    end;
  end;

  procedure WriteTextPart(ATextPart: TIdText);
  var
    Data: string;
    LBodyLine: String;
    i: Integer;
  begin
    if Length(ATextPart.ContentType) = 0 then begin
      ATextPart.ContentType := 'text/plain'; {do not localize}
    end;
    if Length(ATextPart.ContentTransfer) = 0 then begin
      ATextPart.ContentTransfer := 'quoted-printable'; {do not localize}
    end;
    IOHandler.WriteLn(GenerateTextPartContentType(ATextPart.ContentType, ATextPart.CharSet));


    if ATextPart.IsBodyEncodingRequired then begin
      IOHandler.WriteLn(SContentTransferEncoding + ': 8bit'); {do not localize}
    end
    else begin
      IOHandler.WriteLn(SContentTransferEncoding + ': ' + ATextPart.ContentTransfer); {do not localize}
    end;

    LX := ATextPart.ExtraHeaders.Count;  {Debugging}
    IOHandler.Write(ATextPart.ExtraHeaders);
    IOHandler.WriteLn('');

    if AnsiSameText(ATextPart.ContentTransfer, 'quoted-printable') then begin {do not localize}
      for i := 0 to ATextPart.Body.Count - 1 do begin
        LBodyLine := ATextPart.Body[i];
        if (LBodyLine>'') and (LBodyLine[1] = '.') then begin
          ATextPart.Body[i] := '.' + LBodyLine;
        end;
        Data := TIdEncoderQuotedPrintable.EncodeString(ATextPart.Body[i] + EOL);
        if TransferEncoding = iso2022jp then
          IOHandler.Write(Encode2022JP(Data))
        else
          IOHandler.Write(Data);
      end;
    end else if AnsiSameText(ATextPart.ContentTransfer, 'base64') then begin  {do not localize}
      LDestIdStream := TIdStream.Create( TIdTCPStream.Create(Self), True);
      try
        LEncoder := TIdMessageEncoder(TIdMessageEncoderMIME.Create(Self));
        try
          LStrStream := TStringStream.Create('');
          ATextPart.Body.SaveToStream(LStrStream);
          LStrStream.Seek(0, soFromBeginning);
          try
            LEncoder.Encode(LStrStream, LDestIdStream.Stream);
          finally
            FreeAndNil(LStrStream);
          end;
        finally
          LEncoder.Free;
        end;
      finally
        FreeAndNil(LDestIdStream);
      end;
    end else begin
      LX := ATextPart.Body.Count;
      IOHandler.Write(ATextPart.Body);
    end;
    IOHandler.WriteLn('');
  end;

var
  LBodyLine: String;
  LTextPart: TIdText;
  LAddedTextPart: Boolean;
  LLastPart: integer;
begin
  LBoundary := '';

  AMsg.InitializeISO(TransferEncoding, HeaderEncoding, ISOCharSet);
  BeginWork(wmWrite);
  try
    if (AnsiSameText(AMsg.ContentTransferEncoding, 'base64') or {do not localize}
      AnsiSameText(AMsg.ContentTransferEncoding, 'quoted-printable')) then begin  {do not localize}
      //CC2: The user wants the body encoded.
      if AMsg.MessageParts.Count > 0 then begin
        //CC2: We cannot deal with parts within a body encoding (user has to do
        //this manually, if the user really wants to). Note this should have been trapped in TIdMessage.GenerateHeader.
        raise EIdException.Create(RSMsgClientInvalidForTransferEncoding);
      end;
      IOHandler.WriteLn('');     //This is the blank line after the headers
      DoStatus(hsStatusText, [RSMsgClientEncodingText]);
      //CC2: Now output AMsg.Body in the chosen encoding...
      LDestIdStream := TIdStream.Create(TIdTCPStream.Create(Self), True);
      try
        if AnsiSameText(AMsg.ContentTransferEncoding, 'base64') then begin  {do not localize}
          LEncoder := TIdMessageEncoder(TIdMessageEncoderMIME.Create(Self));
        end else begin  {'quoted-printable'}
          LEncoder := TIdMessageEncoder(TIdMessageEncoderQuotedPrintable.Create(Self));
        end;
        try
          LStrStream := TStringStream.Create('');
          AMsg.Body.SaveToStream(LStrStream);
          LStrStream.Seek(0, soFromBeginning);
          try
            LEncoder.Encode(LStrStream, LDestIdStream.Stream);
          finally
            FreeAndNil(LStrStream);
          end;
        finally
          LEncoder.Free;
        end;
      finally
        FreeAndNil(LDestIdStream);
      end;
    end else if ((AMsg.Encoding = meUU) or (AMsg.Encoding = meXX)) then begin
      IOHandler.WriteLn('');     //This is the blank line after the headers
      //CC2: It is NOT Mime.  It is a body followed by optional attachments
      DoStatus(hsStatusText, [RSMsgClientEncodingText]);
      // Write out Body first
                                                                                                 
      if TransferEncoding = iso2022jp then begin
        for i := 0 to AMsg.Body.Count - 1 do begin
          LBodyLine := AMsg.Body[i];
          if (LBodyLine>'') and (LBodyLine = '.') then begin
            IOHandler.WriteLn('.' + Encode2022JP(LBodyLine));
          end else begin
            IOHandler.WriteLn(Encode2022JP(LBodyLine));
          end;
        end;
      end else begin
        WriteBodyText(AMsg);
      end;
      IOHandler.WriteLn('');
      if AMsg.MessageParts.Count > 0 then begin
        //The message has attachments.
        for i := 0 to AMsg.MessageParts.Count - 1 do begin
          //CC: Added support for TIdText...
          if AMsg.MessageParts.Items[i] is TIdText then begin
            IOHandler.WriteLn('');
            IOHandler.WriteLn('------- Start of text attachment -------'); {do not localize}
            DoStatus(hsStatusText,  [RSMsgClientEncodingText]);
            WriteTextPart(AMsg.MessageParts.Items[i] as TIdText);
            IOHandler.WriteLn('------- End of text attachment -------');   {do not localize}
          end else if AMsg.MessageParts.Items[i] is TIdAttachment then begin
            DoStatus(hsStatusText, [RSMsgClientEncodingAttachment]);
            if AMsg.Encoding = meUU then begin
              LEncoder := TIdMessageEncoderUUE.Create(nil);
            end else begin  //AMsg.Encoding = meXX
              LEncoder := TIdMessageEncoderXXE.Create(nil);
            end;
            LDestIdStream := TIdStream.Create(TIdTCPStream.Create(Self), True);
            try
              with LEncoder do
              try
                Filename := TIdAttachment(AMsg.MessageParts[i]).Filename;
                //TIdAttachmentFile(AMsg.MessageParts[i]).StoredPathName := Filename;
                LSrcStream := TIdAttachment(AMsg.MessageParts[i]).OpenLoadStream;
                try
                  Encode(LSrcStream, LDestIdStream.Stream);
                finally
                  TIdAttachment(AMsg.MessageParts[i]).CloseLoadStream;
                end;
              finally
                Free;
              end;
            finally
              FreeAndNil(LDestIdStream);
            end;
          end;
          IOHandler.WriteLn('');
        end;
      end;
    end else begin
      //CC2: It is MIME-encoding...
      LAddedTextPart := False;
      //######### OUTPUT THE PREAMBLE TEXT ########
      IOHandler.WriteLn('');     //This is the blank line after the headers
      if AMsg.Body.Count>0 then begin
        //CC2: The message has a body text.  There are now a few possibilities...
        if ((AMsg.ConvertPreamble = True) and (AMsg.MessageParts.TextPartCount = 0)) then begin
          //CC2: There is no text part, the user has not changed ConvertPreamble from
          //its default of True, so the user has probably put his message into
          //the body by mistake instead of putting it in a TIdText part.
          //Create a TIdText part from the .Body text...
          LTextPart := TIdText.Create(AMsg.MessageParts);
          LTextPart.Body.Text := AMsg.Body.Text;
          LTextPart.ContentType := 'text/plain';  {do not localize}
          LTextPart.ContentTransfer := '7bit';    {do not localize}
          //Have to remember that we added a text part, which is the last part
          //in the collection, because we need it to be outputted first...
          LAddedTextPart := True;
          //CC2: Insert our standard preamble text...
          IOHandler.WriteLn(SThisIsMultiPartMessageInMIMEFormat);
        end else begin
          //CC2: Hopefully the user has put suitable text in the preamble, or this
          //is an already-received message which already has a preamble text...
          WriteBodyText(AMsg);
        end;
      end else begin
        //CC2: The user has specified no body text: he presumably has the message in
        //a TIdText part.  Add the "standard" MIME preamble text for non-html
        //email clients...
        IOHandler.WriteLn(SThisIsMultiPartMessageInMIMEFormat);
      end;
      IOHandler.WriteLn('');
      //######### DECIDE ON THE BOUNDARY ########
      AMsg.MIMEBoundary.Clear;
      LBoundary := IdMIMEBoundaryStrings.IndyMIMEBoundary;
      AMsg.MIMEBoundary.Push(LBoundary, -1);  //-1 is "top level"
      //######### OUTPUT THE PARTS ########
      //CC2: Write the text parts in their order, if you change the order you
      //can mess up mutipart sequences.
      //The exception is due to ConvertPreamble, which may have added a text
      //part at the end (the only place a TIdText part can be added), but it
      //needs to be outputted first...
      LLastPart := AMsg.MessageParts.Count - 1;
      if LAddedTextPart then begin
          IOHandler.WriteLn('--' + LBoundary);
          DoStatus(hsStatusText,  [RSMsgClientEncodingText]);
          WriteTextPart(AMsg.MessageParts.Items[LLastPart] as TIdText);
          IOHandler.WriteLn('');
          Dec(LLastPart);  //Don't output it again in the following "for" loop
      end;
      for i := 0 to LLastPart do begin
        LLine := AMsg.MessageParts.Items[i].ContentType;
        if AnsiSameText(Copy(LLine, 1, 10), 'multipart/') then begin  {do not localize}
          //A multipart header.  Write out the CURRENT boundary first...
          IOHandler.WriteLn('--' + LBoundary);
          //Make the current boundary and this part number active...
          AMsg.MIMEBoundary.Push(LBoundary, i);
          //Now need to generate a new boundary by adding a random character to
          //the current boundary...
          LBoundary := LBoundary + IdMIMEBoundaryStrings.GenerateRandomChar;
          IOHandler.WriteLn('Content-Type: '+LLine + ';');            {do not localize}
          IOHandler.WriteLn('        boundary="' + LBoundary + '"');  {do not localize}
          IOHandler.WriteLn('');
        end else begin
          //Not a multipart header, see if it is a part change...
          if AMsg.MessageParts.Items[i].ParentPart <> AMsg.MIMEBoundary.ParentPart then begin
            IOHandler.WriteLn('--' + LBoundary + '--');
            AMsg.MIMEBoundary.Pop;
            LBoundary := AMsg.MIMEBoundary.Boundary;
            IOHandler.WriteLn('--' + LBoundary);
          end else begin
            IOHandler.WriteLn('--' + LBoundary);
          end;
          if AMsg.MessageParts.Items[i] is TIdText then begin
            DoStatus(hsStatusText,  [RSMsgClientEncodingText]);
            WriteTextPart(AMsg.MessageParts.Items[i] as TIdText);
            IOHandler.WriteLn('');
          end;
          if AMsg.MessageParts.Items[i] is TIdAttachment then begin
            LAttachment := TIdAttachment(AMsg.MessageParts[i]);
            DoStatus(hsStatusText, [RSMsgClientEncodingAttachment]);
            if Length(LAttachment.ContentTransfer) = 0 then begin
              LAttachment.ContentTransfer := 'base64'; {do not localize}
            end;
            if Length(LAttachment.ContentDisposition) = 0 then begin
              LAttachment.ContentDisposition := 'attachment'; {do not localize}
            end;
            if Length(LAttachment.ContentType) = 0 then begin
              if AnsiSameText(LAttachment.ContentTransfer, 'base64') then begin {do not localize}
                LAttachment.ContentType := 'application/octet-stream'; {do not localize}
              end else begin
                {CC4: Set default type if not base64 encoded...}
                LAttachment.ContentType := 'text/plain'; {do not localize}
              end;
            end;
            IOHandler.WriteLn('Content-Type: ' + LAttachment.ContentType + ';'); {do not localize}
            IOHandler.WriteLn('        name="' + ExtractFileName(LAttachment.FileName) + '"'); {do not localize}
            IOHandler.WriteLn('Content-Transfer-Encoding: ' + LAttachment.ContentTransfer); {do not localize}
            IOHandler.WriteLn('Content-Disposition: ' + LAttachment.ContentDisposition +';'); {do not localize}
            IOHandler.WriteLn('        filename="' + ExtractFileName(LAttachment.FileName) + '"'); {do not localize}
            if LAttachment.ContentID>'' then begin
              IOHandler.WriteLn('Content-ID: '+LAttachment.ContentID); {Do not Localize}
            end;
            IOHandler.Write(LAttachment.ExtraHeaders);
            IOHandler.WriteLn('');
            LDestIdStream := TIdStream.Create( TIdTCPStream.Create(Self), True);
            try
              if ((AnsiSameText(LAttachment.ContentTransfer, 'base64') = False) and {do not localize}
                (AnsiSameText(LAttachment.ContentTransfer, 'quoted-printable') = False)) then begin {do not localize}
                LSrcStream := TIdAttachment(AMsg.MessageParts[i]).OpenLoadStream;
                try
                  while GetLine(LSrcStream, LLine) = True do begin
                    LDestIdStream.Write(LLine);
                  end;
                finally
                  TIdAttachment(AMsg.MessageParts[i]).CloseLoadStream;
                end;
              end else begin
                if AnsiSameText(LAttachment.ContentTransfer, 'base64') then begin {do not localize}
                  LEncoder := TIdMessageEncoder(TIdMessageEncoderMIME.Create(Self));
                end else begin  {'quoted-printable'}
                  LEncoder := TIdMessageEncoder(TIdMessageEncoderQuotedPrintable.Create(Self));
                end;
                try
                  //LEncoder.Filename := TIdAttachmentFile(AMsg.MessageParts[i]).Filename;
                  LEncoder.Filename := TIdAttachment(AMsg.MessageParts[i]).Filename;
                  //TIdAttachmentFile(AMsg.MessageParts[i]).StoredPathName := LEncoder.Filename;
                  LSrcStream := TIdAttachment(AMsg.MessageParts[i]).OpenLoadStream;
                  try
                    LEncoder.Encode(LSrcStream, LDestIdStream.Stream);
                  finally
                    TIdAttachment(AMsg.MessageParts[i]).CloseLoadStream;
                  end;
                finally
                  LEncoder.Free;
                end;
              end;
            finally
              FreeAndNil(LDestIdStream);
            end;
            IOHandler.WriteLn('');
          end;
        end;
      end;
      for i := 0 to AMsg.MIMEBoundary.Count - 1 do begin
        IOHandler.WriteLn('--' + AMsg.MIMEBoundary.Boundary + '--');
        AMsg.MIMEBoundary.Pop;
      end;
    end;
  finally
    EndWork(wmWrite);
  end;
End;//SendBody

// 2001-Oct-29 Don Siders Added AHeadersOnly parameter
                                                                              
//  versions like TIdMessageClient.ProcessMessage?
procedure TIdMessageClient.SendMsg(AMsg: TIdMessage; const AHeadersOnly: Boolean = False);
begin
  if AMsg.NoEncode then begin
    IOHandler.Write(AMsg.Headers);
    IOHandler.WriteLn('');
    if not AHeadersOnly then begin
      IOHandler.Write(AMsg.Body);
    end;
  end else begin
    SendHeader(AMsg);
    //IOHandler.WriteLn('');  CC2: Moved into SendBody
    if (not AHeadersOnly) then SendBody(AMsg);
  end;
end;

function TIdMessageClient.ReceiveHeader(AMsg: TIdMessage; const AAltTerm: string = ''): string;
begin
  BeginWork(wmRead); try
    repeat
      Result := IOHandler.ReadLn;
      // Exchange Bug: Exchange sometimes returns . when getting a message instead of
      // '' then a . - That is there is no seperation between the header and the message for an
      // empty message.
      if ((Length(AAltTerm) = 0) and (Result = '.')) or
         ({APR: why? (Length(AAltTerm) > 0) and }(Result = AAltTerm)) then begin
        Break;
      end else if Result <> '' then begin
        AMsg.Headers.Append(Result);
      end;
    until False;
    AMsg.ProcessHeaders;
  finally EndWork(wmRead); end;
end;

procedure TIdMessageclient.ProcessMessage(AMsg: TIdMessage; AHeaderOnly: Boolean = False);
begin
  if IOHandler <> nil then begin
    ReceiveHeader(AMsg);
    if (not AHeaderOnly) then begin
      ReceiveBody(AMsg);
    end;
  end;
end;

{$IFNDEF DotNetExclude}
procedure TIdMessageClient.ProcessMessage(AMsg: TIdMessage; const AStream: TStream; AHeaderOnly: Boolean = False);
var
  LVoidStream: TMemoryStream;
begin
  LVoidStream := TMemoryStream.Create; try
    IOHandler := TIdIOHandlerStream.Create(nil); try
      IOHandler.Open;
    todo; // constructor to IOHS - Dont free
//      TIdIOHandlerStream(IOHandler).SourceReadStream := AStream;
//      TIdIOHandlerStream(IOHandler).TargetWriteStream := LVoidStream;
      ReceiveHeader(AMsg);
      if not AHeaderOnly then
      begin
        ReceiveBody(AMsg);
      end;
    finally
      IOHandler.Free;
      IOHandler := nil;
    end;
  finally FreeAndNil(LVoidStream); end;
end;
{$ENDIF}

{$IFNDEF DotNetExclude}
procedure TIdMessageClient.ProcessMessage(AMsg: TIdMessage; const AFilename: string; AHeaderOnly: Boolean = False);
var
  LStream: TFileStream;
begin
  LStream := TFileStream.Create(AFileName, fmOpenRead);
  try
    ProcessMessage(AMsg, LStream, AHeaderOnly);
  finally
    FreeAndNil(LStream);
  end;
end;
{$ENDIF}

procedure TIdMessageClient.WriteBodyText(AMsg: TIdMessage);
var
  i: integer;
  LBodyLine: String;
begin
  for i := 0 to AMsg.Body.Count - 1 do begin
    LBodyLine := AMsg.Body[i];
    if Copy(AMsg.Body[i], 1, 1) = '.' then
    begin
      IOHandler.WriteLn('.' + LBodyLine);
    end
    else begin
      IOHandler.WriteLn(LBodyLine);
    end;
  end;
end;

destructor TIdMessageClient.Destroy;
begin
  inherited;
end;

end.
