OverbyteIcsMimeUtils.pas 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022
  1. {*_* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2. Author: François PIETTE
  3. Object: Mime support routines (RFC2045).
  4. Creation: May 03, 2003 (Extracted from SmtpProt unit)
  5. Version: 6.00
  6. EMail: francois.piette@overbyte.be http://www.overbyte.be
  7. Support: Use the mailing list twsocket@elists.org
  8. Follow "support" link at http://www.overbyte.be for subscription.
  9. Legal issues: Copyright (C) 1997-2006 by François PIETTE
  10. Rue de Grady 24, 4053 Embourg, Belgium. Fax: +32-4-365.74.56
  11. <francois.piette@overbyte.be>
  12. This software is provided 'as-is', without any express or
  13. implied warranty. In no event will the author be held liable
  14. for any damages arising from the use of this software.
  15. Permission is granted to anyone to use this software for any
  16. purpose, including commercial applications, and to alter it
  17. and redistribute it freely, subject to the following
  18. restrictions:
  19. 1. The origin of this software must not be misrepresented,
  20. you must not claim that you wrote the original software.
  21. If you use this software in a product, an acknowledgment
  22. in the product documentation would be appreciated but is
  23. not required.
  24. 2. Altered source versions must be plainly marked as such, and
  25. must not be misrepresented as being the original software.
  26. 3. This notice may not be removed or altered from any source
  27. distribution.
  28. 4. You must register this software by sending a picture postcard
  29. to the author. Use a nice stamp and mention your name, street
  30. address, EMail address and any comment you like to say.
  31. History:
  32. May 03, 2003 V1.00 Initial release
  33. Jun 19, 2003 V1.01 Fixed SplitQuotedPrintableString. Thanks to Arno Garrels
  34. <arno.garrels@gmx.de>
  35. Jan 12, 2004 V1.02 Marc HUBAUT <mhu@wanadoo.fr> fixed DoFileEncBase64 in case
  36. of file size is a multple of 3.
  37. May 31, 2004 V1.03 Used ICSDEFS.INC, added const with version and copyright
  38. May 28, 2005 V1.04 Piotr Hellrayzer Dalek <enigmatical@interia.pl>
  39. added a fast quoted-printable encoder. Arno Garrels
  40. <arno.garrels@gmx.de> added some routines and fixed a bug in
  41. func. SplitQuotedPrintableString.
  42. Jan 28, 2006 V1.05 Gerhard Rattinger fixed TSysCharSet for Delphi 3
  43. Mar 26, 2006 V6.00 New version 6.00 started
  44. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  45. unit OverbyteIcsMimeUtils;
  46. {$B-} { Enable partial boolean evaluation }
  47. {$T-} { Untyped pointers }
  48. {$X+} { Enable extended syntax }
  49. {$I OverbyteIcsDefs.inc}
  50. {$IFDEF DELPHI6_UP}
  51. {$WARN SYMBOL_PLATFORM OFF}
  52. {$WARN SYMBOL_LIBRARY OFF}
  53. {$WARN SYMBOL_DEPRECATED OFF}
  54. {$ENDIF}
  55. {$IFNDEF VER80} { Not for Delphi 1 }
  56. {$H+} { Use long strings }
  57. {$J+} { Allow typed constant to be modified }
  58. {$ENDIF}
  59. {$IFDEF BCB3_UP}
  60. {$ObjExportAll On}
  61. {$ENDIF}
  62. interface
  63. {$R-}
  64. uses
  65. SysUtils, Classes;
  66. {$IFNDEF DELPHI4_UP}
  67. type
  68. TSysCharSet = set of Char;
  69. {$ENDIF}
  70. const
  71. TMimeUtilsVersion = 105;
  72. CopyRight : String = ' MimeUtils (c) 1997-2006 F. Piette V1.05 ';
  73. SpecialsRFC822 : TSysCharSet = ['(', ')', '<', '>', '@', ',', ';', ':',
  74. '\', '"', '[', ']', '.'];
  75. HexTable : array[0..15] of Char = ('0','1','2','3','4','5','6','7','8','9',
  76. 'A','B','C','D','E','F'); {HLX}
  77. { Functions to encode/decode string as a "quoted-printable" string RFC2045}
  78. function EncodeQuotedPrintable(const S: String) : String;
  79. function DecodeQuotedPrintable(const S: String) : String;
  80. function SplitQuotedPrintableString(const S : String) : String;
  81. { Find a Content-Type from a file name }
  82. function FilenameToContentType(FileName : String) : String;
  83. { Base 64 encoding }
  84. function Base64Encode(Input : String) : String;
  85. { Similar to Base64Encode, returns just a coded line }
  86. function Base64EncodeEx(Input : String;
  87. MaxCol : Integer;
  88. var cPos : Integer) : String;
  89. function Base64Decode(Input : String) : String;
  90. function InitFileEncBase64(const FileName : String;
  91. ShareMode : Word) : TStream;
  92. function DoFileEncBase64(var Stream : TStream;
  93. var More : Boolean) : String;
  94. procedure EndFileEncBase64(var Stream : TStream);
  95. { Dot at start of line escaping for SMTP and NNTP (double the dot) }
  96. procedure DotEscape(var S : String);
  97. { Text wrap and folding } {AG}
  98. {function IcsWrapText(const Line,
  99. BreakStr : String;
  100. BreakChars : TSysCharSet;
  101. MaxCol : Integer;
  102. QuoteChars : TSysCharSet): String;}
  103. { Similar to IcsWrapText, returns just a single line } {AG}
  104. function IcsWrapTextEx(const Line, BreakStr : String;
  105. BreakChars : TSysCharSet;
  106. MaxCol : Integer;
  107. QuoteChars : TSysCharSet;
  108. var cPos : Integer): String;
  109. { Unfolds folded headers } {AG}
  110. function UnFoldHdrLine(const S : String): String;
  111. {Helper function }
  112. function NeedsEncoding(const S : String) : Boolean; {AG}
  113. function NeedsEncodingPChar(S : PChar) : Boolean; {FP}
  114. { MIME In-Line-Encoding plus Folding, see comments in function source } {AG}
  115. function HdrEncodeInLine(const Input : String;
  116. Specials : TSysCharSet; { Try const SpecialsRFC822 }
  117. EncType : Char; { Either 'Q' or 'B' }
  118. const CharSet : String; { e.g. 'iso-8859-1' }
  119. MaxCol : Integer;
  120. DoFold : Boolean): String;
  121. { Alternate to functions
  122. { EncodeQuotedPrintable + SplitQuotedPrintableString + DotEscape }
  123. function StrEncodeQP(const Input : String; {HLX, AG}
  124. MaxCol : Integer;
  125. Specials : TSysCharSet): String;
  126. { Similar to StrEncodeQP, returns just a single line } {AG}
  127. function StrEncodeQPEx(const Buf : String;
  128. MaxCol : Integer;
  129. Specials : TSysCharSet;
  130. ShortSpace : Boolean; {'_' e.g. for in-line}
  131. var cPos : Integer;
  132. DoFold : Boolean) : String;
  133. procedure FoldHdrLine(HdrLines : TStrings; {AG}
  134. const HdrLine : String);
  135. function FoldString(const Input : String; {AG}
  136. BreakChars : TSysCharSet;
  137. MaxCol : Integer): String;
  138. implementation
  139. {$IFDEF DELPHI1}
  140. { LeadBytes is a char set that indicates which char values are lead bytes
  141. in multibyte character sets (Japanese, Chinese, etc).
  142. This set is always empty for western locales. }
  143. const
  144. LeadBytes: set of Char = [];
  145. {$ENDIF}
  146. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  147. {$IFDEF DELPHI1}
  148. { Delphi 1 miss the SetLength procedure. So we rewrite it. }
  149. procedure SetLength(var S: string; NewLength: Integer);
  150. begin
  151. S[0] := chr(NewLength);
  152. end;
  153. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  154. function TrimRight(Str : String) : String;
  155. var
  156. I : Integer;
  157. begin
  158. I := Length(Str);
  159. while (I > 0) and (Str[I] in [' ', #9]) do
  160. I := I - 1;
  161. Result := Copy(Str, 1, I);
  162. end;
  163. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  164. function TrimLeft(Str : String) : String;
  165. var
  166. I : Integer;
  167. begin
  168. if Str[1] <> ' ' then
  169. Result := Str
  170. else begin
  171. I := 1;
  172. while (I <= Length(Str)) and (Str[I] = ' ') do
  173. I := I + 1;
  174. Result := Copy(Str, I, Length(Str) - I + 1);
  175. end;
  176. end;
  177. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  178. function Trim(Str : String) : String;
  179. begin
  180. Result := TrimLeft(TrimRight(Str));
  181. end;
  182. {$ENDIF}
  183. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  184. { See also SplitQuotedPrintableString ! }
  185. function EncodeQuotedPrintable(const S: String) : String;
  186. var
  187. I, J : Integer;
  188. begin
  189. Result := '';
  190. I := 1;
  191. while I <= Length(S) do begin
  192. J := I;
  193. while (I <= Length(S)) and
  194. (S[I] <> '=') and
  195. (S[I] >= ' ') and
  196. (Ord(S[I]) <= 126) do
  197. Inc(I);
  198. if I > Length(S) then begin
  199. if J = 1 then
  200. Result := S { Optimisation }
  201. else
  202. Result := Result + Copy(S, J, I - J);
  203. Exit;
  204. end;
  205. Result := Result + Copy(S, J, I - J) + '=' +
  206. UpperCase(IntToHex(Ord(S[I]), 2));
  207. Inc(I);
  208. end;
  209. end;
  210. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  211. { A line ending with an equal sign is continued on the next line. This is }
  212. { what RFC2045 refers as a "soft line break". }
  213. { This routine doesn't take care of the equal sign at the end of string. }
  214. { It is simply ignored. The caller must check that condition and merge }
  215. { successives lines. But the routine handle embedded soft line break. }
  216. function DecodeQuotedPrintable(const S: String) : String;
  217. var
  218. I, J : Integer;
  219. begin
  220. Result := '';
  221. I := 1;
  222. while I <= Length(S) do begin
  223. J := I;
  224. while (I <= Length(S)) and (S[I] <> '=') do
  225. Inc(I);
  226. Result := Result + Copy(S, J, I - J);
  227. if I >= Length(S) then
  228. break;
  229. if S[I + 1] = #13 then { Could also check for #10 }
  230. { Soft line break, nothing to do except continuing }
  231. else
  232. Result := Result + Char(StrToInt('$' + Copy(S, I + 1, 2)));
  233. Inc(I, 3);
  234. end;
  235. end;
  236. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  237. function SplitQuotedPrintableString(const S : String) : String;
  238. var
  239. I, J : Integer;
  240. begin
  241. if Length(S) <= 76 then begin
  242. { No need to split }
  243. Result := S;
  244. Exit;
  245. end;
  246. Result := '';
  247. J := 1;
  248. I := 76;
  249. while TRUE do begin
  250. if S[I - 1] = '=' then
  251. Dec(I)
  252. else if S[I - 2] = '=' then
  253. Dec(I, 2);
  254. Result := Result + Copy(S, J, I - J) + '=' + #13#10;
  255. J := I;
  256. Inc(I, 75);
  257. if I > Length(S) then begin
  258. Result := Result + Copy(S, J, I - J);
  259. break;
  260. end;
  261. end;
  262. end;
  263. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  264. procedure DotEscape(var S : String);
  265. var
  266. I : Integer;
  267. begin
  268. if S = '' then
  269. Exit;
  270. if S[1] = '.' then begin
  271. Insert('.', S, 1);
  272. I := 3;
  273. end
  274. else
  275. I := 1;
  276. while I < (Length(S) - 2) do begin
  277. if (S[I] = #13) and (S[I + 1] = #10) and (S[I + 2] = '.') then begin
  278. Insert('.', S, I + 2);
  279. Inc(I, 4);
  280. continue;
  281. end;
  282. Inc(I);
  283. end;
  284. end;
  285. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  286. function FilenameToContentType(FileName : String) : String;
  287. var
  288. Ext : String;
  289. begin
  290. { We probably should the registry to find MIME type for known file types }
  291. Ext := LowerCase(ExtractFileExt(FileName));
  292. if Length(Ext) > 1 then
  293. Ext := Copy(Ext, 2, Length(Ext));
  294. if (Ext = 'htm') or (Ext = 'html') then
  295. Result := 'text/html'
  296. else if Ext = 'gif' then
  297. Result := 'image/gif'
  298. else if Ext = 'bmp' then
  299. Result := 'image/bmp'
  300. else if (Ext = 'jpg') or (Ext = 'jpeg') then
  301. Result := 'image/jpeg'
  302. else if Ext = 'txt' then
  303. Result := 'text/plain'
  304. else
  305. Result := 'application/octet-stream';
  306. end;
  307. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  308. function InitFileEncBase64(
  309. const FileName : String;
  310. ShareMode : Word) : TStream;
  311. begin
  312. Result := TFileStream.Create(FileName, fmOpenRead or ShareMode);
  313. {Result := TBufferedFileStream.Create(FileName, fmOpenRead or ShareMode, 4096);}
  314. end;
  315. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  316. const
  317. Base64Out: array [0..64] of Char = (
  318. 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
  319. 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
  320. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
  321. 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
  322. '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '='
  323. );
  324. Base64In: array[0..127] of Byte = (
  325. 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  326. 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  327. 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  328. 255, 255, 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55,
  329. 56, 57, 58, 59, 60, 61, 255, 255, 255, 64, 255, 255, 255,
  330. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
  331. 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
  332. 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32,
  333. 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
  334. 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255
  335. );
  336. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  337. {$IFDEF OLD_VERSION}
  338. function DoFileEncBase64(
  339. var Stream : TStream;
  340. var More : Boolean) : String;
  341. const
  342. HLX_MULTIPLIER = 3; { for three lines at once }
  343. MAX_LENGTH = 76; {HLX: Longer lines = less CRLF's, RFC does allow lines *that* long}
  344. MAX_LENGTH_MULTIPLIED = (MAX_LENGTH + 2) * HLX_MULTIPLIER;
  345. MAX_READ = ((MAX_LENGTH * 3)div 4) * HLX_MULTIPLIER;
  346. MAX_READ_MOD = (MAX_LENGTH * 3) div 4;
  347. var
  348. Count, Place : Integer;
  349. DataIn : array [0..MAX_READ] of Byte;
  350. DataOut : array [0..MAX_LENGTH_MULTIPLIED + 8] of Byte;
  351. ByteCount : Integer;
  352. I : Integer;
  353. { HLX: The following code is rewritten, so it loads data in MAX_READ chunks and
  354. encodes all loaded data. The trick relies on the fact that TriggerGetData's
  355. MsgLine buffer can hold up to 1024 chars. We'll encode 3 lines at once,
  356. add CRLF's, and return all three as one: component will see it as one,
  357. server will still see it as three.
  358. I've noticed a strange behavior: having HLX_MULTIPLIER > 3, data aren't
  359. sent completely, although it shouldn't occur
  360. (see: TCustomSmtpClient.DataNext) }
  361. begin
  362. Count := 0;
  363. Place := 0;
  364. ByteCount := Stream.Read(DataIn, MAX_READ);
  365. while Place < ByteCount do begin
  366. DataOut[Count] := (DataIn[Place] and $FC) shr 2;
  367. Inc(Count);
  368. DataOut[Count] := (DataIn[Place] and $03) shl 4;
  369. Inc(Place);
  370. if Place < ByteCount then begin
  371. DataOut[Count] := DataOut[Count] + (DataIn[Place] and $F0) shr 4;
  372. Inc(Count);
  373. DataOut[Count] := (DataIn[Place] and $0F) shl 2;
  374. Inc(Place);
  375. if Place < ByteCount then begin
  376. DataOut[Count] := DataOut[Count] + (DataIn[Place] and $C0) shr 6;
  377. Inc(Count);
  378. DataOut[Count] := (DataIn[Place] and $3F);
  379. Inc(Place);
  380. Inc(Count);
  381. end
  382. else begin
  383. Inc(Count);
  384. DataOut[Count] := $40;
  385. Inc(Count);
  386. end;
  387. end
  388. else begin
  389. Inc(Count);
  390. DataOut[Count] := $40;
  391. Inc(Count);
  392. DataOut[Count] := $40;
  393. Inc(Count);
  394. end;
  395. end;
  396. { Moved out of the main loop, so it has the chance to work in the }
  397. { processor's L1 Cache }
  398. SetLength(Result, Count);
  399. for I := 0 to Count - 1 do
  400. DataOut[I] := Byte(Base64Out[DataOut[I]]);
  401. Move(DataOut[0], Result[1], Count);
  402. { Splitting lines }
  403. I := MAX_LENGTH + 1;
  404. while I < Count do begin;
  405. Insert(#13#10, Result, I);
  406. Inc(I, MAX_LENGTH + 2);
  407. Inc(Count);
  408. end;
  409. More := (ByteCount = MAX_READ);
  410. end;
  411. {$ENDIF}
  412. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  413. function DoFileEncBase64(
  414. var Stream : TStream;
  415. var More : Boolean) : String;
  416. const
  417. MAX_LENGTH = 72;
  418. var
  419. Count : Integer;
  420. DataIn : array [0..2] of Byte;
  421. DataOut : array [0..MAX_LENGTH + 8] of Byte;
  422. ByteCount : Integer;
  423. I : Integer;
  424. begin
  425. Count := 0;
  426. ByteCount := 0;
  427. while Count < MAX_LENGTH do begin
  428. ByteCount := Stream.Read(DataIn, 3);
  429. if ByteCount = 0 then {<=MHU}
  430. Break; {<=MHU}
  431. DataOut[Count] := (DataIn[0] and $FC) shr 2;
  432. DataOut[Count + 1] := (DataIn[0] and $03) shl 4;
  433. if ByteCount > 1 then begin
  434. DataOut[Count + 1] := DataOut[Count + 1] +
  435. (DataIn[1] and $F0) shr 4;
  436. DataOut[Count + 2] := (DataIn[1] and $0F) shl 2;
  437. if ByteCount > 2 then begin
  438. DataOut[Count + 2] := DataOut[Count + 2] +
  439. (DataIn[2] and $C0) shr 6;
  440. DataOut[Count + 3] := (DataIn[2] and $3F);
  441. end
  442. else begin
  443. DataOut[Count + 3] := $40;
  444. end;
  445. end
  446. else begin
  447. DataOut[Count + 2] := $40;
  448. DataOut[Count + 3] := $40;
  449. end;
  450. for I := 0 to 3 do
  451. DataOut[Count + I] := Byte(Base64Out[DataOut[Count + I]]);
  452. Count := Count + 4;
  453. if (Count > MAX_LENGTH) or (ByteCount < 3) then
  454. break;
  455. end;
  456. DataOut[Count] := $0;
  457. Result := StrPas(@DataOut[0]);
  458. More := (ByteCount = 3);
  459. end;
  460. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  461. procedure EndFileEncBase64(var Stream : TStream);
  462. begin
  463. if Assigned(Stream) then begin
  464. Stream.Destroy;
  465. Stream := nil;
  466. end;
  467. end;
  468. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  469. function Base64Encode(Input : String) : String;
  470. var
  471. Final : String;
  472. Count : Integer;
  473. Len : Integer;
  474. begin
  475. Final := '';
  476. Count := 1;
  477. Len := Length(Input);
  478. while Count <= Len do begin
  479. Final := Final + Base64Out[(Byte(Input[Count]) and $FC) shr 2];
  480. if (Count + 1) <= Len then begin
  481. Final := Final + Base64Out[((Byte(Input[Count]) and $03) shl 4) +
  482. ((Byte(Input[Count+1]) and $F0) shr 4)];
  483. if (Count+2) <= Len then begin
  484. Final := Final + Base64Out[((Byte(Input[Count+1]) and $0F) shl 2) +
  485. ((Byte(Input[Count+2]) and $C0) shr 6)];
  486. Final := Final + Base64Out[(Byte(Input[Count+2]) and $3F)];
  487. end
  488. else begin
  489. Final := Final + Base64Out[(Byte(Input[Count+1]) and $0F) shl 2];
  490. Final := Final + '=';
  491. end
  492. end
  493. else begin
  494. Final := Final + Base64Out[(Byte(Input[Count]) and $03) shl 4];
  495. Final := Final + '==';
  496. end;
  497. Count := Count + 3;
  498. end;
  499. Result := Final;
  500. end;
  501. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  502. { Similar to Base64Encode, returns just a coded line }
  503. function Base64EncodeEx(Input : String;
  504. MaxCol : Integer;
  505. var cPos : Integer) : String;
  506. var
  507. Len : Integer;
  508. begin
  509. Len := Length(Input);
  510. while cPos <= Len do begin
  511. if Length(Result) >= MaxCol then
  512. Exit;
  513. Result := Result + Base64Out[(Byte(Input[cPos]) and $FC) shr 2];
  514. if (cPos + 1) <= Len then begin
  515. Result := Result + Base64Out[((Byte(Input[cPos]) and $03) shl 4) +
  516. ((Byte(Input[cPos + 1]) and $F0) shr 4)];
  517. if (cPos + 2) <= Len then begin
  518. Result := Result + Base64Out[((Byte(Input[cPos + 1]) and $0F) shl 2) +
  519. ((Byte(Input[cPos + 2]) and $C0) shr 6)];
  520. Result := Result + Base64Out[(Byte(Input[cPos + 2]) and $3F)];
  521. end
  522. else begin
  523. Result := Result + Base64Out[(Byte(Input[cPos + 1]) and $0F) shl 2];
  524. Result := Result + '=';
  525. end
  526. end
  527. else begin
  528. Result := Result + Base64Out[(Byte(Input[cPos]) and $03) shl 4];
  529. Result := Result + '==';
  530. end;
  531. Inc(cPos, 3);
  532. end;
  533. end;
  534. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  535. function Base64Decode(Input : String) : String;
  536. var
  537. Final : String;
  538. Count : Integer;
  539. Len : Integer;
  540. DataIn0 : Byte;
  541. DataIn1 : Byte;
  542. DataIn2 : Byte;
  543. DataIn3 : Byte;
  544. begin
  545. Final := '';
  546. Count := 1;
  547. Len := Length(Input);
  548. while Count <= Len do begin
  549. DataIn0 := Base64In[Byte(Input[Count])];
  550. DataIn1 := Base64In[Byte(Input[Count+1])];
  551. DataIn2 := Base64In[Byte(Input[Count+2])];
  552. DataIn3 := Base64In[Byte(Input[Count+3])];
  553. Final := Final + Char(((DataIn0 and $3F) shl 2) +
  554. ((DataIn1 and $30) shr 4));
  555. if DataIn2 <> $40 then begin
  556. Final := Final + Char(((DataIn1 and $0F) shl 4) +
  557. ((DataIn2 and $3C) shr 2));
  558. if DataIn3 <> $40 then
  559. Final := Final + Char(((DataIn2 and $03) shl 6) +
  560. (DataIn3 and $3F));
  561. end;
  562. Count := Count + 4;
  563. end;
  564. Result := Final;
  565. end;
  566. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  567. { This function takes the QuoteChars as a parameter and it returns just a }
  568. { line. }
  569. function IcsWrapTextEx(const Line, BreakStr : String;
  570. BreakChars : TSysCharSet;
  571. MaxCol : Integer;
  572. QuoteChars : TSysCharSet;
  573. var cPos : Integer): String;
  574. var
  575. Col : Integer;
  576. LinePos, LineLen : Integer;
  577. BreakLen, BreakPos : Integer;
  578. QuoteChar, CurChar : Char;
  579. ExistingBreak : Boolean;
  580. begin
  581. Col := 1;
  582. LinePos := cPos;
  583. BreakPos := 0;
  584. QuoteChar := ' ';
  585. ExistingBreak := False;
  586. LineLen := Length(Line);
  587. BreakLen := Length(BreakStr);
  588. Result := '';
  589. while cPos <= LineLen do begin
  590. CurChar := Line[cPos];
  591. if CurChar in LeadBytes then begin
  592. Inc(cPos);
  593. Inc(Col);
  594. end
  595. else if CurChar = BreakStr[1] then begin
  596. if QuoteChar = ' ' then begin
  597. ExistingBreak := CompareText(BreakStr, Copy(Line, cPos, BreakLen)) = 0;
  598. if ExistingBreak then begin
  599. Inc(cPos, BreakLen-1);
  600. BreakPos := cPos;
  601. end;
  602. end
  603. end
  604. else if CurChar in BreakChars then begin
  605. if QuoteChar = ' ' then
  606. BreakPos := cPos
  607. end
  608. else if CurChar in QuoteChars then
  609. if CurChar = QuoteChar then
  610. QuoteChar := ' '
  611. else if QuoteChar = ' ' then
  612. QuoteChar := CurChar;
  613. Inc(cPos);
  614. Inc(Col);
  615. if not (QuoteChar in QuoteChars) and
  616. (ExistingBreak or ((Col > MaxCol) and (BreakPos > LinePos))) then begin
  617. { Col := cPos - BreakPos; }
  618. Result := Result + Copy(Line, LinePos, BreakPos - LinePos + 1);
  619. if not (CurChar in QuoteChars) then
  620. while (cPos <= LineLen) and (Line[cPos] in BreakChars + [#13, #10]) do
  621. Inc(cPos);
  622. if ExistingBreak then
  623. Result := Copy(Result, 1, Length(Result) - BreakLen);
  624. Inc(BreakPos);
  625. cPos := BreakPos;
  626. Exit;
  627. end;
  628. end;
  629. Result := Result + Copy(Line, LinePos, MaxInt);
  630. cPos := MaxInt;
  631. end;
  632. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  633. { Unfold header lines } {AG}
  634. { RFC822 says "Unfolding is accomplished by regarding CRLF immediately }
  635. { followed by a LWSP-char as equivalent to the LWSP-char." }
  636. function UnFoldHdrLine(const S : String): String;
  637. var
  638. I, J : Integer;
  639. begin
  640. SetLength(Result, Length(S));
  641. J := 1;
  642. I := 1;
  643. while I <= Length(S) do begin
  644. if S[I] = #13 then begin
  645. if (I + 2 <= Length(S)) and
  646. (S[I + 1] = #10) and
  647. (S[I + 2] in [#09, #32]) then begin
  648. Result[J] := #32;
  649. Inc(J);
  650. Inc(I, 2);
  651. end;
  652. end
  653. else begin
  654. Result[J] := S[I];
  655. Inc(J);
  656. end;
  657. Inc(I);
  658. end;
  659. SetLength(Result, J - 1);
  660. end;
  661. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} {AG}
  662. function NeedsEncoding(const S : String) : Boolean;
  663. var
  664. I : Integer;
  665. begin
  666. for I := 1 to Length(S) do
  667. if S[I] in [#0..#8, #11, #12, #14..#31, #127..#255] then begin
  668. Result := True;
  669. Exit;
  670. end;
  671. Result := False;
  672. end;
  673. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  674. function NeedsEncodingPChar(S : PChar) : Boolean;
  675. begin
  676. while S^ <> #0 do begin
  677. if S^ in [#0..#8, #11, #12, #14..#31, #127..#255] then begin
  678. Result := True;
  679. Exit;
  680. end;
  681. Inc(S);
  682. end;
  683. Result := False;
  684. end;
  685. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  686. function HdrEncodeInLine(const Input : String;
  687. Specials : TSysCharSet;
  688. EncType : Char; { Either 'Q' or 'B' }
  689. const CharSet : String; { e.g. 'iso-8859-1' }
  690. MaxCol : Integer;
  691. DoFold : Boolean): String;
  692. const
  693. Suffix = '?=';
  694. var
  695. Len,
  696. lPos,
  697. LenRes : Integer;
  698. Prefix,
  699. Res : String;
  700. begin
  701. Result := '';
  702. if DoFold and (MaxCol < 25) then
  703. MaxCol := 25;
  704. if Length(CharSet) < 4 then
  705. raise Exception.Create('Function ''HdrEncodeInLine'', invalid CharSet: ' +
  706. '' + Charset + '');
  707. if not (EncType in ['Q', 'B']) then
  708. raise Exception.Create('Function ''HdrEncodeInLine'', invalid EncType: ' +
  709. '' + EncType + '');
  710. Res := '';
  711. Prefix := '=?' + LowerCase(CharSet) + '?' + EncType + '?';
  712. Len := Length(Input);
  713. lPos := 1;
  714. if EncType = 'Q' then begin
  715. if lPos <= Len then
  716. begin
  717. Res := StrEncodeQPEx(Input,
  718. MaxCol - Length(Prefix) - 2,
  719. Specials + ['?', '=', ' ', '_'],
  720. True,
  721. lPos,
  722. DoFold);
  723. if Length(Res) = 0 then
  724. Exit;
  725. if Res[Length(Res)] = '=' then
  726. SetLength(Res, Length(Res) - 1);
  727. Result := Prefix + Res + Suffix;
  728. end;
  729. while lPos <= Length(Input) do begin
  730. Res := StrEncodeQPEx(Input,
  731. MaxCol - Length(Prefix) - 2,
  732. Specials + ['?', '=', ' ', '_'],
  733. True,
  734. lPos,
  735. DoFold);
  736. if Length(Res) > 0 then begin
  737. if Res[Length(Res)] = '=' then
  738. SetLength(Res, Length(Res) - 1);
  739. Result := Result + #13#10#09 + Prefix + Res + Suffix;
  740. end;
  741. end;
  742. end
  743. else begin
  744. { Base64 }
  745. { taken from function B64Encode and modified slightly }
  746. if not DoFold then
  747. MaxCol := MaxInt;
  748. Res := Res + Prefix;
  749. LenRes := Length(Prefix) + 2;
  750. while lPos <= Len do begin
  751. if (LenRes + 4 > MaxCol) then begin
  752. Res := Res + Suffix + #13#10#09 + Prefix;
  753. LenRes := Length(Prefix) + 2;
  754. end;
  755. Res := Res + Base64Out[(Byte(Input[lPos]) and $FC) shr 2];
  756. if (lPos + 1) <= Len then begin
  757. Res := Res + Base64Out[((Byte(Input[lPos]) and $03) shl 4) +
  758. ((Byte(Input[lPos + 1]) and $F0) shr 4)];
  759. if (lPos + 2) <= Len then begin
  760. Res := Res + Base64Out[((Byte(Input[lPos + 1]) and $0F) shl 2) +
  761. ((Byte(Input[lPos + 2]) and $C0) shr 6)];
  762. Res := Res + Base64Out[(Byte(Input[lPos + 2]) and $3F)];
  763. end
  764. else begin
  765. Res := Res + Base64Out[(Byte(Input[lPos + 1]) and $0F) shl 2];
  766. Res := Res + '=';
  767. end
  768. end
  769. else begin
  770. Res := Res + Base64Out[(Byte(Input[lPos]) and $03) shl 4];
  771. Res := Res + '==';
  772. end;
  773. Inc(LenRes, 4);
  774. Inc(lPos, 3);
  775. end;
  776. Result := Res + Suffix;
  777. end;
  778. end;
  779. { * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  780. { Piotr Hellrayzer Dalek <enigmatical@interia.pl>, AG }
  781. { Use it to code message text that includes extended ASCII chars, passing }
  782. { empty Specials '[]' will work mostly. }
  783. { Param MaxCol should be set to 1 below max. line length }
  784. function StrEncodeQP(const Input : String;
  785. MaxCol : Integer;
  786. Specials : TSysCharSet) : String;
  787. var
  788. cPos, rPos, lPos :Integer;
  789. begin;
  790. SetLength(Result, Length(Input) * 2);
  791. cPos := 1;
  792. lPos := 1;
  793. for rPos := 1 to Length(Input) do begin
  794. if (Ord(Input[rPos]) > 126) or
  795. (Ord(Input[rPos]) < 32) or
  796. (Input[rPos] = '=') or
  797. (Input[rPos] in Specials) then begin
  798. Result[cPos] := '=';
  799. Inc(cPos);
  800. Result[cPos] := HexTable[(Ord(Input[rPos]) shr 4) and 15];
  801. Inc(cPos);
  802. Result[cPos] := HexTable[Ord(Input[rPos]) and 15];
  803. Inc(cPos);
  804. Inc(lPos, 3);
  805. if lPos >= MaxCol then begin
  806. Result[cPos] := '=';
  807. Inc(cPos);
  808. Result[cPos] := #13;
  809. Inc(cPos);
  810. Result[cPos] := #10;
  811. Inc(cPos);
  812. lPos := 1;
  813. end;
  814. end
  815. else begin
  816. Result[cPos] := Input[rPos];
  817. Inc(cPos);
  818. Inc(lPos);
  819. if lPos >= MaxCol then begin
  820. Result[cPos] := '=';
  821. Inc(cPos);
  822. Result[cPos] := #13;
  823. Inc(cPos);
  824. Result[cPos] := #10;
  825. Inc(cPos);
  826. lPos := 1;
  827. end;
  828. end;
  829. { Grow }
  830. if cPos > Length(Result) - 3 then
  831. SetLength(Result, Length(Result) + MaxCol);
  832. end;
  833. Setlength(Result, cPos - 1);
  834. end;
  835. { * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  836. { Similar to StrEncodeQP, returns just a coded line }
  837. function StrEncodeQPEx(const Buf : String;
  838. MaxCol : Integer;
  839. Specials : TSysCharSet;
  840. ShortSpace : Boolean;
  841. var cPos : Integer;
  842. DoFold : Boolean) : String;
  843. var
  844. lPosRes : Integer;
  845. begin
  846. lPosRes := 1;
  847. if not DoFold then
  848. MaxCol := Length(Buf);
  849. SetLength(Result, MaxCol);
  850. while cPos <= Length(Buf) do begin
  851. if (Ord(Buf[cPos]) > 126) or
  852. (Ord(Buf[cPos]) < 32) or
  853. (Buf[cPos] in Specials) or
  854. (Buf[cPos] = '=') then begin
  855. if (Buf[cPos] = ' ') and ShortSpace then begin
  856. Result[lPosRes] := '_';
  857. Inc(lPosRes);
  858. Inc(cPos);
  859. end
  860. else
  861. if lPosRes < MaxCol - 2 then begin
  862. Result[lPosRes] := '=';
  863. Inc(lPosRes);
  864. Result[lPosRes] := HexTable[(Ord(Buf[cPos]) shr 4) and 15];
  865. Inc(lPosRes);
  866. Result[lPosRes] := HexTable[Ord(Buf[cPos]) and 15];
  867. Inc(lPosRes);
  868. Inc(cPos);
  869. end
  870. else begin
  871. Result[lPosRes] := '=';
  872. Inc(lPosRes);
  873. Break;
  874. end;
  875. end else
  876. if lPosRes < MaxCol then begin
  877. Result[lPosRes] := Buf[cPos];
  878. Inc(lPosRes);
  879. Inc(cPos);
  880. end
  881. else begin
  882. Result[lPosRes] := '=';
  883. Inc(lPosRes);
  884. Break;
  885. end;
  886. end;
  887. SetLength(Result, lPosRes - 1);
  888. end;
  889. { * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  890. { RFC822 - 3.1.1. LONG HEADER FIELDS }
  891. { This is just my (AG) interpretation of folding long header lines. }
  892. { Probably further BreakChars are possible here. However before you modify }
  893. { this procedure you should refer to RFC822. Also note that header lines may}
  894. { be encoded 'in-line' as described in RFC2047. The passed HdrLine String }
  895. { *MUST not include CRLF except they are followed by one of the space chars,}
  896. { means that a already folded line should work. If a string doesn't include }
  897. { one of the BreakChars it won't fold to the next line! }
  898. procedure FoldHdrLine(HdrLines : TStrings;
  899. const HdrLine : String);
  900. const
  901. BreakChars = [#09, #32, ';', ',', '>', ']'];
  902. var
  903. rPos : Integer;
  904. begin
  905. rPos := 1;
  906. if rPos <= Length(HdrLine) then
  907. HdrLines.Add(Trim(IcsWrapTextEx(HdrLine, #13#10#09,
  908. BreakChars, 76, [], rPos)));
  909. while rPos <= Length(HdrLine) do
  910. HdrLines.Add(#09 + Trim(IcsWrapTextEx(HdrLine, #13#10#09,
  911. BreakChars, 76, [], rPos)))
  912. end;
  913. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} {AG}
  914. function FoldString(const Input : String;
  915. BreakChars : TSysCharSet;
  916. MaxCol : Integer): String;
  917. var
  918. rPos : Integer;
  919. begin
  920. rPos := 1;
  921. if rPos <= Length(Input) then
  922. Result := Trim(IcsWrapTextEx(Input, #13#10#09,
  923. BreakChars, MaxCol, [], rPos));
  924. while rPos <= Length(Input) do
  925. Result := Result + #13#10#09 + Trim(IcsWrapTextEx(Input, #13#10#09,
  926. BreakChars, MaxCol,
  927. [], rPos))
  928. end;
  929. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  930. end.