2696 lines
88 KiB
Plaintext
2696 lines
88 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.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<TIdIOHandlerClass>;
|
|
{$ELSE}
|
|
// TODO: flesh out to match TList<TIdIOHandlerClass> 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.
|