{
  $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.
