{
  $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.38    11/10/2004 8:25:54 AM  JPMugaas
  Fix for AV caused by short-circut boolean evaluation.

  Rev 1.37    27.08.2004 21:58:20  Andreas Hausladen
  Speed optimization ("const" for string parameters)

  Rev 1.36    8/2/04 5:44:40 PM  RLebeau
  Moved ConnectTimeout over from TIdIOHandlerStack

  Rev 1.35    7/21/2004 12:22:32 PM  BGooijen
  Fix to .connected

  Rev 1.34    6/30/2004 12:31:34 PM  BGooijen
  Added OnSocketAllocated

  Rev 1.33    4/24/04 12:52:52 PM  RLebeau
  Added setter method to UseNagle property

  Rev 1.32    2004.04.18 12:52:02 AM  czhower
  Big bug fix with server disconnect and several other bug fixed that I found
  along the way.

  Rev 1.31    2004.02.03 4:16:46 PM  czhower
  For unit name changes.

  Rev 1.30    2/2/2004 11:46:46 AM  BGooijen
  Dotnet and TransparentProxy

  Rev 1.29    2/1/2004 9:44:00 PM  JPMugaas
  Start on reenabling Transparant proxy.

  Rev 1.28    2004.01.20 10:03:28 PM  czhower
  InitComponent

  Rev 1.27    1/2/2004 12:02:16 AM  BGooijen
  added OnBeforeBind/OnAfterBind

  Rev 1.26    12/31/2003 9:51:56 PM  BGooijen
  Added IPv6 support

  Rev 1.25    11/4/2003 10:37:40 PM  BGooijen
  JP's patch to fix the bound port

  Rev 1.24    10/19/2003 5:21:26 PM  BGooijen
  SetSocketOption

  Rev 1.23    10/18/2003 1:44:06 PM  BGooijen
  Added include

  Rev 1.22    2003.10.14 1:26:54 PM  czhower
  Uupdates + Intercept support

  Rev 1.21    10/9/2003 8:09:06 PM  SPerry
  bug fixes

  Rev 1.20    8/10/2003 2:05:50 PM  SGrobety
  Dotnet

  Rev 1.19    2003.10.07 10:18:26 PM  czhower
  Uncommneted todo code that is now non dotnet.

  Rev 1.18    2003.10.02 8:23:42 PM  czhower
  DotNet Excludes

  Rev 1.17    2003.10.01 9:11:18 PM  czhower
  .Net

  Rev 1.16    2003.10.01 5:05:12 PM  czhower
  .Net

  Rev 1.15    2003.10.01 2:46:38 PM  czhower
  .Net

  Rev 1.14    2003.10.01 11:16:32 AM  czhower
  .Net

  Rev 1.13    2003.09.30 1:22:58 PM  czhower
  Stack split for DotNet

  Rev 1.12    7/4/2003 08:26:44 AM  JPMugaas
  Optimizations.

  Rev 1.11    7/1/2003 03:39:44 PM  JPMugaas
  Started numeric IP function API calls for more efficiency.

  Rev 1.10    2003.06.30 5:41:56 PM  czhower
  -Fixed AV that occurred sometimes when sockets were closed with chains
  -Consolidated code that was marked by a todo for merging as it no longer
  needed to be separate
  -Removed some older code that was no longer necessary

  Passes bubble tests.

  Rev 1.9    6/3/2003 11:45:58 PM  BGooijen
  Added .Connected

  Rev 1.8    2003.04.22 7:45:34 PM  czhower

  Rev 1.7    4/2/2003 3:24:56 PM  BGooijen
  Moved transparantproxy from ..stack to ..socket

  Rev 1.6    2/28/2003 9:51:56 PM  BGooijen
  removed the field: FReadTimeout: Integer, it hided the one in TIdIOHandler

  Rev 1.5    2/26/2003 1:15:38 PM  BGooijen
  FBinding is now freed in IdIOHandlerSocket, instead of in IdIOHandlerStack

  Rev 1.4    2003.02.25 1:36:08 AM  czhower

  Rev 1.3    2002.12.07 12:26:26 AM  czhower

  Rev 1.2    12-6-2002 20:09:14  BGooijen
  Changed SetDestination to search for the last ':', instead of the first

  Rev 1.1    12-6-2002 18:54:14  BGooijen
  Added IPv6-support

  Rev 1.0    11/13/2002 08:45:08 AM  JPMugaas
}

