/*
 * TIP Class oriented Internet protocol library
 *
 * Copyright 2003 Giancarlo Niccolai <gian@niccolai.ws>
 * Copyright 2007 Hannes Ziegler (INetErrorDesc(), lastErrorCode(), lastErrorMessage())
 * Copyright 2007 Toninho@fwi (::nWrite to work like ::nRead)
 * Copyright 2009 Luiz Rafael Culik (luiz at xharbour dot com dot br) (Proxy connection)
 * Copyright 2009 Viktor Szakats (vszakats.net/harbour) (SSL support)
 * Copyright 2015 Jean Lefebvre (STARTTLS support)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file LICENSE.txt.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA (or visit https://www.gnu.org/licenses/).
 *
 * As a special exception, the Harbour Project gives permission for
 * additional uses of the text contained in its release of Harbour.
 *
 * The exception is that, if you link the Harbour libraries with other
 * files to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * Your use of that executable is in no way restricted on account of
 * linking the Harbour library code into it.
 *
 * This exception does not however invalidate any other reasons why
 * the executable file might be covered by the GNU General Public License.
 *
 * This exception applies only to the code released by the Harbour
 * Project under the name Harbour.  If you copy code from other
 * Harbour Project or Free Software Foundation releases into a copy of
 * Harbour, as the General Public License permits, the exception does
 * not apply to the code that you add in this way.  To avoid misleading
 * anyone as to the status of such modified files, you must delete
 * this exception notice from them.
 *
 * If you write modifications of your own for Harbour, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.
 *
 */

#include "hbclass.ch"

#include "error.ch"
#include "fileio.ch"

#if defined( _SSL_DEBUG_TEMP )
   #include "simpleio.ch"
#endif

#include "hbssl.ch"
#undef __HBEXTREQ__
#include "hbssl.hbx"

#define RCV_BUF_SIZE  Int( ::InetRcvBufSize( ::SocketCon ) / 2 )
#define SND_BUF_SIZE  Int( ::InetSndBufSize( ::SocketCon ) / 2 )

/* Inet Client class */
CREATE CLASS TIPClient

   CLASS VAR bInitSocks  INIT .F.
   CLASS VAR cCRLF       INIT tip_CRLF()

   VAR oUrl                      /* URL to which to connect */
   VAR oCredentials              /* credential needed to access the service */
   VAR nStatus           INIT 0  /* basic status */
   VAR SocketCon
   VAR bTrace

   VAR nDefaultRcvBuffSize
   VAR nDefaultSndBuffSize

   VAR nLength           INIT -1 /* Input stream length */
   VAR nRead             INIT 0  /* Input stream data read by the app*/
   VAR nLastRead         INIT 0  /* Last physical read amount */

   VAR nDefaultPort
   VAR nConnTimeout
   VAR bInitialized      INIT .F.

   VAR cReply
   VAR nAccessMode
   VAR nWrite            INIT 0
   VAR nLastWrite        INIT 0

   VAR bEof              INIT .F.
   VAR isOpen            INIT .F.

   VAR exGauge              /* Gauge control; it can be a codeblock or a function pointer. */

   VAR lSSL              INIT .F.

   VAR lHasSSL           INIT tip_SSL()
   VAR ssl_ctx
   VAR ssl
   VAR nSSLError         INIT 0

   VAR Cargo

   /* Data for proxy connection */
   VAR cProxyHost        INIT ""
   VAR nProxyPort        INIT 0
   VAR cProxyUser
   VAR cProxyPassword
   VAR lProxyXferSSL     INIT .F.  /* SSL should only be enabled after proxy connection
                                      NOTE: I've only checked HTTP POST / GET, unsure if
                                      there are other functions with this issue. */

   METHOD New( oUrl, xTrace, oCredentials )
   METHOD Open( cUrl )

   METHOD EnableSSL( lEnable )

   METHOD Read( nLen )
   METHOD ReadToFile( cFile, nMode, nSize )
   METHOD Write( cData, nLen, lCommit )
   METHOD Commit()
   METHOD WriteFromFile( cFile )
   METHOD Reset()
   METHOD Close()

   METHOD SetProxy( cProxyHost, nProxyPort, cProxyUser, cProxyPassword )

   METHOD lastErrorCode() INLINE ::nLastError
   METHOD lastErrorMessage( SocketCon ) INLINE ::inetErrorDesc( SocketCon )

   METHOD InetRcvBufSize( SocketCon, nSizeBuff )
   METHOD InetSndBufSize( SocketCon, nSizeBuff )

   METHOD InetTimeOut( SocketCon, nConnTimeout )

   METHOD HasSSL() INLINE ::lHasSSL

   PROTECTED:

   VAR nLastError INIT 0

   METHOD OpenProxy( cServer, nPort, cProxy, nProxyPort, cResp, cUserName, cPassword, cUserAgent )
   METHOD ReadHTTPProxyResponse( cResponse )

   /* Methods to log data if needed */
   METHOD inetRecv( SocketCon, cStr1, len )
   METHOD inetRecvLine( SocketCon, nRet, size )
   METHOD inetRecvAll( SocketCon, cRet, size )
   METHOD inetCount( SocketCon )
   METHOD inetSendAll( SocketCon, cData, nLen )
   METHOD inetErrorCode( SocketCon )
   METHOD inetErrorDesc( SocketCon )
   METHOD inetConnect( cServer, nPort, SocketCon )

   METHOD Log( ... )

