IdMessageClient.pas 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.85 1/6/05 4:38:30 PM RLebeau
  18. Bug fix for decoding Text part headers
  19. }
  20. {
  21. Rev 1.84 11/30/04 10:44:44 AM RLebeau
  22. Bug fix for previous checkin
  23. }
  24. {
  25. Rev 1.83 11/30/2004 12:10:40 PM JPMugaas
  26. Fix for compiler error.
  27. }
  28. {
  29. Rev 1.82 11/28/04 2:22:04 PM RLebeau
  30. Updated a few hard-coded strings to use resource strings instead
  31. }
  32. {
  33. Rev 1.81 28/11/2004 20:08:14 CCostelloe
  34. MessagePart.Boundary now (correctly) holds decoded MIME boundary
  35. }
  36. {
  37. Rev 1.80 11/27/2004 8:58:14 PM JPMugaas
  38. Compile errors.
  39. }
  40. {
  41. Rev 1.79 10/26/2004 10:25:46 PM JPMugaas
  42. Updated refs.
  43. }
  44. {
  45. Rev 1.78 24.09.2004 02:16:48 Andreas Hausladen
  46. Added ReadTIdBytesFromStream and ReadCharFromStream function to supress .NET
  47. warnings.
  48. }
  49. {
  50. Rev 1.77 27.08.2004 22:04:32 Andreas Hausladen
  51. speed optimization ("const" for string parameters)
  52. Fixed "blank line multiplication"
  53. }
  54. {
  55. Rev 1.76 27.08.2004 00:21:32 Andreas Hausladen
  56. Undo last changes (temporary)
  57. }
  58. {
  59. Rev 1.75 26.08.2004 22:14:16 Andreas Hausladen
  60. Fixed last line blank line read/write bug
  61. }
  62. {
  63. Rev 1.74 7/23/04 7:17:20 PM RLebeau
  64. TFileStream access right tweak for ProcessMessage()
  65. }
  66. {
  67. Rev 1.73 28/06/2004 23:58:12 CCostelloe
  68. Bug fix
  69. }
  70. {
  71. Rev 1.72 6/11/2004 9:38:08 AM DSiders
  72. Added "Do not Localize" comments.
  73. }
  74. {
  75. Rev 1.71 2004.06.06 4:53:04 PM czhower
  76. Undid 1.70. Not needed, just masked an existing bug and did not fix it.
  77. }
  78. {
  79. Rev 1.70 06/06/2004 01:23:54 CCostelloe
  80. OnWork fix
  81. }
  82. {
  83. Rev 1.69 6/4/04 12:41:56 PM RLebeau
  84. ContentTransferEncoding bug fix
  85. }
  86. {
  87. Rev 1.68 2004.05.20 1:39:08 PM czhower
  88. Last of the IdStream updates
  89. }
  90. {
  91. Rev 1.67 2004.05.20 11:36:52 AM czhower
  92. IdStreamVCL
  93. }
  94. {
  95. Rev 1.66 2004.05.20 11:12:56 AM czhower
  96. More IdStream conversions
  97. }
  98. {
  99. Rev 1.65 2004.05.19 3:06:34 PM czhower
  100. IdStream / .NET fix
  101. }
  102. {
  103. Rev 1.64 19/05/2004 00:54:30 CCostelloe
  104. Bug fix (though I claim in my defence that it is only a hint fix)
  105. }
  106. {
  107. Rev 1.63 16/05/2004 18:55:06 CCostelloe
  108. New TIdText/TIdAttachment processing
  109. }
  110. {
  111. Rev 1.62 2004.05.03 11:15:16 AM czhower
  112. Fixed compile error and added use of constants.
  113. }
  114. {
  115. Rev 1.61 5/2/04 8:02:12 PM RLebeau
  116. Updated TIdIOHandlerStreamMsg to keep track of the last character received
  117. from the stream so that extra CR LF characters are not added to the end of
  118. the message data unnecessarily.
  119. }
  120. {
  121. Rev 1.60 4/23/04 1:54:58 PM RLebeau
  122. One more tweak for TIdIOHandlerStreamMsg support
  123. }
  124. {
  125. Rev 1.59 4/23/04 1:21:16 PM RLebeau
  126. Minor tweaks for TIdIOHandlerStreamMsg support
  127. }
  128. {
  129. Rev 1.58 23/04/2004 20:48:10 CCostelloe
  130. Added TIdIOHandlerStreamMsg to stop looping if no terminating \r\n.\r\n and
  131. added support for emails that are attachments only
  132. }
  133. {
  134. Rev 1.57 2004.04.18 1:39:22 PM czhower
  135. Bug fix for .NET with attachments, and several other issues found along the
  136. way.
  137. }
  138. {
  139. Rev 1.56 2004.04.16 11:31:00 PM czhower
  140. Size fix to IdBuffer, optimizations, and memory leaks
  141. Rev 1.55 2004.03.07 10:36:08 AM czhower
  142. SendMsg now calls OnWork with NoEncode = True
  143. Rev 1.54 2004.03.04 1:02:58 AM czhower
  144. Const removed from arguemtns (1 not needed + 1 incorrect)
  145. Rev 1.53 2004.03.03 7:18:32 PM czhower
  146. Fixed AV bug with ProcessMessage
  147. Rev 1.52 2004.03.03 11:54:34 AM czhower
  148. IdStream change
  149. Rev 1.51 2/3/04 12:25:50 PM RLebeau
  150. Updated WriteTextPart() function inside of SendBody() to write the ContentID
  151. property is it is assigned.
  152. Rev 1.50 2004.02.03 5:44:02 PM czhower
  153. Name changes
  154. Rev 1.49 2004.02.03 2:12:16 PM czhower
  155. $I path change
  156. Rev 1.48 1/27/2004 4:04:06 PM SPerry
  157. StringStream ->IdStringStream
  158. Rev 1.47 2004.01.27 12:03:28 AM czhower
  159. Properly named a local variable to fix a .net conflict.
  160. Rev 1.46 1/25/2004 3:52:32 PM JPMugaas
  161. Fixes for abstract SSL interface to work in NET.
  162. Rev 1.45 24/01/2004 19:24:30 CCostelloe
  163. Cleaned up warnings
  164. Rev 1.44 1/21/2004 1:30:06 PM JPMugaas
  165. InitComponent
  166. Rev 1.43 16/01/2004 17:39:34 CCostelloe
  167. Added support for BinHex 4.0 encoding
  168. Rev 1.42 11/01/2004 19:53:40 CCostelloe
  169. Revisions for TIdMessage SaveToFile & LoadFromFile for D7 & D8
  170. Rev 1.40 08/01/2004 23:46:16 CCostelloe
  171. Changes to ProcessMessage to get TIdMessage.LoadFromFile working in D7
  172. Rev 1.39 08/01/2004 00:31:06 CCostelloe
  173. Start of reimplementing LoadFrom/SaveToFile
  174. Rev 1.38 22/12/2003 00:44:52 CCostelloe
  175. .NET fixes
  176. Rev 1.37 11/11/2003 12:06:26 AM BGooijen
  177. Did all todo's ( TStream to TIdStream mainly )
  178. Rev 1.36 2003.10.24 10:43:10 AM czhower
  179. TIdSTream to dos
  180. Rev 1.35 10/17/2003 12:37:36 AM DSiders
  181. Added localization comments.
  182. Added resource string for exception message.
  183. Rev 1.34 2003.10.14 9:57:12 PM czhower
  184. Compile todos
  185. Rev 1.33 10/12/2003 1:49:56 PM BGooijen
  186. Changed comment of last checkin
  187. Rev 1.32 10/12/2003 1:43:40 PM BGooijen
  188. Changed IdCompilerDefines.inc to Core\IdCompilerDefines.inc
  189. Rev 1.30 10/11/2003 4:21:14 PM BGooijen
  190. Compiles in D7 again
  191. Rev 1.29 10/10/2003 10:42:28 PM BGooijen
  192. DotNet
  193. Rev 1.28 9/10/2003 1:50:52 PM SGrobety
  194. DotNet
  195. Rev 1.27 10/8/2003 9:53:42 PM GGrieve
  196. Remove $IFDEFs
  197. Rev 1.26 05/10/2003 16:39:52 CCostelloe
  198. Set default ContentType
  199. Rev 1.25 03/10/2003 21:03:40 CCostelloe
  200. Bug fixes
  201. Rev 1.24 2003.10.02 9:27:52 PM czhower
  202. DotNet Excludes
  203. Rev 1.23 01/10/2003 17:58:56 HHariri
  204. More fixes for Multipart Messages and also fixes for incorrect transfer
  205. encoding settings
  206. Rev 1.20 01/10/2003 10:57:56 CCostelloe
  207. Fixed GenerateTextPartContentType (was ignoring ContentType)
  208. Rev 1.19 26/09/2003 01:03:48 CCostelloe
  209. Modified ProcessAttachment in ReceiveBody to update message's Encoding if
  210. attachment was XX-encoded. Added decoding of message bodies encoded as
  211. base64 or quoted-printable. Added support for nested MIME parts
  212. (ParentPart). Added support for TIdText in UU and XX encoding. Added
  213. missing base64 and QP support where needed. Rewrote/rearranged most of code.
  214. Rev 1.18 04/09/2003 20:44:56 CCostelloe
  215. In SendBody, removed blank line between boundaries and Text part header;
  216. recoded wDoublePoint
  217. Rev 1.17 30/08/2003 18:40:44 CCostelloe
  218. Updated to use IdMessageCoderMIME's new random boundaries
  219. Rev 1.16 8/8/2003 12:27:18 PM JPMugaas
  220. Should now compile.
  221. Rev 1.15 07/08/2003 00:39:06 CCostelloe
  222. Modified SendBody to deal with unencoded attachments (otherwise 7bit
  223. attachments had the attachment header written out as 7bit but was encoded as
  224. base64)
  225. Rev 1.14 11/07/2003 01:14:20 CCostelloe
  226. SendHeader changed to support new IdMessage.GenerateHeader putting generated
  227. headers in IdMessage.LastGeneratedHeaders.
  228. Rev 1.13 6/15/2003 01:13:10 PM JPMugaas
  229. Minor fixes and cleanups.
  230. Rev 1.12 5/18/2003 02:31:44 PM JPMugaas
  231. Reworked some things so IdSMTP and IdDirectSMTP can share code including
  232. stuff for pipelining.
  233. Rev 1.11 5/8/2003 03:18:06 PM JPMugaas
  234. Flattened ou the SASL authentication API, made a custom descendant of SASL
  235. enabled TIdMessageClient classes.
  236. Rev 1.10 5/8/2003 11:28:02 AM JPMugaas
  237. Moved feature negoation properties down to the ExplicitTLSClient level as
  238. feature negotiation goes hand in hand with explicit TLS support.
  239. Rev 1.9 5/8/2003 02:17:58 AM JPMugaas
  240. Fixed an AV in IdPOP3 with SASL list on forms. Made exceptions for SASL
  241. mechanisms missing more consistant, made IdPOP3 support feature feature
  242. negotiation, and consolidated some duplicate code.
  243. Rev 1.8 3/17/2003 02:16:06 PM JPMugaas
  244. Now descends from ExplicitTLS base class.
  245. Rev 1.7 2/24/2003 07:25:18 PM JPMugaas
  246. Now compiles with new code.
  247. Rev 1.6 12-8-2002 21:12:36 BGooijen
  248. Changed calls to Writeln to IOHandler.WriteLn, because the parent classes
  249. don't provide Writeln, System.Writeln was assumed by the compiler
  250. Rev 1.5 12-8-2002 21:08:58 BGooijen
  251. The TIdIOHandlerStream was not Opened before used, fixed that.
  252. Rev 1.4 12/6/2002 05:30:22 PM JPMugaas
  253. Now decend from TIdTCPClientCustom instead of TIdTCPClient.
  254. Rev 1.3 12/5/2002 02:54:06 PM JPMugaas
  255. Updated for new API definitions.
  256. Rev 1.2 11/23/2002 03:33:44 AM JPMugaas
  257. Reverted changes because they were problematic. Kudzu didn't explain why.
  258. Rev 1.1 11/19/2002 05:35:30 PM JPMugaas
  259. Fixed problem with a line starting with a ".". A double period should only
  260. be used if the line is really just one "." and no other cases.
  261. Rev 1.0 11/13/2002 07:56:58 AM JPMugaas
  262. }
  263. unit IdMessageClient;
  264. {
  265. 2003-10-04 Ciaran Costelloe (see comments starting CC4)
  266. If attachment not base64 encoded and has no ContentType, set to text/plain
  267. 2003-Sep-20 Ciaran Costelloe
  268. Modified ProcessAttachment in ReceiveBody to update message's Encoding
  269. if attachment was XX-encoded. Added decoding of message bodies
  270. encoded as base64 or quoted-printable. Added support for nested MIME parts
  271. (ParentPart). Added support for TIdText in UU and XX encoding. Added
  272. missing base64 and QP support where needed.
  273. Rewrote/rearranged most of code.
  274. 2001-Oct-29 Don Siders
  275. Modified TIdMessageClient.SendMsg to use AHeadersOnly argument.
  276. 2001-Dec-1 Don Siders
  277. Save ContentDisposition in TIdMessageClient.ProcessAttachment
  278. 2003-Sep-04 Ciaran Costelloe (CC comments)
  279. Commented-out IOHandler.WriteLn(''); in SendBody which used to insert a blank line
  280. between boundary and text attachment header, causing the attachment header to
  281. be parsed as part of the attachment text (the blank line is the delimiter for
  282. the end of the header).
  283. 2003-Sep-11 Ciaran Costelloe (CC2 comments)
  284. Added support in decoding for message body (as distinct from message parts) being
  285. encoded.
  286. Added support for generating encoded message body.
  287. }
  288. interface
  289. {$i IdCompilerDefines.inc}
  290. uses
  291. Classes,
  292. IdCoderMIME,
  293. IdExplicitTLSClientServerBase,
  294. IdGlobal,
  295. IdHeaderList,
  296. IdIOHandlerStream,
  297. IdBaseComponent,
  298. IdMessage;
  299. type
  300. TIdIOHandlerStreamMsg = class(TIdIOHandlerStream)
  301. protected
  302. FTerminatorWasRead: Boolean;
  303. FEscapeLines: Boolean;
  304. FUnescapeLines: Boolean;
  305. FLastByteRecv: Byte;
  306. function ReadDataFromSource(var VBuffer: TIdBytes): Integer; override;
  307. public
  308. constructor Create(
  309. AOwner: TComponent;
  310. AReceiveStream: TStream;
  311. ASendStream: TStream = nil
  312. ); override; //Should this be reintroduce instead of override?
  313. function Readable(AMSec: Integer = IdTimeoutDefault): Boolean; override;
  314. function ReadLn(ATerminator: string; ATimeout: Integer = IdTimeoutDefault;
  315. AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil): string; override;
  316. procedure WriteLn(const AOut: string; AByteEncoding: IIdTextEncoding = nil); override;
  317. property EscapeLines: Boolean read FEscapeLines write FEscapeLines;
  318. property UnescapeLines: Boolean read FUnescapeLines write FUnescapeLines;
  319. published
  320. property MaxLineLength default MaxInt;
  321. end;
  322. TIdMessageClient = class(TIdExplicitTLSClient)
  323. protected
  324. // The length of the folded line
  325. FMsgLineLength: integer;
  326. // The string to be pre-pended to the next line
  327. FMsgLineFold: string;
  328. procedure ReceiveBody(AMsg: TIdMessage; const ADelim: string = '.'); virtual; {do not localize}
  329. function ReceiveHeader(AMsg: TIdMessage; const AAltTerm: string = ''): string; virtual;
  330. procedure SendBody(AMsg: TIdMessage); virtual;
  331. procedure SendHeader(AMsg: TIdMessage); virtual;
  332. procedure EncodeAndWriteText(const ABody: TStrings; AEncoding: IIdTextEncoding);
  333. procedure WriteFoldedLine(const ALine : string);
  334. public
  335. constructor Create(AOwner: TComponent); override;
  336. procedure ProcessMessage(AMsg: TIdMessage; AHeaderOnly: Boolean = False); overload;
  337. procedure ProcessMessage(AMsg: TIdMessage; AStream: TStream; AHeaderOnly: Boolean = False); overload;
  338. procedure ProcessMessage(AMsg: TIdMessage; const AFilename: string; AHeaderOnly: Boolean = False); overload;
  339. procedure SendMsg(AMsg: TIdMessage; AHeadersOnly: Boolean = False); overload; virtual;
  340. //
  341. // property Capabilities;
  342. property MsgLineLength: integer read FMsgLineLength write FMsgLineLength;
  343. property MsgLineFold: string read FMsgLineFold write FMsgLineFold;
  344. end;
  345. implementation
  346. uses
  347. //TODO: Remove these references and make it completely pluggable. Check other spots in Indy as well
  348. IdMessageCoderBinHex4, IdMessageCoderQuotedPrintable, IdMessageCoderMIME,
  349. IdMessageCoderUUE, IdMessageCoderXXE,
  350. //
  351. IdGlobalProtocols,
  352. IdCoderBinHex4,
  353. IdCoderHeader, IdHeaderCoderBase, IdMessageCoder, IdComponent, IdException,
  354. IdResourceStringsProtocols, IdTCPConnection, IdTCPStream, IdIOHandler,
  355. IdAttachment, IdText,
  356. SysUtils;
  357. const
  358. SContentType = 'Content-Type'; {do not localize}
  359. SContentTransferEncoding = 'Content-Transfer-Encoding'; {do not localize}
  360. SThisIsMultiPartMessageInMIMEFormat = 'This is a multi-part message in MIME format'; {do not localize}
  361. function GetLongestLine(var ALine : String; const ADelim : String) : String;
  362. var
  363. i, fnd, delimLen : Integer;
  364. begin
  365. Result := '';
  366. fnd := 0;
  367. delimLen := Length(ADelim);
  368. for i := 1 to Length(ALine) do
  369. begin
  370. if ALine[i] = ADelim[1] then
  371. begin
  372. if Copy(ALine, i, delimLen) = ADelim then
  373. begin
  374. fnd := i;
  375. end;
  376. end;
  377. end;
  378. if fnd > 0 then
  379. begin
  380. Result := Copy(ALine, 1, fnd - 1);
  381. ALine := Copy(ALine, fnd + delimLen, MaxInt);
  382. end;
  383. end;
  384. procedure RemoveLastBlankLine(Body: TStrings);
  385. var
  386. Count: Integer;
  387. begin
  388. if Assigned(Body) then begin
  389. { Remove the last blank line. The last blank line is added again in
  390. TIdMessageClient.SendBody(). }
  391. Count := Body.Count;
  392. if (Count > 0) and (Body[Count - 1] = '') then begin
  393. Body.Delete(Count - 1);
  394. end;
  395. end;
  396. end;
  397. ////////////////////////
  398. // TIdIOHandlerStreamMsg
  399. ////////////////////////
  400. constructor TIdIOHandlerStreamMsg.Create(
  401. AOwner: TComponent;
  402. AReceiveStream: TStream;
  403. ASendStream: TStream = nil
  404. );
  405. begin
  406. inherited Create(AOwner, AReceiveStream, ASendStream);
  407. FTerminatorWasRead := False;
  408. FEscapeLines := False; // do not set this to True! This is for users to set manually...
  409. FUnescapeLines := False; // do not set this to True! This is for users to set manually...
  410. FLastByteRecv := 0;
  411. MaxLineLength := MaxInt;
  412. end;
  413. function TIdIOHandlerStreamMsg.Readable(AMSec: integer = IdTimeoutDefault): Boolean;
  414. begin
  415. if not FTerminatorWasRead then begin
  416. Result := inherited Readable(AMSec);
  417. if Result then begin
  418. Exit;
  419. end;
  420. end;
  421. Result := ReceiveStream <> nil;
  422. end;
  423. function TIdIOHandlerStreamMsg.ReadDataFromSource(var VBuffer: TIdBytes): Integer;
  424. var
  425. LTerminator: String;
  426. begin
  427. if not FTerminatorWasRead then
  428. begin
  429. Result := inherited ReadDataFromSource(VBuffer);
  430. if Result > 0 then begin
  431. FLastByteRecv := VBuffer[Result-1];
  432. Exit;
  433. end;
  434. // determine whether the stream ended with a line
  435. // break, adding an extra CR and/or LF if needed...
  436. if (FLastByteRecv = Ord(LF)) then begin
  437. // don't add an extra line break
  438. LTerminator := '.' + EOL;
  439. end else if (FLastByteRecv = Ord(CR)) then begin
  440. // add extra LF
  441. LTerminator := LF + '.' + EOL;
  442. end else begin
  443. // add extra CRLF
  444. LTerminator := EOL + '.' + EOL;
  445. end;
  446. FTerminatorWasRead := True;
  447. // in theory, CopyTIdString() will write the string
  448. // into the byte array using 1-byte characters even
  449. // under DotNet where strings are usually Unicode
  450. // instead of ASCII...
  451. CopyTIdString(LTerminator, VBuffer, 0);
  452. Result := Length(LTerminator);
  453. end else begin
  454. Result := 0;
  455. end;
  456. end;
  457. function TIdIOHandlerStreamMsg.ReadLn(ATerminator: string;
  458. ATimeout: Integer = IdTimeoutDefault; AMaxLineLength: Integer = -1;
  459. AByteEncoding: IIdTextEncoding = nil): string;
  460. begin
  461. Result := inherited ReadLn(ATerminator, ATimeout, AMaxLineLength, AByteEncoding);
  462. if FEscapeLines and TextStartsWith(Result, '.') and (not FTerminatorWasRead) then begin {Do not Localize}
  463. Result := '.' + Result; {Do not Localize}
  464. end;
  465. end;
  466. procedure TIdIOHandlerStreamMsg.WriteLn(const AOut: string; AByteEncoding: IIdTextEncoding = nil);
  467. var
  468. LOut: String;
  469. begin
  470. LOut := AOut;
  471. if FUnescapeLines and TextStartsWith(LOut, '..') then begin {Do not Localize}
  472. IdDelete(LOut, 1, 1);
  473. end;
  474. inherited WriteLn(LOut, AByteEncoding);
  475. end;
  476. ///////////////////
  477. // TIdMessageClient
  478. ///////////////////
  479. constructor TIdMessageClient.Create(AOwner: TComponent);
  480. begin
  481. inherited Create(AOwner);
  482. FMsgLineLength := 79;
  483. FMsgLineFold := TAB;
  484. end;
  485. procedure TIdMessageClient.WriteFoldedLine(const ALine : string);
  486. var
  487. ins, s, line, spare : String;
  488. msgLen, insLen : Word;
  489. begin
  490. s := ALine;
  491. // To give an amount of thread-safety
  492. ins := FMsgLineFold;
  493. insLen := Length(ins);
  494. msgLen := FMsgLineLength;
  495. // Do first line
  496. if length(s) > FMsgLineLength then
  497. begin
  498. spare := Copy(s, 1, msgLen);
  499. line := GetLongestLine(spare, ' '); {do not localize}
  500. s := spare + Copy(s, msgLen + 1, length(s));
  501. IOHandler.WriteLn(line);
  502. // continue with the folded lines
  503. while length(s) > (msgLen - insLen) do
  504. begin
  505. spare := Copy(s, 1, (msgLen - insLen));
  506. line := GetLongestLine(spare, ' '); {do not localize}
  507. s := ins + spare + Copy(s, (msgLen - insLen) + 1, length(s));
  508. IOHandler.WriteLn(line);
  509. end;
  510. // complete the output with what's left
  511. if Trim(s) <> '' then
  512. begin
  513. IOHandler.WriteLn(ins + s);
  514. end;
  515. end
  516. else begin
  517. IOHandler.WriteLn(s);
  518. end;
  519. end;
  520. procedure TIdMessageClient.ReceiveBody(AMsg: TIdMessage; const ADelim: string = '.'); {do not localize}
  521. var
  522. LMsgEnd: Boolean;
  523. LActiveDecoder: TIdMessageDecoder;
  524. LLine: string;
  525. LParentPart: integer;
  526. LPreviousParentPart: integer;
  527. LEncoding, LCharsetEncoding: IIdTextEncoding;
  528. LContentTransferEncoding: string;
  529. LUnknownContentTransferEncoding: Boolean;
  530. // TODO - move this procedure into TIdIOHandler as a new Capture method?
  531. procedure CaptureAndDecodeCharset;
  532. var
  533. LMStream: TMemoryStream;
  534. begin
  535. LMStream := TMemoryStream.Create;
  536. try
  537. IOHandler.Capture(LMStream, ADelim, True, IndyTextEncoding_8Bit);
  538. LMStream.Position := 0;
  539. // TODO: if the Content-Type is HTML, parse the HTML data looking for a charset
  540. // declaration, and if found then use that instead of the MIME charset...
  541. ReadStringsAsCharSet(LMStream, AMsg.Body, AMsg.CharSet);
  542. finally
  543. LMStream.Free;
  544. end;
  545. end;
  546. // RLebeau 11/2/2013: TIdMessage.Headers is a TIdHeaderList, but
  547. // TIdMessageDecoder.Headers is a plain TStringList. Although TIdHeaderList
  548. // is a TStrings descendant, it reintroduces its own Values[] property
  549. // instead of implementing the TStrings.Values[] property, so we cannot
  550. // access TIdMessage.Headers using a TStrings pointer or else the wrong
  551. // property will be invoked and we won't get the right value when accessing
  552. // TIdMessage.Headers since TStrings and TIdHeaderList use different
  553. // NameValueSeparator implementations, so we have to access them separately...
  554. function GetHeaderValue(const AName: string): string;
  555. begin
  556. if AMsg.IsMsgSinglePartMime then begin
  557. Result := AMsg.Headers.Values[AName];
  558. end else begin
  559. Result := LActiveDecoder.Headers.Values[AName];
  560. end;
  561. end;
  562. {Only set AUseBodyAsTarget to True if you want the input stream stored in TIdMessage.Body
  563. instead of TIdText.Body: this happens with some single-part messages.}
  564. procedure ProcessTextPart(var VDecoder: TIdMessageDecoder; AUseBodyAsTarget: Boolean);
  565. var
  566. LMStream: TMemoryStream;
  567. i: integer;
  568. LTxt : TIdText;
  569. LNewDecoder: TIdMessageDecoder;
  570. begin
  571. LMStream := TMemoryStream.Create;
  572. try
  573. LParentPart := AMsg.MIMEBoundary.ParentPart;
  574. LNewDecoder := VDecoder.ReadBody(LMStream, LMsgEnd);
  575. try
  576. LMStream.Position := 0;
  577. if AUseBodyAsTarget then begin
  578. // TODO: if the Content-Type is HTML, parse the HTML data looking for a charset
  579. // declaration, and if found then use that instead of the MIME charset...
  580. if AMsg.IsMsgSinglePartMime then begin
  581. ReadStringsAsCharSet(LMStream, AMsg.Body, AMsg.CharSet);
  582. end else begin
  583. ReadStringsAsContentType(LMStream, AMsg.Body, VDecoder.Headers.Values[SContentType], QuoteMIME);
  584. end;
  585. end else begin
  586. LTxt := TIdText.Create(AMsg.MessageParts);
  587. try
  588. ReadStringsAsContentType(LMStream, LTxt.Body, GetHeaderValue(SContentType), QuoteMIME);
  589. RemoveLastBlankLine(LTxt.Body);
  590. LTxt.ContentType := LTxt.ResolveContentType(GetHeaderValue(SContentType));
  591. LTxt.CharSet := LTxt.GetCharSet(GetHeaderValue(SContentType)); {do not localize}
  592. LTxt.ContentTransfer := GetHeaderValue(SContentTransferEncoding); {do not localize}
  593. LTxt.ContentID := GetHeaderValue('Content-ID'); {do not localize}
  594. LTxt.ContentLocation := GetHeaderValue('Content-Location'); {do not localize}
  595. LTxt.ContentDescription := GetHeaderValue('Content-Description'); {do not localize}
  596. LTxt.ContentDisposition := GetHeaderValue('Content-Disposition'); {do not localize}
  597. if not AMsg.IsMsgSinglePartMime then begin
  598. for i := 0 to VDecoder.Headers.Count-1 do begin
  599. if LTxt.Headers.IndexOfName(VDecoder.Headers.Names[i]) < 0 then begin
  600. LTxt.ExtraHeaders.AddValue(
  601. VDecoder.Headers.Names[i],
  602. IndyValueFromIndex(VDecoder.Headers, i)
  603. );
  604. end;
  605. end;
  606. end;
  607. LTxt.Filename := VDecoder.Filename;
  608. if IsHeaderMediaType(LTxt.ContentType, 'multipart') then begin {do not localize}
  609. LTxt.ParentPart := LPreviousParentPart;
  610. // RLebeau 08/17/09 - According to RFC 2045 Section 6.4:
  611. // "If an entity is of type "multipart" the Content-Transfer-Encoding is not
  612. // permitted to have any value other than "7bit", "8bit" or "binary"."
  613. //
  614. // However, came across one message where the "Content-Type" was set to
  615. // "multipart/related" and the "Content-Transfer-Encoding" was set to
  616. // "quoted-printable". Outlook and Thunderbird were apparently able to parse
  617. // the message correctly, but Indy was not. So let's check for that scenario
  618. // and ignore illegal "Content-Transfer-Encoding" values if present...
  619. if LTxt.ContentTransfer <> '' then begin
  620. if not IsHeaderValue(LTxt.ContentTransfer, ['7bit', '8bit', 'binary']) then begin {do not localize}
  621. LTxt.ContentTransfer := '';
  622. end;
  623. end;
  624. end else begin
  625. LTxt.ParentPart := LParentPart;
  626. end;
  627. except
  628. LTxt.Free;
  629. raise;
  630. end;
  631. end;
  632. except
  633. LNewDecoder.Free;
  634. raise;
  635. end;
  636. VDecoder.Free;
  637. VDecoder := LNewDecoder;
  638. finally
  639. LMStream.Free;
  640. end;
  641. end;
  642. procedure ProcessAttachment(var VDecoder: TIdMessageDecoder);
  643. var
  644. LDestStream: TStream;
  645. i: integer;
  646. LAttachment: TIdAttachment;
  647. LNewDecoder: TIdMessageDecoder;
  648. begin
  649. LParentPart := AMsg.MIMEBoundary.ParentPart;
  650. AMsg.DoCreateAttachment(VDecoder.Headers, LAttachment);
  651. Assert(Assigned(LAttachment), 'Attachment must not be unassigned here!'); {Do not localize}
  652. try
  653. LNewDecoder := nil;
  654. try
  655. LDestStream := LAttachment.PrepareTempStream;
  656. try
  657. LNewDecoder := VDecoder.ReadBody(LDestStream, LMsgEnd);
  658. finally
  659. LAttachment.FinishTempStream;
  660. end;
  661. LAttachment.ContentType := LAttachment.ResolveContentType(GetHeaderValue(SContentType));
  662. LAttachment.CharSet := LAttachment.GetCharSet(GetHeaderValue(SContentType));
  663. if VDecoder is TIdMessageDecoderUUE then begin
  664. LAttachment.ContentTransfer := TIdMessageDecoderUUE(VDecoder).CodingType; {do not localize}
  665. end else begin
  666. //Watch out for BinHex 4.0 encoding: no ContentTransfer is specified
  667. //in the header, but we need to set it to something meaningful for us...
  668. if IsHeaderMediaType(LAttachment.ContentType, 'application/mac-binhex40') then begin {do not localize}
  669. LAttachment.ContentTransfer := 'binhex40'; {do not localize}
  670. end else begin
  671. LAttachment.ContentTransfer := GetHeaderValue(SContentTransferEncoding);
  672. end;
  673. end;
  674. LAttachment.ContentDisposition := GetHeaderValue('Content-Disposition'); {do not localize}
  675. LAttachment.ContentID := GetHeaderValue('Content-ID'); {do not localize}
  676. LAttachment.ContentLocation := GetHeaderValue('Content-Location'); {do not localize}
  677. LAttachment.ContentDescription := GetHeaderValue('Content-Description'); {do not localize}
  678. if not AMsg.IsMsgSinglePartMime then begin
  679. for i := 0 to VDecoder.Headers.Count-1 do begin
  680. if LAttachment.Headers.IndexOfName(VDecoder.Headers.Names[i]) < 0 then begin
  681. LAttachment.ExtraHeaders.AddValue(
  682. VDecoder.Headers.Names[i],
  683. IndyValueFromIndex(VDecoder.Headers, i)
  684. );
  685. end;
  686. end;
  687. end;
  688. LAttachment.Filename := VDecoder.Filename;
  689. if IsHeaderMediaType(LAttachment.ContentType, 'multipart') then begin {do not localize}
  690. LAttachment.ParentPart := LPreviousParentPart;
  691. // RLebeau 08/17/09 - According to RFC 2045 Section 6.4:
  692. // "If an entity is of type "multipart" the Content-Transfer-Encoding is not
  693. // permitted to have any value other than "7bit", "8bit" or "binary"."
  694. //
  695. // However, came across one message where the "Content-Type" was set to
  696. // "multipart/related" and the "Content-Transfer-Encoding" was set to
  697. // "quoted-printable". Outlook and Thunderbird were apparently able to parse
  698. // the message correctly, but Indy was not. So let's check for that scenario
  699. // and ignore illegal "Content-Transfer-Encoding" values if present...
  700. if LAttachment.ContentTransfer <> '' then begin
  701. if not IsHeaderValue(LAttachment.ContentTransfer, ['7bit', '8bit', 'binary']) then begin {do not localize}
  702. LAttachment.ContentTransfer := '';
  703. end;
  704. end;
  705. end else begin
  706. LAttachment.ParentPart := LParentPart;
  707. end;
  708. except
  709. LNewDecoder.Free;
  710. raise;
  711. end;
  712. VDecoder.Free;
  713. VDecoder := LNewDecoder;
  714. except
  715. //This should also remove the Item from the TCollection.
  716. //Note that Delete does not exist in the TCollection.
  717. LAttachment.Free;
  718. raise;
  719. end;
  720. end;
  721. begin
  722. LMsgEnd := False;
  723. // RLebeau 08/09/09 - TIdNNTP.GetBody() calls TIdMessage.Clear() before then
  724. // calling ReceiveBody(), thus the TIdMessage.ContentTransferEncoding value
  725. // is not available for use below. What is the best way to detect that so
  726. // the user could be allowed to set up the IOHandler.DefStringEncoding
  727. // beforehand?
  728. LUnknownContentTransferEncoding := False;
  729. if AMsg.NoDecode then begin
  730. LEncoding := IndyTextEncoding_8Bit;
  731. end else
  732. begin
  733. LContentTransferEncoding := ExtractHeaderItem(AMsg.ContentTransferEncoding);
  734. if LContentTransferEncoding = '' then begin
  735. // RLebeau 04/08/2014: According to RFC 2045 Section 6.1:
  736. // "Content-Transfer-Encoding: 7BIT" is assumed if the
  737. // Content-Transfer-Encoding header field is not present."
  738. if IsHeaderMediaType(AMsg.ContentType, 'application/mac-binhex40') then begin {Do not Localize}
  739. LContentTransferEncoding := 'binhex40'; {do not localize}
  740. end
  741. else if (AMsg.Encoding = meMIME) and (AMsg.MIMEBoundary.Count > 0) and (not AMsg.NoDecode) then begin
  742. LContentTransferEncoding := '7bit'; {do not localize}
  743. end;
  744. end
  745. else if IsHeaderMediaType(AMsg.ContentType, 'multipart') then {do not localize}
  746. begin
  747. // RLebeau 08/17/09 - According to RFC 2045 Section 6.4:
  748. // "If an entity is of type "multipart" the Content-Transfer-Encoding is not
  749. // permitted to have any value other than "7bit", "8bit" or "binary"."
  750. //
  751. // However, came across one message where the "Content-Type" was set to
  752. // "multipart/related" and the "Content-Transfer-Encoding" was set to
  753. // "quoted-printable". Outlook and Thunderbird were apparently able to parse
  754. // the message correctly, but Indy was not. So let's check for that scenario
  755. // and ignore illegal "Content-Transfer-Encoding" values if present...
  756. if PosInStrArray(LContentTransferEncoding, ['7bit', '8bit', 'binary'], False) = -1 then begin {do not localize}
  757. LContentTransferEncoding := '';
  758. //LUnknownContentTransferEncoding := True;
  759. end;
  760. end;
  761. if LContentTransferEncoding <> '' then begin
  762. case PosInStrArray(LContentTransferEncoding, ['7bit', 'quoted-printable', 'base64', '8bit', 'binary'], False) of {do not localize}
  763. 0..2: LEncoding := IndyTextEncoding_ASCII;
  764. 3..4: LEncoding := IndyTextEncoding_8Bit;
  765. else
  766. // According to RFC 2045 Section 6.4:
  767. // "Any entity with an unrecognized Content-Transfer-Encoding must be
  768. // treated as if it has a Content-Type of "application/octet-stream",
  769. // regardless of what the Content-Type header field actually says."
  770. LEncoding := IndyTextEncoding_8Bit;
  771. LContentTransferEncoding := '';
  772. LUnknownContentTransferEncoding := True;
  773. end;
  774. end else begin
  775. LEncoding := IndyTextEncoding_8Bit;
  776. end;
  777. end;
  778. BeginWork(wmRead);
  779. try
  780. if AMsg.NoDecode then begin
  781. CaptureAndDecodeCharset;
  782. end else begin
  783. LActiveDecoder := nil;
  784. try
  785. if ((not LUnknownContentTransferEncoding) and
  786. ((AMsg.Encoding = meMIME) and (AMsg.MIMEBoundary.Count > 0)) or
  787. ((AMsg.Encoding = mePlainText) and (not IsHeaderValue(AMsg.ContentTransferEncoding, ['base64', 'quoted-printable']))) {do not localize}
  788. ) then begin
  789. {NOTE: You hit this code path with multipart MIME messages and with
  790. plain-text messages (which may have UUE or XXE attachments embedded).}
  791. LCharsetEncoding := CharsetToEncoding(AMsg.CharSet);
  792. repeat
  793. {CC: This code assumes the preamble text (before the first boundary)
  794. is plain text. I cannot imagine it not being, but if it arises, lines
  795. will have to be decoded.}
  796. // TODO: need to figure out a way to handle both transfer encoding
  797. // and charset encoding together! Need to read the raw bytes into
  798. // an intermediate buffer of some kind using the transfer encoding,
  799. // and then decode the characters using the charset afterwards...
  800. //
  801. // Need to do this anyway because ReadLnRFC() processes the LF and
  802. // ADelim values in terms of the charset specified, which is wrong.
  803. // EBCDIC-based charsets totally break that logic! For example, cp1026
  804. // converts #10 (LF) to $25 instead of $0A during encoding, and converts
  805. // $0A (LF) and $2E ('.') to #$83 and #6 during decoding, etc. And what
  806. // if the charset is UTF-16 instead? So we need to read raw bytes into
  807. // a buffer, checking it for handling of line breaks, dot-transparency,
  808. // and message termination, and THEN decode whatever is left using the
  809. // charset...
  810. LLine := IOHandler.ReadLnRFC(LMsgEnd, LF, ADelim, IndyTextEncoding_8Bit);
  811. if LMsgEnd then begin
  812. Break;
  813. end;
  814. if LActiveDecoder = nil then begin
  815. LActiveDecoder := TIdMessageDecoderList.CheckForStart(AMsg, LLine);
  816. end;
  817. // Check again, the if above can set it.
  818. if LActiveDecoder = nil then begin
  819. LLine := LCharsetEncoding.GetString(ToBytes(LLine, IndyTextEncoding_8Bit));
  820. AMsg.Body.Add(LLine);
  821. end else begin
  822. RemoveLastBlankLine(AMsg.Body);
  823. while LActiveDecoder <> nil do begin
  824. LActiveDecoder.SourceStream := TIdTCPStream.Create(Self);
  825. LPreviousParentPart := AMsg.MIMEBoundary.ParentPart;
  826. LActiveDecoder.ReadHeader;
  827. case LActiveDecoder.PartType of
  828. mcptText: ProcessTextPart(LActiveDecoder, False);
  829. mcptAttachment: ProcessAttachment(LActiveDecoder);
  830. mcptIgnore: FreeAndNil(LActiveDecoder);
  831. mcptEOF: begin FreeAndNil(LActiveDecoder); LMsgEnd := True; end;
  832. end;
  833. end;
  834. end;
  835. until LMsgEnd;
  836. RemoveLastBlankLine(AMsg.Body);
  837. end else begin
  838. {These are single-part MIMEs, or else mePlainTexts with the body encoded QP/base64}
  839. AMsg.IsMsgSinglePartMime := True;
  840. LActiveDecoder := TIdMessageDecoderMime.Create(AMsg);
  841. LActiveDecoder.SourceStream := TIdTCPStream.Create(Self);
  842. // RLebeau: override what TIdMessageDecoderMime.InitComponent() assigns
  843. TIdMessageDecoderMime(LActiveDecoder).BodyEncoded := True;
  844. TIdMessageDecoderMime(LActiveDecoder).ReadHeader;
  845. case LActiveDecoder.PartType of
  846. mcptText: begin
  847. if LUnknownContentTransferEncoding then begin
  848. ProcessAttachment(LActiveDecoder);
  849. end else begin
  850. ProcessTextPart(LActiveDecoder, True); //Put the text into TIdMessage.Body
  851. end;
  852. end;
  853. mcptAttachment: ProcessAttachment(LActiveDecoder);
  854. mcptIgnore: FreeAndNil(LActiveDecoder);
  855. mcptEOF: FreeAndNil(LActiveDecoder);
  856. end;
  857. end;
  858. finally
  859. LActiveDecoder.Free;
  860. end;
  861. end;
  862. finally
  863. EndWork(wmRead);
  864. end;
  865. end;
  866. procedure TIdMessageClient.SendHeader(AMsg: TIdMessage);
  867. begin
  868. AMsg.GenerateHeader;
  869. IOHandler.Write(AMsg.LastGeneratedHeaders);
  870. end;
  871. procedure TIdMessageClient.SendBody(AMsg: TIdMessage);
  872. var
  873. i: Integer;
  874. LAttachment: TIdAttachment;
  875. LBoundary: string;
  876. LDestStream: TStream;
  877. LStrStream: TStream;
  878. ISOCharset: string;
  879. HeaderEncoding: Char; { B | Q }
  880. LEncoder: TIdMessageEncoder;
  881. LLine: string;
  882. procedure EncodeStrings(AStrings: TStrings; AEncoderClass: TIdMessageEncoderClass; AByteEncoding: IIdTextEncoding);
  883. var
  884. LStrings: TStringList;
  885. begin
  886. LStrings := TStringList.Create;
  887. try
  888. LEncoder := AEncoderClass.Create(nil);
  889. try
  890. LStrStream := TMemoryStream.Create;
  891. try
  892. // RLebeau 10/06/2010: not using TStrings.SaveToStream() in D2009+
  893. // anymore, as it may save a BOM which we do not want here...
  894. WriteStringToStream(LStrStream, AStrings.Text, AByteEncoding);
  895. LStrStream.Position := 0;
  896. LEncoder.Encode(LStrStream, LStrings);
  897. finally
  898. LStrStream.Free;
  899. end;
  900. finally
  901. LEncoder.Free;
  902. end;
  903. IOHandler.WriteRFCStrings(LStrings, False);
  904. finally
  905. LStrings.Free;
  906. end;
  907. end;
  908. procedure EncodeAttachment(AAttachment: TIdAttachment; AEncoderClass: TIdMessageEncoderClass);
  909. var
  910. LAttachStream: TStream;
  911. begin
  912. LDestStream := TIdTCPStream.Create(Self, 8192);
  913. try
  914. LEncoder := AEncoderClass.Create(nil);
  915. try
  916. LEncoder.Filename := AAttachment.Filename;
  917. LAttachStream := AAttachment.OpenLoadStream;
  918. try
  919. LEncoder.Encode(LAttachStream, LDestStream);
  920. finally
  921. AAttachment.CloseLoadStream;
  922. end;
  923. finally
  924. LEncoder.Free;
  925. end;
  926. finally
  927. LDestStream.Free;
  928. end;
  929. end;
  930. procedure WriteTextPart(ATextPart: TIdText);
  931. var
  932. LEncoding: IIdTextEncoding;
  933. LFileName: String;
  934. begin
  935. if ATextPart.ContentType = '' then begin
  936. ATextPart.ContentType := 'text/plain'; {do not localize}
  937. end;
  938. // RLebeau 08/17/09 - According to RFC 2045 Section 6.4:
  939. // "If an entity is of type "multipart" the Content-Transfer-Encoding is not
  940. // permitted to have any value other than "7bit", "8bit" or "binary"."
  941. //
  942. // However, came across one message where the "Content-Type" was set to
  943. // "multipart/related" and the "Content-Transfer-Encoding" was set to
  944. // "quoted-printable". Outlook and Thunderbird were apparently able to parse
  945. // the message correctly, but Indy was not. So let's check for that scenario
  946. // and ignore illegal "Content-Transfer-Encoding" values if present...
  947. if IsHeaderMediaType(ATextPart.ContentType, 'multipart') then begin {do not localize}
  948. if ATextPart.ContentTransfer <> '' then begin
  949. if not IsHeaderValue(ATextPart.ContentTransfer, ['7bit', '8bit', 'binary']) then begin {do not localize}
  950. ATextPart.ContentTransfer := '';
  951. end;
  952. end;
  953. end
  954. else if ATextPart.ContentTransfer = '' then begin
  955. ATextPart.ContentTransfer := 'quoted-printable'; {do not localize}
  956. end
  957. else if (not IsHeaderValue(ATextPart.ContentTransfer, ['quoted-printable', 'base64'])) {do not localize}
  958. and ATextPart.IsBodyEncodingRequired then
  959. begin
  960. ATextPart.ContentTransfer := '8bit'; {do not localize}
  961. end;
  962. if ATextPart.ContentDisposition = '' then begin
  963. ATextPart.ContentDisposition := 'inline'; {do not localize}
  964. end;
  965. LFileName := EncodeHeader(ExtractFileName(ATextPart.FileName), '', HeaderEncoding, ISOCharSet); {do not localize}
  966. if ATextPart.ContentType <> '' then begin
  967. IOHandler.Write('Content-Type: ' + ATextPart.ContentType); {do not localize}
  968. if ATextPart.CharSet <> '' then begin
  969. IOHandler.Write('; charset="' + ATextPart.CharSet + '"'); {do not localize}
  970. end;
  971. if LFileName <> '' then begin
  972. IOHandler.WriteLn(';'); {do not localize}
  973. IOHandler.Write(TAB + 'name="' + LFileName + '"'); {do not localize}
  974. end;
  975. IOHandler.WriteLn;
  976. end;
  977. if ATextPart.ContentTransfer <> '' then begin
  978. IOHandler.WriteLn(SContentTransferEncoding + ': ' + ATextPart.ContentTransfer); {do not localize}
  979. end;
  980. IOHandler.Write('Content-Disposition: ' + ATextPart.ContentDisposition); {do not localize}
  981. if LFileName <> '' then begin
  982. IOHandler.WriteLn(';'); {do not localize}
  983. IOHandler.Write(TAB + 'filename="' + LFileName + '"'); {do not localize}
  984. end;
  985. IOHandler.WriteLn;
  986. if ATextPart.ContentID <> '' then begin
  987. IOHandler.WriteLn('Content-ID: ' + ATextPart.ContentID); {do not localize}
  988. end;
  989. if ATextPart.ContentDescription <> '' then begin
  990. IOHandler.WriteLn('Content-Description: ' + ATextPart.ContentDescription); {do not localize}
  991. end;
  992. IOHandler.Write(ATextPart.ExtraHeaders);
  993. IOHandler.WriteLn;
  994. LEncoding := CharsetToEncoding(ATextPart.CharSet);
  995. case PosInStrArray(ExtractHeaderItem(ATextPart.ContentTransfer), ['quoted-printable', 'base64'], False) of {do not localize}
  996. 0: EncodeStrings(ATextPart.Body, TIdMessageEncoderQuotedPrintable, LEncoding);
  997. 1: EncodeStrings(ATextPart.Body, TIdMessageEncoderMIME, LEncoding);
  998. else
  999. IOHandler.WriteRFCStrings(ATextPart.Body, False, LEncoding);
  1000. { No test for last line break necessary because IOHandler.WriteRFCStrings() uses WriteLn(). }
  1001. end;
  1002. end;
  1003. var
  1004. LFileName, LContentTransferEncoding: String;
  1005. LTextPart: TIdText;
  1006. LAddedTextPart: Boolean;
  1007. LLastPart: Integer;
  1008. LEncoding: IIdTextEncoding;
  1009. LAttachStream: TStream;
  1010. begin
  1011. LBoundary := '';
  1012. AMsg.InitializeISO(HeaderEncoding, ISOCharSet);
  1013. BeginWork(wmWrite);
  1014. try
  1015. LContentTransferEncoding := ExtractHeaderItem(AMsg.ContentTransferEncoding);
  1016. if (not AMsg.IsMsgSinglePartMime) and
  1017. (PosInStrArray(LContentTransferEncoding, ['base64', 'quoted-printable'], False) <> -1) then {do not localize}
  1018. begin
  1019. //CC2: The user wants the body encoded.
  1020. if AMsg.MessageParts.Count > 0 then begin
  1021. //CC2: We cannot deal with parts within a body encoding (user has to do
  1022. //this manually, if the user really wants to). Note this should have been trapped in TIdMessage.GenerateHeader.
  1023. raise EIdException.Create(RSMsgClientInvalidForTransferEncoding); // TODO: create a new Exception class for this
  1024. end;
  1025. IOHandler.WriteLn; //This is the blank line after the headers
  1026. DoStatus(hsStatusText, [RSMsgClientEncodingText]);
  1027. LEncoding := CharsetToEncoding(AMsg.CharSet);
  1028. //CC2: Now output AMsg.Body in the chosen encoding...
  1029. if TextIsSame(LContentTransferEncoding, 'base64') then begin {do not localize}
  1030. EncodeStrings(AMsg.Body, TIdMessageEncoderMIME, LEncoding);
  1031. end else begin {'quoted-printable'}
  1032. EncodeStrings(AMsg.Body, TIdMessageEncoderQuotedPrintable, LEncoding);
  1033. end;
  1034. end
  1035. else if AMsg.Encoding = mePlainText then begin
  1036. IOHandler.WriteLn; //This is the blank line after the headers
  1037. //CC2: It is NOT Mime. It is a body followed by optional attachments
  1038. DoStatus(hsStatusText, [RSMsgClientEncodingText]);
  1039. // Write out Body first
  1040. LEncoding := CharsetToEncoding(AMsg.CharSet);
  1041. EncodeAndWriteText(AMsg.Body, LEncoding);
  1042. IOHandler.WriteLn;
  1043. if AMsg.MessageParts.Count > 0 then begin
  1044. //The message has attachments.
  1045. for i := 0 to AMsg.MessageParts.Count - 1 do begin
  1046. //CC: Added support for TIdText...
  1047. if AMsg.MessageParts.Items[i] is TIdText then begin
  1048. IOHandler.WriteLn;
  1049. IOHandler.WriteLn('------- Start of text attachment -------'); {do not localize}
  1050. DoStatus(hsStatusText, [RSMsgClientEncodingText]);
  1051. WriteTextPart(TIdText(AMsg.MessageParts.Items[i]));
  1052. IOHandler.WriteLn('------- End of text attachment -------'); {do not localize}
  1053. end
  1054. else if AMsg.MessageParts.Items[i] is TIdAttachment then begin
  1055. LAttachment := TIdAttachment(AMsg.MessageParts[i]);
  1056. DoStatus(hsStatusText, [RSMsgClientEncodingAttachment]);
  1057. if LAttachment.ContentTransfer = '' then begin
  1058. //The user has nothing specified: see has he set a preference in
  1059. //TIdMessage.AttachmentEncoding (AttachmentEncoding is really an
  1060. //old and somewhat deprecated property, but we can still support it)...
  1061. if PosInStrArray(AMsg.AttachmentEncoding, ['UUE', 'XXE']) <> -1 then begin {do not localize}
  1062. LAttachment.ContentTransfer := AMsg.AttachmentEncoding;
  1063. end else begin
  1064. //We default to UUE (rather than XXE)...
  1065. LAttachment.ContentTransfer := 'UUE'; {do not localize}
  1066. end;
  1067. end;
  1068. case PosInStrArray(ExtractHeaderItem(LAttachment.ContentTransfer), ['UUE', 'XXE'], False) of {do not localize}
  1069. 0: EncodeAttachment(LAttachment, TIdMessageEncoderUUE);
  1070. 1: EncodeAttachment(LAttachment, TIdMessageEncoderXXE);
  1071. end;
  1072. end;
  1073. IOHandler.WriteLn;
  1074. end;
  1075. end;
  1076. end
  1077. else begin
  1078. //CC2: It is MIME-encoding...
  1079. LAddedTextPart := False;
  1080. //######### OUTPUT THE PREAMBLE TEXT ########
  1081. {For single-part MIME messages, we want the message part headers to be appended
  1082. to the message headers. Otherwise, add the blank separator between header and
  1083. body...}
  1084. if not AMsg.IsMsgSinglePartMime then begin
  1085. IOHandler.WriteLn; //This is the blank line after the headers
  1086. //if AMsg.Body.Count > 0 then begin
  1087. if not AMsg.IsBodyEmpty then begin
  1088. //CC2: The message has a body text. There are now a few possibilities.
  1089. //First up, if ConvertPreamble is False then the user explicitly does not want us
  1090. //to convert the .Body since he had to change it from the default False.
  1091. //Secondly, if AMsg.MessageParts.TextPartCount > 0, he may have put the
  1092. //message text in the part, so don't convert the body.
  1093. //Thirdly, if AMsg.MessageParts.Count = 0, then it has no other parts
  1094. //anyway: in this case, output it without boundaries.
  1095. //if (AMsg.ConvertPreamble and (AMsg.MessageParts.TextPartCount = 0)) then begin
  1096. if AMsg.ConvertPreamble and (AMsg.MessageParts.TextPartCount = 0) and (AMsg.MessageParts.Count > 0) then begin
  1097. //CC2: There is no text part, the user has not changed ConvertPreamble from
  1098. //its default of True, so the user has probably put his message into
  1099. //the body by mistake instead of putting it in a TIdText part.
  1100. //Create a TIdText part from the .Body text...
  1101. LTextPart := TIdText.Create(AMsg.MessageParts, AMsg.Body);
  1102. LTextPart.CharSet := AMsg.CharSet;
  1103. LTextPart.ContentType := 'text/plain'; {do not localize}
  1104. LTextPart.ContentTransfer := 'quoted-printable'; {do not localize}
  1105. //Have to remember that we added a text part, which is the last part
  1106. //in the collection, because we need it to be outputted first...
  1107. LAddedTextPart := True;
  1108. //CC2: Insert our standard preamble text...
  1109. IOHandler.WriteLn(SThisIsMultiPartMessageInMIMEFormat);
  1110. end else begin
  1111. //CC2: Hopefully the user has put suitable text in the preamble, or this
  1112. //is an already-received message which already has a preamble text...
  1113. LEncoding := CharsetToEncoding(AMsg.CharSet);
  1114. EncodeAndWriteText(AMsg.Body, LEncoding);
  1115. end;
  1116. end
  1117. else begin
  1118. //CC2: The user has specified no body text: he presumably has the message in
  1119. //a TIdText part, but it may have no text at all (a message consisting only
  1120. //of headers, which is allowed under the RFC, which will have a parts count
  1121. //of 0).
  1122. if AMsg.MessageParts.Count <> 0 then begin
  1123. //Add the "standard" MIME preamble text for non-html email clients...
  1124. IOHandler.WriteLn(SThisIsMultiPartMessageInMIMEFormat);
  1125. end;
  1126. end;
  1127. IOHandler.WriteLn;
  1128. //######### SET UP THE BOUNDARY STACK ########
  1129. LBoundary := AMsg.MIMEBoundary.Boundary;
  1130. if LBoundary = '' then begin
  1131. LBoundary := TIdMIMEBoundaryStrings.GenerateBoundary;
  1132. AMsg.MIMEBoundary.Push(LBoundary, -1); //-1 is "top level"
  1133. end;
  1134. end;
  1135. //######### OUTPUT THE PARTS ########
  1136. //CC2: Write the text parts in their order, if you change the order you
  1137. //can mess up mutipart sequences.
  1138. //The exception is due to ConvertPreamble, which may have added a text
  1139. //part at the end (the only place a TIdText part can be added), but it
  1140. //needs to be outputted first...
  1141. LLastPart := AMsg.MessageParts.Count - 1;
  1142. if LAddedTextPart then begin
  1143. IOHandler.WriteLn('--' + LBoundary); {do not localize}
  1144. DoStatus(hsStatusText, [RSMsgClientEncodingText]);
  1145. WriteTextPart(AMsg.MessageParts.Items[LLastPart] as TIdText);
  1146. IOHandler.WriteLn;
  1147. Dec(LLastPart); //Don't output it again in the following "for" loop
  1148. end;
  1149. for i := 0 to LLastPart do begin
  1150. LLine := AMsg.MessageParts.Items[i].ContentType;
  1151. if IsHeaderMediaType(LLine, 'multipart') then begin {do not localize}
  1152. //A multipart header. Write out the CURRENT boundary first...
  1153. IOHandler.WriteLn('--' + LBoundary); {do not localize}
  1154. //Make the current boundary and this part number active...
  1155. //Now need to generate a new boundary...
  1156. LBoundary := TIdMIMEBoundaryStrings.GenerateBoundary;
  1157. AMsg.MIMEBoundary.Push(LBoundary, i);
  1158. //Make sure the header does not already have a pre-existing
  1159. //boundary since we just generated a new one...
  1160. IOHandler.WriteLn('Content-Type: ' + RemoveHeaderEntry(LLine, 'boundary', QuoteMIME) + ';'); {do not localize}
  1161. IOHandler.WriteLn(TAB + 'boundary="' + LBoundary + '"'); {do not localize}
  1162. IOHandler.WriteLn;
  1163. end
  1164. else begin
  1165. //Not a multipart header, see if it is a part change...
  1166. if not AMsg.IsMsgSinglePartMime then begin
  1167. while AMsg.MessageParts.Items[i].ParentPart <> AMsg.MIMEBoundary.ParentPart do begin
  1168. IOHandler.WriteLn('--' + LBoundary + '--'); {do not localize}
  1169. IOHandler.WriteLn;
  1170. AMsg.MIMEBoundary.Pop; //This also pops AMsg.MIMEBoundary.ParentPart
  1171. if AMsg.MIMEBoundary.Count = 0 then begin
  1172. raise EIdException.Create(RSMsgClientUnexpectedEndOfMIMEBoundaries); // TODO: create a new Exception class for this
  1173. end;
  1174. LBoundary := AMsg.MIMEBoundary.Boundary;
  1175. end;
  1176. IOHandler.WriteLn('--' + LBoundary); {do not localize}
  1177. end;
  1178. if AMsg.MessageParts.Items[i] is TIdText then begin
  1179. DoStatus(hsStatusText, [RSMsgClientEncodingText]);
  1180. WriteTextPart(AMsg.MessageParts.Items[i] as TIdText);
  1181. IOHandler.WriteLn;
  1182. end
  1183. else if AMsg.MessageParts.Items[i] is TIdAttachment then begin
  1184. LAttachment := TIdAttachment(AMsg.MessageParts[i]);
  1185. DoStatus(hsStatusText, [RSMsgClientEncodingAttachment]);
  1186. if LAttachment.ContentTransfer = '' then begin
  1187. LContentTransferEncoding := 'base64'; {do not localize}
  1188. LAttachment.ContentTransfer := LContentTransferEncoding;
  1189. end else begin;
  1190. LContentTransferEncoding := ExtractHeaderItem(LAttachment.ContentTransfer);
  1191. end;
  1192. if LAttachment.ContentDisposition = '' then begin
  1193. LAttachment.ContentDisposition := 'attachment'; {do not localize}
  1194. end;
  1195. if LAttachment.ContentType = '' then begin
  1196. if TextIsSame(LContentTransferEncoding, 'base64') then begin {do not localize}
  1197. LAttachment.ContentType := 'application/octet-stream'; {do not localize}
  1198. end else begin
  1199. {CC4: Set default type if not base64 encoded...}
  1200. LAttachment.ContentType := 'text/plain'; {do not localize}
  1201. end;
  1202. end;
  1203. LFileName := EncodeHeader(ExtractFileName(LAttachment.FileName), '', HeaderEncoding, ISOCharSet); {do not localize}
  1204. if TextIsSame(LContentTransferEncoding, 'binhex40') then begin {do not localize}
  1205. //This is special - you do NOT write out any Content-Transfer-Encoding
  1206. //header! We also have to write a Content-Type specified in RFC 1741
  1207. //(overriding any ContentType present, if necessary).
  1208. LAttachment.ContentType := 'application/mac-binhex40'; {do not localize}
  1209. IOHandler.Write('Content-Type: ' + LAttachment.ContentType); {do not localize}
  1210. if LAttachment.CharSet <> '' then begin
  1211. IOHandler.Write('; charset="' + LAttachment.CharSet + '"'); {do not localize}
  1212. end;
  1213. if LFileName <> '' then begin
  1214. IOHandler.WriteLn(';'); {do not localize}
  1215. IOHandler.Write(TAB + 'name="' + LFileName + '"'); {do not localize}
  1216. end;
  1217. IOHandler.WriteLn;
  1218. end
  1219. else begin
  1220. IOHandler.Write('Content-Type: ' + LAttachment.ContentType); {do not localize}
  1221. if LAttachment.CharSet <> '' then begin
  1222. IOHandler.Write('; charset="' + LAttachment.CharSet + '"'); {do not localize}
  1223. end;
  1224. if LFileName <> '' then begin
  1225. IOHandler.WriteLn(';');
  1226. IOHandler.Write(TAB + 'name="' + LFileName + '"'); {do not localize}
  1227. end;
  1228. IOHandler.WriteLn;
  1229. IOHandler.WriteLn('Content-Transfer-Encoding: ' + LAttachment.ContentTransfer); {do not localize}
  1230. IOHandler.Write('Content-Disposition: ' + LAttachment.ContentDisposition); {do not localize}
  1231. if LFileName <> '' then begin
  1232. IOHandler.WriteLn(';');
  1233. IOHandler.Write(TAB + 'filename="' + LFileName + '"'); {do not localize}
  1234. end;
  1235. IOHandler.WriteLn;
  1236. end;
  1237. if LAttachment.ContentID <> '' then begin
  1238. IOHandler.WriteLn('Content-ID: '+ LAttachment.ContentID); {Do not Localize}
  1239. end;
  1240. if LAttachment.ContentDescription <> '' then begin
  1241. IOHandler.WriteLn('Content-Description: ' + LAttachment.ContentDescription); {Do not localize}
  1242. end;
  1243. IOHandler.Write(LAttachment.ExtraHeaders);
  1244. IOHandler.WriteLn;
  1245. case PosInStrArray(LContentTransferEncoding, ['base64', 'quoted-printable', 'binhex40'], False) of {do not localize}
  1246. 0: EncodeAttachment(LAttachment, TIdMessageEncoderMIME);
  1247. 1: EncodeAttachment(LAttachment, TIdMessageEncoderQuotedPrintable);
  1248. 2: EncodeAttachment(LAttachment, TIdMessageEncoderBinHex4);
  1249. else
  1250. begin
  1251. LEncoding := CharsetToEncoding(LAttachment.Charset);
  1252. LAttachStream := LAttachment.OpenLoadStream;
  1253. try
  1254. while ReadLnFromStream(LAttachStream, LLine, -1, LEncoding) do begin
  1255. IOHandler.WriteLnRFC(LLine, LEncoding);
  1256. end;
  1257. finally
  1258. LAttachment.CloseLoadStream;
  1259. end;
  1260. end;
  1261. end;
  1262. IOHandler.WriteLn;
  1263. end;
  1264. end;
  1265. end;
  1266. if AMsg.MessageParts.Count > 0 then begin
  1267. for i := 0 to AMsg.MIMEBoundary.Count - 1 do begin
  1268. if not AMsg.IsMsgSinglePartMime then begin
  1269. IOHandler.WriteLn('--' + AMsg.MIMEBoundary.Boundary + '--');
  1270. IOHandler.WriteLn;
  1271. end;
  1272. AMsg.MIMEBoundary.Pop;
  1273. end;
  1274. end;
  1275. end;
  1276. finally
  1277. EndWork(wmWrite);
  1278. end;
  1279. end;
  1280. procedure TIdMessageClient.SendMsg(AMsg: TIdMessage; AHeadersOnly: Boolean = False);
  1281. begin
  1282. BeginWork(wmWrite);
  1283. try
  1284. if AMsg.NoEncode then begin
  1285. IOHandler.Write(AMsg.Headers);
  1286. IOHandler.WriteLn;
  1287. if not AHeadersOnly then begin
  1288. IOHandler.WriteRFCStrings(AMsg.Body, False);
  1289. end;
  1290. end else begin
  1291. SendHeader(AMsg);
  1292. if (not AHeadersOnly) then begin
  1293. SendBody(AMsg);
  1294. end;
  1295. end;
  1296. finally
  1297. EndWork(wmWrite);
  1298. end;
  1299. end;
  1300. function TIdMessageClient.ReceiveHeader(AMsg: TIdMessage; const AAltTerm: string = ''): string;
  1301. var
  1302. LMsgEnd: Boolean;
  1303. begin
  1304. BeginWork(wmRead);
  1305. try
  1306. repeat
  1307. Result := IOHandler.ReadLnRFC(LMsgEnd);
  1308. // Exchange Bug: Exchange sometimes returns . when getting a message instead of
  1309. // '' then a . - That is there is no seperation between the header and the message for an
  1310. // empty message.
  1311. if ((Length(AAltTerm) = 0) and LMsgEnd) or {do not localize}
  1312. ({APR: why? (Length(AAltTerm) > 0) and }(Result = AAltTerm)) then begin
  1313. Break;
  1314. end else if Result <> '' then begin
  1315. AMsg.Headers.Append(Result);
  1316. end;
  1317. until False;
  1318. AMsg.ProcessHeaders;
  1319. finally
  1320. EndWork(wmRead);
  1321. end;
  1322. end;
  1323. procedure TIdMessageClient.ProcessMessage(AMsg: TIdMessage; AHeaderOnly: Boolean = False);
  1324. begin
  1325. if IOHandler <> nil then begin
  1326. //Don't call ReceiveBody if the message ended at the end of the headers
  1327. //(ReceiveHeader() would have returned '.' in that case)...
  1328. BeginWork(wmRead);
  1329. try
  1330. if ReceiveHeader(AMsg) = '' then begin
  1331. if not AHeaderOnly then begin
  1332. ReceiveBody(AMsg);
  1333. end;
  1334. end;
  1335. finally
  1336. EndWork(wmRead);
  1337. end;
  1338. end;
  1339. end;
  1340. procedure TIdMessageClient.ProcessMessage(AMsg: TIdMessage; AStream: TStream; AHeaderOnly: Boolean = False);
  1341. var
  1342. LIOHandler: TIdIOHandlerStreamMsg;
  1343. begin
  1344. LIOHandler := TIdIOHandlerStreamMsg.Create(nil, AStream);
  1345. try
  1346. LIOHandler.FreeStreams := False;
  1347. LIOHandler.MaxLineLength := MaxInt;
  1348. IOHandler := LIOHandler;
  1349. try
  1350. IOHandler.Open;
  1351. ProcessMessage(AMsg, AHeaderOnly);
  1352. finally
  1353. IOHandler := nil;
  1354. end;
  1355. finally
  1356. LIOHandler.Free;
  1357. end;
  1358. end;
  1359. procedure TIdMessageClient.ProcessMessage(AMsg: TIdMessage; const AFilename: string; AHeaderOnly: Boolean = False);
  1360. var
  1361. LStream: TStream;
  1362. begin
  1363. LStream := TIdReadFileExclusiveStream.Create(AFileName);
  1364. try
  1365. ProcessMessage(AMsg, LStream, AHeaderOnly);
  1366. finally
  1367. LStream.Free;
  1368. end;
  1369. end;
  1370. procedure TIdMessageClient.EncodeAndWriteText(const ABody: TStrings; AEncoding: IIdTextEncoding);
  1371. begin
  1372. Assert(ABody<>nil);
  1373. Assert(IOHandler<>nil);
  1374. // TODO: encode the text...
  1375. IOHandler.WriteRFCStrings(ABody, False, AEncoding);
  1376. end;
  1377. end.