{ $Project$ $Workfile$ $Revision$ $DateUTC$ $Id$ This file is part of the Indy (Internet Direct) project, and is offered under the dual-licensing agreement described on the Indy website. (http://www.indyproject.org/) Copyright: (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. } { $Log$ } { Rev 1.15 10/26/2004 10:27:42 PM JPMugaas Updated refs. Rev 1.14 27.08.2004 22:03:58 Andreas Hausladen speed optimization ("const" for string parameters) Rev 1.13 8/10/04 1:41:00 PM RLebeau Added FreeSourceStream property to TIdMessageDecoder Rev 1.12 7/23/04 6:43:26 PM RLebeau Added extra exception handling to Encode() Rev 1.11 29/05/2004 21:22:40 CCostelloe Added support for decoding attachments with a Content-Transfer-Encoding of binary Rev 1.10 2004.05.20 1:39:12 PM czhower Last of the IdStream updates Rev 1.9 2004.05.20 11:36:56 AM czhower IdStreamVCL Rev 1.8 2004.05.20 11:12:58 AM czhower More IdStream conversions Rev 1.7 2004.05.19 3:06:38 PM czhower IdStream / .NET fix Rev 1.6 2004.02.03 5:44:02 PM czhower Name changes Rev 1.5 1/21/2004 1:17:20 PM JPMugaas InitComponent Rev 1.4 10/11/2003 4:40:24 PM BGooijen Fix for DotNet Rev 1.3 10/10/2003 10:42:54 PM BGooijen DotNet Rev 1.2 26/09/2003 01:04:22 CCostelloe Minor change, if any Rev 1.1 07/08/2003 00:46:46 CCostelloe Function ReadLnSplit added Rev 1.0 11/13/2002 07:57:04 AM JPMugaas } unit IdMessageCoder; interface {$i IdCompilerDefines.inc} uses Classes, IdComponent, IdGlobal, IdMessage, IdBaseComponent; type TIdMessageCoderPartType = (mcptText, mcptAttachment, mcptIgnore, mcptEOF); TIdMessageDecoder = class(TIdComponent) protected FFilename: string; FFreeSourceStream: Boolean; // Dont use TIdHeaderList for FHeaders - we dont know that they will all be like MIME. FHeaders: TStrings; FPartType: TIdMessageCoderPartType; FSourceStream: TStream; procedure InitComponent; override; public function ReadBody(ADestStream: TStream; var AMsgEnd: Boolean): TIdMessageDecoder; virtual; abstract; procedure ReadHeader; virtual; //CC: ATerminator param added because Content-Transfer-Encoding of binary needs //an ATerminator of EOL... function ReadLn(const ATerminator: string = LF; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; //RLebeau: added for RFC 822 retrieves function ReadLnRFC(var VMsgEnd: Boolean; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): String; overload; function ReadLnRFC(var VMsgEnd: Boolean; const ALineTerminator: String; const ADelim: String = '.'; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): String; overload; {do not localize} destructor Destroy; override; // property Filename: string read FFilename; property FreeSourceStream: Boolean read FFreeSourceStream write FFreeSourceStream; property Headers: TStrings read FHeaders; property PartType: TIdMessageCoderPartType read FPartType; property SourceStream: TStream read FSourceStream write FSourceStream; end; TIdMessageDecoderInfo = class public function CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder; virtual; abstract; constructor Create; virtual; end; TIdMessageDecoderList = class protected FMessageCoders: TStrings; public class function ByName(const AName: string): TIdMessageDecoderInfo; class function CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder; constructor Create; destructor Destroy; override; class procedure RegisterDecoder(const AMessageCoderName: string; AMessageCoderInfo: TIdMessageDecoderInfo); end; TIdMessageEncoder = class(TIdComponent) protected FFilename: string; FPermissionCode: integer; // procedure InitComponent; override; public procedure Encode(const AFilename: string; ADest: TStream); overload; procedure Encode(ASrc: TStream; ADest: TStrings); overload; procedure Encode(ASrc: TStream; ADest: TStream); overload; virtual; abstract; published property Filename: string read FFilename write FFilename; property PermissionCode: integer read FPermissionCode write FPermissionCode; end; TIdMessageEncoderClass = class of TIdMessageEncoder; TIdMessageEncoderInfo = class protected FMessageEncoderClass: TIdMessageEncoderClass; public constructor Create; virtual; procedure InitializeHeaders(AMsg: TIdMessage); virtual; // property MessageEncoderClass: TIdMessageEncoderClass read FMessageEncoderClass; end; TIdMessageEncoderList = class protected FMessageCoders: TStrings; public class function ByName(const AName: string): TIdMessageEncoderInfo; constructor Create; destructor Destroy; override; class procedure RegisterEncoder(const AMessageEncoderName: string; AMessageEncoderInfo: TIdMessageEncoderInfo); end; implementation uses IdException, IdResourceStringsProtocols, IdTCPStream, IdBuffer, SysUtils; var GMessageDecoderList: TIdMessageDecoderList = nil; GMessageEncoderList: TIdMessageEncoderList = nil; { TIdMessageDecoderList } class function TIdMessageDecoderList.ByName(const AName: string): TIdMessageDecoderInfo; var I: Integer; begin Result := nil; if GMessageDecoderList <> nil then begin I := GMessageDecoderList.FMessageCoders.IndexOf(AName); if I <> -1 then begin Result := TIdMessageDecoderInfo(GMessageDecoderList.FMessageCoders.Objects[I]); end; end; if Result = nil then begin raise EIdException.Create(RSMessageDecoderNotFound + ': ' + AName); {Do not Localize} end; end; class function TIdMessageDecoderList.CheckForStart(ASender: TIdMessage; const ALine: string): TIdMessageDecoder; var i: integer; begin Result := nil; if GMessageDecoderList <> nil then begin for i := 0 to GMessageDecoderList.FMessageCoders.Count - 1 do begin Result := TIdMessageDecoderInfo(GMessageDecoderList.FMessageCoders.Objects[i]).CheckForStart(ASender, ALine); if Result <> nil then begin Break; end; end; end; end; constructor TIdMessageDecoderList.Create; begin inherited; FMessageCoders := TStringList.Create; end; destructor TIdMessageDecoderList.Destroy; {$IFNDEF USE_OBJECT_ARC} var i: integer; {$ENDIF} begin {$IFNDEF USE_OBJECT_ARC} for i := 0 to FMessageCoders.Count - 1 do begin TIdMessageDecoderInfo(FMessageCoders.Objects[i]).Free; end; {$ENDIF} FreeAndNil(FMessageCoders); inherited Destroy; end; class procedure TIdMessageDecoderList.RegisterDecoder(const AMessageCoderName: string; AMessageCoderInfo: TIdMessageDecoderInfo); begin if GMessageDecoderList = nil then begin GMessageDecoderList := TIdMessageDecoderList.Create; end; GMessageDecoderList.FMessageCoders.AddObject(AMessageCoderName, AMessageCoderInfo); end; { TIdMessageDecoderInfo } constructor TIdMessageDecoderInfo.Create; begin inherited Create; end; { TIdMessageDecoder } procedure TIdMessageDecoder.InitComponent; begin inherited; FFreeSourceStream := True; FHeaders := TStringList.Create; end; destructor TIdMessageDecoder.Destroy; begin FreeAndNil(FHeaders); if FFreeSourceStream then begin FreeAndNil(FSourceStream); end else begin FSourceStream := nil; end; inherited Destroy; end; procedure TIdMessageDecoder.ReadHeader; begin end; // this is copied from TIdIOHandler.ReadLn() and then adjusted to read from // a TStream, with the same sematics as Idglobal.ReadLnFromStream() but with // support for searching for a caller-specified terminator. function DoReadLnFromStream(AStream: TStream; ATerminator: string; AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; const LBUFMAXSIZE = 2048; var LBuffer: TIdBuffer; LSize: Integer; LStartPos: Integer; LTermPos: Integer; LTerm, LTemp: TIdBytes; LStrmStartPos, LStrmPos, LStrmSize: TIdStreamSize; begin Assert(AStream<>nil); LTerm := nil; // keep the compiler happy { we store the stream size for the whole routine to prevent so do not incur a performance penalty with TStream.Size. It has to use something such as Seek each time the size is obtained} {4 seek vs 3 seek} LStrmStartPos := AStream.Position; LStrmPos := LStrmStartPos; LStrmSize := AStream.Size; if LStrmPos >= LStrmSize then begin Result := ''; Exit; end; SetLength(LTemp, LBUFMAXSIZE); LBuffer := TIdBuffer.Create; try EnsureEncoding(AByteEncoding); {$IFDEF STRING_IS_ANSI} EnsureEncoding(ADestEncoding, encOSDefault); {$ENDIF} if AMaxLineLength < 0 then begin AMaxLineLength := MaxInt; end; // User may pass '' if they need to pass arguments beyond the first. if ATerminator = '' then begin ATerminator := LF; end; LTerm := ToBytes(ATerminator, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); LTermPos := -1; LStartPos := 0; repeat LSize := IndyMin(LStrmSize - LStrmPos, LBUFMAXSIZE); LSize := ReadTIdBytesFromStream(AStream, LTemp, LSize); if LSize < 1 then begin LStrmPos := LStrmStartPos + LBuffer.Size; Break; end; Inc(LStrmPos, LSize); LBuffer.Write(LTemp, LSize, 0); LTermPos := LBuffer.IndexOf(LTerm, LStartPos); if LTermPos > -1 then begin if (AMaxLineLength > 0) and (LTermPos > AMaxLineLength) then begin LStrmPos := LStrmStartPos + AMaxLineLength; LTermPos := AMaxLineLength; end else begin LStrmPos := LStrmStartPos + LTermPos + Length(LTerm); end; Break; end; LStartPos := IndyMax(LBuffer.Size-(Length(LTerm)-1), 0); if (AMaxLineLength > 0) and (LStartPos >= AMaxLineLength) then begin LStrmPos := LStrmStartPos + AMaxLineLength; LTermPos := AMaxLineLength; Break; end; until LStrmPos >= LStrmSize; // Extract actual data if (ATerminator = LF) and (LTermPos > 0) and (LTermPos < LBuffer.Size) then begin if (LBuffer.PeekByte(LTermPos) = Ord(LF)) and (LBuffer.PeekByte(LTermPos-1) = Ord(CR)) then begin Dec(LTermPos); end; end; AStream.Position := LStrmPos; Result := LBuffer.ExtractToString(LTermPos, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); finally LBuffer.Free; end; end; function TIdMessageDecoder.ReadLn(const ATerminator: string = LF; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; begin if SourceStream is TIdTCPStream then begin Result := TIdTCPStream(SourceStream).Connection.IOHandler.ReadLn( ATerminator, IdTimeoutDefault, -1, AByteEncoding{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end else begin Result := DoReadLnFromStream(SourceStream, ATerminator, -1, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; end; function TIdMessageDecoder.ReadLnRFC(var VMsgEnd: Boolean; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): String; begin Result := ReadLnRFC(VMsgEnd, LF, '.', AByteEncoding{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF}); {do not localize} end; function TIdMessageDecoder.ReadLnRFC(var VMsgEnd: Boolean; const ALineTerminator: String; const ADelim: String = '.'; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): String; begin Result := ReadLn(ALineTerminator, AByteEncoding{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF}); // Do not use ATerminator since always ends with . (standard) if Result = ADelim then {do not localize} begin VMsgEnd := True; Exit; end; if TextStartsWith(Result, '..') then begin {do not localize} IdDelete(Result, 1, 1); end; VMsgEnd := False; end; { TIdMessageEncoderInfo } constructor TIdMessageEncoderInfo.Create; begin inherited Create; end; procedure TIdMessageEncoderInfo.InitializeHeaders(AMsg: TIdMessage); begin // end; { TIdMessageEncoderList } class function TIdMessageEncoderList.ByName(const AName: string): TIdMessageEncoderInfo; var I: Integer; begin Result := nil; if GMessageEncoderList <> nil then begin I := GMessageEncoderList.FMessageCoders.IndexOf(AName); if I <> -1 then begin Result := TIdMessageEncoderInfo(GMessageEncoderList.FMessageCoders.Objects[I]); end; end; if Result = nil then begin raise EIdException.Create(RSMessageEncoderNotFound + ': ' + AName); {Do not Localize} end; end; constructor TIdMessageEncoderList.Create; begin inherited; FMessageCoders := TStringList.Create; end; destructor TIdMessageEncoderList.Destroy; {$IFNDEF USE_OBJECT_ARC} var i: integer; {$ENDIF} begin {$IFNDEF USE_OBJECT_ARC} for i := 0 to FMessageCoders.Count - 1 do begin TIdMessageEncoderInfo(FMessageCoders.Objects[i]).Free; end; {$ENDIF} FreeAndNil(FMessageCoders); inherited Destroy; end; class procedure TIdMessageEncoderList.RegisterEncoder(const AMessageEncoderName: string; AMessageEncoderInfo: TIdMessageEncoderInfo); begin if GMessageEncoderList = nil then begin GMessageEncoderList := TIdMessageEncoderList.Create; end; GMessageEncoderList.FMessageCoders.AddObject(AMessageEncoderName, AMessageEncoderInfo); end; { TIdMessageEncoder } procedure TIdMessageEncoder.Encode(const AFilename: string; ADest: TStream); var LSrcStream: TStream; begin LSrcStream := TIdReadFileExclusiveStream.Create(AFileName); try Encode(LSrcStream, ADest); finally FreeAndNil(LSrcStream); end; end; procedure TIdMessageEncoder.Encode(ASrc: TStream; ADest: TStrings); var LDestStream: TStream; begin // TODO: provide an Encode() implementation that can save its output directly // to ADest without having to waste memory encoding the data entirely to // memory first. In Delphi 2009+ in particular, TStrings.LoadFromStream() // wastes a lot of memory handling large streams... LDestStream := TMemoryStream.Create; try Encode(ASrc, LDestStream); LDestStream.Position := 0; ADest.LoadFromStream(LDestStream); finally FreeAndNil(LDestStream); end; end; procedure TIdMessageEncoder.InitComponent; begin inherited InitComponent; FPermissionCode := 660; end; initialization finalization FreeAndNil(GMessageDecoderList); FreeAndNil(GMessageEncoderList); end.