ENDCLASS

METHOD New( oUrl, xTrace, oCredentials ) CLASS TIPClient

   LOCAL oErr
   LOCAL oLog
   LOCAL lSSL

   DO CASE
   CASE HB_ISSTRING( xTrace ) .OR. hb_defaultValue( xTrace, .F. )
      oLog := TIPLog():New( iif( HB_ISSTRING( xTrace ), xTrace, NIL ) )
      ::bTrace := {| cMsg | iif( PCount() > 0, oLog:Add( cMsg ), oLog:Close() ) }
   CASE HB_ISEVALITEM( xTrace )
      ::bTrace := xTrace
   ENDCASE

   IF HB_ISSTRING( oUrl )
      oUrl := TUrl():New( oUrl )
   ENDIF

   SWITCH oURL:cProto
   CASE "ftp"
   CASE "http"
   CASE "pop"
   CASE "smtp"
      lSSL := .F.
      EXIT
   CASE "ftps"
   CASE "https"
   CASE "pop3s"
   CASE "pops"
   CASE "smtps"
      IF ::lHasSSL
         lSSL := .T.
         EXIT
      ENDIF
   OTHERWISE
      oErr := ErrorNew()
      oErr:Args          := { Self, oURL:cProto }
      oErr:CanDefault    := .F.
      oErr:CanRetry      := .F.
      oErr:CanSubstitute := .T.
      oErr:Description   := "unsupported protocol"
      oErr:GenCode       := EG_UNSUPPORTED
      oErr:Operation     := ::className() + ":new()"
      oErr:Severity      := ES_ERROR
      oErr:SubCode       := 1081
      oErr:SubSystem     := "BASE"
      Eval( ErrorBlock(), oErr )
   ENDSWITCH

   IF ! ::bInitSocks
      hb_inetInit()
      IF ::lHasSSL
         SSL_init()
         RAND_seed( hb_randStr( 20 ) + hb_TToS( hb_DateTime() ) + NetName() )
      ENDIF
      ::bInitSocks := .T.
   ENDIF

   IF ::lHasSSL .AND. lSSL
      ::EnableSSL( .T. )
   ENDIF

   ::oUrl         := oUrl
   ::oCredentials := oCredentials

   RETURN Self

METHOD Open( cUrl ) CLASS TIPClient

   LOCAL nPort

   IF HB_ISSTRING( cUrl )
      ::oUrl := TUrl():New( cUrl )
   ENDIF

   nPort := iif( ::oUrl:nPort == -1, ::nDefaultPort, ::oUrl:nPort )

   ::SocketCon := hb_inetCreate()

   ::InetTimeOut( ::SocketCon )

   IF ::cProxyHost == ""
      ::inetConnect( ::oUrl:cServer, nPort, ::SocketCon )
      IF ::inetErrorCode( ::SocketCon ) != 0
         RETURN .F.
      ENDIF
   ELSEIF ! ::OpenProxy( ::oUrl:cServer, nPort, ::cProxyHost, ::nProxyPort,, ::cProxyUser, ::cProxyPassword, "Mozilla/3.0 compatible" )
      RETURN .F.
   ENDIF
   ::isOpen := .T.

   RETURN .T.

