| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561 |
- {
- $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;
- 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): string; override;
- procedure WriteLn(const AOut: string; AByteEncoding: IIdTextEncoding = nil); 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);
- public
- constructor Create(AOwner: TComponent); 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
- //TODO: Remove these references and make it completely pluggable. Check other spots in Indy as well
- IdMessageCoderBinHex4, IdMessageCoderQuotedPrintable, IdMessageCoderMIME,
- IdMessageCoderUUE, IdMessageCoderXXE,
- //
- IdGlobalProtocols,
- IdCoderBinHex4,
- IdCoderHeader, IdHeaderCoderBase, IdMessageCoder, IdComponent, IdException,
- IdResourceStringsProtocols, IdTCPConnection, IdTCPStream, IdIOHandler,
- IdAttachment, IdText,
- 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): string;
- begin
- Result := inherited ReadLn(ATerminator, ATimeout, AMaxLineLength, AByteEncoding);
- 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);
- 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);
- end;
- ///////////////////
- // TIdMessageClient
- ///////////////////
- constructor TIdMessageClient.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- 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);
- LMStream.Position := 0;
- // TODO: if the Content-Type is HTML, parse the HTML data looking for a charset
- // declaration, and if found then use that instead of the MIME charset...
- ReadStringsAsCharSet(LMStream, AMsg.Body, AMsg.CharSet);
- finally
- LMStream.Free;
- end;
- end;
- // RLebeau 11/2/2013: TIdMessage.Headers is a TIdHeaderList, but
- // TIdMessageDecoder.Headers is a plain TStringList. Although TIdHeaderList
- // is a TStrings descendant, 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;
- begin
- LMStream := TMemoryStream.Create;
- try
- LParentPart := AMsg.MIMEBoundary.ParentPart;
- LNewDecoder := VDecoder.ReadBody(LMStream, LMsgEnd);
- try
- LMStream.Position := 0;
- if AUseBodyAsTarget then begin
- // TODO: if the Content-Type is HTML, parse the HTML data looking for a charset
- // declaration, and if found then use that instead of the MIME charset...
- if AMsg.IsMsgSinglePartMime then begin
- ReadStringsAsCharSet(LMStream, AMsg.Body, AMsg.CharSet);
- end else begin
- ReadStringsAsContentType(LMStream, AMsg.Body, VDecoder.Headers.Values[SContentType], QuoteMIME);
- end;
- end else begin
- LTxt := TIdText.Create(AMsg.MessageParts);
- try
- ReadStringsAsContentType(LMStream, LTxt.Body, GetHeaderValue(SContentType), QuoteMIME);
- 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 not IsHeaderValue(LTxt.ContentTransfer, ['7bit', '8bit', 'binary']) 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
- LMStream.Free;
- 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 not IsHeaderValue(LAttachment.ContentTransfer, ['7bit', '8bit', 'binary']) 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 := ExtractHeaderItem(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) and (not AMsg.NoDecode) 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 (not IsHeaderValue(AMsg.ContentTransferEncoding, ['base64', 'quoted-printable']))) {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);
- 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));
- 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
- LActiveDecoder.Free;
- 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);
- var
- LStrings: TStringList;
- begin
- LStrings := TStringList.Create;
- try
- LEncoder := AEncoderClass.Create(nil);
- 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);
- LStrStream.Position := 0;
- LEncoder.Encode(LStrStream, LStrings);
- finally
- LStrStream.Free;
- end;
- finally
- LEncoder.Free;
- end;
- IOHandler.WriteRFCStrings(LStrings, False);
- finally
- LStrings.Free;
- end;
- end;
- procedure EncodeAttachment(AAttachment: TIdAttachment; AEncoderClass: TIdMessageEncoderClass);
- var
- LAttachStream: TStream;
- begin
- LDestStream := TIdTCPStream.Create(Self, 8192);
- try
- LEncoder := AEncoderClass.Create(nil);
- try
- LEncoder.Filename := AAttachment.Filename;
- LAttachStream := AAttachment.OpenLoadStream;
- try
- LEncoder.Encode(LAttachStream, LDestStream);
- finally
- AAttachment.CloseLoadStream;
- end;
- finally
- LEncoder.Free;
- end;
- finally
- LDestStream.Free;
- 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 not IsHeaderValue(ATextPart.ContentTransfer, ['7bit', '8bit', 'binary']) 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 (not IsHeaderValue(ATextPart.ContentTransfer, ['quoted-printable', 'base64'])) {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;
- 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);
- case PosInStrArray(ExtractHeaderItem(ATextPart.ContentTransfer), ['quoted-printable', 'base64'], False) of {do not localize}
- 0: EncodeStrings(ATextPart.Body, TIdMessageEncoderQuotedPrintable, LEncoding);
- 1: EncodeStrings(ATextPart.Body, TIdMessageEncoderMIME, LEncoding);
- else
- IOHandler.WriteRFCStrings(ATextPart.Body, False, LEncoding);
- { No test for last line break necessary because IOHandler.WriteRFCStrings() uses WriteLn(). }
- end;
- end;
- var
- LFileName, LContentTransferEncoding: String;
- LTextPart: TIdText;
- LAddedTextPart: Boolean;
- LLastPart: Integer;
- LEncoding: IIdTextEncoding;
- LAttachStream: TStream;
- begin
- LBoundary := '';
- AMsg.InitializeISO(HeaderEncoding, ISOCharSet);
- BeginWork(wmWrite);
- try
- LContentTransferEncoding := ExtractHeaderItem(AMsg.ContentTransferEncoding);
- if (not AMsg.IsMsgSinglePartMime) and
- (PosInStrArray(LContentTransferEncoding, ['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); // TODO: create a new Exception class for this
- 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(LContentTransferEncoding, 'base64') then begin {do not localize}
- EncodeStrings(AMsg.Body, TIdMessageEncoderMIME, LEncoding);
- end else begin {'quoted-printable'}
- EncodeStrings(AMsg.Body, TIdMessageEncoderQuotedPrintable, LEncoding);
- 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(ExtractHeaderItem(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
- if AMsg.MIMEBoundary.Count = 0 then begin
- raise EIdException.Create(RSMsgClientUnexpectedEndOfMIMEBoundaries); // TODO: create a new Exception class for this
- end;
- 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
- LContentTransferEncoding := 'base64'; {do not localize}
- LAttachment.ContentTransfer := LContentTransferEncoding;
- end else begin;
- LContentTransferEncoding := ExtractHeaderItem(LAttachment.ContentTransfer);
- end;
- if LAttachment.ContentDisposition = '' then begin
- LAttachment.ContentDisposition := 'attachment'; {do not localize}
- end;
- if LAttachment.ContentType = '' then begin
- if TextIsSame(LContentTransferEncoding, '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;
- LFileName := EncodeHeader(ExtractFileName(LAttachment.FileName), '', HeaderEncoding, ISOCharSet); {do not localize}
- if TextIsSame(LContentTransferEncoding, '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(LContentTransferEncoding, ['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;
- LIOHandler.MaxLineLength := MaxInt;
- 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
- LStream.Free;
- 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;
- end.
|