{ $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.123 2/8/05 5:27:06 PM RLebeau Bug fix for ReadLn(). Added try..finally block to ReadLnSplit(). Rev 1.122 1/27/05 3:09:30 PM RLebeau Updated AllData() to call ReadFromSource() directly instead of using CheckForDataOnSource(), since ReadFromSource() can return a disconnect conditon. When data is in the InputBuffer, Connected() always return True even if the socket is actually disconnected. Rev 1.121 12/21/04 3:21:40 AM RLebeau Removed compiler warning Rev 1.120 17/12/2004 17:11:28 ANeillans Compiler fix Rev 1.119 12/12/04 2:23:52 PM RLebeau Added WriteRFCStrings() method Rev 1.118 12/11/2004 9:04:50 PM DSiders Fixed comparison error in WaitFor. Rev 1.117 12/10/04 2:00:24 PM RLebeau Updated WaitFor() to not return more data than actually needed. Updated AllData() to not concatenate the Result on every iteration of the loop. Rev 1.116 11/29/04 10:37:18 AM RLebeau Updated write buffering methods to prevent Access Violations when used incorrectly. Rev 1.115 11/4/04 12:41:08 PM RLebeau Bug fix for ReadLn() Rev 1.114 10/26/2004 8:43:00 PM JPMugaas Should be more portable with new references to TIdStrings and TIdStringList. Rev 1.113 27.08.2004 21:58:18 Andreas Hausladen Speed optimization ("const" for string parameters) Rev 1.112 8/2/04 5:49:20 PM RLebeau Moved ConnectTimeout over from TIdIOHandlerSocket Rev 1.111 2004.08.01 19:36:14 czhower Code optimization to WriteFile Rev 1.110 7/24/04 12:53:54 PM RLebeau Compiler fix for WriteFile() Rev 1.109 7/23/04 6:39:14 PM RLebeau Added extra exception handling to WriteFile() Rev 1.108 7/21/2004 5:45:10 PM JPMugaas Updated with Remy's change. This should work better and fix a problem with looping with ReadStream and ReadUntilDisconnect. Rev 1.107 7/21/2004 12:22:18 PM BGooijen Reverted back 2 versions Rev 1.104 6/29/04 12:16:16 PM RLebeau Updated ReadChar() to call ReadBytes() directly instead of ReadString() Rev 1.103 6/17/04 3:01:56 PM RLebeau Changed ReadStream() to not extract too many bytes from the InputBuffer when an error occurs Rev 1.102 6/12/04 11:36:44 AM RLebeau Changed ReadString() to pass the ABytes parameter to ReadBytes() instead of the LBuf length Rev 1.100 6/10/2004 6:52:12 PM JPMugaas Regeneration to fix a bug in the package generator that I created. OOPS!!! Rev 1.99 6/9/04 7:36:26 PM RLebeau ReadString() bug fix Rev 1.98 07/06/2004 20:55:36 CCostelloe Fix for possible memory leak. Rev 1.97 5/29/04 10:46:24 PM RLebeau Updated AllData() to only append values to the result when there is actual data in the buffer. Rev 1.96 29/05/2004 21:07:40 CCostelloe Bug fix (may need more investigation) Rev 1.95 2004.05.20 1:39:54 PM czhower Last of the IdStream updates Rev 1.94 2004.05.20 12:34:22 PM czhower Removed more non .NET compatible stream read and writes Rev 1.93 2004.05.20 11:39:02 AM czhower IdStreamVCL Rev 1.92 5/3/2004 12:57:00 PM BGooijen Fixes for 0-based Rev 1.91 2004.05.03 11:15:44 AM czhower Changed Find to IndexOf and made 0 based to be consistent. Rev 1.90 4/24/04 12:40:04 PM RLebeau Added Write() overload for Char type. Rev 1.89 4/18/2004 11:58:00 PM BGooijen ReadBytes with count=-1 reads everything available, ( and waits ReadTimeOut time for data) Rev 1.88 4/18/04 2:44:24 PM RLebeau Read/write support for Int64 values Rev 1.87 2004.04.18 12:51:58 AM czhower Big bug fix with server disconnect and several other bug fixed that I found along the way. Rev 1.86 2004.04.16 11:30:28 PM czhower Size fix to IdBuffer, optimizations, and memory leaks Rev 1.85 2004.04.08 7:06:46 PM czhower Peek support. Rev 1.84 2004.04.08 3:56:28 PM czhower Fixed bug with Intercept byte count. Also removed Bytes from Buffer. Rev 1.83 2004.04.08 2:08:00 AM czhower Saved before checkin this time... Rev 1.82 7/4/2004 4:08:46 PM SGrobety Re-introduce the IOHandler.MaxCapturedLines property Rev 1.81 2004.04.07 3:59:46 PM czhower Bug fix for WriteDirect. Rev 1.79 2004.03.07 11:48:38 AM czhower Flushbuffer fix + other minor ones found Rev 1.78 2004.03.03 11:54:58 AM czhower IdStream change Rev 1.77 2004.03.02 2:47:08 PM czhower .Net overloads Rev 1.76 2004.03.01 5:12:28 PM czhower -Bug fix for shutdown of servers when connections still existed (AV) -Implicit HELP support in CMDserver -Several command handler bugs -Additional command handler functionality. Rev 1.75 2004.02.03 4:16:44 PM czhower For unit name changes. Rev 1.74 2004.01.21 9:36:00 PM czhower .Net overload Rev 1.73 2004.01.21 12:19:58 AM czhower .Readln overload for .net Rev 1.72 2004.01.20 10:03:26 PM czhower InitComponent Rev 1.71 1/11/2004 5:51:04 PM BGooijen Added AApend parameter to ReadBytes Rev 1.70 12/30/2003 7:17:56 PM BGooijen .net Rev 1.69 2003.12.28 1:05:54 PM czhower .Net changes. Rev 1.68 2003.12.28 11:53:28 AM czhower Removed warning in .net. Rev 1.67 2003.11.29 10:15:30 AM czhower InternalBuffer --> InputBuffer for consistency. Rev 1.66 11/23/03 1:46:28 PM RLebeau Removed "var" specifier from TStrings parameter of ReadStrings(). Rev 1.65 11/4/2003 10:27:56 PM DSiders Removed exceptions moved to IdException.pas. Rev 1.64 2003.10.24 10:44:52 AM czhower IdStream implementation, bug fixes. Rev 1.63 10/22/03 2:05:40 PM RLebeau Fix for TIdIOHandler::Write(TStream) where it was not reading the stream into the TIdBytes correctly. Rev 1.62 10/19/2003 5:55:44 PM BGooijen Fixed todo in PerformCapture Rev 1.61 2003.10.18 12:58:50 PM czhower Added comment Rev 1.60 2003.10.18 12:42:04 PM czhower Intercept.Disconnect is now called Rev 1.59 10/15/2003 7:39:28 PM DSiders Added a formatted resource string for the exception raised in TIdIOHandler.MakeIOHandler. Rev 1.58 2003.10.14 1:26:50 PM czhower Uupdates + Intercept support Rev 1.57 2003.10.11 5:48:22 PM czhower -VCL fixes for servers -Chain suport for servers (Super core) -Scheduler upgrades -Full yarn support Rev 1.56 9/10/2003 1:50:38 PM SGrobety Removed all "const" keywords from boolean parameter interfaces. Might trigger changes in other units. Rev 1.55 10/5/2003 10:39:56 PM BGooijen Write buffering Rev 1.54 10/4/2003 11:03:12 PM BGooijen ReadStream, and functions with network ordering Rev 1.53 10/4/2003 7:10:46 PM BGooijen ReadXXXXX Rev 1.52 10/4/2003 3:55:02 PM BGooijen ReadString, and some Write functions Rev 1.51 04/10/2003 13:38:32 HHariri Write(Integer) support Rev 1.50 10/3/2003 12:09:30 AM BGooijen DotNet Rev 1.49 2003.10.02 8:29:14 PM czhower Changed names of byte conversion routines to be more readily understood and not to conflict with already in use ones. Rev 1.48 2003.10.02 1:18:50 PM czhower Changed read methods to be overloaded and more consistent. Will break some code, but nearly all code that uses them is Input. Rev 1.47 2003.10.02 10:16:26 AM czhower .Net Rev 1.46 2003.10.01 9:11:16 PM czhower .Net Rev 1.45 2003.10.01 2:46:36 PM czhower .Net Rev 1.42 2003.10.01 11:16:32 AM czhower .Net Rev 1.41 2003.10.01 1:37:34 AM czhower .Net Rev 1.40 2003.10.01 1:12:34 AM czhower .Net Rev 1.39 2003.09.30 1:22:56 PM czhower Stack split for DotNet Rev 1.38 2003.09.18 5:17:58 PM czhower Implemented OnWork Rev 1.37 2003.08.21 10:43:42 PM czhower Fix to ReadStream from Doychin Rev 1.36 08/08/2003 17:32:26 CCostelloe Removed "virtual" from function ReadLnSplit Rev 1.35 07/08/2003 00:25:08 CCostelloe Function ReadLnSplit added Rev 1.34 2003.07.17 1:05:12 PM czhower More IOCP improvements. Rev 1.33 2003.07.14 11:00:50 PM czhower More IOCP fixes. Rev 1.32 2003.07.14 12:54:30 AM czhower Fixed graceful close detection if it occurs after connect. Rev 1.31 2003.07.10 7:40:24 PM czhower Comments Rev 1.30 2003.07.10 4:34:56 PM czhower Fixed AV, added some new comments Rev 1.29 7/1/2003 5:50:44 PM BGooijen Fixed ReadStream Rev 1.28 6/30/2003 10:26:08 AM BGooijen forgot to remove some code regarding to TIdBuffer.Find Rev 1.27 6/29/2003 10:56:26 PM BGooijen Removed .Memory from the buffer, and added some extra methods Rev 1.26 2003.06.25 4:30:00 PM czhower Temp hack fix for AV problem. Working on real solution now. Rev 1.25 23/6/2003 22:33:14 GGrieve fix CheckForDataOnSource - specify timeout Rev 1.24 23/6/2003 06:46:52 GGrieve allow block on checkForData Rev 1.23 6/4/2003 1:07:08 AM BGooijen changed comment Rev 1.22 6/3/2003 10:40:34 PM BGooijen FRecvBuffer bug fixed, it was freed, but never recreated, resulting in an AV Rev 1.21 2003.06.03 6:28:04 PM czhower Made check for data virtual Rev 1.20 2003.06.03 3:43:24 PM czhower Resolved InputBuffer inconsistency. Added new method and renamed old one. Rev 1.19 5/25/2003 03:56:04 AM JPMugaas Updated for unit rename. Rev 1.18 2003.04.17 11:01:12 PM czhower Rev 1.17 4/16/2003 3:29:30 PM BGooijen minor change in ReadBuffer Rev 1.16 4/1/2003 7:54:24 PM BGooijen ReadLn default terminator changed to LF Rev 1.15 3/27/2003 3:24:06 PM BGooijen MaxLine* is now published Rev 1.14 2003.03.25 7:42:12 PM czhower try finally to WriteStrings Rev 1.13 3/24/2003 11:01:36 PM BGooijen WriteStrings is now buffered to increase speed Rev 1.12 3/19/2003 1:02:32 PM BGooijen changed class function ConstructDefaultIOHandler a little (default parameter) Rev 1.11 3/13/2003 10:18:16 AM BGooijen Server side fibers, bug fixes Rev 1.10 3/5/2003 11:03:06 PM BGooijen Added Intercept here Rev 1.9 2/25/2003 11:02:12 PM BGooijen InputBufferToStream now accepts a bytecount Rev 1.8 2003.02.25 1:36:00 AM czhower Rev 1.7 12-28-2002 22:28:16 BGooijen removed warning, added initialization and finalization part. Rev 1.6 12-16-2002 20:43:28 BGooijen Added class function ConstructIOHandler(....), and removed some comments Rev 1.5 12-15-2002 23:02:38 BGooijen added SendBufferSize Rev 1.4 12-15-2002 20:50:32 BGooijen FSendBufferSize was not initialized Rev 1.3 12-14-2002 22:14:54 BGooijen improved method to detect timeouts in ReadLn. Rev 1.2 12/11/2002 04:09:28 AM JPMugaas Updated for new API. Rev 1.1 2002.12.07 12:25:56 AM czhower Rev 1.0 11/13/2002 08:44:50 AM JPMugaas } unit IdIOHandler; interface {$I IdCompilerDefines.inc} uses Classes, IdException, IdAntiFreezeBase, IdBuffer, IdBaseComponent, IdComponent, IdGlobal, IdExceptionCore, IdIntercept, IdResourceStringsCore, IdStream; (*$HPPEMIT '#if defined(_VCL_ALIAS_RECORDS)' *) (*$HPPEMIT '#if !defined(UNICODE)' *) (*$HPPEMIT '#pragma alias "@Idiohandler@TIdIOHandler@SetPortA$qqri"="@Idiohandler@TIdIOHandler@SetPort$qqri"' *) (*$HPPEMIT '#else' *) (*$HPPEMIT '#pragma alias "@Idiohandler@TIdIOHandler@SetPortW$qqri"="@Idiohandler@TIdIOHandler@SetPort$qqri"' *) (*$HPPEMIT '#endif' *) (*$HPPEMIT '#endif' *) const GRecvBufferSizeDefault = 32 * 1024; GSendBufferSizeDefault = 32 * 1024; IdMaxLineLengthDefault = 16 * 1024; // S.G. 6/4/2004: Maximum number of lines captured // S.G. 6/4/2004: Default to "unlimited" Id_IOHandler_MaxCapturedLines = -1; type EIdIOHandler = class(EIdException); EIdIOHandlerRequiresLargeStream = class(EIdIOHandler); EIdIOHandlerStreamDataTooLarge = class(EIdIOHandler); TIdIOHandlerClass = class of TIdIOHandler; { How does this fit in in the hierarchy against TIdIOHandlerSocket Destination - Socket - otehr file descendats it TIdIOHandler should only implement an interface. No default functionality except very simple read/write functions such as ReadUInt32, etc. Functions that cannot really be optimized beyond their default implementations. Some default implementations offer basic non optmized implementations. Yes, I know this comment conflicts. Its being worked on. } TIdIOHandler = class(TIdComponent) private FLargeStream: Boolean; protected FClosedGracefully: Boolean; FConnectTimeout: Integer; FDestination: string; FHost: string; // IOHandlers typically receive more data than they need to complete each // request. They store this extra data in InputBuffer for future methods to // use. InputBuffer is what collects the input and keeps it if the current // method does not need all of it. // FInputBuffer: TIdBuffer; {$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FIntercept: TIdConnectionIntercept; FMaxCapturedLines: Integer; FMaxLineAction: TIdMaxLineAction; FMaxLineLength: Integer; FOpened: Boolean; FPort: Integer; FReadLnSplit: Boolean; FReadLnTimedOut: Boolean; FReadTimeOut: Integer; //TODO: FRecvBufferSize: Integer; FSendBufferSize: Integer; FWriteBuffer: TIdBuffer; FWriteBufferThreshold: Integer; FDefStringEncoding : IIdTextEncoding; {$IFDEF STRING_IS_ANSI} FDefAnsiEncoding : IIdTextEncoding; {$ENDIF} procedure SetDefStringEncoding(const AEncoding : IIdTextEncoding); {$IFDEF STRING_IS_ANSI} procedure SetDefAnsiEncoding(const AEncoding: IIdTextEncoding); {$ENDIF} // procedure BufferRemoveNotify(ASender: TObject; ABytes: Integer); function GetDestination: string; virtual; procedure InitComponent; override; procedure InterceptReceive(var VBuffer: TIdBytes); {$IFNDEF USE_OBJECT_ARC} procedure Notification(AComponent: TComponent; Operation: TOperation); override; {$ENDIF} procedure PerformCapture(const ADest: TObject; out VLineCount: Integer; const ADelim: string; AUsesDotTransparency: Boolean; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); virtual; procedure RaiseConnClosedGracefully; procedure SetDestination(const AValue: string); virtual; procedure SetHost(const AValue: string); virtual; procedure SetPort(AValue: Integer); virtual; procedure SetIntercept(AValue: TIdConnectionIntercept); virtual; // This is the main Read function which all other default implementations // use. function ReadFromSource(ARaiseExceptionIfDisconnected: Boolean = True; ATimeout: Integer = IdTimeoutDefault; ARaiseExceptionOnTimeout: Boolean = True): Integer; function ReadDataFromSource(var VBuffer: TIdBytes): Integer; virtual; abstract; function WriteDataToTarget(const ABuffer: TIdBytes; const AOffset, ALength: Integer): Integer; virtual; abstract; function SourceIsAvailable: Boolean; virtual; abstract; function CheckForError(ALastResult: Integer): Integer; virtual; abstract; procedure RaiseError(AError: Integer); virtual; abstract; public procedure AfterAccept; virtual; function Connected: Boolean; virtual; destructor Destroy; override; // CheckForDisconnect allows the implementation to check the status of the // connection at the request of the user or this base class. procedure CheckForDisconnect(ARaiseExceptionIfDisconnected: Boolean = True; AIgnoreBuffer: Boolean = False); virtual; abstract; // Does not wait or raise any exceptions. Just reads whatever data is // available (if any) into the buffer. Must NOT raise closure exceptions. // It is used to get avialable data, and check connection status. That is // it can set status flags about the connection. function CheckForDataOnSource(ATimeout: Integer = 0): Boolean; virtual; procedure Close; virtual; procedure CloseGracefully; virtual; class function MakeDefaultIOHandler(AOwner: TComponent = nil) : TIdIOHandler; class function MakeIOHandler(ABaseType: TIdIOHandlerClass; AOwner: TComponent = nil): TIdIOHandler; // Variant of MakeIOHandler() which returns nil if it cannot find a registered IOHandler class function TryMakeIOHandler(ABaseType: TIdIOHandlerClass; AOwner: TComponent = nil): TIdIOHandler; class procedure RegisterIOHandler; class procedure SetDefaultClass; function WaitFor(const AString: string; ARemoveFromBuffer: Boolean = True; AInclusive: Boolean = False; AByteEncoding: IIdTextEncoding = nil; ATimeout: Integer = IdTimeoutDefault {$IFDEF STRING_IS_ANSI}; AAnsiEncoding: IIdTextEncoding = nil{$ENDIF} ): string; // This is different than WriteDirect. WriteDirect goes // directly to the network or next level. WriteBuffer allows for buffering // using WriteBuffers. This should be the only call to WriteDirect // unless the calls that bypass this are aware of WriteBuffering or are // intended to bypass it. procedure Write(const ABuffer: TIdBytes; const ALength: Integer = -1; const AOffset: Integer = 0); overload; virtual; // This is the main write function which all other default implementations // use. If default implementations are used, this must be implemented. procedure WriteDirect(const ABuffer: TIdBytes; const ALength: Integer = -1; const AOffset: Integer = 0); // procedure Open; virtual; function Readable(AMSec: Integer = IdTimeoutDefault): Boolean; virtual; // // Optimal Extra Methods // // These methods are based on the core methods. While they can be // overridden, they are so simple that it is rare a more optimal method can // be implemented. Because of this they are not overrideable. // // // Write Methods // // Only the ones that have a hope of being better optimized in descendants // have been marked virtual procedure Write(const AOut: string; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; virtual; procedure WriteLn(AEncoding: IIdTextEncoding = nil); overload; procedure WriteLn(const AOut: string; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; virtual; procedure WriteLnRFC(const AOut: string = ''; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); virtual; procedure Write(AValue: TStrings; AWriteLinesCount: Boolean = False; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; virtual; procedure Write(AValue: Byte); overload; procedure Write(AValue: Char; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; // for iOS64, Delphi's Longint and LongWord are 64bit, so we can't rely on // Write(Longint) and ReadLongint() being 32bit anymore, for instance when // sending/reading a TStream with LargeStream=False. So adding new (U)IntX // methods and deprecating the old ones... // procedure Write(AValue: Int16; AConvert: Boolean = True); overload; procedure Write(AValue: UInt16; AConvert: Boolean = True); overload; procedure Write(AValue: Int32; AConvert: Boolean = True); overload; procedure Write(AValue: UInt32; AConvert: Boolean = True); overload; procedure Write(AValue: Int64; AConvert: Boolean = True); overload; procedure Write(AValue: TIdUInt64; AConvert: Boolean = True); overload; // procedure Write(AStream: TStream; ASize: TIdStreamSize = 0; AWriteByteCount: Boolean = False); overload; virtual; procedure WriteRFCStrings(AStrings: TStrings; AWriteTerminator: Boolean = True; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); // Not overloaded because it does not have a unique type for source // and could be easily unresolvable with future additions function WriteFile(const AFile: String; AEnableTransferFile: Boolean = False): Int64; virtual; // // Read methods // function AllData(AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; virtual; function InputLn(const AMask: string = ''; AEcho: Boolean = True; ATabWidth: Integer = 8; AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; AAnsiEncoding: IIdTextEncoding = nil{$ENDIF} ): string; virtual; // Capture // Not virtual because each calls PerformCapture which is virtual procedure Capture(ADest: TStream; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; // .Net overload procedure Capture(ADest: TStream; ADelim: string; AUsesDotTransparency: Boolean = True; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; procedure Capture(ADest: TStream; out VLineCount: Integer; const ADelim: string = '.'; AUsesDotTransparency: Boolean = True; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; procedure Capture(ADest: TStrings; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; // .Net overload procedure Capture(ADest: TStrings; const ADelim: string; AUsesDotTransparency: Boolean = True; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; procedure Capture(ADest: TStrings; out VLineCount: Integer; const ADelim: string = '.'; AUsesDotTransparency: Boolean = True; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; // // Read___ // Cannot overload, compiler cannot overload on return values // procedure ReadBytes(var VBuffer: TIdBytes; AByteCount: Integer; AAppend: Boolean = True); virtual; // ReadLn function ReadLn(AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; overload; // .Net overload function ReadLn(ATerminator: string; AByteEncoding: IIdTextEncoding {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; overload; function ReadLn(ATerminator: string; ATimeout: Integer = IdTimeoutDefault; AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; overload; virtual; //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; function ReadLnWait(AFailCount: Integer = MaxInt; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; virtual; // Added for retrieving lines over 16K long} function ReadLnSplit(var AWasSplit: Boolean; ATerminator: string = LF; ATimeout: Integer = IdTimeoutDefault; AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; // Read - Simple Types function ReadChar(AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): Char; function ReadByte: Byte; function ReadString(ABytes: Integer; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; // for iOS64, Delphi's Longint and LongWord are changed to 64bit, so we can't // rely on Write(Longint) and ReadLongint() being 32bit anymore, for instance // when sending/reading a TStream with LargeStream=False. So adding new (U)IntX // methods and deprecating the old ones... // function ReadInt16(AConvert: Boolean = True): Int16; function ReadUInt16(AConvert: Boolean = True): UInt16; function ReadInt32(AConvert: Boolean = True): Int32; function ReadUInt32(AConvert: Boolean = True): UInt32; function ReadInt64(AConvert: Boolean = True): Int64; function ReadUInt64(AConvert: Boolean = True): TIdUInt64; // function ReadSmallInt(AConvert: Boolean = True): Int16; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use ReadInt16()'{$ENDIF};{$ENDIF} function ReadWord(AConvert: Boolean = True): UInt16; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use ReadUInt16()'{$ENDIF};{$ENDIF} function ReadLongInt(AConvert: Boolean = True): Int32; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use ReadInt32()'{$ENDIF};{$ENDIF} function ReadLongWord(AConvert: Boolean = True): UInt32; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use ReadUInt32()'{$ENDIF};{$ENDIF} // procedure ReadStream(AStream: TStream; AByteCount: TIdStreamSize = -1; AReadUntilDisconnect: Boolean = False); virtual; procedure ReadStrings(ADest: TStrings; AReadLinesCount: Integer = -1; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); // procedure Discard(AByteCount: Int64); procedure DiscardAll; // // WriteBuffering Methods // procedure WriteBufferCancel; virtual; procedure WriteBufferClear; virtual; procedure WriteBufferClose; virtual; procedure WriteBufferFlush; overload; //.Net overload procedure WriteBufferFlush(AByteCount: Integer); overload; virtual; procedure WriteBufferOpen; overload; //.Net overload procedure WriteBufferOpen(AThreshold: Integer); overload; virtual; function WriteBufferingActive: Boolean; // // InputBuffer Methods // function InputBufferIsEmpty: Boolean; // // These two are direct access and do no reading of connection procedure InputBufferToStream(AStream: TStream; AByteCount: Integer = -1); function InputBufferAsString(AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; // // Properties // property ConnectTimeout: Integer read FConnectTimeout write FConnectTimeout default 0; property ClosedGracefully: Boolean read FClosedGracefully; // TODO: Need to name this consistent. Originally no access was allowed, // but new model requires it for writing. Will decide after next set // of changes are complete what to do with Buffer prop. // // Is used by SuperCore property InputBuffer: TIdBuffer read FInputBuffer; //currently an option, as LargeFile support changes the data format property LargeStream: Boolean read FLargeStream write FLargeStream; property MaxCapturedLines: Integer read FMaxCapturedLines write FMaxCapturedLines default Id_IOHandler_MaxCapturedLines; property Opened: Boolean read FOpened; property ReadTimeout: Integer read FReadTimeOut write FReadTimeOut default IdTimeoutDefault; property ReadLnTimedout: Boolean read FReadLnTimedout ; property WriteBufferThreshold: Integer read FWriteBufferThreshold; property DefStringEncoding : IIdTextEncoding read FDefStringEncoding write SetDefStringEncoding; {$IFDEF STRING_IS_ANSI} property DefAnsiEncoding : IIdTextEncoding read FDefAnsiEncoding write SetDefAnsiEncoding; {$ENDIF} // // Events // property OnWork; property OnWorkBegin; property OnWorkEnd; published property Destination: string read GetDestination write SetDestination; property Host: string read FHost write SetHost; property Intercept: TIdConnectionIntercept read FIntercept write SetIntercept; property MaxLineLength: Integer read FMaxLineLength write FMaxLineLength default IdMaxLineLengthDefault; property MaxLineAction: TIdMaxLineAction read FMaxLineAction write FMaxLineAction; property Port: Integer read FPort write SetPort; // RecvBufferSize is used by some methods that read large amounts of data. // RecvBufferSize is the amount of data that will be requested at each read // cycle. RecvBuffer is used to receive then send to the Intercepts, after // that it goes to InputBuffer property RecvBufferSize: Integer read FRecvBufferSize write FRecvBufferSize default GRecvBufferSizeDefault; // SendBufferSize is used by some methods that have to break apart large // amounts of data into smaller pieces. This is the buffer size of the // chunks that it will create and use. property SendBufferSize: Integer read FSendBufferSize write FSendBufferSize default GSendBufferSizeDefault; end; implementation uses //facilitate inlining only. {$IFDEF DOTNET} {$IFDEF USE_INLINE} System.IO, {$ENDIF} {$ENDIF} {$IFDEF WIN32_OR_WIN64} Windows, {$ENDIF} {$IFDEF USE_VCL_POSIX} {$IFDEF DARWIN} Macapi.CoreServices, {$ENDIF} {$ENDIF} {$IFDEF HAS_UNIT_Generics_Collections} System.Generics.Collections, {$ENDIF} IdStack, IdStackConsts, IdResourceStrings, SysUtils; type {$IFDEF HAS_GENERICS_TList} TIdIOHandlerClassList = TList; {$ELSE} // TODO: flesh out to match TList for non-Generics compilers TIdIOHandlerClassList = TList; {$ENDIF} var GIOHandlerClassDefault: TIdIOHandlerClass = nil; GIOHandlerClassList: TIdIOHandlerClassList = nil; {$IFDEF DCC} {$IFNDEF VCL_7_OR_ABOVE} // RLebeau 5/13/2015: The Write(Int64) and ReadInt64() methods produce an // "Internal error URW533" compiler error in Delphi 5, and an "Internal // error URW699" compiler error in Delphi 6, so need to use some workarounds // for those versions... {$DEFINE AVOID_URW_ERRORS} {$ENDIF} {$ENDIF} { TIdIOHandler } procedure TIdIOHandler.Close; //do not do FInputBuffer.Clear; here. //it breaks reading when remote connection does a disconnect var // under ARC, convert a weak reference to a strong reference before working with it LIntercept: TIdConnectionIntercept; begin try LIntercept := Intercept; if LIntercept <> nil then begin LIntercept.Disconnect; end; finally FOpened := False; WriteBufferClear; end; end; destructor TIdIOHandler.Destroy; begin Close; FreeAndNil(FInputBuffer); FreeAndNil(FWriteBuffer); inherited Destroy; end; procedure TIdIOHandler.AfterAccept; begin // end; procedure TIdIOHandler.Open; begin FOpened := False; FClosedGracefully := False; WriteBufferClear; FInputBuffer.Clear; FOpened := True; end; // under ARC, all weak references to a freed object get nil'ed automatically {$IFNDEF USE_OBJECT_ARC} procedure TIdIOHandler.Notification(AComponent: TComponent; Operation: TOperation); begin if (Operation = opRemove) and (AComponent = FIntercept) then begin FIntercept := nil; end; inherited Notification(AComponent, OPeration); end; {$ENDIF} procedure TIdIOHandler.SetIntercept(AValue: TIdConnectionIntercept); begin {$IFDEF USE_OBJECT_ARC} // under ARC, all weak references to a freed object get nil'ed automatically FIntercept := AValue; {$ELSE} if FIntercept <> AValue then begin // remove self from the Intercept's free notification list if Assigned(FIntercept) then begin FIntercept.RemoveFreeNotification(Self); end; FIntercept := AValue; // add self to the Intercept's free notification list if Assigned(AValue) then begin AValue.FreeNotification(Self); end; end; {$ENDIF} end; class procedure TIdIOHandler.SetDefaultClass; begin GIOHandlerClassDefault := Self; RegisterIOHandler; end; procedure TIdIOHandler.SetDefStringEncoding(const AEncoding: IIdTextEncoding); var LEncoding: IIdTextEncoding; begin if FDefStringEncoding <> AEncoding then begin LEncoding := AEncoding; EnsureEncoding(LEncoding); FDefStringEncoding := LEncoding; end; end; {$IFDEF STRING_IS_ANSI} procedure TIdIOHandler.SetDefAnsiEncoding(const AEncoding: IIdTextEncoding); var LEncoding: IIdTextEncoding; begin if FDefAnsiEncoding <> AEncoding then begin LEncoding := AEncoding; EnsureEncoding(LEncoding, encOSDefault); FDefAnsiEncoding := LEncoding; end; end; {$ENDIF} class function TIdIOHandler.MakeDefaultIOHandler(AOwner: TComponent = nil): TIdIOHandler; begin Result := GIOHandlerClassDefault.Create(AOwner); end; class procedure TIdIOHandler.RegisterIOHandler; begin if GIOHandlerClassList = nil then begin GIOHandlerClassList := TIdIOHandlerClassList.Create; end; {$IFNDEF DOTNET_EXCLUDE} //TODO: Reenable this. Dot net wont allow class references as objects // Use an array? if GIOHandlerClassList.IndexOf(Self) = -1 then begin GIOHandlerClassList.Add(Self); end; {$ENDIF} end; { Creates an IOHandler of type ABaseType, or descendant. } class function TIdIOHandler.MakeIOHandler(ABaseType: TIdIOHandlerClass; AOwner: TComponent = nil): TIdIOHandler; begin Result := TryMakeIOHandler(ABaseType, AOwner); if not Assigned(Result) then begin raise EIdException.CreateFmt(RSIOHandlerTypeNotInstalled, [ABaseType.ClassName]); end; end; class function TIdIOHandler.TryMakeIOHandler(ABaseType: TIdIOHandlerClass; AOwner: TComponent = nil): TIdIOHandler; var i: Integer; begin if GIOHandlerClassList <> nil then begin for i := GIOHandlerClassList.Count - 1 downto 0 do begin if TIdIOHandlerClass(GIOHandlerClassList[i]).InheritsFrom(ABaseType) then begin Result := TIdIOHandlerClass(GIOHandlerClassList[i]).Create; Exit; end; end; end; Result := nil; end; function TIdIOHandler.GetDestination: string; begin Result := FDestination; end; procedure TIdIOHandler.SetDestination(const AValue: string); begin FDestination := AValue; end; procedure TIdIOHandler.BufferRemoveNotify(ASender: TObject; ABytes: Integer); begin DoWork(wmRead, ABytes); end; procedure TIdIOHandler.WriteBufferOpen(AThreshold: Integer); begin if FWriteBuffer <> nil then begin FWriteBuffer.Clear; end else begin FWriteBuffer := TIdBuffer.Create; end; FWriteBufferThreshold := AThreshold; end; procedure TIdIOHandler.WriteBufferClose; begin try WriteBufferFlush; finally FreeAndNil(FWriteBuffer); end; end; procedure TIdIOHandler.WriteBufferFlush(AByteCount: Integer); var LBytes: TIdBytes; begin if FWriteBuffer <> nil then begin if FWriteBuffer.Size > 0 then begin FWriteBuffer.ExtractToBytes(LBytes, AByteCount); WriteDirect(LBytes); end; end; end; procedure TIdIOHandler.WriteBufferClear; begin if FWriteBuffer <> nil then begin FWriteBuffer.Clear; end; end; procedure TIdIOHandler.WriteBufferCancel; begin WriteBufferClear; WriteBufferClose; end; procedure TIdIOHandler.Write(const AOut: string; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); begin if AOut <> '' then begin AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ASrcEncoding := iif(ASrcEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} Write( ToBytes(AOut, -1, 1, AByteEncoding {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF} ) ); end; end; procedure TIdIOHandler.Write(AValue: Byte); begin Write(ToBytes(AValue)); end; procedure TIdIOHandler.Write(AValue: Char; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); begin AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ASrcEncoding := iif(ASrcEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} Write( ToBytes(AValue, AByteEncoding {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF} ) ); end; procedure TIdIOHandler.Write(AValue: UInt32; AConvert: Boolean = True); begin if AConvert then begin AValue := GStack.HostToNetwork(AValue); end; Write(ToBytes(AValue)); end; procedure TIdIOHandler.Write(AValue: Int32; AConvert: Boolean = True); begin if AConvert then begin AValue := Int32(GStack.HostToNetwork(UInt32(AValue))); end; Write(ToBytes(AValue)); end; {$IFDEF HAS_UInt64} {$IFDEF BROKEN_UInt64_HPPEMIT} {$DEFINE HAS_TIdUInt64_QuadPart} {$ENDIF} {$ELSE} {$IFNDEF HAS_QWord} {$DEFINE HAS_TIdUInt64_QuadPart} {$ENDIF} {$ENDIF} procedure TIdIOHandler.Write(AValue: Int64; AConvert: Boolean = True); {$IFDEF AVOID_URW_ERRORS} var h: Int64; {$ELSE} {$IFDEF HAS_TIdUInt64_QuadPart} var h: TIdUInt64; {$ENDIF} {$ENDIF} begin if AConvert then begin {$IFDEF AVOID_URW_ERRORS} // assigning to a local variable to avoid an "Internal error URW533" compiler // error in Delphi 5, and an "Internal error URW699" compiler error in Delphi // 6. Later versions seem OK without it... h := GStack.HostToNetwork(UInt64(AValue)); AValue := h; {$ELSE} {$IFDEF HAS_TIdUInt64_QuadPart} // assigning to a local variable if UInt64 is not a native type, or if using // a C++Builder version that has problems with UInt64 parameters... h.QuadPart := UInt64(AValue); h := GStack.HostToNetwork(h); AValue := Int64(h.QuadPart); {$ELSE} AValue := Int64(GStack.HostToNetwork(UInt64(AValue))); {$ENDIF} {$ENDIF} end; Write(ToBytes(AValue)); end; procedure TIdIOHandler.Write(AValue: TIdUInt64; AConvert: Boolean = True); begin if AConvert then begin AValue := GStack.HostToNetwork(AValue); end; Write(ToBytes(AValue)); end; procedure TIdIOHandler.Write(AValue: TStrings; AWriteLinesCount: Boolean = False; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); var i: Integer; LBufferingStarted: Boolean; begin AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ASrcEncoding := iif(ASrcEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} LBufferingStarted := not WriteBufferingActive; if LBufferingStarted then begin WriteBufferOpen; end; try if AWriteLinesCount then begin Write(AValue.Count); end; for i := 0 to AValue.Count - 1 do begin WriteLn(AValue.Strings[i], AByteEncoding {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF} ); end; if LBufferingStarted then begin WriteBufferClose; end; except if LBufferingStarted then begin WriteBufferCancel; end; raise; end; end; procedure TIdIOHandler.Write(AValue: UInt16; AConvert: Boolean = True); begin if AConvert then begin AValue := GStack.HostToNetwork(AValue); end; Write(ToBytes(AValue)); end; procedure TIdIOHandler.Write(AValue: Int16; AConvert: Boolean = True); begin if AConvert then begin AValue := Int16(GStack.HostToNetwork(UInt16(AValue))); end; Write(ToBytes(AValue)); end; function TIdIOHandler.ReadString(ABytes: Integer; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; var LBytes: TIdBytes; begin if ABytes > 0 then begin ReadBytes(LBytes, ABytes, False); AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ADestEncoding := iif(ADestEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} Result := BytesToString(LBytes, 0, ABytes, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end else begin Result := ''; end; end; procedure TIdIOHandler.ReadStrings(ADest: TStrings; AReadLinesCount: Integer = -1; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); var i: Integer; begin AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ADestEncoding := iif(ADestEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} if AReadLinesCount < 0 then begin AReadLinesCount := ReadInt32; end; for i := 0 to AReadLinesCount - 1 do begin ADest.Add(ReadLn(AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} )); end; end; function TIdIOHandler.ReadUInt16(AConvert: Boolean = True): UInt16; var LBytes: TIdBytes; begin ReadBytes(LBytes, SizeOf(UInt16), False); Result := BytesToUInt16(LBytes); if AConvert then begin Result := GStack.NetworkToHost(Result); end; end; {$I IdDeprecatedImplBugOff.inc} function TIdIOHandler.ReadWord(AConvert: Boolean = True): UInt16; {$I IdDeprecatedImplBugOn.inc} {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin Result := ReadUInt16(AConvert); end; function TIdIOHandler.ReadInt16(AConvert: Boolean = True): Int16; var LBytes: TIdBytes; begin ReadBytes(LBytes, SizeOf(Int16), False); Result := BytesToInt16(LBytes); if AConvert then begin Result := Int16(GStack.NetworkToHost(UInt16(Result))); end; end; {$I IdDeprecatedImplBugOff.inc} function TIdIOHandler.ReadSmallInt(AConvert: Boolean = True): Int16; {$I IdDeprecatedImplBugOn.inc} {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin Result := ReadInt16(AConvert); end; function TIdIOHandler.ReadChar(AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): Char; var I, J, NumChars, NumBytes: Integer; LBytes: TIdBytes; {$IFDEF DOTNET} LChars: array[0..1] of Char; {$ELSE} LChars: TIdWideChars; {$IFDEF STRING_IS_ANSI} LWTmp: TIdUnicodeString; LATmp: TIdBytes; {$ENDIF} {$ENDIF} begin AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ADestEncoding := iif(ADestEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} // 2 Chars to handle UTF-16 surrogates NumBytes := AByteEncoding.GetMaxByteCount(2); SetLength(LBytes, NumBytes); {$IFNDEF DOTNET} SetLength(LChars, 2); {$ENDIF} NumChars := 0; if NumBytes > 0 then begin for I := 1 to NumBytes do begin LBytes[I-1] := ReadByte; NumChars := AByteEncoding.GetChars(LBytes, 0, I, LChars, 0); if NumChars > 0 then begin // RLebeau 10/19/2012: when Indy switched to its own UTF-8 implementation // to avoid the MB_ERR_INVALID_CHARS flag on Windows, it accidentally broke // this loop! Since this is not commonly used, this was not noticed until // now. On Windows at least, GetChars() now returns >0 for an invalid // sequence, so we have to check if any of the returned characters are the // Unicode U+FFFD character, indicating bad data... for J := 0 to NumChars-1 do begin if LChars[J] = TIdWideChar($FFFD) then begin // keep reading... NumChars := 0; Break; end; end; if NumChars > 0 then begin Break; end; end; end; end; {$IFDEF STRING_IS_UNICODE} // RLebeau: if the bytes were decoded into surrogates, the second // surrogate is lost here, as it can't be returned unless we cache // it somewhere for the the next ReadChar() call to retreive. Just // raise an error for now. Users will have to update their code to // read surrogates differently... Assert(NumChars = 1); Result := LChars[0]; {$ELSE} // RLebeau: since we can only return an AnsiChar here, let's convert // the decoded characters, surrogates and all, into their Ansi // representation. This will have the same problem as above if the // conversion results in a multibyte character sequence... SetString(LWTmp, PWideChar(LChars), NumChars); LATmp := ADestEncoding.GetBytes(LWTmp); // convert to Ansi Assert(Length(LATmp) = 1); Result := Char(LATmp[0]); {$ENDIF} end; function TIdIOHandler.ReadByte: Byte; var LBytes: TIdBytes; begin ReadBytes(LBytes, 1, False); Result := LBytes[0]; end; function TIdIOHandler.ReadInt32(AConvert: Boolean): Int32; var LBytes: TIdBytes; begin ReadBytes(LBytes, SizeOf(Int32), False); Result := BytesToInt32(LBytes); if AConvert then begin Result := Int32(GStack.NetworkToHost(UInt32(Result))); end; end; {$I IdDeprecatedImplBugOff.inc} function TIdIOHandler.ReadLongInt(AConvert: Boolean): Int32; {$I IdDeprecatedImplBugOn.inc} {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin Result := ReadInt32(AConvert); end; function TIdIOHandler.ReadInt64(AConvert: boolean): Int64; var LBytes: TIdBytes; {$IFDEF AVOID_URW_ERRORS} h: Int64; {$ELSE} {$IFDEF HAS_TIdUInt64_QuadPart} h: TIdUInt64; {$ENDIF} {$ENDIF} begin ReadBytes(LBytes, SizeOf(Int64), False); Result := BytesToInt64(LBytes); if AConvert then begin {$IFDEF AVOID_URW_ERRORS} // assigning to a local variable to avoid an "Internal error URW533" compiler // error in Delphi 5, and an "Internal error URW699" compiler error in Delphi // 6. Later versions seem OK without it... h := GStack.NetworkToHost(UInt64(Result)); Result := h; {$ELSE} {$IFDEF HAS_TIdUInt64_QuadPart} // assigning to a local variable if UInt64 is not a native type, or if using // a C++Builder version that has problems with UInt64 parameters... h.QuadPart := UInt64(AValue); h := GStack.NetworkToHost(h); Result := Int64(h.QuadPart); {$ELSE} Result := Int64(GStack.NetworkToHost(UInt64(Result))); {$ENDIF} {$ENDIF} end; end; function TIdIOHandler.ReadUInt64(AConvert: boolean): TIdUInt64; var LBytes: TIdBytes; begin ReadBytes(LBytes, SizeOf(TIdUInt64), False); Result := BytesToUInt64(LBytes); if AConvert then begin Result := GStack.NetworkToHost(Result); end; end; function TIdIOHandler.ReadUInt32(AConvert: Boolean): UInt32; var LBytes: TIdBytes; begin ReadBytes(LBytes, SizeOf(UInt32), False); Result := BytesToUInt32(LBytes); if AConvert then begin Result := GStack.NetworkToHost(Result); end; end; {$I IdDeprecatedImplBugOff.inc} function TIdIOHandler.ReadLongWord(AConvert: Boolean): UInt32; {$I IdDeprecatedImplBugOn.inc} {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin Result := ReadUInt32(AConvert); end; function TIdIOHandler.ReadLn(AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin Result := ReadLn(LF, IdTimeoutDefault, -1, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; function TIdIOHandler.ReadLn(ATerminator: string; AByteEncoding: IIdTextEncoding {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin Result := ReadLn(ATerminator, IdTimeoutDefault, -1, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; function TIdIOHandler.ReadLn(ATerminator: string; ATimeout: Integer = IdTimeoutDefault; AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; var LInputBufferSize: Integer; LStartPos: Integer; LTermPos: Integer; LReadLnStartTime: TIdTicks; LTerm, LResult: TIdBytes; begin AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ADestEncoding := iif(ADestEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} if AMaxLineLength < 0 then begin AMaxLineLength := MaxLineLength; end; // User may pass '' if they need to pass arguments beyond the first. if ATerminator = '' then begin ATerminator := LF; end; // TODO: encountered an email that was using charset "cp1026", which encodes // a LF character to byte $25 instead of $0A (and decodes byte $0A to character // #$8E instead of #$A). To account for that, don't encoding the LF using the // specified encoding anymore, force the encoding to what it should be. But // what if UTF-16 is being used? { if ATerminator = LF then begin LTerm := ToBytes(Byte($0A)); end else begin LTerm := ToBytes(ATerminator, AByteEncoding {$IFDEF STRING_IS_ANSI, ADestEncoding{$ENDIF ); end; } LTerm := ToBytes(ATerminator, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); FReadLnSplit := False; FReadLnTimedOut := False; LTermPos := -1; LStartPos := 0; LReadLnStartTime := Ticks64; repeat LInputBufferSize := FInputBuffer.Size; if LInputBufferSize > 0 then begin if LStartPos < LInputBufferSize then begin LTermPos := FInputBuffer.IndexOf(LTerm, LStartPos); end else begin LTermPos := -1; end; LStartPos := IndyMax(LInputBufferSize-(Length(LTerm)-1), 0); end; // if the line length is limited and terminator is found after the limit or not found and the limit is exceeded if (AMaxLineLength > 0) and ((LTermPos > AMaxLineLength) or ((LTermPos = -1) and (LStartPos > AMaxLineLength))) then begin if MaxLineAction = maException then begin raise EIdReadLnMaxLineLengthExceeded.Create(RSReadLnMaxLineLengthExceeded); end; // RLebeau: WARNING - if the line is using multibyte character sequences // and a sequence staddles the AMaxLineLength boundary, this will chop // the sequence, producing invalid data! FReadLnSplit := True; Result := FInputBuffer.ExtractToString(AMaxLineLength, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); Exit; end // ReadFromSource blocks - do not call unless we need to else if LTermPos = -1 then begin // ReadLn needs to call this as data may exist in the buffer, but no EOL yet disconnected CheckForDisconnect(True, True); // Can only return -1 if timeout FReadLnTimedOut := ReadFromSource(True, ATimeout, False) = -1; if (not FReadLnTimedOut) and (ATimeout >= 0) then begin if GetElapsedTicks(LReadLnStartTime) >= UInt32(ATimeout) then begin FReadLnTimedOut := True; end; end; if FReadLnTimedOut then begin Result := ''; Exit; end; end; until LTermPos > -1; // Extract actual data { IMPORTANT!!! When encoding from UTF8 to Unicode or ASCII, you will not always get the same number of bytes that you input so you may have to recalculate LTermPos since that was based on the number of bytes in the input stream. If do not do this, you will probably get an incorrect result or a range check error since the string is shorter then the original buffer position. JPM } // RLebeau 11/19/08: this is no longer needed as the terminator is encoded to raw bytes now ... { Result := FInputBuffer.Extract(LTermPos + Length(ATerminator), AEncoding); LTermPos := IndyMin(LTermPos, Length(Result)); if (ATerminator = LF) and (LTermPos > 0) then begin if Result[LTermPos] = CR then begin Dec(LTermPos); end; end; SetLength(Result, LTermPos); } FInputBuffer.ExtractToBytes(LResult, LTermPos + Length(LTerm)); if (ATerminator = LF) and (LTermPos > 0) then begin if LResult[LTermPos-1] = Ord(CR) then begin Dec(LTermPos); end; end; Result := BytesToString(LResult, 0, LTermPos, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; function TIdIOHandler.ReadLnRFC(var VMsgEnd: Boolean; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin Result := ReadLnRFC(VMsgEnd, LF, '.', AByteEncoding {do not localize} {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; function TIdIOHandler.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 begin VMsgEnd := True; Exit; end; if TextStartsWith(Result, '..') then begin {do not localize} Delete(Result, 1, 1); end; VMsgEnd := False; end; function TIdIOHandler.ReadLnSplit(var AWasSplit: Boolean; ATerminator: string = LF; ATimeout: Integer = IdTimeoutDefault; AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; var FOldAction: TIdMaxLineAction; begin FOldAction := MaxLineAction; MaxLineAction := maSplit; try Result := ReadLn(ATerminator, ATimeout, AMaxLineLength, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); AWasSplit := FReadLnSplit; finally MaxLineAction := FOldAction; end; end; function TIdIOHandler.ReadLnWait(AFailCount: Integer = MaxInt; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; var LAttempts: Integer; begin // MtW: this is mostly used when empty lines could be sent. AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ADestEncoding := iif(ADestEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} Result := ''; LAttempts := 0; while LAttempts < AFailCount do begin Result := Trim(ReadLn(AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} )); if Length(Result) > 0 then begin Exit; end; if ReadLnTimedOut then begin raise EIdReadTimeout.Create(RSReadTimeout); end; Inc(LAttempts); end; raise EIdReadLnWaitMaxAttemptsExceeded.Create(RSReadLnWaitMaxAttemptsExceeded); end; function TIdIOHandler.ReadFromSource(ARaiseExceptionIfDisconnected: Boolean; ATimeout: Integer; ARaiseExceptionOnTimeout: Boolean): Integer; var LByteCount: Integer; LLastError: Integer; LBuffer: TIdBytes; // under ARC, convert a weak reference to a strong reference before working with it LIntercept: TIdConnectionIntercept; begin if ATimeout = IdTimeoutDefault then begin // MtW: check for 0 too, for compatibility if (ReadTimeout = IdTimeoutDefault) or (ReadTimeout = 0) then begin ATimeout := IdTimeoutInfinite; end else begin ATimeout := ReadTimeout; end; end; Result := 0; // Check here as this side may have closed the socket CheckForDisconnect(ARaiseExceptionIfDisconnected); if SourceIsAvailable then begin repeat LByteCount := 0; if Readable(ATimeout) then begin if Opened then begin // No need to call AntiFreeze, the Readable does that. if SourceIsAvailable then begin // TODO: Whey are we reallocating LBuffer every time? This // should be a one time operation per connection. // RLebeau: because the Intercept does not allow the buffer // size to be specified, and the Intercept could potentially // resize the buffer... SetLength(LBuffer, RecvBufferSize); try LByteCount := ReadDataFromSource(LBuffer); if LByteCount > 0 then begin SetLength(LBuffer, LByteCount); LIntercept := Intercept; if LIntercept <> nil then begin LIntercept.Receive(LBuffer); {$IFDEF USE_OBJECT_ARC}LIntercept := nil;{$ENDIF} LByteCount := Length(LBuffer); end; // Pass through LBuffer first so it can go through Intercept //TODO: If not intercept, we can skip this step InputBuffer.Write(LBuffer); end; finally LBuffer := nil; end; end else if ARaiseExceptionIfDisconnected then begin raise EIdClosedSocket.Create(RSStatusDisconnected); end; end else if ARaiseExceptionIfDisconnected then begin raise EIdNotConnected.Create(RSNotConnected); end; if LByteCount < 0 then begin LLastError := CheckForError(LByteCount); if LLastError = Id_WSAETIMEDOUT then begin // Timeout if ARaiseExceptionOnTimeout then begin raise EIdReadTimeout.Create(RSReadTimeout); end; Result := -1; Break; end; FClosedGracefully := True; Close; // Do not raise unless all data has been read by the user if InputBufferIsEmpty and ARaiseExceptionIfDisconnected then begin RaiseError(LLastError); end; LByteCount := 0; end else if LByteCount = 0 then begin FClosedGracefully := True; end; // Check here as other side may have closed connection CheckForDisconnect(ARaiseExceptionIfDisconnected); Result := LByteCount; end else begin // Timeout if ARaiseExceptionOnTimeout then begin raise EIdReadTimeout.Create(RSReadTimeout); end; Result := -1; Break; end; until (LByteCount <> 0) or (not SourceIsAvailable); end else if ARaiseExceptionIfDisconnected then begin raise EIdNotConnected.Create(RSNotConnected); end; end; function TIdIOHandler.CheckForDataOnSource(ATimeout: Integer = 0): Boolean; var LPrevSize: Integer; begin Result := False; // RLebeau - Connected() might read data into the InputBuffer, thus // leaving no data for ReadFromSource() to receive a second time, // causing a result of False when it should be True instead. So we // save the current size of the InputBuffer before calling Connected() // and then compare it afterwards.... LPrevSize := InputBuffer.Size; if Connected then begin // return whether at least 1 byte was received Result := (InputBuffer.Size > LPrevSize) or (ReadFromSource(False, ATimeout, False) > 0); end; end; procedure TIdIOHandler.Write(AStream: TStream; ASize: TIdStreamSize = 0; AWriteByteCount: Boolean = FALSE); var LBuffer: TIdBytes; LStreamPos: TIdStreamSize; LBufSize: Integer; // LBufferingStarted: Boolean; begin if ASize < 0 then begin //"-1" All from current position LStreamPos := AStream.Position; ASize := AStream.Size - LStreamPos; //todo is this step required? AStream.Position := LStreamPos; end else if ASize = 0 then begin //"0" ALL ASize := AStream.Size; AStream.Position := 0; end; //else ">0" number of bytes // RLebeau 3/19/2006: DO NOT ENABLE WRITE BUFFERING IN THIS METHOD! // // When sending large streams, especially with LargeStream enabled, // this can easily cause "Out of Memory" errors. It is the caller's // responsibility to enable/disable write buffering as needed before // calling one of the Write() methods. // // Also, forcing write buffering in this method is having major // impacts on TIdFTP, TIdFTPServer, and TIdHTTPServer. if AWriteByteCount then begin if LargeStream then begin Write(Int64(ASize)); end else begin {$IFDEF STREAM_SIZE_64} if ASize > High(Integer) then begin raise EIdIOHandlerRequiresLargeStream.Create(RSRequiresLargeStream); end; {$ENDIF} Write(Int32(ASize)); end; end; BeginWork(wmWrite, ASize); try SetLength(LBuffer, FSendBufferSize); while ASize > 0 do begin LBufSize := IndyMin(ASize, Length(LBuffer)); // Do not use ReadBuffer. Some source streams are real time and will not // return as much data as we request. Kind of like recv() // NOTE: We use .Size - size must be supported even if real time LBufSize := TIdStreamHelper.ReadBytes(AStream, LBuffer, LBufSize); if LBufSize <= 0 then begin raise EIdNoDataToRead.Create(RSIdNoDataToRead); end; Write(LBuffer, LBufSize); // RLebeau: DoWork() is called in WriteDirect() //DoWork(wmWrite, LBufSize); Dec(ASize, LBufSize); end; finally EndWork(wmWrite); LBuffer := nil; end; end; procedure TIdIOHandler.ReadBytes(var VBuffer: TIdBytes; AByteCount: Integer; AAppend: Boolean = True); begin Assert(FInputBuffer<>nil); if AByteCount > 0 then begin // Read from stack until we have enough data while FInputBuffer.Size < AByteCount do begin // RLebeau: in case the other party disconnects // after all of the bytes were transmitted ok. // No need to throw an exception just yet... if ReadFromSource(False) > 0 then begin if FInputBuffer.Size >= AByteCount then begin Break; // we have enough data now end; end; CheckForDisconnect(True, True); end; FInputBuffer.ExtractToBytes(VBuffer, AByteCount, AAppend); end else if AByteCount < 0 then begin ReadFromSource(False, ReadTimeout, False); CheckForDisconnect(True, True); FInputBuffer.ExtractToBytes(VBuffer, -1, AAppend); end; end; procedure TIdIOHandler.WriteLn(AEncoding: IIdTextEncoding = nil); {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin {$IFNDEF VCL_6_OR_ABOVE} // RLebeau: in Delphi 5, explicitly specifying the nil value for the third // parameter causes a "There is no overloaded version of 'WriteLn' that can // be called with these arguments" compiler error. Must be a compiler bug, // because it compiles fine in Delphi 6. The parameter value is nil by default // anyway, so we don't really need to specify it here at all, but I'm documenting // this so we know for future reference... // WriteLn('', AEncoding); {$ELSE} WriteLn('', AEncoding{$IFDEF STRING_IS_ANSI}, nil{$ENDIF}); {$ENDIF} end; procedure TIdIOHandler.WriteLn(const AOut: string; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); begin // TODO: RLebeau 1/2/2015: encountered an email that was using charset "cp1026", // which encodes a LF character to byte $25 instead of $0A (and decodes // byte $0A to character #$8E instead of #$A). To account for that, don't // encoding the CRLF using the specified encoding anymore, force the encoding // to what it should be... // // But, what to do if the target encoding is UTF-16? { Write(AOut, AByteEncoding{$IFDEF STRING_IS_ANSI, ASrcEncoding{$ENDIF); Write(EOL, Indy8BitEncoding{$IFDEF STRING_IS_ANSI, Indy8BitEncoding{$ENDIF); } // Do as one write so it only makes one call to network Write(AOut + EOL, AByteEncoding {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF} ); end; procedure TIdIOHandler.WriteLnRFC(const AOut: string = ''; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); begin if TextStartsWith(AOut, '.') then begin {do not localize} WriteLn('.' + AOut, AByteEncoding {do not localize} {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF} ); end else begin WriteLn(AOut, AByteEncoding {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF} ); end; end; function TIdIOHandler.Readable(AMSec: Integer): Boolean; begin // In case descendant does not override this or other methods but implements the higher level // methods Result := False; end; procedure TIdIOHandler.SetHost(const AValue: string); begin FHost := AValue; end; procedure TIdIOHandler.SetPort(AValue: Integer); begin FPort := AValue; end; function TIdIOHandler.Connected: Boolean; begin CheckForDisconnect(False); Result := ( ( // Set when closed properly. Reflects actual socket state. (not ClosedGracefully) // Created on Open. Prior to Open ClosedGracefully is still false. and (FInputBuffer <> nil) ) // Buffer must be empty. Even if closed, we are "connected" if we still have // data or (not InputBufferIsEmpty) ) and Opened; end; // TODO: move this into IdGlobal.pas procedure AdjustStreamSize(const AStream: TStream; const ASize: TIdStreamSize); var LStreamPos: TIdStreamSize; begin LStreamPos := AStream.Position; AStream.Size := ASize; // Must reset to original value in cases where size changes position if AStream.Position <> LStreamPos then begin AStream.Position := LStreamPos; end; end; procedure TIdIOHandler.ReadStream(AStream: TStream; AByteCount: TIdStreamSize; AReadUntilDisconnect: Boolean); var i: Integer; LBuf: TIdBytes; LByteCount, LPos: TIdStreamSize; {$IFNDEF STREAM_SIZE_64} LTmp: Int64; {$ENDIF} const cSizeUnknown = -1; begin Assert(AStream<>nil); if (AByteCount = cSizeUnknown) and (not AReadUntilDisconnect) then begin // Read size from connection if LargeStream then begin {$IFDEF STREAM_SIZE_64} LByteCount := ReadInt64; {$ELSE} LTmp := ReadInt64; if LTmp > MaxInt then begin raise EIdIOHandlerStreamDataTooLarge.Create(RSDataTooLarge); end; LByteCount := TIdStreamSize(LTmp); {$ENDIF} end else begin LByteCount := ReadInt32; end; end else begin LByteCount := AByteCount; end; // Presize stream if we know the size - this reduces memory/disk allocations to one time // Have an option for this? user might not want to presize, eg for int64 files if LByteCount > -1 then begin LPos := AStream.Position; if (High(TIdStreamSize) - LPos) < LByteCount then begin raise EIdIOHandlerStreamDataTooLarge.Create(RSDataTooLarge); end; AdjustStreamSize(AStream, LPos + LByteCount); end; if (LByteCount <= cSizeUnknown) and (not AReadUntilDisconnect) then begin AReadUntilDisconnect := True; end; if AReadUntilDisconnect then begin BeginWork(wmRead); end else begin BeginWork(wmRead, LByteCount); end; try // If data already exists in the buffer, write it out first. // should this loop for all data in buffer up to workcount? not just one block? if FInputBuffer.Size > 0 then begin if AReadUntilDisconnect then begin i := FInputBuffer.Size; end else begin i := IndyMin(FInputBuffer.Size, LByteCount); Dec(LByteCount, i); end; FInputBuffer.ExtractToStream(AStream, i); end; // RLebeau - don't call Connected() here! ReadBytes() already // does that internally. Calling Connected() here can cause an // EIdConnClosedGracefully exception that breaks the loop // prematurely and thus leave unread bytes in the InputBuffer. // Let the loop catch the exception before exiting... SetLength(LBuf, RecvBufferSize); // preallocate the buffer repeat if AReadUntilDisconnect then begin i := Length(LBuf); end else begin i := IndyMin(LByteCount, Length(LBuf)); if i < 1 then begin Break; end; end; //TODO: Improve this - dont like the use of the exception handler //DONE -oAPR: Dont use a string, use a memory buffer or better yet the buffer itself. try try ReadBytes(LBuf, i, False); except on E: Exception do begin // RLebeau - ReadFromSource() inside of ReadBytes() // could have filled the InputBuffer with more bytes // than actually requested, so don't extract too // many bytes here... i := IndyMin(i, FInputBuffer.Size); FInputBuffer.ExtractToBytes(LBuf, i, False); if AReadUntilDisconnect then begin if E is EIdConnClosedGracefully then begin Exit; end else if E is EIdSocketError then begin case EIdSocketError(E).LastError of Id_WSAESHUTDOWN, Id_WSAECONNABORTED, Id_WSAECONNRESET: begin Exit; end; end; end; end; raise; end; end; TIdAntiFreezeBase.DoProcess; finally if i > 0 then begin TIdStreamHelper.Write(AStream, LBuf, i); if not AReadUntilDisconnect then begin Dec(LByteCount, i); end; end; end; until False; finally EndWork(wmRead); if AStream.Size > AStream.Position then begin AStream.Size := AStream.Position; end; LBuf := nil; end; end; procedure TIdIOHandler.Discard(AByteCount: Int64); var LSize: Integer; begin Assert(AByteCount >= 0); if AByteCount > 0 then begin BeginWork(wmRead, AByteCount); try repeat LSize := iif(AByteCount < MaxInt, Integer(AByteCount), MaxInt); LSize := IndyMin(LSize, FInputBuffer.Size); if LSize > 0 then begin FInputBuffer.Remove(LSize); Dec(AByteCount, LSize); if AByteCount < 1 then begin Break; end; end; // RLebeau: in case the other party disconnects // after all of the bytes were transmitted ok. // No need to throw an exception just yet... if ReadFromSource(False) < 1 then begin CheckForDisconnect(True, True); end; until False; finally EndWork(wmRead); end; end; end; procedure TIdIOHandler.DiscardAll; begin BeginWork(wmRead); try // If data already exists in the buffer, discard it first. FInputBuffer.Clear; // RLebeau - don't call Connected() here! ReadBytes() already // does that internally. Calling Connected() here can cause an // EIdConnClosedGracefully exception that breaks the loop // prematurely and thus leave unread bytes in the InputBuffer. // Let the loop catch the exception before exiting... repeat //TODO: Improve this - dont like the use of the exception handler try if ReadFromSource(False) > 0 then begin FInputBuffer.Clear; end else begin; CheckForDisconnect(True, True); end; except on E: Exception do begin // RLebeau - ReadFromSource() could have filled the // InputBuffer with more bytes... FInputBuffer.Clear; if E is EIdConnClosedGracefully then begin Break; end else begin raise; end; end; end; TIdAntiFreezeBase.DoProcess; until False; finally EndWork(wmRead); end; end; procedure TIdIOHandler.RaiseConnClosedGracefully; begin (* ************************************************************* // ------ If you receive an exception here, please read. ---------- If this is a SERVER ------------------- The client has disconnected the socket normally and this exception is used to notify the server handling code. This exception is normal and will only happen from within the IDE, not while your program is running as an EXE. If you do not want to see this, add this exception or EIdSilentException to the IDE options as exceptions not to break on. From the IDE just hit F9 again and Indy will catch and handle the exception. Please see the FAQ and help file for possible further information. The FAQ is at http://www.nevrona.com/Indy/FAQ.html If this is a CLIENT ------------------- The server side of this connection has disconnected normaly but your client has attempted to read or write to the connection. You should trap this error using a try..except. Please see the help file for possible further information. // ************************************************************* *) raise EIdConnClosedGracefully.Create(RSConnectionClosedGracefully); end; function TIdIOHandler.InputBufferAsString(AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; begin AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ADestEncoding := iif(ADestEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} Result := FInputBuffer.ExtractToString(FInputBuffer.Size, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; function TIdIOHandler.AllData(AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ): string; var LBytes: Integer; begin Result := ''; BeginWork(wmRead); try if Connected then begin try try repeat LBytes := ReadFromSource(False, 250, False); until LBytes = 0; // -1 on timeout finally if not InputBufferIsEmpty then begin Result := InputBufferAsString(AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; end; except end; end; finally EndWork(wmRead); end; end; procedure TIdIOHandler.PerformCapture(const ADest: TObject; out VLineCount: Integer; const ADelim: string; AUsesDotTransparency: Boolean; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); var s: string; LStream: TStream; LStrings: TStrings; begin VLineCount := 0; AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ADestEncoding := iif(ADestEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} LStream := nil; LStrings := nil; if ADest is TStrings then begin LStrings := TStrings(ADest); end else if ADest is TStream then begin LStream := TStream(ADest); end else begin raise EIdObjectTypeNotSupported.Create(RSObjectTypeNotSupported); end; BeginWork(wmRead); try repeat s := ReadLn(AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); if s = ADelim then begin Exit; end; // S.G. 6/4/2004: All the consumers to protect themselves against memory allocation attacks if FMaxCapturedLines > 0 then begin if VLineCount > FMaxCapturedLines then begin raise EIdMaxCaptureLineExceeded.Create(RSMaximumNumberOfCaptureLineExceeded); end; end; // For RFC retrieves that use dot transparency // No length check necessary, if only one byte it will be byte x + #0. if AUsesDotTransparency then begin if TextStartsWith(s, '..') then begin Delete(s, 1, 1); end; end; // Write to output Inc(VLineCount); if LStrings <> nil then begin LStrings.Add(s); end else if LStream <> nil then begin WriteStringToStream(LStream, s+EOL, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; until False; finally EndWork(wmRead); end; end; function TIdIOHandler.InputLn(const AMask: String = ''; AEcho: Boolean = True; ATabWidth: Integer = 8; AMaxLineLength: Integer = -1; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; AAnsiEncoding: IIdTextEncoding = nil{$ENDIF} ): String; var i: Integer; LChar: Char; LTmp: string; begin Result := ''; AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} AAnsiEncoding := iif(AAnsiEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} if AMaxLineLength < 0 then begin AMaxLineLength := MaxLineLength; end; repeat LChar := ReadChar(AByteEncoding {$IFDEF STRING_IS_ANSI}, AAnsiEncoding{$ENDIF} ); i := Length(Result); if i <= AMaxLineLength then begin case LChar of BACKSPACE: begin if i > 0 then begin SetLength(Result, i - 1); if AEcho then begin Write(BACKSPACE + ' ' + BACKSPACE, AByteEncoding {$IFDEF STRING_IS_ANSI}, AAnsiEncoding{$ENDIF} ); end; end; end; TAB: begin if ATabWidth > 0 then begin i := ATabWidth - (i mod ATabWidth); LTmp := StringOfChar(' ', i); Result := Result + LTmp; if AEcho then begin Write(LTmp, AByteEncoding {$IFDEF STRING_IS_ANSI}, AAnsiEncoding{$ENDIF} ); end; end else begin Result := Result + LChar; if AEcho then begin Write(LChar, AByteEncoding {$IFDEF STRING_IS_ANSI}, AAnsiEncoding{$ENDIF} ); end; end; end; LF: ; CR: ; #27: ; //ESC - currently not supported else Result := Result + LChar; if AEcho then begin if Length(AMask) = 0 then begin Write(LChar, AByteEncoding {$IFDEF STRING_IS_ANSI}, AAnsiEncoding{$ENDIF} ); end else begin Write(AMask, AByteEncoding {$IFDEF STRING_IS_ANSI}, AAnsiEncoding{$ENDIF} ); end; end; end; end; until LChar = LF; // Remove CR trail i := Length(Result); while (i > 0) and CharIsInSet(Result, i, EOL) do begin Dec(i); end; SetLength(Result, i); if AEcho then begin WriteLn(AByteEncoding); end; end; //TODO: Add a time out (default to infinite) and event to pass data //TODO: Add a max size argument as well. //TODO: Add a case insensitive option function TIdIOHandler.WaitFor(const AString: string; ARemoveFromBuffer: Boolean = True; AInclusive: Boolean = False; AByteEncoding: IIdTextEncoding = nil; ATimeout: Integer = IdTimeoutDefault {$IFDEF STRING_IS_ANSI}; AAnsiEncoding: IIdTextEncoding = nil{$ENDIF} ): string; var LBytes: TIdBytes; LPos: Integer; begin Result := ''; AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} AAnsiEncoding := iif(AAnsiEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} LBytes := ToBytes(AString, AByteEncoding {$IFDEF STRING_IS_ANSI}, AAnsiEncoding{$ENDIF} ); LPos := 0; repeat LPos := InputBuffer.IndexOf(LBytes, LPos); if LPos <> -1 then begin if ARemoveFromBuffer and AInclusive then begin Result := InputBuffer.ExtractToString(LPos+Length(LBytes), AByteEncoding {$IFDEF STRING_IS_ANSI}, AAnsiEncoding{$ENDIF} ); end else begin Result := InputBuffer.ExtractToString(LPos, AByteEncoding {$IFDEF STRING_IS_ANSI}, AAnsiEncoding{$ENDIF} ); if ARemoveFromBuffer then begin InputBuffer.Remove(Length(LBytes)); end; if AInclusive then begin Result := Result + AString; end; end; Exit; end; LPos := IndyMax(0, InputBuffer.Size - (Length(LBytes)-1)); ReadFromSource(True, ATimeout, True); until False; end; procedure TIdIOHandler.Capture(ADest: TStream; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin Capture(ADest, '.', True, AByteEncoding {do not localize} {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; procedure TIdIOHandler.Capture(ADest: TStream; out VLineCount: Integer; const ADelim: string = '.'; AUsesDotTransparency: Boolean = True; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin PerformCapture(ADest, VLineCount, ADelim, AUsesDotTransparency, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; procedure TIdIOHandler.Capture(ADest: TStream; ADelim: string; AUsesDotTransparency: Boolean = True; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); var LLineCount: Integer; begin PerformCapture(ADest, LLineCount, '.', AUsesDotTransparency, AByteEncoding {do not localize} {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; procedure TIdIOHandler.Capture(ADest: TStrings; out VLineCount: Integer; const ADelim: string = '.'; AUsesDotTransparency: Boolean = True; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin PerformCapture(ADest, VLineCount, ADelim, AUsesDotTransparency, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; procedure TIdIOHandler.Capture(ADest: TStrings; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); var LLineCount: Integer; begin PerformCapture(ADest, LLineCount, '.', True, AByteEncoding {do not localize} {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; procedure TIdIOHandler.Capture(ADest: TStrings; const ADelim: string; AUsesDotTransparency: Boolean = True; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF} ); var LLineCount: Integer; begin PerformCapture(ADest, LLineCount, ADelim, AUsesDotTransparency, AByteEncoding {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF} ); end; procedure TIdIOHandler.InputBufferToStream(AStream: TStream; AByteCount: Integer = -1); {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin FInputBuffer.ExtractToStream(AStream, AByteCount); end; function TIdIOHandler.InputBufferIsEmpty: Boolean; {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin Result := FInputBuffer.Size = 0; end; procedure TIdIOHandler.Write(const ABuffer: TIdBytes; const ALength: Integer = -1; const AOffset: Integer = 0); var LLength: Integer; begin LLength := IndyLength(ABuffer, ALength, AOffset); if LLength > 0 then begin if FWriteBuffer = nil then begin WriteDirect(ABuffer, LLength, AOffset); end else begin // Write Buffering is enabled FWriteBuffer.Write(ABuffer, LLength, AOffset); if (FWriteBuffer.Size >= WriteBufferThreshold) and (WriteBufferThreshold > 0) then begin repeat WriteBufferFlush(WriteBufferThreshold); until FWriteBuffer.Size < WriteBufferThreshold; end; end; end; end; procedure TIdIOHandler.WriteRFCStrings(AStrings: TStrings; AWriteTerminator: Boolean = True; AByteEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); var i: Integer; begin AByteEncoding := iif(AByteEncoding, FDefStringEncoding); {$IFDEF STRING_IS_ANSI} ASrcEncoding := iif(ASrcEncoding, FDefAnsiEncoding, encOSDefault); {$ENDIF} for i := 0 to AStrings.Count - 1 do begin WriteLnRFC(AStrings[i], AByteEncoding {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF} ); end; if AWriteTerminator then begin WriteLn('.', AByteEncoding {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF} ); end; end; function TIdIOHandler.WriteFile(const AFile: String; AEnableTransferFile: Boolean): Int64; var //TODO: There is a way in linux to dump a file to a socket as well. use it. LStream: TStream; {$IFDEF WIN32_OR_WIN64} LOldErrorMode : Integer; {$ENDIF} begin Result := 0; {$IFDEF WIN32_OR_WIN64} LOldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS); try {$ENDIF} if not FileExists(AFile) then begin raise EIdFileNotFound.CreateFmt(RSFileNotFound, [AFile]); end; LStream := TIdReadFileExclusiveStream.Create(AFile); try Write(LStream); Result := LStream.Size; finally FreeAndNil(LStream); end; {$IFDEF WIN32_OR_WIN64} finally SetErrorMode(LOldErrorMode) end; {$ENDIF} end; function TIdIOHandler.WriteBufferingActive: Boolean; {$IFDEF USE_CLASSINLINE}inline;{$ENDIF} begin Result := FWriteBuffer <> nil; end; procedure TIdIOHandler.CloseGracefully; begin FClosedGracefully := True end; procedure TIdIOHandler.InterceptReceive(var VBuffer: TIdBytes); var // under ARC, convert a weak reference to a strong reference before working with it LIntercept: TIdConnectionIntercept; begin LIntercept := Intercept; if LIntercept <> nil then begin LIntercept.Receive(VBuffer); end; end; procedure TIdIOHandler.InitComponent; begin inherited InitComponent; FRecvBufferSize := GRecvBufferSizeDefault; FSendBufferSize := GSendBufferSizeDefault; FMaxLineLength := IdMaxLineLengthDefault; FMaxCapturedLines := Id_IOHandler_MaxCapturedLines; FLargeStream := False; FReadTimeOut := IdTimeoutDefault; FInputBuffer := TIdBuffer.Create(BufferRemoveNotify); FDefStringEncoding := IndyTextEncoding_ASCII; {$IFDEF STRING_IS_ANSI} FDefAnsiEncoding := IndyTextEncoding_OSDefault; {$ENDIF} end; procedure TIdIOHandler.WriteBufferFlush; begin WriteBufferFlush(-1); end; procedure TIdIOHandler.WriteBufferOpen; begin WriteBufferOpen(-1); end; procedure TIdIOHandler.WriteDirect(const ABuffer: TIdBytes; const ALength: Integer = -1; const AOffset: Integer = 0); var LTemp: TIdBytes; LPos: Integer; LSize: Integer; LByteCount: Integer; LLastError: Integer; // under ARC, convert a weak reference to a strong reference before working with it LIntercept: TIdConnectionIntercept; begin // Check if disconnected CheckForDisconnect(True, True); LIntercept := Intercept; if LIntercept <> nil then begin // TODO: pass offset/size parameters to the Intercept // so that a copy is no longer needed here LTemp := ToBytes(ABuffer, ALength, AOffset); LIntercept.Send(LTemp); {$IFDEF USE_OBJECT_ARC}LIntercept := nil;{$ENDIF} LSize := Length(LTemp); LPos := 0; end else begin LTemp := ABuffer; LSize := IndyLength(LTemp, ALength, AOffset); LPos := AOffset; end; while LSize > 0 do begin LByteCount := WriteDataToTarget(LTemp, LPos, LSize); if LByteCount < 0 then begin LLastError := CheckForError(LByteCount); if LLastError <> Id_WSAETIMEDOUT then begin FClosedGracefully := True; Close; end; RaiseError(LLastError); end; // TODO - Have a AntiFreeze param which allows the send to be split up so that process // can be called more. Maybe a prop of the connection, MaxSendSize? TIdAntiFreezeBase.DoProcess(False); if LByteCount = 0 then begin FClosedGracefully := True; end; // Check if other side disconnected CheckForDisconnect; DoWork(wmWrite, LByteCount); Inc(LPos, LByteCount); Dec(LSize, LByteCount); end; end; initialization finalization FreeAndNil(GIOHandlerClassList) end.