METHOD EnableSSL( lEnable ) CLASS TIPClient

   LOCAL lSuccess

   IF ::lSSL == lEnable
      RETURN .T.
   ENDIF

   ::lProxyXferSSL := .F.

   IF lEnable
      IF ::lHasSSL
         ::ssl_ctx := SSL_CTX_new()
         ::ssl := SSL_new( ::ssl_ctx )
         ::lSSL := .T.
         lSuccess := .T.
      ELSE
         lSuccess := .F.
      ENDIF
   ELSE
      IF ::lHasSSL
         ::lSSL := .F.
         lSuccess := .T.
      ELSE
         lSuccess := .T.
      ENDIF
   ENDIF

   RETURN lSuccess

METHOD OpenProxy( cServer, nPort, cProxy, nProxyPort, cResp, cUserName, cPassword, cUserAgent ) CLASS TIPClient

   LOCAL cRequest
   LOCAL lRet := .F.
   LOCAL tmp

   ::inetConnect( cProxy, nProxyPort, ::SocketCon )

   IF ( tmp := ::inetErrorCode( ::SocketCon ) ) == 0
      cRequest := ;
         "CONNECT " + cServer + ":" + hb_ntos( nPort ) + " HTTP/1.1" + Chr( 13 ) + Chr( 10 ) + ;
         "Proxy-Connection: Keep-Alive" + Chr( 13 ) + Chr( 10 )
      IF HB_ISSTRING( cUserAgent ) .AND. ! cUserAgent == ""
         cRequest += "User-Agent: " + cUserAgent + Chr( 13 ) + Chr( 10 )
      ENDIF
      IF HB_ISSTRING( cUserName ) .AND. ! cUserName == ""
         cRequest += "Proxy-Authorization: Basic " + hb_base64Encode( cUserName + ":" + hb_defaultValue( cPassword, "" ) ) + Chr( 13 ) + Chr( 10 )
      ENDIF
      cRequest += Chr( 13 ) + Chr( 10 )
      ::inetSendAll( ::SocketCon, cRequest )
      cResp := ""
      IF ::ReadHTTPProxyResponse( @cResp ) .AND. ;
         ( tmp := At( " ", cResp ) ) > 0 .AND. ;
         Val( SubStr( cResp, tmp + 1 ) ) == 200
         lRet := .T.
      ELSE
         ::close()
      ENDIF
   ELSE
      cResp := hb_ntos( tmp )
   ENDIF

   /* Enable SSL after proxy connection is OK */
   IF lRet .AND. ::lSSL .AND. ::lHasSSL
      __tip_SSLConnectFD( ::ssl, ::SocketCon )
      ::lProxyXferSSL := .T.
   ELSE
      ::lProxyXferSSL := .F.
   ENDIF

   RETURN lRet

METHOD ReadHTTPProxyResponse( /* @ */ cResponse ) CLASS TIPClient

   LOCAL cBuffer

   DO WHILE .T.
      cBuffer := Space( 1 )
      IF ::inetRecv( ::SocketCon, @cBuffer, hb_BLen( cBuffer ) ) <= 0
         RETURN .F.
      ENDIF
      cResponse += cBuffer
      IF hb_BRight( cResponse, 4 ) == e"\r\n\r\n"
         EXIT
      ENDIF
   ENDDO

   RETURN .T.

METHOD Close() CLASS TIPClient

   LOCAL nRet

   IF Empty( ::SocketCon )
      nRet := -1
   ELSE
      nRet := hb_inetClose( ::SocketCon )

      IF ::lSSL .AND. ::lHasSSL
         SSL_shutdown( ::ssl )
         ::ssl := NIL
         ::ssl_ctx := NIL
      ENDIF

      ::SocketCon := NIL
      ::isOpen := .F.
      ::lProxyXferSSL := .F.
   ENDIF

   IF HB_ISEVALITEM( ::bTrace )
      /* Call with no parameter to signal end of logging session */
      Eval( ::bTrace )
   ENDIF

   RETURN nRet