unit IdIOHandlerSocket;

interface

{$I IdCompilerDefines.inc}

uses
  Classes,
  IdCustomTransparentProxy,
  IdBaseComponent,
  IdGlobal,
  IdIOHandler,
  IdSocketHandle;

const
  IdDefTimeout = 0;
  IdBoundPortDefault = 0;

type
  {
  TIdIOHandlerSocket is the base class for socket IOHandlers that implement a
  binding.

  Descendants
    -TIdIOHandlerStack
    -TIdIOHandlerChain
  }
  TIdIOHandlerSocket = class(TIdIOHandler)
  protected
    FBinding: TIdSocketHandle;
    FBoundIP: string;
    FBoundPort: TIdPort;
    FBoundPortMax: TIdPort;
    FBoundPortMin: TIdPort;
    FDefaultPort: TIdPort;
    FOnBeforeBind: TNotifyEvent;
    FOnAfterBind: TNotifyEvent;
    FOnSocketAllocated: TNotifyEvent;
    {$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FTransparentProxy: TIdCustomTransparentProxy;
    FImplicitTransparentProxy: Boolean;
    FUseNagle: Boolean;
    FReuseSocket: TIdReuseSocket;
    FIPVersion: TIdIPVersion;
    //
    procedure ConnectClient; virtual;
    procedure DoBeforeBind; virtual;
    procedure DoAfterBind; virtual;
    procedure DoSocketAllocated; virtual;
    procedure InitComponent; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    function GetDestination: string; override;
    procedure SetDestination(const AValue: string); override;
    function GetReuseSocket: TIdReuseSocket;
    procedure SetReuseSocket(AValue: TIdReuseSocket);
    function GetTransparentProxy: TIdCustomTransparentProxy; virtual;
    procedure SetTransparentProxy(AProxy: TIdCustomTransparentProxy); virtual;
    function GetUseNagle: Boolean;
    procedure SetUseNagle(AValue: Boolean);
    //
    function SourceIsAvailable: Boolean; override;
    function CheckForError(ALastResult: Integer): Integer; override;
    procedure RaiseError(AError: Integer); override;
  public
    procedure AfterAccept; override;
    destructor Destroy; override;
    function BindingAllocated: Boolean;
    procedure Close; override;
    function Connected: Boolean; override;
    procedure Open; override;
    function WriteFile(const AFile: String; AEnableTransferFile: Boolean = False): Int64; override;
    //
    property Binding: TIdSocketHandle read FBinding;
    property BoundPortMax: TIdPort read FBoundPortMax write FBoundPortMax;
    property BoundPortMin: TIdPort read FBoundPortMin write FBoundPortMin;
    // events
    property OnBeforeBind: TNotifyEvent read FOnBeforeBind write FOnBeforeBind;
    property OnAfterBind: TNotifyEvent read FOnAfterBind write FOnAfterBind;
    property OnSocketAllocated: TNotifyEvent read FOnSocketAllocated write FOnSocketAllocated;
  published
    property BoundIP: string read FBoundIP write FBoundIP;
    property BoundPort: TIdPort read FBoundPort write FBoundPort default IdBoundPortDefault;
    property DefaultPort: TIdPort read FDefaultPort write FDefaultPort;
    property IPVersion: TIdIPVersion read FIPVersion write FIPVersion default ID_DEFAULT_IP_VERSION;
    property ReuseSocket: TIdReuseSocket read GetReuseSocket write SetReuseSocket default rsOSDependent;
    property TransparentProxy: TIdCustomTransparentProxy read GetTransparentProxy write SetTransparentProxy;
    property UseNagle: boolean read GetUseNagle write SetUseNagle default True;
  end;

implementation

uses
  //facilitate inlining only.
  {$IFDEF DOTNET}
    {$IFDEF USE_INLINE}
  System.IO,
    {$ENDIF}
  {$ENDIF}
  {$IFDEF WIN32_OR_WIN64 }
  Windows,
  {$ENDIF}
  SysUtils,
  IdStack,
  IdStackConsts,
  IdSocks;

{ TIdIOHandlerSocket }

procedure TIdIOHandlerSocket.AfterAccept;
begin
  inherited AfterAccept;
  FIPVersion := FBinding.IPVersion;
end;

procedure TIdIOHandlerSocket.Close;
begin
  if FBinding <> nil then begin
    FBinding.CloseSocket;
  end;
  inherited Close;
end;

procedure TIdIOHandlerSocket.ConnectClient;
var
  LBinding: TIdSocketHandle;
begin
  LBinding := Binding;
  DoBeforeBind;
  // Allocate the socket
  LBinding.IPVersion := FIPVersion;
  LBinding.AllocateSocket;
  DoSocketAllocated;
  // Bind the socket
  if BoundIP <> '' then begin
    LBinding.IP := BoundIP;
  end;
  LBinding.Port := BoundPort;
  LBinding.ClientPortMin := BoundPortMin;
  LBinding.ClientPortMax := BoundPortMax;
  LBinding.ReuseSocket := FReuseSocket;

  // RLebeau 11/15/2014: Using the socket bind() function in a Mac OSX sandbox
  // causes the Apple store to reject an app with the following error if it
  // uses Indy client(s) and no Indy server(s):
  //
  // "This app uses one or more entitlements which do not have matching
  // functionality within the app. Apps should have only the minimum set of
  // entitlements necessary for the app to function properly. Please remove
  // all entitlements that are not needed by your app and submit an updated
  // binary for review, including the following:
  //
  // com.apple.security.network.server"
  //
  // Ideally, TIdSocketHandle.Bind() should not call TryBind() if the IP is
  // blank and the Port, ClientPortMin, and ClientPortMax are all 0.  However,
  // TIdSocketHandle.Bind() is used for both clients and servers, and sometimes
  // a server needs to bind to port 0 to get a random ephemeral port, which it
  // can then report to clients.  So lets do the check here instead, as this
  // method is only used for clients...

  {$IFDEF DARWIN}
  // TODO: remove the DARWIN check and just skip the Bind() on all platforms?
  if (LBinding.IP <> '') or (LBinding.Port <> 0) or
     ((LBinding.ClientPortMin <> 0) and (LBinding.ClientPortMax <> 0)) then
  begin
    LBinding.Bind;
  end;
  {$ELSE}
  LBinding.Bind;
  {$ENDIF}

  // Turn off Nagle if specified
  LBinding.UseNagle := FUseNagle;
  DoAfterBind;
end;

function TIdIOHandlerSocket.Connected: Boolean;
begin
  Result := (BindingAllocated and inherited Connected) or (not InputBufferIsEmpty);
end;

destructor TIdIOHandlerSocket.Destroy;
begin
  SetTransparentProxy(nil);
  FreeAndNil(FBinding);
  inherited Destroy;
end;

procedure TIdIOHandlerSocket.DoBeforeBind;
begin
  if Assigned(FOnBeforeBind) then begin
    FOnBeforeBind(self);
  end;
end;

procedure TIdIOHandlerSocket.DoAfterBind;
begin
  if Assigned(FOnAfterBind) then begin
    FOnAfterBind(self);
  end;
end;

procedure TIdIOHandlerSocket.DoSocketAllocated;
begin
  if Assigned(FOnSocketAllocated) then begin
    FOnSocketAllocated(self);
  end;
end;

function TIdIOHandlerSocket.GetDestination: string;
begin
  Result := Host;
  if (Port <> DefaultPort) and (Port > 0) then begin
    Result := Host + ':' + IntToStr(Port);
  end;
end;

procedure TIdIOHandlerSocket.Open;
begin
  inherited Open;

  if not Assigned(FBinding) then begin
    FBinding := TIdSocketHandle.Create(nil);
  end else begin
    FBinding.Reset(True);
  end;
  FBinding.ClientPortMin := BoundPortMin;
  FBinding.ClientPortMax := BoundPortMax;

  //if the IOHandler is used to accept connections then port+host will be empty
  if (Host <> '') and (Port > 0) then begin
    ConnectClient;
  end;
end;

procedure TIdIOHandlerSocket.SetDestination(const AValue: string);
var
  LPortStart: integer;
begin
  // Bas Gooijen 06-Dec-2002: Changed to search the last ':', instead of the first:
  LPortStart := LastDelimiter(':', AValue);
  if LPortStart > 0 then begin
    Host := Copy(AValue, 1, LPortStart-1);
    Port := IndyStrToInt(Trim(Copy(AValue, LPortStart + 1, $FF)), DefaultPort);
  end;
end;

function TIdIOHandlerSocket.BindingAllocated: Boolean;
begin
  Result := FBinding <> nil;
  if Result then begin
    Result := FBinding.HandleAllocated;
  end;
end;

function TIdIOHandlerSocket.WriteFile(const AFile: String;
  AEnableTransferFile: Boolean): Int64;
  {$IFDEF WIN32_OR_WIN64}
var
  LOldErrorMode : Integer;
  {$ENDIF}
begin
  Result := 0;
  {$IFDEF WIN32_OR_WIN64}
  LOldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  try
  {$ENDIF}
  if FileExists(AFile) then begin
    if Assigned(GServeFileProc) and (not WriteBufferingActive)
     {and (Intercept = nil)} and AEnableTransferFile
     then begin
      Result := GServeFileProc(Binding.Handle, AFile);
      Exit;
    end
    else
    begin
      Result := inherited WriteFile(AFile, AEnableTransferFile);
    end;
  end;
  {$IFDEF WIN32_OR_WIN64}
  finally
    SetErrorMode(LOldErrorMode)
  end;
  {$ENDIF}
end;

function TIdIOHandlerSocket.GetReuseSocket: TIdReuseSocket;
begin
  if FBinding <> nil then begin
    Result := FBinding.ReuseSocket;
  end else begin
    Result := FReuseSocket;
  end;
end;

procedure TIdIOHandlerSocket.SetReuseSocket(AValue: TIdReuseSocket);
begin
  FReuseSocket := AValue;
  if FBinding <> nil then begin
    FBinding.ReuseSocket := AValue;
  end;
end;

procedure TIdIOHandlerSocket.SetTransparentProxy(AProxy : TIdCustomTransparentProxy);
var
  LClass: TIdCustomTransparentProxyClass;
  // under ARC, convert a weak reference to a strong reference before working with it
  LTransparentProxy: TIdCustomTransparentProxy;
begin
  LTransparentProxy := FTransparentProxy;

  if LTransparentProxy <> AProxy then
  begin
    // All this is to preserve the compatibility with old version
    // In the case when we have SocksInfo as object created in runtime without owner form it is treated as temporary object
    // In the case when the ASocks points to an object with owner it is treated as component on form.

    // under ARC, all weak references to a freed object get nil'ed automatically

    if Assigned(AProxy) then begin
      if not Assigned(AProxy.Owner) then begin
        if Assigned(LTransparentProxy) and (not FImplicitTransparentProxy) then begin
          FTransparentProxy := nil;
          {$IFNDEF USE_OBJECT_ARC}
          LTransparentProxy.RemoveFreeNotification(Self);
          {$ENDIF}
        end;
        LClass := TIdCustomTransparentProxyClass(AProxy.ClassType);
        if Assigned(LTransparentProxy) and (LTransparentProxy.ClassType <> LClass) then begin
          FTransparentProxy := nil;
          FImplicitTransparentProxy := False;
          IdDisposeAndNil(LTransparentProxy);
        end;
        if not Assigned(LTransparentProxy) then begin
          LTransparentProxy := LClass.Create(Self);
          FTransparentProxy := LTransparentProxy;
          FImplicitTransparentProxy := True;
        end;
        LTransparentProxy.Assign(AProxy);
      end else begin
        if Assigned(LTransparentProxy) then begin
          if FImplicitTransparentProxy then begin
            FTransparentProxy := nil;
            FImplicitTransparentProxy := False;
            IdDisposeAndNil(LTransparentProxy);
          end else begin
            {$IFNDEF USE_OBJECT_ARC}
            LTransparentProxy.RemoveFreeNotification(Self);
            {$ENDIF}
          end;
        end;
        FTransparentProxy := AProxy;
        {$IFNDEF USE_OBJECT_ARC}
        AProxy.FreeNotification(Self);
        {$ENDIF}
      end;
    end
    else if Assigned(LTransparentProxy) then begin
      if FImplicitTransparentProxy then begin
        FTransparentProxy := nil;
        FImplicitTransparentProxy := False;
        IdDisposeAndNil(LTransparentProxy);
      end else begin
        FTransparentProxy := nil;
        {$IFNDEF USE_OBJECT_ARC}
        LTransparentProxy.RemoveFreeNotification(Self);
        {$ENDIF}
      end;
    end;
  end;
end;

function TIdIOHandlerSocket.GetTransparentProxy: TIdCustomTransparentProxy;
var
  // under ARC, convert a weak reference to a strong reference before working with it
  LTransparentProxy: TIdCustomTransparentProxy;
begin
  LTransparentProxy := FTransparentProxy;
  // Necessary at design time for Borland SOAP support
  if LTransparentProxy = nil then begin
    LTransparentProxy := TIdSocksInfo.Create(Self); //default
    FTransparentProxy := LTransparentProxy;
    FImplicitTransparentProxy := True;
  end;
  Result := LTransparentProxy;
end;

function TIdIOHandlerSocket.GetUseNagle: Boolean;
begin
  if FBinding <> nil then begin
    Result := FBinding.UseNagle;
  end else begin
    Result := FUseNagle;
  end;
end;

procedure TIdIOHandlerSocket.SetUseNagle(AValue: Boolean);
begin
  FUseNagle := AValue;
  if FBinding <> nil then begin
    FBinding.UseNagle := AValue;
  end;
end;

// under ARC, all weak references to a freed object get nil'ed automatically
// so this is mostly redundant
procedure TIdIOHandlerSocket.Notification(AComponent: TComponent; Operation: TOperation);
begin
  if (Operation = opRemove) and (AComponent = FTransparentProxy) then begin
    FTransparentProxy := nil;
    FImplicitTransparentProxy := False;
  end;
  inherited Notification(AComponent, Operation);
end;

procedure TIdIOHandlerSocket.InitComponent;
begin
  inherited InitComponent;
  FUseNagle := True;
  FIPVersion := ID_DEFAULT_IP_VERSION;
end;

function TIdIOHandlerSocket.SourceIsAvailable: Boolean;
begin
  Result := BindingAllocated;
end;

function TIdIOHandlerSocket.CheckForError(ALastResult: Integer): Integer;
begin
  Result := GStack.CheckForSocketError(ALastResult, [Id_WSAESHUTDOWN, Id_WSAECONNABORTED, Id_WSAECONNRESET, Id_WSAETIMEDOUT]);
end;

procedure TIdIOHandlerSocket.RaiseError(AError: Integer);
begin
  GStack.RaiseSocketError(AError);
end;

end.
