IdMessageCoder.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  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. procedure InitComponent; override;
  69. public
  70. function ReadBody(ADestStream: TStream; var AMsgEnd: Boolean): TIdMessageDecoder; virtual; abstract;
  71. procedure ReadHeader; virtual;
  72. //CC: ATerminator param added because Content-Transfer-Encoding of binary needs
  73. //an ATerminator of EOL...
  74. function ReadLn(const ATerminator: string = LF; AByteEncoding: IIdTextEncoding = nil
  75. {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
  76. ): string;
  77. //RLebeau: added for RFC 822 retrieves
  78. function ReadLnRFC(var VMsgEnd: Boolean; AByteEncoding: IIdTextEncoding = nil
  79. {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
  80. ): String; overload;
  81. function ReadLnRFC(var VMsgEnd: Boolean; const ALineTerminator: String;
  82. const ADelim: String = '.'; AByteEncoding: IIdTextEncoding = nil
  83. {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
  84. ): String; overload; {do not localize}
  85. destructor Destroy; override;
  86. //
  87. property Filename: string read FFilename;
  88. property FreeSourceStream: Boolean read FFreeSourceStream write FFreeSourceStream;
  89. property Headers: TStrings read FHeaders;
  90. property PartType: TIdMessageCoderPartType read FPartType;
  91. property SourceStream: TStream read FSourceStream write FSourceStream;
  92. end;
  93. TIdMessageDecoderInfo = class
  94. public
  95. function CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder; virtual;
  96. abstract;
  97. constructor Create; virtual;
  98. end;
  99. TIdMessageDecoderList = class
  100. protected
  101. FMessageCoders: TStrings;
  102. public
  103. class function ByName(const AName: string): TIdMessageDecoderInfo;
  104. class function CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder;
  105. constructor Create;
  106. destructor Destroy; override;
  107. class procedure RegisterDecoder(const AMessageCoderName: string;
  108. AMessageCoderInfo: TIdMessageDecoderInfo);
  109. end;
  110. TIdMessageEncoder = class(TIdComponent)
  111. protected
  112. FFilename: string;
  113. FPermissionCode: integer;
  114. //
  115. procedure InitComponent; override;
  116. public
  117. procedure Encode(const AFilename: string; ADest: TStream); overload;
  118. procedure Encode(ASrc: TStream; ADest: TStrings); overload;
  119. procedure Encode(ASrc: TStream; ADest: TStream); overload; virtual; abstract;
  120. published
  121. property Filename: string read FFilename write FFilename;
  122. property PermissionCode: integer read FPermissionCode write FPermissionCode;
  123. end;
  124. TIdMessageEncoderClass = class of TIdMessageEncoder;
  125. TIdMessageEncoderInfo = class
  126. protected
  127. FMessageEncoderClass: TIdMessageEncoderClass;
  128. public
  129. constructor Create; virtual;
  130. procedure InitializeHeaders(AMsg: TIdMessage); virtual;
  131. //
  132. property MessageEncoderClass: TIdMessageEncoderClass read FMessageEncoderClass;
  133. end;
  134. TIdMessageEncoderList = class
  135. protected
  136. FMessageCoders: TStrings;
  137. public
  138. class function ByName(const AName: string): TIdMessageEncoderInfo;
  139. constructor Create;
  140. destructor Destroy; override;
  141. class procedure RegisterEncoder(const AMessageEncoderName: string;
  142. AMessageEncoderInfo: TIdMessageEncoderInfo);
  143. end;
  144. implementation
  145. uses
  146. IdException, IdResourceStringsProtocols,
  147. IdTCPStream, IdBuffer, SysUtils;
  148. var
  149. GMessageDecoderList: TIdMessageDecoderList = nil;
  150. GMessageEncoderList: TIdMessageEncoderList = nil;
  151. { TIdMessageDecoderList }
  152. class function TIdMessageDecoderList.ByName(const AName: string): TIdMessageDecoderInfo;
  153. var
  154. I: Integer;
  155. begin
  156. Result := nil;
  157. if GMessageDecoderList <> nil then begin
  158. I := GMessageDecoderList.FMessageCoders.IndexOf(AName);
  159. if I <> -1 then begin
  160. Result := TIdMessageDecoderInfo(GMessageDecoderList.FMessageCoders.Objects[I]);
  161. end;
  162. end;
  163. if Result = nil then begin
  164. raise EIdException.Create(RSMessageDecoderNotFound + ': ' + AName); {Do not Localize} // TODO: create a new Exception class for this
  165. end;
  166. end;
  167. class function TIdMessageDecoderList.CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder;
  168. var
  169. i: integer;
  170. begin
  171. Result := nil;
  172. if GMessageDecoderList <> nil then begin
  173. for i := 0 to GMessageDecoderList.FMessageCoders.Count - 1 do begin
  174. Result := TIdMessageDecoderInfo(GMessageDecoderList.FMessageCoders.Objects[i]).CheckForStart(ASender, ALine);
  175. if Result <> nil then begin
  176. Break;
  177. end;
  178. end;
  179. end;
  180. end;
  181. constructor TIdMessageDecoderList.Create;
  182. begin
  183. inherited;
  184. FMessageCoders := TStringList.Create;
  185. end;
  186. destructor TIdMessageDecoderList.Destroy;
  187. {$IFNDEF USE_OBJECT_ARC}
  188. var
  189. i: integer;
  190. {$ENDIF}
  191. begin
  192. {$IFNDEF USE_OBJECT_ARC}
  193. for i := 0 to FMessageCoders.Count - 1 do begin
  194. TIdMessageDecoderInfo(FMessageCoders.Objects[i]).Free;
  195. end;
  196. {$ENDIF}
  197. FreeAndNil(FMessageCoders);
  198. inherited Destroy;
  199. end;
  200. class procedure TIdMessageDecoderList.RegisterDecoder(const AMessageCoderName: string;
  201. AMessageCoderInfo: TIdMessageDecoderInfo);
  202. begin
  203. if GMessageDecoderList = nil then begin
  204. GMessageDecoderList := TIdMessageDecoderList.Create;
  205. end;
  206. GMessageDecoderList.FMessageCoders.AddObject(AMessageCoderName, AMessageCoderInfo);
  207. end;
  208. { TIdMessageDecoderInfo }
  209. constructor TIdMessageDecoderInfo.Create;
  210. begin
  211. inherited Create;
  212. end;
  213. { TIdMessageDecoder }
  214. procedure TIdMessageDecoder.InitComponent;
  215. begin
  216. inherited;
  217. FFreeSourceStream := True;
  218. FHeaders := TStringList.Create;
  219. end;
  220. destructor TIdMessageDecoder.Destroy;
  221. begin
  222. FreeAndNil(FHeaders);
  223. if FFreeSourceStream then begin
  224. FreeAndNil(FSourceStream);
  225. end else begin
  226. FSourceStream := nil;
  227. end;
  228. inherited Destroy;
  229. end;
  230. procedure TIdMessageDecoder.ReadHeader;
  231. begin
  232. end;
  233. // this is copied from TIdIOHandler.ReadLn() and then adjusted to read from
  234. // a TStream, with the same sematics as Idglobal.ReadLnFromStream() but with
  235. // support for searching for a caller-specified terminator.
  236. function DoReadLnFromStream(AStream: TStream; ATerminator: string;
  237. AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil
  238. {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
  239. ): string;
  240. const
  241. LBUFMAXSIZE = 2048;
  242. var
  243. LBuffer: TIdBuffer;
  244. LSize: Integer;
  245. LStartPos: Integer;
  246. LTermPos: Integer;
  247. LTerm, LTemp: TIdBytes;
  248. LStrmStartPos, LStrmPos, LStrmSize: TIdStreamSize;
  249. begin
  250. Assert(AStream<>nil);
  251. LTerm := nil; // keep the compiler happy
  252. { we store the stream size for the whole routine to prevent
  253. so do not incur a performance penalty with TStream.Size. It has
  254. to use something such as Seek each time the size is obtained}
  255. {4 seek vs 3 seek}
  256. LStrmStartPos := AStream.Position;
  257. LStrmPos := LStrmStartPos;
  258. LStrmSize := AStream.Size;
  259. if LStrmPos >= LStrmSize then begin
  260. Result := '';
  261. Exit;
  262. end;
  263. SetLength(LTemp, LBUFMAXSIZE);
  264. LBuffer := TIdBuffer.Create;
  265. try
  266. EnsureEncoding(AByteEncoding);
  267. {$IFDEF STRING_IS_ANSI}
  268. EnsureEncoding(ADestEncoding, encOSDefault);
  269. {$ENDIF}
  270. if AMaxLineLength < 0 then begin
  271. AMaxLineLength := MaxInt;
  272. end;
  273. // User may pass '' if they need to pass arguments beyond the first.
  274. if ATerminator = '' then begin
  275. ATerminator := LF;
  276. end;
  277. LTerm := ToBytes(ATerminator, AByteEncoding
  278. {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF}
  279. );
  280. LTermPos := -1;
  281. LStartPos := 0;
  282. repeat
  283. LSize := IndyMin(LStrmSize - LStrmPos, LBUFMAXSIZE);
  284. LSize := ReadTIdBytesFromStream(AStream, LTemp, LSize);
  285. if LSize < 1 then begin
  286. LStrmPos := LStrmStartPos + LBuffer.Size;
  287. Break;
  288. end;
  289. Inc(LStrmPos, LSize);
  290. LBuffer.Write(LTemp, LSize, 0);
  291. LTermPos := LBuffer.IndexOf(LTerm, LStartPos);
  292. if LTermPos > -1 then begin
  293. if (AMaxLineLength > 0) and (LTermPos > AMaxLineLength) then begin
  294. LStrmPos := LStrmStartPos + AMaxLineLength;
  295. LTermPos := AMaxLineLength;
  296. end else begin
  297. LStrmPos := LStrmStartPos + LTermPos + Length(LTerm);
  298. end;
  299. Break;
  300. end;
  301. LStartPos := IndyMax(LBuffer.Size-(Length(LTerm)-1), 0);
  302. if (AMaxLineLength > 0) and (LStartPos >= AMaxLineLength) then begin
  303. LStrmPos := LStrmStartPos + AMaxLineLength;
  304. LTermPos := AMaxLineLength;
  305. Break;
  306. end;
  307. until LStrmPos >= LStrmSize;
  308. // Extract actual data
  309. if (ATerminator = LF) and (LTermPos > 0) and (LTermPos < LBuffer.Size) then begin
  310. if (LBuffer.PeekByte(LTermPos) = Ord(LF)) and
  311. (LBuffer.PeekByte(LTermPos-1) = Ord(CR)) then begin
  312. Dec(LTermPos);
  313. end;
  314. end;
  315. AStream.Position := LStrmPos;
  316. Result := LBuffer.ExtractToString(LTermPos, AByteEncoding
  317. {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF}
  318. );
  319. finally
  320. LBuffer.Free;
  321. end;
  322. end;
  323. function TIdMessageDecoder.ReadLn(const ATerminator: string = LF;
  324. AByteEncoding: IIdTextEncoding = nil
  325. {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
  326. ): string;
  327. begin
  328. if SourceStream is TIdTCPStream then begin
  329. Result := TIdTCPStream(SourceStream).Connection.IOHandler.ReadLn(
  330. ATerminator, IdTimeoutDefault, -1, AByteEncoding{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF}
  331. );
  332. end else begin
  333. Result := DoReadLnFromStream(SourceStream, ATerminator, -1, AByteEncoding
  334. {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF}
  335. );
  336. end;
  337. end;
  338. function TIdMessageDecoder.ReadLnRFC(var VMsgEnd: Boolean;
  339. AByteEncoding: IIdTextEncoding = nil
  340. {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
  341. ): String;
  342. begin
  343. Result := ReadLnRFC(VMsgEnd, LF, '.', AByteEncoding{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF}); {do not localize}
  344. end;
  345. function TIdMessageDecoder.ReadLnRFC(var VMsgEnd: Boolean; const ALineTerminator: String;
  346. const ADelim: String = '.'; AByteEncoding: IIdTextEncoding = nil
  347. {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
  348. ): String;
  349. begin
  350. Result := ReadLn(ALineTerminator, AByteEncoding{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
  351. // Do not use ATerminator since always ends with . (standard)
  352. if Result = ADelim then {do not localize}
  353. begin
  354. VMsgEnd := True;
  355. Exit;
  356. end;
  357. if TextStartsWith(Result, '..') then begin {do not localize}
  358. IdDelete(Result, 1, 1);
  359. end;
  360. VMsgEnd := False;
  361. end;
  362. { TIdMessageEncoderInfo }
  363. constructor TIdMessageEncoderInfo.Create;
  364. begin
  365. inherited Create;
  366. end;
  367. procedure TIdMessageEncoderInfo.InitializeHeaders(AMsg: TIdMessage);
  368. begin
  369. //
  370. end;
  371. { TIdMessageEncoderList }
  372. class function TIdMessageEncoderList.ByName(const AName: string): TIdMessageEncoderInfo;
  373. var
  374. I: Integer;
  375. begin
  376. Result := nil;
  377. if GMessageEncoderList <> nil then begin
  378. I := GMessageEncoderList.FMessageCoders.IndexOf(AName);
  379. if I <> -1 then begin
  380. Result := TIdMessageEncoderInfo(GMessageEncoderList.FMessageCoders.Objects[I]);
  381. end;
  382. end;
  383. if Result = nil then begin
  384. raise EIdException.Create(RSMessageEncoderNotFound + ': ' + AName); {Do not Localize} // TODO: create a new Exception class for this
  385. end;
  386. end;
  387. constructor TIdMessageEncoderList.Create;
  388. begin
  389. inherited;
  390. FMessageCoders := TStringList.Create;
  391. end;
  392. destructor TIdMessageEncoderList.Destroy;
  393. {$IFNDEF USE_OBJECT_ARC}
  394. var
  395. i: integer;
  396. {$ENDIF}
  397. begin
  398. {$IFNDEF USE_OBJECT_ARC}
  399. for i := 0 to FMessageCoders.Count - 1 do begin
  400. TIdMessageEncoderInfo(FMessageCoders.Objects[i]).Free;
  401. end;
  402. {$ENDIF}
  403. FreeAndNil(FMessageCoders);
  404. inherited Destroy;
  405. end;
  406. class procedure TIdMessageEncoderList.RegisterEncoder(const AMessageEncoderName: string;
  407. AMessageEncoderInfo: TIdMessageEncoderInfo);
  408. begin
  409. if GMessageEncoderList = nil then begin
  410. GMessageEncoderList := TIdMessageEncoderList.Create;
  411. end;
  412. GMessageEncoderList.FMessageCoders.AddObject(AMessageEncoderName, AMessageEncoderInfo);
  413. end;
  414. { TIdMessageEncoder }
  415. procedure TIdMessageEncoder.Encode(const AFilename: string; ADest: TStream);
  416. var
  417. LSrcStream: TStream;
  418. begin
  419. LSrcStream := TIdReadFileExclusiveStream.Create(AFileName); try
  420. Encode(LSrcStream, ADest);
  421. finally FreeAndNil(LSrcStream); end;
  422. end;
  423. procedure TIdMessageEncoder.Encode(ASrc: TStream; ADest: TStrings);
  424. var
  425. LDestStream: TStream;
  426. begin
  427. // TODO: provide an Encode() implementation that can save its output directly
  428. // to ADest without having to waste memory encoding the data entirely to
  429. // memory first. In Delphi 2009+ in particular, TStrings.LoadFromStream()
  430. // wastes a lot of memory handling large streams...
  431. LDestStream := TMemoryStream.Create; try
  432. Encode(ASrc, LDestStream);
  433. LDestStream.Position := 0;
  434. ADest.LoadFromStream(LDestStream);
  435. finally FreeAndNil(LDestStream); end;
  436. end;
  437. procedure TIdMessageEncoder.InitComponent;
  438. begin
  439. inherited InitComponent;
  440. FPermissionCode := 660;
  441. end;
  442. initialization
  443. finalization
  444. FreeAndNil(GMessageDecoderList);
  445. FreeAndNil(GMessageEncoderList);
  446. end.