METHOD Reset() CLASS TIPClient

   ::bInitialized := .F.
   ::bEof := .F.

   RETURN .T.

METHOD Commit() CLASS TIPClient
   RETURN .T.

METHOD Read( nLen ) CLASS TIPClient

   LOCAL cStr0
   LOCAL cStr1

   IF ::nLength > 0 .AND. ::nLength == ::nRead
      RETURN NIL
   ENDIF

   IF ! HB_ISNUMERIC( nLen ) .OR. nLen <= 0 .OR. ( ::nLength > 0 .AND. nLen > ::nLength - ::nRead )
      nLen := ::nLength - ::nRead
   ENDIF

   IF nLen <= 0
      // read till end of stream
      cStr1 := Space( RCV_BUF_SIZE )
      cStr0 := ""
      DO WHILE ( ::nLastRead := ::inetRecv( ::SocketCon, @cStr1, RCV_BUF_SIZE ) ) > 0
         ::nRead += ::nLastRead
         cStr0 += hb_BLeft( cStr1, ::nLastRead )
      ENDDO
      ::bEof := .T.
   ELSE
      // read an amount of data
      cStr0 := Space( nLen )

      IF ::lSSL .AND. ::lHasSSL
         /* Getting around implementing the hack used in non-SSL branch for now.
            IMO the proper fix would have been done to hb_inetRecvAll(). [vszakats] */
         ::nLastRead := ::inetRecvAll( ::SocketCon, @cStr0, nLen )
      ELSE
         // S.R. if length of file is less than RCV_BUF_SIZE hb_inetRecvAll() returns 0
         //      ::nLastRead := ::InetRecvAll( ::SocketCon, @cStr0, nLen )
         ::inetRecvAll( ::SocketCon, @cStr0, nLen )
         ::nLastRead := ::inetCount( ::SocketCon )
      ENDIF
      ::nRead += ::nLastRead

      IF ::nLastRead != nLen
         ::bEof := .T.
         cStr0 := hb_BLeft( cStr0, ::nLastRead )
#if 0
         RETURN NIL
#endif
      ENDIF

      IF ::nRead == ::nLength
         ::bEof := .T.
      ENDIF
   ENDIF

   RETURN cStr0

METHOD ReadToFile( /* @ */ cFile, nMode, nSize ) CLASS TIPClient

   LOCAL hFile
   LOCAL cData
   LOCAL nSent := 0

   LOCAL lToMemory := hb_PIsByRef( 1 )

   hb_default( @nSize, 0 )

   IF lToMemory
      cFile := ""
   ENDIF

   IF HB_ISEVALITEM( ::exGauge ) .AND. ;
      ! hb_defaultValue( Eval( ::exGauge, nSent, nSize, Self ), .T. )
      RETURN .F.
   ENDIF

   ::nRead   := 0
   ::nStatus := 1

   DO WHILE ::inetErrorCode( ::SocketCon ) == 0 .AND. ! ::bEof
      IF ( cData := ::Read( RCV_BUF_SIZE ) ) == NIL
         IF hFile != NIL
            hb_vfClose( hFile )
         ENDIF
         RETURN ::inetErrorCode( ::SocketCon ) == 0
      ENDIF
      IF ! lToMemory .AND. hFile == NIL
         IF ( hFile := hb_vfOpen( cFile, hb_bitOr( FO_CREAT + FO_TRUNC + FO_WRITE, hb_defaultValue( nMode, 0 ) ) ) ) == NIL
            ::nStatus := 0
            RETURN .F.
         ENDIF
      ENDIF

      IF lToMemory
         cFile += cData
      ELSEIF hb_vfWrite( hFile, cData ) != hb_BLen( cData )
         hb_vfClose( hFile )
         RETURN .F.
      ENDIF

      nSent += hb_BLen( cData )

      IF HB_ISEVALITEM( ::exGauge ) .AND. ;
         ! hb_defaultValue( Eval( ::exGauge, nSent, nSize, Self ), .T. )
         IF hFile != NIL
            hb_vfClose( hFile )
         ENDIF
         RETURN .F.
      ENDIF
   ENDDO

   IF nSent > 0
      ::Commit()
   ENDIF

   ::nStatus := 2
   IF hFile != NIL
      hb_vfClose( hFile )
   ENDIF

   RETURN ::inetErrorCode( ::SocketCon ) == 0

