IdMessageCoder.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  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.15 10/26/2004 10:27:42 PM JPMugaas
  18. Updated refs.
  19. Rev 1.14 27.08.2004 22:03:58 Andreas Hausladen
  20. speed optimization ("const" for string parameters)
  21. Rev 1.13 8/10/04 1:41:00 PM RLebeau
  22. Added FreeSourceStream property to TIdMessageDecoder
  23. Rev 1.12 7/23/04 6:43:26 PM RLebeau
  24. Added extra exception handling to Encode()
  25. Rev 1.11 29/05/2004 21:22:40 CCostelloe
  26. Added support for decoding attachments with a Content-Transfer-Encoding of
  27. binary
  28. Rev 1.10 2004.05.20 1:39:12 PM czhower
  29. Last of the IdStream updates
  30. Rev 1.9 2004.05.20 11:36:56 AM czhower
  31. IdStreamVCL
  32. Rev 1.8 2004.05.20 11:12:58 AM czhower
  33. More IdStream conversions
  34. Rev 1.7 2004.05.19 3:06:38 PM czhower
  35. IdStream / .NET fix
  36. Rev 1.6 2004.02.03 5:44:02 PM czhower
  37. Name changes
  38. Rev 1.5 1/21/2004 1:17:20 PM JPMugaas
  39. InitComponent
  40. Rev 1.4 10/11/2003 4:40:24 PM BGooijen
  41. Fix for DotNet
  42. Rev 1.3 10/10/2003 10:42:54 PM BGooijen
  43. DotNet
  44. Rev 1.2 26/09/2003 01:04:22 CCostelloe
  45. Minor change, if any
  46. Rev 1.1 07/08/2003 00:46:46 CCostelloe
  47. Function ReadLnSplit added
  48. Rev 1.0 11/13/2002 07:57:04 AM JPMugaas
  49. }
  50. unit IdMessageCoder;
  51. interface
  52. {$i IdCompilerDefines.inc}
  53. uses
  54. Classes,
  55. IdComponent,
  56. IdGlobal,
  57. IdMessage;
  58. type
  59. TIdMessageCoderPartType = (mcptText, mcptAttachment, mcptIgnore, mcptEOF);
  60. TIdMessageDecoder = class(TIdComponent)
  61. protected
  62. FFilename: string;
  63. FFreeSourceStream: Boolean;
  64. // Dont use TIdHeaderList for FHeaders - we dont know that they will all be like MIME.
  65. FHeaders: TStrings;
  66. FPartType: TIdMessageCoderPartType;
  67. FSourceStream: TStream;
  68. public
  69. constructor Create(AOwner: TComponent); override;
  70. destructor Destroy; override;
  71. //
  72. function ReadBody(ADestStream: TStream; var AMsgEnd: Boolean): TIdMessageDecoder; virtual; abstract;
  73. procedure ReadHeader; virtual;
  74. //CC: ATerminator param added because Content-Transfer-Encoding of binary needs
  75. //an ATerminator of EOL...
  76. function ReadLn(const ATerminator: string = LF; AByteEncoding: IIdTextEncoding = nil): string;
  77. //RLebeau: added for RFC 822 retrieves
  78. function ReadLnRFC(var VMsgEnd: Boolean; AByteEncoding: IIdTextEncoding = nil): String; overload;
  79. function ReadLnRFC(var VMsgEnd: Boolean; const ALineTerminator: String;
  80. const ADelim: String = '.'; AByteEncoding: IIdTextEncoding = nil): String; overload; {do not localize}
  81. //
  82. property Filename: string read FFilename;
  83. property FreeSourceStream: Boolean read FFreeSourceStream write FFreeSourceStream;
  84. property Headers: TStrings read FHeaders;
  85. property PartType: TIdMessageCoderPartType read FPartType;
  86. property SourceStream: TStream read FSourceStream write FSourceStream;
  87. end;
  88. TIdMessageDecoderInfo = class
  89. public
  90. function CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder; virtual;
  91. abstract;
  92. constructor Create; virtual;
  93. end;
  94. TIdMessageDecoderList = class
  95. protected
  96. FMessageCoders: TStrings;
  97. public
  98. class function ByName(const AName: string): TIdMessageDecoderInfo;
  99. class function CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder;
  100. constructor Create;
  101. destructor Destroy; override;
  102. class procedure RegisterDecoder(const AMessageCoderName: string;
  103. AMessageCoderInfo: TIdMessageDecoderInfo);
  104. end;
  105. TIdMessageEncoder = class(TIdComponent)
  106. protected
  107. FFilename: string;
  108. FPermissionCode: integer;
  109. //
  110. public
  111. constructor Create(AOwner: TComponent); override;
  112. procedure Encode(const AFilename: string; ADest: TStream); overload;
  113. procedure Encode(ASrc: TStream; ADest: TStrings); overload;
  114. procedure Encode(ASrc: TStream; ADest: TStream); overload; virtual; abstract;
  115. published
  116. property Filename: string read FFilename write FFilename;
  117. property PermissionCode: integer read FPermissionCode write FPermissionCode;
  118. end;
  119. TIdMessageEncoderClass = class of TIdMessageEncoder;
  120. TIdMessageEncoderInfo = class
  121. protected
  122. FMessageEncoderClass: TIdMessageEncoderClass;
  123. public
  124. constructor Create; virtual;
  125. procedure InitializeHeaders(AMsg: TIdMessage); virtual;
  126. //
  127. property MessageEncoderClass: TIdMessageEncoderClass read FMessageEncoderClass;
  128. end;
  129. TIdMessageEncoderList = class
  130. protected
  131. FMessageCoders: TStrings;
  132. public
  133. class function ByName(const AName: string): TIdMessageEncoderInfo;
  134. constructor Create;
  135. destructor Destroy; override;
  136. class procedure RegisterEncoder(const AMessageEncoderName: string;
  137. AMessageEncoderInfo: TIdMessageEncoderInfo);
  138. end;
  139. implementation
  140. uses
  141. IdException, IdResourceStringsProtocols,
  142. IdTCPStream, IdBuffer, SysUtils;
  143. var
  144. GMessageDecoderList: TIdMessageDecoderList = nil;
  145. GMessageEncoderList: TIdMessageEncoderList = nil;
  146. { TIdMessageDecoderList }
  147. class function TIdMessageDecoderList.ByName(const AName: string): TIdMessageDecoderInfo;
  148. var
  149. I: Integer;
  150. begin
  151. Result := nil;
  152. if GMessageDecoderList <> nil then begin
  153. I := GMessageDecoderList.FMessageCoders.IndexOf(AName);
  154. if I <> -1 then begin
  155. Result := TIdMessageDecoderInfo(GMessageDecoderList.FMessageCoders.Objects[I]);
  156. end;
  157. end;
  158. if Result = nil then begin
  159. raise EIdException.CreateFmt(RSMessageDecoderNotFound, [AName]); {Do not Localize} // TODO: create a new Exception class for this
  160. end;
  161. end;
  162. class function TIdMessageDecoderList.CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder;
  163. var
  164. i: integer;
  165. begin
  166. Result := nil;
  167. if GMessageDecoderList <> nil then begin
  168. for i := 0 to GMessageDecoderList.FMessageCoders.Count - 1 do begin
  169. Result := TIdMessageDecoderInfo(GMessageDecoderList.FMessageCoders.Objects[i]).CheckForStart(ASender, ALine);
  170. if Result <> nil then begin
  171. Break;
  172. end;
  173. end;
  174. end;
  175. end;
  176. constructor TIdMessageDecoderList.Create;
  177. begin
  178. inherited;
  179. FMessageCoders := TStringList.Create;
  180. end;
  181. destructor TIdMessageDecoderList.Destroy;
  182. {$IFNDEF USE_OBJECT_ARC}
  183. var
  184. i: integer;
  185. {$ENDIF}
  186. begin
  187. {$IFNDEF USE_OBJECT_ARC}
  188. for i := 0 to FMessageCoders.Count - 1 do begin
  189. TIdMessageDecoderInfo(FMessageCoders.Objects[i]).Free;
  190. end;
  191. {$ENDIF}
  192. FMessageCoders.Free;
  193. inherited Destroy;
  194. end;
  195. class procedure TIdMessageDecoderList.RegisterDecoder(const AMessageCoderName: string;
  196. AMessageCoderInfo: TIdMessageDecoderInfo);
  197. begin
  198. if GMessageDecoderList = nil then begin
  199. GMessageDecoderList := TIdMessageDecoderList.Create;
  200. end;
  201. GMessageDecoderList.FMessageCoders.AddObject(AMessageCoderName, AMessageCoderInfo);
  202. end;
  203. { TIdMessageDecoderInfo }
  204. constructor TIdMessageDecoderInfo.Create;
  205. begin
  206. inherited Create;
  207. end;
  208. { TIdMessageDecoder }
  209. constructor TIdMessageDecoder.Create(AOwner: TComponent);
  210. begin
  211. inherited Create(AOwner);
  212. FFreeSourceStream := True;
  213. FHeaders := TStringList.Create;
  214. end;
  215. destructor TIdMessageDecoder.Destroy;
  216. begin
  217. FHeaders.Free;
  218. if FFreeSourceStream then begin
  219. IdDisposeAndNil(FSourceStream);
  220. end else begin
  221. FSourceStream := nil;
  222. end;
  223. inherited Destroy;
  224. end;
  225. procedure TIdMessageDecoder.ReadHeader;
  226. begin
  227. end;
  228. // this is copied from TIdIOHandler.ReadLn() and then adjusted to read from
  229. // a TStream, with the same sematics as Idglobal.ReadLnFromStream() but with
  230. // support for searching for a caller-specified terminator.
  231. function DoReadLnFromStream(AStream: TStream; ATerminator: string;
  232. AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil): string;
  233. const
  234. LBUFMAXSIZE = 2048;
  235. var
  236. LBuffer: TIdBuffer;
  237. LSize: Integer;
  238. LStartPos: Integer;
  239. LTermPos: Integer;
  240. LTerm, LTemp: TIdBytes;
  241. LStrmStartPos, LStrmPos, LStrmSize: Int64;
  242. begin
  243. Assert(AStream<>nil);
  244. LTerm := nil; // keep the compiler happy
  245. { we store the stream size for the whole routine to prevent
  246. so do not incur a performance penalty with TStream.Size. It has
  247. to use something such as Seek each time the size is obtained}
  248. {4 seek vs 3 seek}
  249. LStrmStartPos := AStream.Position;
  250. LStrmPos := LStrmStartPos;
  251. LStrmSize := AStream.Size;
  252. if LStrmPos >= LStrmSize then begin
  253. Result := '';
  254. Exit;
  255. end;
  256. SetLength(LTemp, LBUFMAXSIZE);
  257. LBuffer := TIdBuffer.Create;
  258. try
  259. EnsureEncoding(AByteEncoding);
  260. if AMaxLineLength < 0 then begin
  261. AMaxLineLength := MaxInt;
  262. end;
  263. // User may pass '' if they need to pass arguments beyond the first.
  264. if ATerminator = '' then begin
  265. ATerminator := LF;
  266. end;
  267. LTerm := ToBytes(ATerminator, AByteEncoding);
  268. LTermPos := -1;
  269. LStartPos := 0;
  270. repeat
  271. LSize := IndyMin(LStrmSize - LStrmPos, LBUFMAXSIZE);
  272. LSize := ReadTIdBytesFromStream(AStream, LTemp, LSize);
  273. if LSize < 1 then begin
  274. LStrmPos := LStrmStartPos + LBuffer.Size;
  275. Break;
  276. end;
  277. Inc(LStrmPos, LSize);
  278. LBuffer.Write(LTemp, LSize, 0);
  279. LTermPos := LBuffer.IndexOf(LTerm, LStartPos);
  280. if LTermPos > -1 then begin
  281. if (AMaxLineLength > 0) and (LTermPos > AMaxLineLength) then begin
  282. LStrmPos := LStrmStartPos + AMaxLineLength;
  283. LTermPos := AMaxLineLength;
  284. end else begin
  285. LStrmPos := LStrmStartPos + LTermPos + Length(LTerm);
  286. end;
  287. Break;
  288. end;
  289. LStartPos := IndyMax(LBuffer.Size-(Length(LTerm)-1), 0);
  290. if (AMaxLineLength > 0) and (LStartPos >= AMaxLineLength) then begin
  291. LStrmPos := LStrmStartPos + AMaxLineLength;
  292. LTermPos := AMaxLineLength;
  293. Break;
  294. end;
  295. until LStrmPos >= LStrmSize;
  296. // Extract actual data
  297. if (ATerminator = LF) and (LTermPos > 0) and (LTermPos < LBuffer.Size) then begin
  298. if (LBuffer.PeekByte(LTermPos) = Ord(LF)) and
  299. (LBuffer.PeekByte(LTermPos-1) = Ord(CR)) then begin
  300. Dec(LTermPos);
  301. end;
  302. end;
  303. AStream.Position := LStrmPos;
  304. Result := LBuffer.ExtractToString(LTermPos, AByteEncoding);
  305. finally
  306. LBuffer.Free;
  307. end;
  308. end;
  309. function TIdMessageDecoder.ReadLn(const ATerminator: string = LF; AByteEncoding: IIdTextEncoding = nil): string;
  310. begin
  311. if SourceStream is TIdTCPStream then begin
  312. Result := TIdTCPStream(SourceStream).Connection.IOHandler.ReadLn(ATerminator, IdTimeoutDefault, -1, AByteEncoding);
  313. end else begin
  314. Result := DoReadLnFromStream(SourceStream, ATerminator, -1, AByteEncoding);
  315. end;
  316. end;
  317. function TIdMessageDecoder.ReadLnRFC(var VMsgEnd: Boolean; AByteEncoding: IIdTextEncoding = nil): String;
  318. begin
  319. Result := ReadLnRFC(VMsgEnd, LF, '.', AByteEncoding); {do not localize}
  320. end;
  321. function TIdMessageDecoder.ReadLnRFC(var VMsgEnd: Boolean; const ALineTerminator: String;
  322. const ADelim: String = '.'; AByteEncoding: IIdTextEncoding = nil): String;
  323. begin
  324. Result := ReadLn(ALineTerminator, AByteEncoding);
  325. // Do not use ATerminator since always ends with . (standard)
  326. if Result = ADelim then {do not localize}
  327. begin
  328. VMsgEnd := True;
  329. Exit;
  330. end;
  331. if TextStartsWith(Result, '..') then begin {do not localize}
  332. IdDelete(Result, 1, 1);
  333. end;
  334. VMsgEnd := False;
  335. end;
  336. { TIdMessageEncoderInfo }
  337. constructor TIdMessageEncoderInfo.Create;
  338. begin
  339. inherited Create;
  340. end;
  341. procedure TIdMessageEncoderInfo.InitializeHeaders(AMsg: TIdMessage);
  342. begin
  343. //
  344. end;
  345. { TIdMessageEncoderList }
  346. class function TIdMessageEncoderList.ByName(const AName: string): TIdMessageEncoderInfo;
  347. var
  348. I: Integer;
  349. begin
  350. Result := nil;
  351. if GMessageEncoderList <> nil then begin
  352. I := GMessageEncoderList.FMessageCoders.IndexOf(AName);
  353. if I <> -1 then begin
  354. Result := TIdMessageEncoderInfo(GMessageEncoderList.FMessageCoders.Objects[I]);
  355. end;
  356. end;
  357. if Result = nil then begin
  358. raise EIdException.CreateFmt(RSMessageEncoderNotFound, [AName]); {Do not Localize} // TODO: create a new Exception class for this
  359. end;
  360. end;
  361. constructor TIdMessageEncoderList.Create;
  362. begin
  363. inherited;
  364. FMessageCoders := TStringList.Create;
  365. end;
  366. destructor TIdMessageEncoderList.Destroy;
  367. {$IFNDEF USE_OBJECT_ARC}
  368. var
  369. i: integer;
  370. {$ENDIF}
  371. begin
  372. {$IFNDEF USE_OBJECT_ARC}
  373. for i := 0 to FMessageCoders.Count - 1 do begin
  374. TIdMessageEncoderInfo(FMessageCoders.Objects[i]).Free;
  375. end;
  376. {$ENDIF}
  377. FMessageCoders.Free;
  378. inherited Destroy;
  379. end;
  380. class procedure TIdMessageEncoderList.RegisterEncoder(const AMessageEncoderName: string;
  381. AMessageEncoderInfo: TIdMessageEncoderInfo);
  382. begin
  383. if GMessageEncoderList = nil then begin
  384. GMessageEncoderList := TIdMessageEncoderList.Create;
  385. end;
  386. GMessageEncoderList.FMessageCoders.AddObject(AMessageEncoderName, AMessageEncoderInfo);
  387. end;
  388. { TIdMessageEncoder }
  389. constructor TIdMessageEncoder.Create(AOwner: TComponent);
  390. begin
  391. inherited Create(AOwner);
  392. FPermissionCode := 660;
  393. end;
  394. procedure TIdMessageEncoder.Encode(const AFilename: string; ADest: TStream);
  395. var
  396. LSrcStream: TStream;
  397. begin
  398. LSrcStream := TIdReadFileExclusiveStream.Create(AFileName);
  399. try
  400. Encode(LSrcStream, ADest);
  401. finally
  402. LSrcStream.Free;
  403. end;
  404. end;
  405. procedure TIdMessageEncoder.Encode(ASrc: TStream; ADest: TStrings);
  406. var
  407. LDestStream: TStream;
  408. begin
  409. // TODO: provide an Encode() implementation that can save its output directly
  410. // to ADest without having to waste memory encoding the data entirely to
  411. // memory first. In Delphi 2009+ in particular, TStrings.LoadFromStream()
  412. // wastes a lot of memory handling large streams...
  413. LDestStream := TMemoryStream.Create;
  414. try
  415. Encode(ASrc, LDestStream);
  416. LDestStream.Position := 0;
  417. ADest.LoadFromStream(LDestStream);
  418. finally
  419. LDestStream.Free;
  420. end;
  421. end;
  422. initialization
  423. finalization
  424. FreeAndNil(GMessageDecoderList);
  425. FreeAndNil(GMessageEncoderList);
  426. end.