IdCoder3to4.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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. $Log$
  13. Rev 1.30 15.09.2004 22:38:22 Andreas Hausladen
  14. Added "Delphi 7.1 compiler warning bug" fix code
  15. Rev 1.29 27.08.2004 22:03:22 Andreas Hausladen
  16. Optimized encoders
  17. speed optimization ("const" for string parameters)
  18. Rev 1.28 7/8/04 5:09:04 PM RLebeau
  19. Updated Encode() to remove use of local TIdBytes variable
  20. Rev 1.27 2004.05.20 1:39:20 PM czhower
  21. Last of the IdStream updates
  22. Rev 1.26 2004.05.20 11:37:08 AM czhower
  23. IdStreamVCL
  24. Rev 1.25 2004.05.20 11:13:12 AM czhower
  25. More IdStream conversions
  26. Rev 1.24 2004.05.19 3:06:54 PM czhower
  27. IdStream / .NET fix
  28. Rev 1.23 2004.03.12 7:54:18 PM czhower
  29. Removed old commented out code.
  30. Rev 1.22 11/03/2004 22:36:14 CCostelloe
  31. Bug fix (1 to 3 spurious extra characters at the end of UUE encoded messages,
  32. see comment starting CC3.
  33. Rev 1.21 2004.02.03 5:44:56 PM czhower
  34. Name changes
  35. Rev 1.20 28/1/2004 6:22:16 PM SGrobety
  36. Removed base 64 encoding stream length check is stream size was provided
  37. Rev 1.19 16/01/2004 17:47:48 CCostelloe
  38. Restructured slightly to allow IdCoderBinHex4 reuse some of its code
  39. Rev 1.18 02/01/2004 20:59:28 CCostelloe
  40. Fixed bugs to get ported code to work in Delphi 7 (changes marked CC2)
  41. Rev 1.17 11/10/2003 7:54:14 PM BGooijen
  42. Did all todo's ( TStream to TIdStream mainly )
  43. Rev 1.16 2003.10.24 10:43:02 AM czhower
  44. TIdSTream to dos
  45. Rev 1.15 22/10/2003 12:25:36 HHariri
  46. Stephanes changes
  47. Rev 1.14 10/16/2003 11:10:18 PM DSiders
  48. Added localization comments, whitespace.
  49. Rev 1.13 2003.10.11 10:00:12 PM czhower
  50. Compiles again
  51. Rev 1.12 10/5/2003 4:31:02 PM GGrieve
  52. use ToBytes for Cardinal to Bytes conversion
  53. Rev 1.11 10/4/2003 9:12:18 PM GGrieve
  54. DotNet
  55. Rev 1.10 2003.06.24 12:02:10 AM czhower
  56. Coders now decode properly again.
  57. Rev 1.9 2003.06.23 10:53:16 PM czhower
  58. Removed unused overriden methods.
  59. Rev 1.8 2003.06.13 6:57:10 PM czhower
  60. Speed improvement
  61. Rev 1.7 2003.06.13 3:41:18 PM czhower
  62. Optimizaitions.
  63. Rev 1.6 2003.06.13 2:24:08 PM czhower
  64. Speed improvement
  65. Rev 1.5 10/6/2003 5:37:02 PM SGrobety
  66. Bug fix in decoders.
  67. Rev 1.4 6/6/2003 4:50:30 PM SGrobety
  68. Reworked the 3to4decoder for performance and stability.
  69. Note that encoders haven't been touched. Will come later. Another problem:
  70. input is ALWAYS a string. Should be a TStream.
  71. 1/ Fix: added filtering for #13,#10 and #32 to the decoding mechanism.
  72. 2/ Optimization: Speed the decoding by a factor 7-10 AND added filtering ;)
  73. Could still do better by using a pointer and a stiding window by a factor 2-3.
  74. 3/ Improvement: instead of writing everything to the output stream, there is
  75. an internal buffer of 4k. It should speed things up when working on large
  76. data (no large chunk of memory pre-allocated while keeping a decent perf by
  77. not requiring every byte to be written separately).
  78. Rev 1.3 28/05/2003 10:06:56 CCostelloe
  79. StripCRLFs changes stripped out at the request of Chad
  80. Rev 1.2 20/05/2003 02:01:00 CCostelloe
  81. Rev 1.1 20/05/2003 01:44:12 CCostelloe
  82. Bug fix: decoder code altered to ensure that any CRLFs inserted by an MTA are
  83. removed
  84. Rev 1.0 11/14/2002 02:14:36 PM JPMugaas
  85. }
  86. unit IdCoder3to4;
  87. interface
  88. {$i IdCompilerDefines.inc}
  89. uses
  90. Classes,
  91. IdCoder,
  92. IdGlobal,
  93. SysUtils;
  94. type
  95. TIdDecodeTable = array[1..127] of Byte;
  96. TIdEncoder3to4 = class(TIdEncoder)
  97. protected
  98. FCodingTable: TIdBytes;
  99. FFillChar: Char;
  100. function InternalEncode(const ABuffer: TIdBytes): TIdBytes;
  101. public
  102. procedure Encode(ASrcStream: TStream; ADestStream: TStream; const ABytes: Integer = -1); override;
  103. property CodingTable: TIdBytes read FCodingTable;
  104. published
  105. property FillChar: Char read FFillChar write FFillChar;
  106. end;
  107. TIdEncoder3to4Class = class of TIdEncoder3to4;
  108. TIdDecoder4to3 = class(TIdDecoder)
  109. protected
  110. FCodingTable: TIdBytes;
  111. FDecodeTable: TIdDecodeTable;
  112. FFillChar: Char;
  113. function InternalDecode(const ABuffer: TIdBytes; const AIgnoreFiller: Boolean = False): TIdBytes;
  114. public
  115. class procedure ConstructDecodeTable(const ACodingTable: String; var ADecodeArray: TIdDecodeTable);
  116. procedure Decode(ASrcStream: TStream; const ABytes: Integer = -1); override;
  117. published
  118. property FillChar: Char read FFillChar write FFillChar;
  119. end;
  120. implementation
  121. uses
  122. IdStream;
  123. { TIdDecoder4to3 }
  124. class procedure TIdDecoder4to3.ConstructDecodeTable(const ACodingTable: string;
  125. var ADecodeArray: TIdDecodeTable);
  126. var
  127. c, i: integer;
  128. begin
  129. //TODO: See if we can find an efficient way, or maybe an option to see if the requested
  130. //decode char is valid, that is it returns a 255 from the DecodeTable, or at maybe
  131. //check its presence in the encode table.
  132. for i := Low(ADecodeArray) to High(ADecodeArray) do begin
  133. ADecodeArray[i] := $FF;
  134. end;
  135. c := 0;
  136. for i := 1 to Length(ACodingTable) do begin
  137. ADecodeArray[Ord(ACodingTable[i])] := c;
  138. Inc(c);
  139. end;
  140. end;
  141. procedure TIdDecoder4to3.Decode(ASrcStream: TStream; const ABytes: Integer = -1);
  142. var
  143. LBuffer: TIdBytes;
  144. LBufSize: Integer;
  145. begin
  146. // No no - this will read the whole thing into memory and what if its MBs?
  147. // need to load it in smaller buffered chunks MaxInt is WAY too big....
  148. LBufSize := IndyLength(ASrcStream, ABytes);
  149. if LBufSize > 0 then begin
  150. SetLength(LBuffer, LBufSize);
  151. TIdStreamHelper.ReadBytes(ASrcStream, LBuffer, LBufSize);
  152. LBuffer := InternalDecode(LBuffer);
  153. if Assigned(FStream) then begin
  154. TIdStreamHelper.Write(FStream, LBuffer);
  155. end;
  156. end;
  157. end;
  158. function TIdDecoder4to3.InternalDecode(const ABuffer: TIdBytes; const AIgnoreFiller: Boolean): TIdBytes;
  159. var
  160. LInBufSize: Integer;
  161. LEmptyBytes: Integer;
  162. LInBytes: TIdBytes;
  163. LOutPos: Integer;
  164. LOutSize: Integer;
  165. LInLimit: Integer;
  166. LInPos: Integer;
  167. begin
  168. SetLength(LInBytes, 4);
  169. LInPos := 0;
  170. LInBufSize := Length(ABuffer);
  171. if (LInBufSize mod 4) <> 0 then begin
  172. LInLimit := (LInBufSize div 4) * 4;
  173. end else begin
  174. LInLimit := LInBufSize;
  175. end;
  176. // Presize output buffer
  177. //CC2, bugfix: was LOutPos := 1;
  178. LOutPos := 0;
  179. LOutSize := (LInLimit div 4) * 3;
  180. SetLength(Result, LOutSize);
  181. while LInPos < LInLimit do begin
  182. // Read 4 bytes in for processing
  183. //CC2 bugfix: was CopyTIdBytes(LIn, LInPos, LInBytes, 0, LInBytesLen);
  184. //CopyTIdBytes(LIn, LInPos-1, LInBytes, 0, LInBytesLen);
  185. // Faster than CopyTIdBytes
  186. LInBytes[0] := ABuffer[LInPos];
  187. LInBytes[1] := ABuffer[LInPos + 1];
  188. LInBytes[2] := ABuffer[LInPos + 2];
  189. LInBytes[3] := ABuffer[LInPos + 3];
  190. // Inc pointer
  191. Inc(LInPos, 4);
  192. // Reduce to 3 bytes
  193. Result[LOutPos] := ((FDecodeTable[LInBytes[0]] and 63) shl 2) or ((FDecodeTable[LInBytes[1]] shr 4) and 3);
  194. Result[LOutPos + 1] := ((FDecodeTable[LInBytes[1]] and 15) shl 4) or ((FDecodeTable[LInBytes[2]] shr 2) and 15);
  195. Result[LOutPos + 2] := ((FDecodeTable[LInBytes[2]] and 3) shl 6) or (FDecodeTable[LInBytes[3]] and 63);
  196. Inc(LOutPos, 3);
  197. // If we dont know how many bytes we need to watch for fill chars. MIME
  198. // is this way.
  199. //
  200. // In best case, the end is not before the end of the input, but the input
  201. // may be right padded with spaces, or even contain the EOL chars.
  202. //
  203. // Because of this we watch for early ends beyond what we originally
  204. // estimated.
  205. end;
  206. // RLebeau: normally, the FillChar does not appear inside the encoded bytes,
  207. // however UUE/XXE does allow it, where encoded lines are prefixed with the
  208. // unencoded data lengths instead...
  209. if (not AIgnoreFiller) and (LInPos > 0) then begin
  210. if ABuffer[LInPos-1] = Ord(FillChar) then begin
  211. if ABuffer[LInPos-2] = Ord(FillChar) then begin
  212. LEmptyBytes := 2;
  213. end else begin
  214. LEmptyBytes := 1;
  215. end;
  216. SetLength(Result, LOutSize - LEmptyBytes);
  217. end;
  218. end;
  219. end;
  220. { TIdEncoder3to4 }
  221. procedure TIdEncoder3to4.Encode(ASrcStream, ADestStream: TStream; const ABytes: Integer = -1);
  222. var
  223. LBuffer: TIdBytes;
  224. LBufSize: Integer;
  225. begin
  226. // No no - this will read the whole thing into memory and what if its MBs?
  227. // need to load it in smaller buffered chunks MaxInt is WAY too big....
  228. LBufSize := IndyLength(ASrcStream, ABytes);
  229. if LBufSize > 0 then begin
  230. SetLength(LBuffer, LBufSize);
  231. TIdStreamHelper.ReadBytes(ASrcStream, LBuffer, LBufSize);
  232. LBuffer := InternalEncode(LBuffer);
  233. TIdStreamHelper.Write(ADestStream, LBuffer);
  234. end;
  235. end;
  236. //TODO: Make this more efficient. Profile it to test, but maybe make single
  237. // calls to ReadBuffer then pull from memory
  238. function TIdEncoder3to4.InternalEncode(const ABuffer: TIdBytes): TIdBytes;
  239. var
  240. LInBufSize : Integer;
  241. LOutSize: Integer;
  242. LLen : integer;
  243. LPos : Integer;
  244. LBufDataLen: Integer;
  245. LIn1, LIn2, LIn3: Byte;
  246. LSize : Integer;
  247. begin
  248. LInBufSize := Length(ABuffer);
  249. LOutSize := ((LInBufSize + 2) div 3) * 4;
  250. SetLength(Result, LOutSize); // we know that the string will grow by 4/3 adjusted to 3 boundary
  251. LLen := 0;
  252. LPos := 0;
  253. // S.G. 21/10/2003: Copy the relevant bytes into the temporary buffer.
  254. // S.G. 21/10/2003: Record the data length and force exit loop when necessary
  255. while LPos < LInBufSize do
  256. begin
  257. Assert((LLen + 4) <= LOutSize,
  258. 'TIdEncoder3to4.Encode: Calculated length exceeded (expected '+ {do not localize}
  259. IntToStr(LOutSize) +
  260. ', about to go '+ {do not localize}
  261. IntToStr(LLen + 4) +
  262. ' at offset ' + {do not localize}
  263. IntToStr(LPos) +
  264. ' of '+ {do not localize}
  265. IntToStr(LInBufSize));
  266. LBufDataLen := LInBufSize - LPos;
  267. if LBufDataLen > 2 then begin
  268. LIn1 := ABuffer[LPos];
  269. LIn2 := ABuffer[LPos+1];
  270. LIn3 := ABuffer[LPos+2];
  271. LSize := 3;
  272. end
  273. else if LBufDataLen > 1 then begin
  274. LIn1 := ABuffer[LPos];
  275. LIn2 := ABuffer[LPos+1];
  276. LIn3 := 0;
  277. LSize := 2;
  278. end
  279. else begin
  280. LIn1 := ABuffer[LPos];
  281. LIn2 := 0;
  282. LIn3 := 0;
  283. LSize := 1;
  284. end;
  285. Inc(LPos, LSize);
  286. //possible to do a better assert than this?
  287. Assert(Length(FCodingTable)>0);
  288. Result[LLen] := FCodingTable[(LIn1 shr 2) and 63];
  289. Result[LLen + 1] := FCodingTable[(((LIn1 and 3) shl 4) or ((LIn2 shr 4) and 15)) and 63];
  290. Result[LLen + 2] := FCodingTable[(((LIn2 and 15) shl 2) or ((LIn3 shr 6) and 3)) and 63];
  291. Result[LLen + 3] := FCodingTable[LIn3 and 63];
  292. Inc(LLen, 4);
  293. if LSize < 3 then begin
  294. Result[LLen-1] := Ord(FillChar);
  295. if LSize = 1 then begin
  296. Result[LLen-2] := Ord(FillChar);
  297. end;
  298. end;
  299. end;
  300. SetLength(Result, LLen);
  301. Assert(LLen = LOutSize,
  302. 'TIdEncoder3to4.Encode: Calculated length not met (expected ' + {do not localize}
  303. IntToStr(LOutSize) +
  304. ', finished at ' + {do not localize}
  305. IntToStr(LLen) +
  306. ', BufSize = ' + {do not localize}
  307. IntToStr(LInBufSize));
  308. end;
  309. end.