METHOD WriteFromFile( cFile ) CLASS TIPClient

   LOCAL nFIn
   LOCAL cData
   LOCAL nLen
   LOCAL nSize, nSent, nBufSize

   ::nWrite  := 0
   ::nStatus := 0
   IF ( nFIn := hb_vfOpen( cFile, FO_READ ) ) == NIL
      RETURN .F.
   ENDIF
   nSize := hb_vfSize( nFIn )
   hb_vfSeek( nFIn, 0 )

   nBufSize := SND_BUF_SIZE

   // allow initialization of the gauge
   nSent := 0

   IF HB_ISEVALITEM( ::exGauge ) .AND. ;
      ! hb_defaultValue( Eval( ::exGauge, nSent, nSize, Self ), .T. )
      hb_vfClose( nFIn )
      RETURN .F.
   ENDIF

   ::nStatus := 1
   cData := Space( nBufSize )
   DO WHILE ( nLen := hb_vfRead( nFIn, @cData, nBufSize ) ) > 0
      IF ::Write( @cData, nLen ) != nLen
         hb_vfClose( nFIn )
         RETURN .F.
      ENDIF
      nSent += nLen
      IF HB_ISEVALITEM( ::exGauge ) .AND. ;
         ! hb_defaultValue( Eval( ::exGauge, nSent, nSize, Self ), .T. )
         hb_vfClose( nFIn )
         RETURN .F.
      ENDIF
   ENDDO

   // it may happen that the file has length 0
   IF nSent > 0
      ::Commit()
   ENDIF

   ::nStatus := 2
   hb_vfClose( nFIn )

   RETURN .T.

METHOD Write( cData, nLen, lCommit ) CLASS TIPClient

   IF ! HB_ISNUMERIC( nLen ) .OR. nLen <= 0
      nLen := hb_BLen( cData )
   ENDIF

   ::nLastWrite := ::inetSendAll( ::SocketCon, cData, nLen )

   IF hb_defaultValue( lCommit, .F. )
      ::Commit()
   ENDIF

   ::nWrite += ::nLastWrite

   RETURN ::nLastWrite

METHOD inetSendAll( SocketCon, cData, nLen ) CLASS TIPClient

   LOCAL nRet

   IF ! HB_ISNUMERIC( nLen ) .OR. nLen <= 0
      nLen := hb_BLen( cData )
   ENDIF

   IF ::lSSL .AND. ( ::cProxyHost == "" .OR. ::lProxyXferSSL )
      IF ::lHasSSL
#if defined( _SSL_DEBUG_TEMP )
         ? "SSL_write()", cData
#endif
         nRet := SSL_write( ::ssl, cData, nLen )
         ::nSSLError := iif( nRet < 0, nRet, 0 )
      ELSE
         nRet := 0
      ENDIF
   ELSE
      nRet := hb_inetSendAll( SocketCon, cData, nLen )
   ENDIF

   IF HB_ISEVALITEM( ::bTrace )
      ::Log( SocketCon, nlen, cData, nRet )
   ENDIF

   RETURN nRet

METHOD inetCount( SocketCon ) CLASS TIPClient

   LOCAL nRet := hb_inetCount( SocketCon )

   IF HB_ISEVALITEM( ::bTrace )
      ::Log( SocketCon, nRet )
   ENDIF

   RETURN nRet

METHOD inetRecv( SocketCon, cStr1, len ) CLASS TIPClient

   LOCAL nRet

   IF ::lSSL .AND. ( ::cProxyHost == "" .OR. ::lProxyXferSSL )
      IF ::lHasSSL
#if defined( _SSL_DEBUG_TEMP )
         ? "SSL_read()"
