restemplate/indy/Protocols/IdMessageClient.pas

1586 lines
60 KiB
Plaintext

{
$Project$
$Workfile$
$Revision$
$DateUTC$
$Id$
This file is part of the Indy (Internet Direct) project, and is offered
under the dual-licensing agreement described on the Indy website.
(http://www.indyproject.org/)
Copyright:
(c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
$Log$
}
{
Rev 1.85 1/6/05 4:38:30 PM RLebeau
Bug fix for decoding Text part headers
}
{
Rev 1.84 11/30/04 10:44:44 AM RLebeau
Bug fix for previous checkin
}
{
Rev 1.83 11/30/2004 12:10:40 PM JPMugaas
Fix for compiler error.
}
{
Rev 1.82 11/28/04 2:22:04 PM RLebeau
Updated a few hard-coded strings to use resource strings instead
}
{
Rev 1.81 28/11/2004 20:08:14 CCostelloe
MessagePart.Boundary now (correctly) holds decoded MIME boundary
}
{
Rev 1.80 11/27/2004 8:58:14 PM JPMugaas
Compile errors.
}
{
Rev 1.79 10/26/2004 10:25:46 PM JPMugaas
Updated refs.
}
{
Rev 1.78 24.09.2004 02:16:48 Andreas Hausladen
Added ReadTIdBytesFromStream and ReadCharFromStream function to supress .NET
warnings.
}
{
Rev 1.77 27.08.2004 22:04:32 Andreas Hausladen
speed optimization ("const" for string parameters)
Fixed "blank line multiplication"
}
{
Rev 1.76 27.08.2004 00:21:32 Andreas Hausladen
Undo last changes (temporary)
}
{
Rev 1.75 26.08.2004 22:14:16 Andreas Hausladen
Fixed last line blank line read/write bug
}
{
Rev 1.74 7/23/04 7:17:20 PM RLebeau
TFileStream access right tweak for ProcessMessage()
}
{
Rev 1.73 28/06/2004 23:58:12 CCostelloe
Bug fix
}
{
Rev 1.72 6/11/2004 9:38:08 AM DSiders
Added "Do not Localize" comments.
}
{
Rev 1.71 2004.06.06 4:53:04 PM czhower
Undid 1.70. Not needed, just masked an existing bug and did not fix it.
}
{
Rev 1.70 06/06/2004 01:23:54 CCostelloe
OnWork fix
}
{
Rev 1.69 6/4/04 12:41:56 PM RLebeau
ContentTransferEncoding bug fix
}
{
Rev 1.68 2004.05.20 1:39:08 PM czhower
Last of the IdStream updates
}
{
Rev 1.67 2004.05.20 11:36:52 AM czhower
IdStreamVCL
}
{
Rev 1.66 2004.05.20 11:12:56 AM czhower
More IdStream conversions
}
{
Rev 1.65 2004.05.19 3:06:34 PM czhower
IdStream / .NET fix
}
{
Rev 1.64 19/05/2004 00:54:30 CCostelloe
Bug fix (though I claim in my defence that it is only a hint fix)
}
{
Rev 1.63 16/05/2004 18:55:06 CCostelloe
New TIdText/TIdAttachment processing
}
{
Rev 1.62 2004.05.03 11:15:16 AM czhower
Fixed compile error and added use of constants.
}
{
Rev 1.61 5/2/04 8:02:12 PM RLebeau
Updated TIdIOHandlerStreamMsg to keep track of the last character received
from the stream so that extra CR LF characters are not added to the end of
the message data unnecessarily.
}
{
Rev 1.60 4/23/04 1:54:58 PM RLebeau
One more tweak for TIdIOHandlerStreamMsg support
}
{
Rev 1.59 4/23/04 1:21:16 PM RLebeau
Minor tweaks for TIdIOHandlerStreamMsg support
}
{
Rev 1.58 23/04/2004 20:48:10 CCostelloe
Added TIdIOHandlerStreamMsg to stop looping if no terminating \r\n.\r\n and
added support for emails that are attachments only
}
{
Rev 1.57 2004.04.18 1:39:22 PM czhower
Bug fix for .NET with attachments, and several other issues found along the
way.
}
{
Rev 1.56 2004.04.16 11:31:00 PM czhower
Size fix to IdBuffer, optimizations, and memory leaks
Rev 1.55 2004.03.07 10:36:08 AM czhower
SendMsg now calls OnWork with NoEncode = True
Rev 1.54 2004.03.04 1:02:58 AM czhower
Const removed from arguemtns (1 not needed + 1 incorrect)
Rev 1.53 2004.03.03 7:18:32 PM czhower
Fixed AV bug with ProcessMessage
Rev 1.52 2004.03.03 11:54:34 AM czhower
IdStream change
Rev 1.51 2/3/04 12:25:50 PM RLebeau
Updated WriteTextPart() function inside of SendBody() to write the ContentID
property is it is assigned.
Rev 1.50 2004.02.03 5:44:02 PM czhower
Name changes
Rev 1.49 2004.02.03 2:12:16 PM czhower
$I path change
Rev 1.48 1/27/2004 4:04:06 PM SPerry
StringStream ->IdStringStream
Rev 1.47 2004.01.27 12:03:28 AM czhower
Properly named a local variable to fix a .net conflict.
Rev 1.46 1/25/2004 3:52:32 PM JPMugaas
Fixes for abstract SSL interface to work in NET.
Rev 1.45 24/01/2004 19:24:30 CCostelloe
Cleaned up warnings
Rev 1.44 1/21/2004 1:30:06 PM JPMugaas
InitComponent
Rev 1.43 16/01/2004 17:39:34 CCostelloe
Added support for BinHex 4.0 encoding
Rev 1.42 11/01/2004 19:53:40 CCostelloe
Revisions for TIdMessage SaveToFile & LoadFromFile for D7 & D8
Rev 1.40 08/01/2004 23:46:16 CCostelloe
Changes to ProcessMessage to get TIdMessage.LoadFromFile working in D7
Rev 1.39 08/01/2004 00:31:06 CCostelloe
Start of reimplementing LoadFrom/SaveToFile
Rev 1.38 22/12/2003 00:44:52 CCostelloe
.NET fixes
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.
}
interface
{$i IdCompilerDefines.inc}
uses
Classes,
IdCoderMIME,
IdExplicitTLSClientServerBase,
IdGlobal,
IdHeaderList,
IdIOHandlerStream,
IdBaseComponent,
IdMessage,
IdTCPClient;
type
TIdIOHandlerStreamMsg = class(TIdIOHandlerStream)
protected
FTerminatorWasRead: Boolean;
FEscapeLines: Boolean;
FUnescapeLines: Boolean;
FLastByteRecv: Byte;
function ReadDataFromSource(var VBuffer: TIdBytes): Integer; override;
public
constructor Create(
AOwner: TComponent;
AReceiveStream: TStream;
ASendStream: TStream = nil
); override; //Should this be reintroduce instead of override?
function Readable(AMSec: Integer = IdTimeoutDefault): Boolean; override;
function ReadLn(ATerminator: string; ATimeout: Integer = IdTimeoutDefault;
AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; override;
procedure WriteLn(const AOut: string; AByteEncoding: IIdTextEncoding = nil
{$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF}
); override;
property EscapeLines: Boolean read FEscapeLines write FEscapeLines;
property UnescapeLines: Boolean read FUnescapeLines write FUnescapeLines;
published
property MaxLineLength default MaxInt;
end;
TIdMessageClient = class(TIdExplicitTLSClient)
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; {do not localize}
function ReceiveHeader(AMsg: TIdMessage; const AAltTerm: string = ''): string; virtual;
procedure SendBody(AMsg: TIdMessage); virtual;
procedure SendHeader(AMsg: TIdMessage); virtual;
procedure EncodeAndWriteText(const ABody: TStrings; AEncoding: IIdTextEncoding);
procedure WriteFoldedLine(const ALine : string);
procedure InitComponent; override;
public
{$IFDEF WORKAROUND_INLINE_CONSTRUCTORS}
constructor Create(AOwner: TComponent); reintroduce; overload;
{$ENDIF}
destructor Destroy; override;
procedure ProcessMessage(AMsg: TIdMessage; AHeaderOnly: Boolean = False); overload;
procedure ProcessMessage(AMsg: TIdMessage; AStream: TStream; AHeaderOnly: Boolean = False); overload;
procedure ProcessMessage(AMsg: TIdMessage; const AFilename: string; AHeaderOnly: Boolean = False); overload;
procedure SendMsg(AMsg: TIdMessage; AHeadersOnly: Boolean = False); overload; virtual;
//
// property Capabilities;
property MsgLineLength: integer read FMsgLineLength write FMsgLineLength;
property MsgLineFold: string read FMsgLineFold write FMsgLineFold;
end;
implementation
uses
//facilitate inlining only.
{$IFDEF DOTNET}
System.IO,
IdStreamNET,
{$ELSE}
IdStreamVCL,
{$ENDIF}
//TODO: Remove these references and make it completely pluggable. Check other spots in Indy as well
IdMessageCoderBinHex4, IdMessageCoderQuotedPrintable, IdMessageCoderMIME,
IdMessageCoderUUE, IdMessageCoderXXE,
//
IdGlobalProtocols,
IdCoder, IdCoder3to4, IdCoderBinHex4,
IdCoderHeader, IdHeaderCoderBase, IdMessageCoder, IdComponent, IdException,
IdResourceStringsProtocols, IdTCPConnection, IdTCPStream, IdIOHandler,
IdAttachmentFile, IdText, IdAttachment,
SysUtils;
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 GetLongestLine(var ALine : String; const ADelim : String) : String;
var
i, fnd, delimLen : Integer;
begin
Result := '';
fnd := 0;
delimLen := Length(ADelim);
for i := 1 to Length(ALine) do
begin
if ALine[i] = ADelim[1] then
begin
if Copy(ALine, i, delimLen) = ADelim then
begin
fnd := i;
end;
end;
end;
if fnd > 0 then
begin
Result := Copy(ALine, 1, fnd - 1);
ALine := Copy(ALine, fnd + delimLen, MaxInt);
end;
end;
procedure RemoveLastBlankLine(Body: TStrings);
var
Count: Integer;
begin
if Assigned(Body) then begin
{ Remove the last blank line. The last blank line is added again in
TIdMessageClient.SendBody(). }
Count := Body.Count;
if (Count > 0) and (Body[Count - 1] = '') then begin
Body.Delete(Count - 1);
end;
end;
end;
////////////////////////
// TIdIOHandlerStreamMsg
////////////////////////
constructor TIdIOHandlerStreamMsg.Create(
AOwner: TComponent;
AReceiveStream: TStream;
ASendStream: TStream = nil
);
begin
inherited Create(AOwner, AReceiveStream, ASendStream);
FTerminatorWasRead := False;
FEscapeLines := False; // do not set this to True! This is for users to set manually...
FUnescapeLines := False; // do not set this to True! This is for users to set manually...
FLastByteRecv := 0;
MaxLineLength := MaxInt;
end;
function TIdIOHandlerStreamMsg.Readable(AMSec: integer = IdTimeoutDefault): Boolean;
begin
if not FTerminatorWasRead then begin
Result := inherited Readable(AMSec);
if Result then begin
Exit;
end;
end;
Result := ReceiveStream <> nil;
end;
function TIdIOHandlerStreamMsg.ReadDataFromSource(var VBuffer: TIdBytes): Integer;
var
LTerminator: String;
begin
if not FTerminatorWasRead then
begin
Result := inherited ReadDataFromSource(VBuffer);
if Result > 0 then begin
FLastByteRecv := VBuffer[Result-1];
Exit;
end;
// determine whether the stream ended with a line
// break, adding an extra CR and/or LF if needed...
if (FLastByteRecv = Ord(LF)) then begin
// don't add an extra line break
LTerminator := '.' + EOL;
end else if (FLastByteRecv = Ord(CR)) then begin
// add extra LF
LTerminator := LF + '.' + EOL;
end else begin
// add extra CRLF
LTerminator := EOL + '.' + EOL;
end;
FTerminatorWasRead := True;
// in theory, CopyTIdString() will write the string
// into the byte array using 1-byte characters even
// under DotNet where strings are usually Unicode
// instead of ASCII...
CopyTIdString(LTerminator, VBuffer, 0);
Result := Length(LTerminator);
end else begin
Result := 0;
end;
end;
function TIdIOHandlerStreamMsg.ReadLn(ATerminator: string;
ATimeout: Integer = IdTimeoutDefault; AMaxLineLength: Integer = -1;
AByteEncoding: IIdTextEncoding = nil
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
begin
Result := inherited ReadLn(ATerminator, ATimeout, AMaxLineLength,
AByteEncoding{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
if FEscapeLines and TextStartsWith(Result, '.') and (not FTerminatorWasRead) then begin {Do not Localize}
Result := '.' + Result; {Do not Localize}
end;
end;
procedure TIdIOHandlerStreamMsg.WriteLn(const AOut: string; AByteEncoding: IIdTextEncoding = nil
{$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF}
);
var
LOut: String;
begin
LOut := AOut;
if FUnescapeLines and TextStartsWith(LOut, '..') then begin {Do not Localize}
IdDelete(LOut, 1, 1);
end;
inherited WriteLn(LOut, AByteEncoding{$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF});
end;
///////////////////
// TIdMessageClient
///////////////////
{$IFDEF WORKAROUND_INLINE_CONSTRUCTORS}
constructor TIdMessageClient.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
{$ENDIF}
procedure TIdMessageClient.InitComponent;
begin
inherited InitComponent;
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, ' '); {do not localize}
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, ' '); {do not localize}
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 = '.'); {do not localize}
var
LMsgEnd: Boolean;
LActiveDecoder: TIdMessageDecoder;
LLine: string;
LParentPart: integer;
LPreviousParentPart: integer;
LEncoding, LCharsetEncoding: IIdTextEncoding;
LContentTransferEncoding: string;
LUnknownContentTransferEncoding: Boolean;
// TODO - move this procedure into TIdIOHandler as a new Capture method?
procedure CaptureAndDecodeCharset;
var
LMStream: TMemoryStream;
begin
LMStream := TMemoryStream.Create;
try
IOHandler.Capture(LMStream, ADelim, True, IndyTextEncoding_8Bit{$IFDEF STRING_IS_ANSI}, IndyTextEncoding_8Bit{$ENDIF});
LMStream.Position := 0;
// TODO: when String is AnsiString, TIdIMAP4 uses 8bit as the destination
// encoding, should this be doing the same? Otherwise, we could just use
// AMsg.Body.LoadFromStream() instead...
ReadStringsAsCharSet(LMStream, AMsg.Body, AMsg.CharSet{$IFDEF STRING_IS_ANSI}, CharsetToEncoding(AMsg.CharSet){$ENDIF});
finally
FreeAndNil(LMStream);
end;
end;
// RLebeau 11/2/2013: TIdMessage.Headers is a TIdHeaderList, but
// TIdMessageDecoder.Headers is a plain TStringList. Although TIdHeaderList
// is a TStrings descendant, but it reintroduces its own Values[] property
// instead of implementing the TStrings.Values[] property, so we cannot
// access TIdMessage.Headers using a TStrings pointer or else the wrong
// property will be invoked and we won't get the right value when accessing
// TIdMessage.Headers since TStrings and TIdHeaderList use different
// NameValueSeparator implementations, so we have to access them separately...
function GetHeaderValue(const AName: string): string;
begin
if AMsg.IsMsgSinglePartMime then begin
Result := AMsg.Headers.Values[AName];
end else begin
Result := LActiveDecoder.Headers.Values[AName];
end;
end;
{Only set AUseBodyAsTarget to True if you want the input stream stored in TIdMessage.Body
instead of TIdText.Body: this happens with some single-part messages.}
procedure ProcessTextPart(var VDecoder: TIdMessageDecoder; AUseBodyAsTarget: Boolean);
var
LMStream: TMemoryStream;
i: integer;
LTxt : TIdText;
LNewDecoder: TIdMessageDecoder;
{$IFDEF STRING_IS_ANSI}
LAnsiEncoding: IIdTextEncoding;
{$ENDIF}
begin
LMStream := TMemoryStream.Create;
try
LParentPart := AMsg.MIMEBoundary.ParentPart;
LNewDecoder := VDecoder.ReadBody(LMStream, LMsgEnd);
try
LMStream.Position := 0;
if AUseBodyAsTarget then begin
if AMsg.IsMsgSinglePartMime then begin
{$IFDEF STRING_IS_ANSI}
LAnsiEncoding := CharsetToEncoding(AMsg.CharSet);
{$ENDIF}
ReadStringsAsCharSet(LMStream, AMsg.Body, AMsg.CharSet{$IFDEF STRING_IS_ANSI}, LAnsiEncoding{$ENDIF});
end else begin
{$IFDEF STRING_IS_ANSI}
LAnsiEncoding := ContentTypeToEncoding(VDecoder.Headers.Values[SContentType], QuoteMIME);
{$ENDIF}
ReadStringsAsContentType(LMStream, AMsg.Body, VDecoder.Headers.Values[SContentType], QuoteMIME{$IFDEF STRING_IS_ANSI}, LAnsiEncoding{$ENDIF});
end;
end else begin
LTxt := TIdText.Create(AMsg.MessageParts);
try
{$IFDEF STRING_IS_ANSI}
LAnsiEncoding := ContentTypeToEncoding(GetHeaderValue(SContentType), QuoteMIME);
{$ENDIF}
ReadStringsAsContentType(LMStream, LTxt.Body, GetHeaderValue(SContentType), QuoteMIME{$IFDEF STRING_IS_ANSI}, LAnsiEncoding{$ENDIF});
RemoveLastBlankLine(LTxt.Body);
LTxt.ContentType := LTxt.ResolveContentType(GetHeaderValue(SContentType));
LTxt.CharSet := LTxt.GetCharSet(GetHeaderValue(SContentType)); {do not localize}
LTxt.ContentTransfer := GetHeaderValue(SContentTransferEncoding); {do not localize}
LTxt.ContentID := GetHeaderValue('Content-ID'); {do not localize}
LTxt.ContentLocation := GetHeaderValue('Content-Location'); {do not localize}
LTxt.ContentDescription := GetHeaderValue('Content-Description'); {do not localize}
LTxt.ContentDisposition := GetHeaderValue('Content-Disposition'); {do not localize}
if not AMsg.IsMsgSinglePartMime then begin
for i := 0 to VDecoder.Headers.Count-1 do begin
if LTxt.Headers.IndexOfName(VDecoder.Headers.Names[i]) < 0 then begin
LTxt.ExtraHeaders.AddValue(
VDecoder.Headers.Names[i],
IndyValueFromIndex(VDecoder.Headers, i)
);
end;
end;
end;
LTxt.Filename := VDecoder.Filename;
if IsHeaderMediaType(LTxt.ContentType, 'multipart') then begin {do not localize}
LTxt.ParentPart := LPreviousParentPart;
// RLebeau 08/17/09 - According to RFC 2045 Section 6.4:
// "If an entity is of type "multipart" the Content-Transfer-Encoding is not
// permitted to have any value other than "7bit", "8bit" or "binary"."
//
// However, came across one message where the "Content-Type" was set to
// "multipart/related" and the "Content-Transfer-Encoding" was set to
// "quoted-printable". Outlook and Thunderbird were apparently able to parse
// the message correctly, but Indy was not. So let's check for that scenario
// and ignore illegal "Content-Transfer-Encoding" values if present...
if LTxt.ContentTransfer <> '' then begin
if PosInStrArray(LTxt.ContentTransfer, ['7bit', '8bit', 'binary'], False) = -1 then begin {do not localize}
LTxt.ContentTransfer := '';
end;
end;
end else begin
LTxt.ParentPart := LParentPart;
end;
except
LTxt.Free;
raise;
end;
end;
except
LNewDecoder.Free;
raise;
end;
VDecoder.Free;
VDecoder := LNewDecoder;
finally
FreeAndNil(LMStream);
end;
end;
procedure ProcessAttachment(var VDecoder: TIdMessageDecoder);
var
LDestStream: TStream;
i: integer;
LAttachment: TIdAttachment;
LNewDecoder: TIdMessageDecoder;
begin
LParentPart := AMsg.MIMEBoundary.ParentPart;
AMsg.DoCreateAttachment(VDecoder.Headers, LAttachment);
Assert(Assigned(LAttachment), 'Attachment must not be unassigned here!'); {Do not localize}
try
LNewDecoder := nil;
try
LDestStream := LAttachment.PrepareTempStream;
try
LNewDecoder := VDecoder.ReadBody(LDestStream, LMsgEnd);
finally
LAttachment.FinishTempStream;
end;
LAttachment.ContentType := LAttachment.ResolveContentType(GetHeaderValue(SContentType));
LAttachment.CharSet := LAttachment.GetCharSet(GetHeaderValue(SContentType));
if VDecoder is TIdMessageDecoderUUE then begin
LAttachment.ContentTransfer := TIdMessageDecoderUUE(VDecoder).CodingType; {do not localize}
end else begin
//Watch out for BinHex 4.0 encoding: no ContentTransfer is specified
//in the header, but we need to set it to something meaningful for us...
if IsHeaderMediaType(LAttachment.ContentType, 'application/mac-binhex40') then begin {do not localize}
LAttachment.ContentTransfer := 'binhex40'; {do not localize}
end else begin
LAttachment.ContentTransfer := GetHeaderValue(SContentTransferEncoding);
end;
end;
LAttachment.ContentDisposition := GetHeaderValue('Content-Disposition'); {do not localize}
LAttachment.ContentID := GetHeaderValue('Content-ID'); {do not localize}
LAttachment.ContentLocation := GetHeaderValue('Content-Location'); {do not localize}
LAttachment.ContentDescription := GetHeaderValue('Content-Description'); {do not localize}
if not AMsg.IsMsgSinglePartMime then begin
for i := 0 to VDecoder.Headers.Count-1 do begin
if LAttachment.Headers.IndexOfName(VDecoder.Headers.Names[i]) < 0 then begin
LAttachment.ExtraHeaders.AddValue(
VDecoder.Headers.Names[i],
IndyValueFromIndex(VDecoder.Headers, i)
);
end;
end;
end;
LAttachment.Filename := VDecoder.Filename;
if IsHeaderMediaType(LAttachment.ContentType, 'multipart') then begin {do not localize}
LAttachment.ParentPart := LPreviousParentPart;
// RLebeau 08/17/09 - According to RFC 2045 Section 6.4:
// "If an entity is of type "multipart" the Content-Transfer-Encoding is not
// permitted to have any value other than "7bit", "8bit" or "binary"."
//
// However, came across one message where the "Content-Type" was set to
// "multipart/related" and the "Content-Transfer-Encoding" was set to
// "quoted-printable". Outlook and Thunderbird were apparently able to parse
// the message correctly, but Indy was not. So let's check for that scenario
// and ignore illegal "Content-Transfer-Encoding" values if present...
if LAttachment.ContentTransfer <> '' then begin
if PosInStrArray(LAttachment.ContentTransfer, ['7bit', '8bit', 'binary'], False) = -1 then begin {do not localize}
LAttachment.ContentTransfer := '';
end;
end;
end else begin
LAttachment.ParentPart := LParentPart;
end;
except
LNewDecoder.Free;
raise;
end;
VDecoder.Free;
VDecoder := LNewDecoder;
except
//This should also remove the Item from the TCollection.
//Note that Delete does not exist in the TCollection.
LAttachment.Free;
raise;
end;
end;
begin
LMsgEnd := False;
// RLebeau 08/09/09 - TIdNNTP.GetBody() calls TIdMessage.Clear() before then
// calling ReceiveBody(), thus the TIdMessage.ContentTransferEncoding value
// is not available for use below. What is the best way to detect that so
// the user could be allowed to set up the IOHandler.DefStringEncoding
// beforehand?
LUnknownContentTransferEncoding := False;
if AMsg.NoDecode then begin
LEncoding := IndyTextEncoding_8Bit;
end else
begin
LContentTransferEncoding := AMsg.ContentTransferEncoding;
if LContentTransferEncoding = '' then begin
// RLebeau 04/08/2014: According to RFC 2045 Section 6.1:
// "Content-Transfer-Encoding: 7BIT" is assumed if the
// Content-Transfer-Encoding header field is not present."
if IsHeaderMediaType(AMsg.ContentType, 'application/mac-binhex40') then begin {Do not Localize}
LContentTransferEncoding := 'binhex40'; {do not localize}
end
else if (AMsg.Encoding = meMIME) and (AMsg.MIMEBoundary.Count > 0) then begin
LContentTransferEncoding := '7bit'; {do not localize}
end;
end
else if IsHeaderMediaType(AMsg.ContentType, 'multipart') then {do not localize}
begin
// RLebeau 08/17/09 - According to RFC 2045 Section 6.4:
// "If an entity is of type "multipart" the Content-Transfer-Encoding is not
// permitted to have any value other than "7bit", "8bit" or "binary"."
//
// However, came across one message where the "Content-Type" was set to
// "multipart/related" and the "Content-Transfer-Encoding" was set to
// "quoted-printable". Outlook and Thunderbird were apparently able to parse
// the message correctly, but Indy was not. So let's check for that scenario
// and ignore illegal "Content-Transfer-Encoding" values if present...
if PosInStrArray(LContentTransferEncoding, ['7bit', '8bit', 'binary'], False) = -1 then begin {do not localize}
LContentTransferEncoding := '';
//LUnknownContentTransferEncoding := True;
end;
end;
if LContentTransferEncoding <> '' then begin
case PosInStrArray(LContentTransferEncoding, ['7bit', 'quoted-printable', 'base64', '8bit', 'binary'], False) of {do not localize}
0..2: LEncoding := IndyTextEncoding_ASCII;
3..4: LEncoding := IndyTextEncoding_8Bit;
else
// According to RFC 2045 Section 6.4:
// "Any entity with an unrecognized Content-Transfer-Encoding must be
// treated as if it has a Content-Type of "application/octet-stream",
// regardless of what the Content-Type header field actually says."
LEncoding := IndyTextEncoding_8Bit;
LContentTransferEncoding := '';
LUnknownContentTransferEncoding := True;
end;
end else begin
LEncoding := IndyTextEncoding_8Bit;
end;
end;
BeginWork(wmRead);
try
if AMsg.NoDecode then begin
CaptureAndDecodeCharset;
end else begin
LActiveDecoder := nil;
try
if ((not LUnknownContentTransferEncoding) and
((AMsg.Encoding = meMIME) and (AMsg.MIMEBoundary.Count > 0)) or
((AMsg.Encoding = mePlainText) and (PosInStrArray(AMsg.ContentTransferEncoding, ['base64', 'quoted-printable'], False) = -1)) {do not localize}
) then begin
{NOTE: You hit this code path with multipart MIME messages and with
plain-text messages (which may have UUE or XXE attachments embedded).}
LCharsetEncoding := CharsetToEncoding(AMsg.CharSet);
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.}
// TODO: need to figure out a way to handle both transfer encoding
// and charset encoding together! Need to read the raw bytes into
// an intermediate buffer of some kind using the transfer encoding,
// and then decode the characters using the charset afterwards...
//
// Need to do this anyway because ReadLnRFC() processes the LF and
// ADelim values in terms of the charset specified, which is wrong.
// EBCDIC-based charsets totally break that logic! For example, cp1026
// converts #10 (LF) to $25 instead of $0A during encoding, and converts
// $0A (LF) and $2E ('.') to #$83 and #6 during decoding, etc. And what
// if the charset is UTF-16 instead? So we need to read raw bytes into
// a buffer, checking it for handling of line breaks, dot-transparency,
// and message termination, and THEN decode whatever is left using the
// charset...
LLine := IOHandler.ReadLnRFC(LMsgEnd, LF, ADelim, IndyTextEncoding_8Bit{$IFDEF STRING_IS_ANSI}, IndyTextEncoding_8Bit{$ENDIF});
if LMsgEnd then begin
Break;
end;
if LActiveDecoder = nil then begin
LActiveDecoder := TIdMessageDecoderList.CheckForStart(AMsg, LLine);
end;
// Check again, the if above can set it.
if LActiveDecoder = nil then begin
LLine := LCharsetEncoding.GetString(ToBytes(LLine, IndyTextEncoding_8Bit{$IFDEF STRING_IS_ANSI}, IndyTextEncoding_8Bit{$ENDIF}));
AMsg.Body.Add(LLine);
end else begin
RemoveLastBlankLine(AMsg.Body);
while LActiveDecoder <> nil do begin
LActiveDecoder.SourceStream := TIdTCPStream.Create(Self);
LPreviousParentPart := AMsg.MIMEBoundary.ParentPart;
LActiveDecoder.ReadHeader;
case LActiveDecoder.PartType of
mcptText: ProcessTextPart(LActiveDecoder, False);
mcptAttachment: ProcessAttachment(LActiveDecoder);
mcptIgnore: FreeAndNil(LActiveDecoder);
mcptEOF: begin FreeAndNil(LActiveDecoder); LMsgEnd := True; end;
end;
end;
end;
until LMsgEnd;
RemoveLastBlankLine(AMsg.Body);
end else begin
{These are single-part MIMEs, or else mePlainTexts with the body encoded QP/base64}
AMsg.IsMsgSinglePartMime := True;
LActiveDecoder := TIdMessageDecoderMime.Create(AMsg);
LActiveDecoder.SourceStream := TIdTCPStream.Create(Self);
// RLebeau: override what TIdMessageDecoderMime.InitComponent() assigns
TIdMessageDecoderMime(LActiveDecoder).BodyEncoded := True;
TIdMessageDecoderMime(LActiveDecoder).ReadHeader;
case LActiveDecoder.PartType of
mcptText: begin
if LUnknownContentTransferEncoding then begin
ProcessAttachment(LActiveDecoder);
end else begin
ProcessTextPart(LActiveDecoder, True); //Put the text into TIdMessage.Body
end;
end;
mcptAttachment: ProcessAttachment(LActiveDecoder);
mcptIgnore: FreeAndNil(LActiveDecoder);
mcptEOF: FreeAndNil(LActiveDecoder);
end;
end;
finally
FreeAndNil(LActiveDecoder);
end;
end;
finally
EndWork(wmRead);
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;
LDestStream: TStream;
LStrStream: TStream;
ISOCharset: string;
HeaderEncoding: Char; { B | Q }
LEncoder: TIdMessageEncoder;
LLine: string;
procedure EncodeStrings(AStrings: TStrings; AEncoderClass: TIdMessageEncoderClass; AByteEncoding: IIdTextEncoding
{$IFDEF STRING_IS_ANSI}; AAnsiEncoding: IIdTextEncoding{$ENDIF});
var
LStrings: TStringList;
begin
{$IFDEF STRING_IS_ANSI}
EnsureEncoding(AAnsiEncoding, encOSDefault);
{$ENDIF}
LStrings := TStringList.Create; try
LEncoder := AEncoderClass.Create(Self); try
LStrStream := TMemoryStream.Create; try
// RLebeau 10/06/2010: not using TStrings.SaveToStream() in D2009+
// anymore, as it may save a BOM which we do not want here...
WriteStringToStream(LStrStream, AStrings.Text, AByteEncoding{$IFDEF STRING_IS_ANSI}, AAnsiEncoding{$ENDIF});
LStrStream.Position := 0;
LEncoder.Encode(LStrStream, LStrings);
finally FreeAndNil(LStrStream); end;
finally FreeAndNil(LEncoder); end;
IOHandler.WriteRFCStrings(LStrings, False);
finally FreeAndNil(LStrings); end;
end;
procedure EncodeAttachment(AAttachment: TIdAttachment; AEncoderClass: TIdMessageEncoderClass);
var
LAttachStream: TStream;
begin
LDestStream := TIdTCPStream.Create(Self, 8192); try
LEncoder := AEncoderClass.Create(Self); try
LEncoder.Filename := AAttachment.Filename;
LAttachStream := AAttachment.OpenLoadStream; try
LEncoder.Encode(LAttachStream, LDestStream);
finally AAttachment.CloseLoadStream; end;
finally FreeAndNil(LEncoder); end;
finally FreeAndNil(LDestStream); end;
end;
procedure WriteTextPart(ATextPart: TIdText);
var
LEncoding: IIdTextEncoding;
LFileName: String;
begin
if ATextPart.ContentType = '' then begin
ATextPart.ContentType := 'text/plain'; {do not localize}
end;
// RLebeau 08/17/09 - According to RFC 2045 Section 6.4:
// "If an entity is of type "multipart" the Content-Transfer-Encoding is not
// permitted to have any value other than "7bit", "8bit" or "binary"."
//
// However, came across one message where the "Content-Type" was set to
// "multipart/related" and the "Content-Transfer-Encoding" was set to
// "quoted-printable". Outlook and Thunderbird were apparently able to parse
// the message correctly, but Indy was not. So let's check for that scenario
// and ignore illegal "Content-Transfer-Encoding" values if present...
if IsHeaderMediaType(ATextPart.ContentType, 'multipart') then begin {do not localize}
if ATextPart.ContentTransfer <> '' then begin
if PosInStrArray(ATextPart.ContentTransfer, ['7bit', '8bit', 'binary'], False) = -1 then begin {do not localize}
ATextPart.ContentTransfer := '';
end;
end;
end
else if ATextPart.ContentTransfer = '' then begin
ATextPart.ContentTransfer := 'quoted-printable'; {do not localize}
end
else if (PosInStrArray(ATextPart.ContentTransfer, ['quoted-printable', 'base64'], False) = -1) {do not localize}
and ATextPart.IsBodyEncodingRequired then
begin
ATextPart.ContentTransfer := '8bit'; {do not localize}
end;
if ATextPart.ContentDisposition = '' then begin
ATextPart.ContentDisposition := 'inline'; {do not localize}
end;
// TODO: when STRING_IS_ANSI is defined, provide a way for the user to specify the AnsiString encoding for header values...
LFileName := EncodeHeader(ExtractFileName(ATextPart.FileName), '', HeaderEncoding, ISOCharSet); {do not localize}
if ATextPart.ContentType <> '' then begin
IOHandler.Write('Content-Type: ' + ATextPart.ContentType); {do not localize}
if ATextPart.CharSet <> '' then begin
IOHandler.Write('; charset="' + ATextPart.CharSet + '"'); {do not localize}
end;
if LFileName <> '' then begin
IOHandler.WriteLn(';'); {do not localize}
IOHandler.Write(TAB + 'name="' + LFileName + '"'); {do not localize}
end;
IOHandler.WriteLn;
end;
if ATextPart.ContentTransfer <> '' then begin
IOHandler.WriteLn(SContentTransferEncoding + ': ' + ATextPart.ContentTransfer); {do not localize}
end;
IOHandler.Write('Content-Disposition: ' + ATextPart.ContentDisposition); {do not localize}
if LFileName <> '' then begin
IOHandler.WriteLn(';'); {do not localize}
IOHandler.Write(TAB + 'filename="' + LFileName + '"'); {do not localize}
end;
IOHandler.WriteLn;
if ATextPart.ContentID <> '' then begin
IOHandler.WriteLn('Content-ID: ' + ATextPart.ContentID); {do not localize}
end;
if ATextPart.ContentDescription <> '' then begin
IOHandler.WriteLn('Content-Description: ' + ATextPart.ContentDescription); {do not localize}
end;
IOHandler.Write(ATextPart.ExtraHeaders);
IOHandler.WriteLn;
LEncoding := CharsetToEncoding(ATextPart.CharSet);
if TextIsSame(ATextPart.ContentTransfer, 'quoted-printable') then begin {do not localize}
EncodeStrings(ATextPart.Body, TIdMessageEncoderQuotedPrintable, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end
else if TextIsSame(ATextPart.ContentTransfer, 'base64') then begin {do not localize}
EncodeStrings(ATextPart.Body, TIdMessageEncoderMIME, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end else
begin
IOHandler.WriteRFCStrings(ATextPart.Body, False, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
{ No test for last line break necessary because IOHandler.WriteRFCStrings() uses WriteLn(). }
end;
end;
var
LFileName: String;
LTextPart: TIdText;
LAddedTextPart: Boolean;
LLastPart: Integer;
LEncoding: IIdTextEncoding;
LAttachStream: TStream;
begin
LBoundary := '';
AMsg.InitializeISO(HeaderEncoding, ISOCharSet);
BeginWork(wmWrite);
try
if (not AMsg.IsMsgSinglePartMime) and
(PosInStrArray(AMsg.ContentTransferEncoding, ['base64', 'quoted-printable'], False) <> -1) then {do not localize}
begin
//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]);
LEncoding := CharsetToEncoding(AMsg.CharSet);
//CC2: Now output AMsg.Body in the chosen encoding...
if TextIsSame(AMsg.ContentTransferEncoding, 'base64') then begin {do not localize}
EncodeStrings(AMsg.Body, TIdMessageEncoderMIME, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end else begin {'quoted-printable'}
EncodeStrings(AMsg.Body, TIdMessageEncoderQuotedPrintable, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end
else if AMsg.Encoding = mePlainText 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
LEncoding := CharsetToEncoding(AMsg.CharSet);
EncodeAndWriteText(AMsg.Body, LEncoding);
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(TIdText(AMsg.MessageParts.Items[i]));
IOHandler.WriteLn('------- End of text attachment -------'); {do not localize}
end
else if AMsg.MessageParts.Items[i] is TIdAttachment then begin
LAttachment := TIdAttachment(AMsg.MessageParts[i]);
DoStatus(hsStatusText, [RSMsgClientEncodingAttachment]);
if LAttachment.ContentTransfer = '' then begin
//The user has nothing specified: see has he set a preference in
//TIdMessage.AttachmentEncoding (AttachmentEncoding is really an
//old and somewhat deprecated property, but we can still support it)...
if PosInStrArray(AMsg.AttachmentEncoding, ['UUE', 'XXE']) <> -1 then begin {do not localize}
LAttachment.ContentTransfer := AMsg.AttachmentEncoding;
end else begin
//We default to UUE (rather than XXE)...
LAttachment.ContentTransfer := 'UUE'; {do not localize}
end;
end;
case PosInStrArray(LAttachment.ContentTransfer, ['UUE', 'XXE'], False) of {do not localize}
0: EncodeAttachment(LAttachment, TIdMessageEncoderUUE);
1: EncodeAttachment(LAttachment, TIdMessageEncoderXXE);
end;
end;
IOHandler.WriteLn;
end;
end;
end
else begin
//CC2: It is MIME-encoding...
LAddedTextPart := False;
//######### OUTPUT THE PREAMBLE TEXT ########
{For single-part MIME messages, we want the message part headers to be appended
to the message headers. Otherwise, add the blank separator between header and
body...}
if not AMsg.IsMsgSinglePartMime then begin
IOHandler.WriteLn; //This is the blank line after the headers
//if AMsg.Body.Count > 0 then begin
if not AMsg.IsBodyEmpty then begin
//CC2: The message has a body text. There are now a few possibilities.
//First up, if ConvertPreamble is False then the user explicitly does not want us
//to convert the .Body since he had to change it from the default False.
//Secondly, if AMsg.MessageParts.TextPartCount > 0, he may have put the
//message text in the part, so don't convert the body.
//Thirdly, if AMsg.MessageParts.Count = 0, then it has no other parts
//anyway: in this case, output it without boundaries.
//if (AMsg.ConvertPreamble and (AMsg.MessageParts.TextPartCount = 0)) then begin
if AMsg.ConvertPreamble and (AMsg.MessageParts.TextPartCount = 0) and (AMsg.MessageParts.Count > 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, AMsg.Body);
LTextPart.CharSet := AMsg.CharSet;
LTextPart.ContentType := 'text/plain'; {do not localize}
LTextPart.ContentTransfer := 'quoted-printable'; {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...
LEncoding := CharsetToEncoding(AMsg.CharSet);
EncodeAndWriteText(AMsg.Body, LEncoding);
end;
end
else begin
//CC2: The user has specified no body text: he presumably has the message in
//a TIdText part, but it may have no text at all (a message consisting only
//of headers, which is allowed under the RFC, which will have a parts count
//of 0).
if AMsg.MessageParts.Count <> 0 then begin
//Add the "standard" MIME preamble text for non-html email clients...
IOHandler.WriteLn(SThisIsMultiPartMessageInMIMEFormat);
end;
end;
IOHandler.WriteLn;
//######### SET UP THE BOUNDARY STACK ########
LBoundary := AMsg.MIMEBoundary.Boundary;
if LBoundary = '' then begin
LBoundary := TIdMIMEBoundaryStrings.GenerateBoundary;
AMsg.MIMEBoundary.Push(LBoundary, -1); //-1 is "top level"
end;
end;
//######### 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); {do not localize}
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 IsHeaderMediaType(LLine, 'multipart') then begin {do not localize}
//A multipart header. Write out the CURRENT boundary first...
IOHandler.WriteLn('--' + LBoundary); {do not localize}
//Make the current boundary and this part number active...
//Now need to generate a new boundary...
LBoundary := TIdMIMEBoundaryStrings.GenerateBoundary;
AMsg.MIMEBoundary.Push(LBoundary, i);
//Make sure the header does not already have a pre-existing
//boundary since we just generated a new one...
IOHandler.WriteLn('Content-Type: ' + RemoveHeaderEntry(LLine, 'boundary', QuoteMIME) + ';'); {do not localize}
IOHandler.WriteLn(TAB + 'boundary="' + LBoundary + '"'); {do not localize}
IOHandler.WriteLn;
end
else begin
//Not a multipart header, see if it is a part change...
if not AMsg.IsMsgSinglePartMime then begin
while AMsg.MessageParts.Items[i].ParentPart <> AMsg.MIMEBoundary.ParentPart do begin
IOHandler.WriteLn('--' + LBoundary + '--'); {do not localize}
IOHandler.WriteLn;
AMsg.MIMEBoundary.Pop; //This also pops AMsg.MIMEBoundary.ParentPart
LBoundary := AMsg.MIMEBoundary.Boundary;
end;
IOHandler.WriteLn('--' + LBoundary); {do not localize}
end;
if AMsg.MessageParts.Items[i] is TIdText then begin
DoStatus(hsStatusText, [RSMsgClientEncodingText]);
WriteTextPart(AMsg.MessageParts.Items[i] as TIdText);
IOHandler.WriteLn;
end
else if AMsg.MessageParts.Items[i] is TIdAttachment then begin
LAttachment := TIdAttachment(AMsg.MessageParts[i]);
DoStatus(hsStatusText, [RSMsgClientEncodingAttachment]);
if LAttachment.ContentTransfer = '' then begin
LAttachment.ContentTransfer := 'base64'; {do not localize}
end;
if LAttachment.ContentDisposition = '' then begin
LAttachment.ContentDisposition := 'attachment'; {do not localize}
end;
if LAttachment.ContentType = '' then begin
if TextIsSame(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;
// TODO: when STRING_IS_ANSI is defined, provide a way for the user to specify the AnsiString encoding for header values...
LFileName := EncodeHeader(ExtractFileName(LAttachment.FileName), '', HeaderEncoding, ISOCharSet); {do not localize}
if TextIsSame(LAttachment.ContentTransfer, 'binhex40') then begin {do not localize}
//This is special - you do NOT write out any Content-Transfer-Encoding
//header! We also have to write a Content-Type specified in RFC 1741
//(overriding any ContentType present, if necessary).
LAttachment.ContentType := 'application/mac-binhex40'; {do not localize}
IOHandler.Write('Content-Type: ' + LAttachment.ContentType); {do not localize}
if LAttachment.CharSet <> '' then begin
IOHandler.Write('; charset="' + LAttachment.CharSet + '"'); {do not localize}
end;
if LFileName <> '' then begin
IOHandler.WriteLn(';'); {do not localize}
IOHandler.Write(TAB + 'name="' + LFileName + '"'); {do not localize}
end;
IOHandler.WriteLn;
end
else begin
IOHandler.Write('Content-Type: ' + LAttachment.ContentType); {do not localize}
if LAttachment.CharSet <> '' then begin
IOHandler.Write('; charset="' + LAttachment.CharSet + '"'); {do not localize}
end;
if LFileName <> '' then begin
IOHandler.WriteLn(';');
IOHandler.Write(TAB + 'name="' + LFileName + '"'); {do not localize}
end;
IOHandler.WriteLn;
IOHandler.WriteLn('Content-Transfer-Encoding: ' + LAttachment.ContentTransfer); {do not localize}
IOHandler.Write('Content-Disposition: ' + LAttachment.ContentDisposition); {do not localize}
if LFileName <> '' then begin
IOHandler.WriteLn(';');
IOHandler.Write(TAB + 'filename="' + LFileName + '"'); {do not localize}
end;
IOHandler.WriteLn;
end;
if LAttachment.ContentID <> '' then begin
IOHandler.WriteLn('Content-ID: '+ LAttachment.ContentID); {Do not Localize}
end;
if LAttachment.ContentDescription <> '' then begin
IOHandler.WriteLn('Content-Description: ' + LAttachment.ContentDescription); {Do not localize}
end;
IOHandler.Write(LAttachment.ExtraHeaders);
IOHandler.WriteLn;
case PosInStrArray(LAttachment.ContentTransfer, ['base64', 'quoted-printable', 'binhex40'], False) of {do not localize}
0: EncodeAttachment(LAttachment, TIdMessageEncoderMIME);
1: EncodeAttachment(LAttachment, TIdMessageEncoderQuotedPrintable);
2: EncodeAttachment(LAttachment, TIdMessageEncoderBinHex4);
else
begin
LEncoding := CharsetToEncoding(LAttachment.Charset);
LAttachStream := LAttachment.OpenLoadStream;
try
while ReadLnFromStream(LAttachStream, LLine, -1, LEncoding) do begin
IOHandler.WriteLnRFC(LLine, LEncoding);
end;
finally
LAttachment.CloseLoadStream;
end;
end;
end;
IOHandler.WriteLn;
end;
end;
end;
if AMsg.MessageParts.Count > 0 then begin
for i := 0 to AMsg.MIMEBoundary.Count - 1 do begin
if not AMsg.IsMsgSinglePartMime then begin
IOHandler.WriteLn('--' + AMsg.MIMEBoundary.Boundary + '--');
IOHandler.WriteLn;
end;
AMsg.MIMEBoundary.Pop;
end;
end;
end;
finally
EndWork(wmWrite);
end;
end;
procedure TIdMessageClient.SendMsg(AMsg: TIdMessage; AHeadersOnly: Boolean = False);
begin
BeginWork(wmWrite);
try
if AMsg.NoEncode then begin
IOHandler.Write(AMsg.Headers);
IOHandler.WriteLn;
if not AHeadersOnly then begin
IOHandler.WriteRFCStrings(AMsg.Body, False);
end;
end else begin
SendHeader(AMsg);
if (not AHeadersOnly) then begin
SendBody(AMsg);
end;
end;
finally
EndWork(wmWrite);
end;
end;
function TIdMessageClient.ReceiveHeader(AMsg: TIdMessage; const AAltTerm: string = ''): string;
var
LMsgEnd: Boolean;
begin
BeginWork(wmRead);
try
repeat
Result := IOHandler.ReadLnRFC(LMsgEnd);
// 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 LMsgEnd) or {do not localize}
({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
//Don't call ReceiveBody if the message ended at the end of the headers
//(ReceiveHeader() would have returned '.' in that case)...
BeginWork(wmRead);
try
if ReceiveHeader(AMsg) = '' then begin
if not AHeaderOnly then begin
ReceiveBody(AMsg);
end;
end;
finally
EndWork(wmRead);
end;
end;
end;
procedure TIdMessageClient.ProcessMessage(AMsg: TIdMessage; AStream: TStream; AHeaderOnly: Boolean = False);
var
LIOHandler: TIdIOHandlerStreamMsg;
begin
LIOHandler := TIdIOHandlerStreamMsg.Create(nil, AStream);
try
LIOHandler.FreeStreams := False;
IOHandler := LIOHandler;
try
IOHandler.Open;
ProcessMessage(AMsg, AHeaderOnly);
finally
IOHandler := nil;
end;
finally
LIOHandler.Free;
end;
end;
procedure TIdMessageClient.ProcessMessage(AMsg: TIdMessage; const AFilename: string; AHeaderOnly: Boolean = False);
var
LStream: TStream;
begin
LStream := TIdReadFileExclusiveStream.Create(AFileName); try
ProcessMessage(AMsg, LStream, AHeaderOnly);
finally FreeAndNil(LStream); end;
end;
procedure TIdMessageClient.EncodeAndWriteText(const ABody: TStrings; AEncoding: IIdTextEncoding);
begin
Assert(ABody<>nil);
Assert(IOHandler<>nil);
// TODO: encode the text...
IOHandler.WriteRFCStrings(ABody, False, AEncoding);
end;
destructor TIdMessageClient.Destroy;
begin
inherited Destroy;
end;
end.