517 lines
15 KiB
Plaintext
517 lines
15 KiB
Plaintext
{
|
|
$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.
|