#endif
         nRet := SSL_read( ::ssl, @cStr1, len )
         ::nSSLError := iif( nRet < 0, nRet, 0 )
      ELSE
         nRet := 0
      ENDIF
   ELSE
      nRet := hb_inetRecv( SocketCon, @cStr1, len )
   ENDIF

   IF HB_ISEVALITEM( ::bTrace )
      ::Log( SocketCon, "", len, iif( nRet >= 0, cStr1, nRet ) )
   ENDIF

   RETURN nRet

METHOD inetRecvLine( SocketCon, nRet, size ) CLASS TIPClient

   LOCAL cRet

   IF ::lSSL
      IF ::lHasSSL
         nRet := hb_SSL_read_line( ::ssl, @cRet, size, ::nConnTimeout )
#if defined( _SSL_DEBUG_TEMP )
         ? "hb_SSL_read_line()", cRet
#endif
         IF nRet == 0 .OR. cRet == ""
            cRet := NIL
         ENDIF
         ::nSSLError := iif( nRet < 0, nRet, 0 )
      ELSE
         cRet := ""
         nRet := 0
      ENDIF
   ELSE
      cRet := hb_inetRecvLine( SocketCon, @nRet, size )
   ENDIF

   IF HB_ISEVALITEM( ::bTrace )
      ::Log( SocketCon, "", size, cRet )
   ENDIF

   RETURN cRet

METHOD inetRecvAll( SocketCon, cRet, size ) CLASS TIPClient

   LOCAL nRet

   IF ::lSSL
      IF ::lHasSSL
         nRet := hb_SSL_read_all( ::ssl, @cRet, size, ::nConnTimeout )
#if defined( _SSL_DEBUG_TEMP )
         ? "hb_SSL_read_all()", cRet
#endif
         IF nRet == 0 .OR. cRet == ""
            cRet := NIL
         ENDIF
         ::nSSLError := iif( nRet < 0, nRet, 0 )
      ELSE
         cRet := ""
         nRet := 0
      ENDIF
   ELSE
      nRet := hb_inetRecvAll( SocketCon, @cRet, size )
   ENDIF

   IF HB_ISEVALITEM( ::bTrace )
      ::Log( SocketCon, "", size, iif( nRet >= 0, cRet, nRet ) )
   ENDIF

   RETURN nRet

METHOD inetErrorCode( SocketCon ) CLASS TIPClient

   LOCAL nRet

   IF ::lSSL
      IF ::lHasSSL
         nRet := iif( ::nSSLError == 0, 0, SSL_get_error( ::ssl, ::nSSLError ) )
      ELSE
         nRet := 0
      ENDIF
   ELSE
      nRet := hb_inetErrorCode( SocketCon )
   ENDIF

   ::nLastError := nRet

   IF HB_ISEVALITEM( ::bTrace )
      ::Log( SocketCon, nRet )
   ENDIF

   RETURN nRet

METHOD inetErrorDesc( SocketCon ) CLASS TIPClient

   hb_default( @SocketCon, ::SocketCon )

   IF ! Empty( SocketCon )
      IF ::lSSL
         IF ::lHasSSL .AND. ::nSSLError != 0
            RETURN ERR_error_string( SSL_get_error( ::ssl, ::nSSLError ) )
         ENDIF
      ELSE
         RETURN hb_inetErrorDesc( SocketCon )
      ENDIF
   ENDIF

   RETURN ""

/* BROKEN, should test number of parameters and act accordingly, see doc/inet.txt */
METHOD PROCEDURE inetConnect( cServer, nPort, SocketCon ) CLASS TIPClient

   hb_inetConnect( cServer, nPort, SocketCon )

   /* IMPORTANT: if internet connection is off and address is not
                 resolved and it is SSL compliant, then RTE must
                 be avoided [pritpal] */
   IF hb_inetStatus( SocketCon ) == -1
      RETURN
   ENDIF

   IF hb_defaultValue( ::nDefaultSndBuffSize, 0 ) > 0
      ::InetSndBufSize( SocketCon, ::nDefaultSndBuffSize )
   ENDIF

   IF hb_defaultValue( ::nDefaultRcvBuffSize, 0 ) > 0
      ::InetRcvBufSize( SocketCon, ::nDefaultRcvBuffSize )
   ENDIF

   IF ::lSSL .AND. ::lHasSSL .AND. ::cProxyHost == ""
      __tip_SSLConnectFD( ::ssl, SocketCon )  /* Proxy will do this in OpenProxy() */
      ::lProxyXferSSL := .T.
   ELSE
      ::lProxyXferSSL := .F.
   ENDIF

   IF HB_ISEVALITEM( ::bTrace )
      ::Log( cServer, nPort, SocketCon )
   ENDIF

   RETURN

