IdMessageCoderYenc.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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.7 27.08.2004 22:04:00 Andreas Hausladen
  18. speed optimization ("const" for string parameters)
  19. Rev 1.6 2004.05.20 1:39:16 PM czhower
  20. Last of the IdStream updates
  21. Rev 1.5 2004.05.20 11:37:00 AM czhower
  22. IdStreamVCL
  23. Rev 1.4 2004.05.20 11:13:06 AM czhower
  24. More IdStream conversions
  25. Rev 1.3 2004.05.19 3:06:44 PM czhower
  26. IdStream / .NET fix
  27. Rev 1.2 2004.02.03 5:44:06 PM czhower
  28. Name changes
  29. Rev 1.1 5/9/2003 2:14:42 PM BGooijen
  30. Streams are now buffered, speed is now about 75 times as fast as before
  31. Rev 1.0 11/13/2002 07:57:22 AM JPMugaas
  32. }
  33. {*===========================================================================*}
  34. {* DESCRIPTION *}
  35. {*****************************************************************************}
  36. {* PROJECT : Indy 10 *}
  37. {* AUTHOR : Bas Gooijen ([email protected]) *}
  38. {* MAINTAINER : Bas Gooijen *}
  39. {*...........................................................................*}
  40. {* DESCRIPTION *}
  41. {* yEnc messagepart encoder/decoded *}
  42. {* *}
  43. {* QUICK NOTES: *}
  44. {* MULTIPART-MESSAGES ARE _NOT_ SUPPORTED *}
  45. {* THIS CODE IS ALPHA *}
  46. {* *}
  47. {* implemented according to version 1.3 *}
  48. {* http://www.easynews.com/yenc/yenc-draft.1.3.txt *}
  49. {* http://www.easynews.com/yenc/yEnc-Notes3.txt *}
  50. {* http://www.yenc.org/develop.htm *}
  51. {*...........................................................................*}
  52. {* HISTORY *}
  53. {* DATE VERSION AUTHOR REASONS *}
  54. {* *}
  55. {* 07/07/2002 1.0 Bas Gooijen Initial start *}
  56. {*****************************************************************************}
  57. unit IdMessageCoderYenc;
  58. interface
  59. {$i IdCompilerDefines.inc}
  60. uses
  61. Classes,
  62. IdMessageCoder, IdMessage, IdExceptionCore, IdGlobal;
  63. type
  64. EIdMessageYencException = class(EIdMessageException);
  65. EIdMessageYencInvalidSizeException = class(EIdMessageYencException);
  66. EIdMessageYencInvalidCRCException = class(EIdMessageYencException);
  67. EIdMessageYencCorruptionException = class(EIdMessageYencException);
  68. TIdMessageDecoderYenc = class(TIdMessageDecoder)
  69. protected
  70. FPart: Integer;
  71. FLine: Integer;
  72. FSize: Integer;
  73. public
  74. function ReadBody(ADestStream: TStream; var AMsgEnd: Boolean): TIdMessageDecoder; override;
  75. end;
  76. TIdMessageDecoderInfoYenc = class(TIdMessageDecoderInfo)
  77. public
  78. function CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder; override;
  79. end;
  80. TIdMessageEncoderYenc = class(TIdMessageEncoder)
  81. public
  82. procedure Encode(ASrc: TStream; ADest: TStream); override;
  83. end;
  84. TIdMessageEncoderInfoYenc = class(TIdMessageEncoderInfo)
  85. public
  86. constructor Create; override;
  87. procedure InitializeHeaders(AMsg: TIdMessage); override;
  88. end;
  89. const
  90. BUFLEN = 4096;
  91. //fixed chars in integer form for easy byte comparison
  92. B_PERIOD = $2E;
  93. B_EQUALS = $3D;
  94. B_TAB = $09;
  95. B_LF = $0A;
  96. B_CR = $0D;
  97. B_NUL = $00;
  98. implementation
  99. uses
  100. IdHashCRC,
  101. IdResourceStringsProtocols,
  102. SysUtils;
  103. function GetStrValue(const Line, Option: string; const AMaxCount: Integer = MaxInt) : string;
  104. var
  105. LStart, LEnd: Integer;
  106. begin
  107. LStart := IndyPos(LowerCase(Option) + '=', LowerCase(Line));
  108. if LStart = 0 then
  109. begin
  110. Result := ''; {Do not Localize}
  111. Exit;
  112. end;
  113. Inc(LStart, Length(Option) + 1);
  114. Result := Copy(Line, LStart, AMaxCount);
  115. LEnd := IndyPos(' ', Result) ; {Do not Localize}
  116. if LEnd > 0 then begin
  117. Result := Copy(Result, 1, LEnd - 1);
  118. end;
  119. end;
  120. function GetIntValue(const Line, Option: string): Integer;
  121. var
  122. LValue: String;
  123. begin
  124. LValue := GetStrValue(Line, Option, $FFFF);
  125. if LValue <> '' then begin
  126. Result := IndyStrToInt(LValue);
  127. end else begin
  128. Result := 0;
  129. end;
  130. end;
  131. { TIdMessageDecoderInfoYenc }
  132. function TIdMessageDecoderInfoYenc.CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder;
  133. function GetName: string;
  134. var
  135. LStart: Integer;
  136. begin
  137. LStart := IndyPos('name=', LowerCase(ALine)); {Do not Localize}
  138. if LStart > 0 then begin
  139. Result := Copy(ALine, LStart+5, MaxInt);
  140. end else begin
  141. Result := '';
  142. end;
  143. end;
  144. var
  145. LYenc: TIdMessageDecoderYenc;
  146. begin
  147. if TextStartsWith(ALine, '=ybegin ') {Do not Localize} then
  148. begin
  149. LYenc := TIdMessageDecoderYenc.Create(ASender);
  150. try
  151. LYenc.FSize := GetIntValue(ALine, 'size'); {Do not Localize}
  152. LYenc.FLine := GetIntValue(ALine, 'line'); {Do not Localize}
  153. LYenc.FPart := GetIntValue(ALine, 'part'); {Do not Localize}
  154. LYenc.FFilename := GetName;
  155. LYenc.FPartType := mcptAttachment;
  156. except
  157. FreeAndNil(LYenc);
  158. raise;
  159. end;
  160. Result := LYenc;
  161. end else begin
  162. Result := nil;
  163. end;
  164. end;
  165. { TIdMessageDecoderYenc }
  166. function TIdMessageDecoderYenc.ReadBody(ADestStream: TStream; var AMsgEnd: Boolean): TIdMessageDecoder;
  167. var
  168. LLine: string;
  169. LLinePos: Integer;
  170. LChar: Byte;
  171. LBytesDecoded: Integer;
  172. LPartSize: Integer;
  173. LCrc32: string;
  174. LMsgEnd: Boolean;
  175. LOutputBuffer: TIdBytes;
  176. LOutputBufferUsed: Integer;
  177. LHash: UInt32;
  178. LH: TIdHashCRC32;
  179. LEncoding: IIdTextEncoding;
  180. procedure FlushOutputBuffer;
  181. begin
  182. //TODO: this uses Array of Characters. Unless its dealing in Unicode or MBCS it should
  183. // be using TIdBuffer
  184. if Assigned(ADestStream) then begin
  185. WriteTIdBytesToStream(ADestStream, LOutputBuffer, LOutputBufferUsed);
  186. end;
  187. LOutputBufferUsed := 0;
  188. end;
  189. procedure AddByteToOutputBuffer(const AChar: Byte);
  190. begin
  191. LOutputBuffer[LOutputBufferUsed] := AChar;
  192. Inc(LOutputBufferUsed);
  193. if LOutputBufferUsed >= BUFLEN then begin
  194. FlushOutputBuffer;
  195. end;
  196. end;
  197. begin
  198. SetLength(LOutputBuffer, BUFLEN);
  199. AMsgEnd := False;
  200. Result := nil;
  201. LPartSize := FSize;
  202. LOutputBufferUsed := 0;
  203. LBytesDecoded := 0;
  204. LH := TIdHashCRC32.Create;
  205. try
  206. LH.HashStart(LHash);
  207. //note that we have to do hashing here because there's no seek
  208. // in the TStream class, changing definitions in this API might
  209. // break something, and storing in an extra buffer will just eat space
  210. LEncoding := IndyTextEncoding_8Bit;
  211. repeat
  212. LLine := ReadLnRFC(LMsgEnd, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
  213. if (IndyPos('=yend', LowerCase(LLine)) <> 0) or LMsgEnd then {Do not Localize}
  214. begin
  215. Break;
  216. end;
  217. if TextStartsWith(LLine, '=ypart ') then begin {Do not Localize}
  218. LPartSize := GetIntValue(LLine, 'end') - GetIntValue(LLine, 'begin') + 1; {Do not Localize}
  219. end
  220. else begin
  221. LLinePos := 1;
  222. while LLinePos <= Length(LLine) do
  223. begin
  224. LChar := Byte(LLine[LLinePos]);
  225. if LChar = B_EQUALS then begin
  226. // invalid file, escape character may not appear at end of line
  227. if LLinePos = Length(LLine) then begin
  228. raise EIdMessageYencCorruptionException.Create(RSYencFileCorrupted);
  229. end;
  230. Inc(LLinePos);
  231. LChar := Byte(LLine[LLinePos]);
  232. Dec(LChar, 64);
  233. end;
  234. Dec(LChar, 42);
  235. AddByteToOutputBuffer(LChar);
  236. LH.HashByte(LHash, LChar);
  237. Inc(LLinePos);
  238. Inc(LBytesDecoded);
  239. end;
  240. end;
  241. until False;
  242. LH.HashEnd(LHash);
  243. FlushOutputBuffer;
  244. if LPartSize <> LBytesDecoded then begin
  245. raise EIdMessageYencInvalidSizeException.Create(RSYencInvalidSize);
  246. end;
  247. LCrc32 := LowerCase(GetStrValue(LLine, 'crc32', $FFFF)); {Do not Localize}
  248. if LCrc32 <> '' then begin
  249. //done this way because values can be computed faster than strings and we don't
  250. //have to mess with charactor case.
  251. if IndyStrToInt64('$' + LCrc32) <> LHash then begin
  252. raise EIdMessageYencInvalidCRCException.Create(RSYencInvalidCRC);
  253. end;
  254. end;
  255. finally
  256. FreeAndNil(LH);
  257. end;
  258. end;
  259. constructor TIdMessageEncoderInfoYenc.Create;
  260. begin
  261. inherited Create;
  262. FMessageEncoderClass := TIdMessageEncoderYenc;
  263. end;
  264. procedure TIdMessageEncoderInfoYenc.InitializeHeaders(AMsg: TIdMessage);
  265. begin
  266. //
  267. end;
  268. { TIdMessageEncoderYenc }
  269. procedure TIdMessageEncoderYenc.Encode(ASrc: TStream; ADest: TStream);
  270. const
  271. LineSize = 128;
  272. var
  273. s: String;
  274. i, LSSize: TIdStreamSize;
  275. LInput: Byte;
  276. LOutput: Byte;
  277. LCurrentLineLength: Integer;
  278. LOutputBuffer: TIdBytes;
  279. LOutputBufferUsed: Integer;
  280. LInputBuffer: TIdBytes;
  281. LInputBufferPos: Integer;
  282. LInputBufferSize: Integer;
  283. LHash: UInt32;
  284. LH: TIdHashCRC32;
  285. procedure FlushOutputBuffer;
  286. begin
  287. WriteTIdBytesToStream(ADest, LOutputBuffer, LOutputBufferUsed);
  288. LOutputBufferUsed := 0;
  289. end;
  290. procedure AddByteToOutputBuffer(const AChar: Byte);
  291. begin
  292. LOutputBuffer[LOutputBufferUsed] := AChar;
  293. Inc(LOutputBufferUsed);
  294. if LOutputBufferUsed >= BUFLEN then begin
  295. FlushOutputBuffer;
  296. end;
  297. end;
  298. function ReadByteFromInputBuffer: Byte;
  299. begin
  300. if LInputBufferPos >= LInputBufferSize then begin
  301. LInputBufferSize := ReadTIdBytesFromStream(ASrc, LInputBuffer, BUFLEN);
  302. LInputBufferPos := 0;
  303. end;
  304. Result := LInputBuffer[LInputBufferPos];
  305. Inc(LInputBufferPos);
  306. end;
  307. begin
  308. SetLength(LOutputBuffer, BUFLEN);
  309. SetLength(LInputBuffer, BUFLEN);
  310. LSSize := IndyLength(ASrc);
  311. LCurrentLineLength := 0;
  312. LOutputBufferUsed := 0;
  313. LH := TIdHashCRC32.Create;
  314. try
  315. LH.HashStart(LHash);
  316. s := '=ybegin line=' + IntToStr(LineSize) + ' size=' + IntToStr(LSSize) + ' name=' + FFilename + EOL; {do not localize}
  317. WriteStringToStream(ADest, s);
  318. i := 0;
  319. while i < LSSize do
  320. begin
  321. LInput := ReadByteFromInputBuffer;
  322. Inc(i);
  323. LH.HashByte(LHash, LInput);
  324. LOutput := LInput;
  325. Inc(LOutput, 42);
  326. if LOutput in [B_NUL, B_LF, B_CR, B_EQUALS, B_TAB, B_PERIOD] then begin {do not localize}
  327. AddByteToOutputBuffer(B_EQUALS);
  328. Inc(LCurrentLineLength);
  329. Inc(LOutput, 64);
  330. end;
  331. AddByteToOutputBuffer(LOutput);
  332. Inc(LCurrentLineLength);
  333. if LCurrentLineLength = 1 then begin
  334. if LOutput = B_PERIOD then begin
  335. AddByteToOutputBuffer(LOutput);
  336. Inc(LCurrentLineLength);
  337. end;
  338. end;
  339. if LCurrentLineLength >= LineSize then
  340. begin
  341. AddByteToOutputBuffer(B_CR);
  342. AddByteToOutputBuffer(B_LF);
  343. LCurrentLineLength := 0;
  344. end;
  345. end;
  346. FlushOutputBuffer;
  347. s := EOL + '=yend size=' + IntToStr(LSSize) + ' crc32=' + {do not localize}
  348. LowerCase(IntToHex(LHash, 8)) + EOL;
  349. WriteStringToStream(ADest, s);
  350. finally
  351. FreeAndNil(LH);
  352. end;
  353. end;
  354. initialization
  355. TIdMessageDecoderList.RegisterDecoder('yEnc', TIdMessageDecoderInfoYenc.Create); {Do not Localize}
  356. TIdMessageEncoderList.RegisterEncoder('yEnc', TIdMessageEncoderInfoYenc.Create); {Do not Localize}
  357. end.