/* Methods to manage buffers */
METHOD InetRcvBufSize( SocketCon, nSizeBuff ) CLASS TIPClient

   IF HB_ISNUMERIC( nSizeBuff ) .AND. nSizeBuff > 0
      hb_inetSetRcvBufSize( SocketCon, nSizeBuff )
   ENDIF

   RETURN hb_inetGetRcvBufSize( SocketCon )

METHOD InetSndBufSize( SocketCon, nSizeBuff ) CLASS TIPClient

   IF HB_ISNUMERIC( nSizeBuff ) .AND. nSizeBuff > 0
      hb_inetSetSndBufSize( SocketCon, nSizeBuff )
   ENDIF

   RETURN hb_inetGetSndBufSize( SocketCon )

METHOD InetTimeOut( SocketCon, nConnTimeout ) CLASS TIPClient

   IF HB_ISNUMERIC( nConnTimeout )
      ::nConnTimeout := nConnTimeout
   ENDIF
   IF HB_ISNUMERIC( ::nConnTimeout )
      RETURN hb_inetTimeout( SocketCon, ::nConnTimeout )
   ENDIF

   RETURN NIL

/* Called from another method with list of parameters and, as last parameter, return code
   of function being logged.
   Example, I want to log MyFunc( a, b, c ) which returns m,
            ::Log( a, b, c, m ) */
METHOD Log( ... ) CLASS TIPClient

   LOCAL xVar
   LOCAL cMsg

   IF HB_ISEVALITEM( ::bTrace )

      cMsg := DToS( Date() ) + "-" + Time() + Space( 2 ) + ;
         SubStr( ProcName( 1 ), RAt( ":", ProcName( 1 ) ) ) + ;
         "( "

      FOR EACH xVar IN hb_AParams()

         // Preserves CRLF on result
         IF xVar:__enumIsLast()
            cMsg += hb_CStr( xVar )
         ELSE
            cMsg += hb_StrReplace( AllTrim( hb_CStr( xVar ) ), Chr( 13 ) + Chr( 10 ), { "<cr>", "<lf>" } )
         ENDIF

         DO CASE
         CASE xVar:__enumIsLast()
            cMsg += " <<" + hb_eol() + hb_eol()
         CASE xVar:__enumIndex() == PCount() - 1
            cMsg += " )" + hb_eol() + ">> "
         OTHERWISE
            cMsg += ", "
         ENDCASE
      NEXT

      Eval( ::bTrace, cMsg )
   ENDIF

   RETURN Self

METHOD SetProxy( cProxyHost, nProxyPort, cProxyUser, cProxyPassword ) CLASS TIPClient

   IF __clsParent( cProxyHost:classH(), "TURL" )
      ::cProxyHost     := cProxyHost:cServer
      ::nProxyPort     := cProxyHost:nPort
      ::cProxyUser     := cProxyHost:cUserid
      ::cProxyPassword := cProxyHost:cPassword
   ELSE
      ::cProxyHost     := hb_defaultValue( cProxyHost, "" )
      ::nProxyPort     := hb_defaultValue( nProxyPort, 0 )
      ::cProxyUser     := hb_defaultValue( cProxyUser, "" )
      ::cProxyPassword := hb_defaultValue( cProxyPassword, "" )
   ENDIF

   RETURN Self

FUNCTION tip_SSL()
   RETURN hb_IsFunction( "__HBEXTERN__HBSSL__" )

FUNCTION __tip_SSLConnectFD( ssl, SocketCon )

   SSL_set_mode( ssl, HB_SSL_MODE_AUTO_RETRY )
   SSL_set_fd( ssl, hb_inetFD( SocketCon ) )
   SSL_connect( ssl )

   /* TODO: Add error handling */

   RETURN .T.
