/home/ivoiecob/email.hirewise-va.com/vendor/afterlogic/mailso/lib/MailSo/Net/NetClient.php
<?php
/**
 * This code is licensed under AGPLv3 license or Afterlogic Software License
 * if commercial version of the product was purchased.
 * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
 */

namespace MailSo\Net;

/**
 * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
 * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
 * @copyright Copyright (c) 2019, Afterlogic Corp.
 *
 * @category MailSo
 * @package Net
 */
abstract class NetClient
{
    /**
     * @var resource
     */
    protected $rConnect;

    /**
     * @var bool
     */
    protected $bUnreadBuffer;

    /**
     * @var bool
     */
    protected $bRunningCallback;

    /**
     * @var string
     */
    protected $sResponseBuffer;

    /**
     * @var int
     */
    protected $iSecurityType;

    /**
     * @var string
     */
    protected $sConnectedHost;

    /**
     * @var int
     */
    protected $iConnectedPort;

    /**
     * @var bool
     */
    protected $bSecure;

    /**
     * @var int
     */
    protected $iConnectTimeOut;

    /**
     * @var int
     */
    protected $iSocketTimeOut;

    /**
     * @var int
     */
    protected $iStartConnectTime;

    /**
     * @var \MailSo\Log\Logger
     */
    protected $oLogger;

    /**
     * @var bool
     */
    public $__AUTOLOGOUT__;

    /**
     * @access protected
     */
    protected function __construct()
    {
        $this->rConnect = null;
        $this->bUnreadBuffer = false;
        $this->bRunningCallback = false;
        $this->oLogger = null;

        $this->__AUTOLOGOUT__ = true;

        $this->sResponseBuffer = '';

        $this->iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::NONE;
        $this->sConnectedHost = '';
        $this->iConnectedPort = 0;

        $this->bSecure = false;

        $this->iConnectTimeOut = 10;
        $this->iSocketTimeOut = 10;

        $this->clear();
    }

    /**
     * @return void
     */
    public function __destruct()
    {
        try {
            if ($this->__AUTOLOGOUT__) {
                $this->LogoutAndDisconnect();
            } else {
                $this->Disconnect();
            }
        } catch (\Exception $oException) {
        }
    }

    /**
     * @return void
     */
    public function clear()
    {
        $this->sResponseBuffer = '';

        $this->sConnectedHost = '';
        $this->iConnectedPort = 0;

        $this->iStartConnectTime = 0;
        $this->bSecure = false;
    }

    /**
     * @return string
     */
    public function GetConnectedHost()
    {
        return $this->sConnectedHost;
    }

    /**
     * @return int
     */
    public function GetConnectedPort()
    {
        return $this->iConnectedPort;
    }

    /**
     * @param int $iConnectTimeOut = 10
     * @param int $iSocketTimeOut = 10
     *
     * @return void
     */
    public function SetTimeOuts($iConnectTimeOut = 10, $iSocketTimeOut = 10)
    {
        $this->iConnectTimeOut = $iConnectTimeOut;
        $this->iSocketTimeOut = $iSocketTimeOut;
    }

    /**
     * @return resource|null
     */
    public function ConnectionResource()
    {
        return $this->rConnect;
    }

    /**
     * @param int $iErrNo
     * @param string $sErrStr
     * @param string $sErrFile
     * @param int $iErrLine
     *
     * @return bool
     */
    public function capturePhpErrorWithException($iErrNo, $sErrStr, $sErrFile, $iErrLine)
    {
        throw new \MailSo\Base\Exceptions\Exception($sErrStr, $iErrNo);
    }

    /**
     * @param string $sServerName
     * @param int $iPort
     * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT
     * @param bool $bVerifySsl = false
     *
     * @return void
     *
     * @throws \MailSo\Base\Exceptions\InvalidArgumentException
     * @throws \MailSo\Net\Exceptions\SocketAlreadyConnectedException
     * @throws \MailSo\Net\Exceptions\SocketCanNotConnectToHostException
     */
    public function Connect(
        $sServerName,
        $iPort,
        $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT,
        $bVerifySsl = false
    ) {
        if (!\MailSo\Base\Validator::NotEmptyString($sServerName, true) || !\MailSo\Base\Validator::PortInt($iPort)) {
            $this->writeLogException(
                new \MailSo\Base\Exceptions\InvalidArgumentException(),
                \MailSo\Log\Enumerations\Type::ERROR,
                true
            );
        }

        if ($this->IsConnected()) {
            $this->writeLogException(
                new Exceptions\SocketAlreadyConnectedException(),
                \MailSo\Log\Enumerations\Type::ERROR,
                true
            );
        }

        $sServerName = \trim($sServerName);

        $sErrorStr = '';
        $iErrorNo = 0;

        $this->sConnectedHost = $sServerName;
        $this->iConnectedPort = $iPort;
        $this->iSecurityType = $iSecurityType;
        $this->bSecure = \MailSo\Net\Enumerations\ConnectionSecurityType::UseSSL(
            $this->iConnectedPort,
            $this->iSecurityType
        );

        $this->sConnectedHost = \in_array(\strtolower(\substr($this->sConnectedHost, 0, 6)), array('ssl://', 'tcp://')) ?
            \substr($this->sConnectedHost, 6) : $this->sConnectedHost;

        $this->sConnectedHost = ($this->bSecure ? 'ssl://' : 'tcp://').$this->sConnectedHost;
        //		$this->sConnectedHost = ($this->bSecure ? 'ssl://' : '').$this->sConnectedHost;

        if (!$this->bSecure && \MailSo\Net\Enumerations\ConnectionSecurityType::SSL === $this->iSecurityType) {
            $this->writeLogException(
                new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('SSL isn\'t supported: ('.\implode(', ', \stream_get_transports()).')'),
                \MailSo\Log\Enumerations\Type::ERROR,
                true
            );
        }

        $this->iStartConnectTime = \microtime(true);
        $this->writeLog(
            'Start connection to "'.$this->sConnectedHost.':'.$this->iConnectedPort.'"',
            \MailSo\Log\Enumerations\Type::NOTE
        );

        $bVerifySsl = !!$bVerifySsl;
        $aStreamContextSettings = array(
            'ssl' => array(
                'verify_host' => $bVerifySsl,
                'verify_peer' => $bVerifySsl,
                'verify_peer_name' => $bVerifySsl,
                'allow_self_signed' => !$bVerifySsl
            )
        );

        \MailSo\Hooks::Run('Net.NetClient.StreamContextSettings/Filter', array(&$aStreamContextSettings));

        $rStreamContext = \stream_context_create($aStreamContextSettings);

        \set_error_handler(array(&$this, 'capturePhpErrorWithException'));

        try {
            $this->rConnect = \stream_socket_client(
                $this->sConnectedHost.':'.$this->iConnectedPort,
                $iErrorNo,
                $sErrorStr,
                $this->iConnectTimeOut,
                STREAM_CLIENT_CONNECT,
                $rStreamContext
            );
        } catch (\Exception $oExc) {
            $sErrorStr = $oExc->getMessage();
            $iErrorNo = $oExc->getCode();
        }

        \restore_error_handler();

        if (!\is_resource($this->rConnect)) {
            $this->writeLogException(
                new Exceptions\SocketCanNotConnectToHostException(
                    \MailSo\Base\Utils::ConvertSystemString($sErrorStr),
                    (int) $iErrorNo,
                    'Can\'t connect to host "'.$this->sConnectedHost.':'.$this->iConnectedPort.'"'
                ),
                \MailSo\Log\Enumerations\Type::NOTICE,
                true
            );
        }

        $this->writeLog(
            (\microtime(true) - $this->iStartConnectTime).' (raw connection)',
            \MailSo\Log\Enumerations\Type::TIME
        );

        if ($this->rConnect) {
            if (\MailSo\Base\Utils::FunctionExistsAndEnabled('stream_set_timeout')) {
                @\stream_set_timeout($this->rConnect, $this->iSocketTimeOut);
            }
        }
    }

    /**
     * @param int $iCryptoType = STREAM_CRYPTO_METHOD_TLS_CLIENT
     */
    public function EnableCrypto($iCryptoType = STREAM_CRYPTO_METHOD_TLS_CLIENT)
    {
        if (\is_resource($this->rConnect) &&
            \MailSo\Base\Utils::FunctionExistsAndEnabled('stream_socket_enable_crypto')) {
            if (!@\stream_socket_enable_crypto($this->rConnect, true, $iCryptoType)) {
                $this->writeLogException(
                    new \MailSo\Net\Exceptions\Exception('Cannot enable STARTTLS. [type='.$iCryptoType.']'),
                    \MailSo\Log\Enumerations\Type::ERROR,
                    true
                );
            }
        }
    }

    /**
     * @return void
     */
    public function Disconnect()
    {
        if (\is_resource($this->rConnect)) {
            $bResult = \fclose($this->rConnect);

            $this->writeLog('Disconnected from "'.$this->sConnectedHost.':'.$this->iConnectedPort.'" ('.
                (($bResult) ? 'success' : 'unsuccess').')', \MailSo\Log\Enumerations\Type::NOTE);

            if (0 !== $this->iStartConnectTime) {
                $this->writeLog(
                    (\microtime(true) - $this->iStartConnectTime).' (net session)',
                    \MailSo\Log\Enumerations\Type::TIME
                );

                $this->iStartConnectTime = 0;
            }

            $this->rConnect = null;
        }
    }

    public function Logout()
    {
    }

    /**
     * @retun void
     *
     * @throws \MailSo\Net\Exceptions\Exception
     */
    public function LogoutAndDisconnect()
    {
        if (\method_exists($this, 'Logout') && !$this->bUnreadBuffer && !$this->bRunningCallback) {
            $this->Logout();
        }

        $this->Disconnect();
    }

    /**
     * @param bool $bThrowExceptionOnFalse = false
     *
     * @return bool
     */
    public function IsConnected($bThrowExceptionOnFalse = false)
    {
        $bResult = \is_resource($this->rConnect);
        if (!$bResult && $bThrowExceptionOnFalse) {
            $this->writeLogException(
                new Exceptions\SocketConnectionDoesNotAvailableException(),
                \MailSo\Log\Enumerations\Type::ERROR,
                true
            );
        }

        return $bResult;
    }

    /**
     * @return void
     *
     * @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException
     */
    public function IsConnectedWithException()
    {
        $this->IsConnected(true);
    }

    /**
     * @return array|bool
     */
    public function StreamContextParams()
    {
        return \is_resource($this->rConnect) && \MailSo\Base\Utils::FunctionExistsAndEnabled('stream_context_get_options')
            ? \stream_context_get_params($this->rConnect) : false;
    }

    /**
     * @param string $sRaw
     * @param bool $bWriteToLog = true
     * @param string $sFakeRaw = ''
     *
     * @return void
     *
     * @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException
     * @throws \MailSo\Net\Exceptions\SocketWriteException
     */
    protected function sendRaw($sRaw, $bWriteToLog = true, $sFakeRaw = '')
    {
        if ($this->bUnreadBuffer) {
            $this->writeLogException(
                new Exceptions\SocketUnreadBufferException(),
                \MailSo\Log\Enumerations\Type::ERROR,
                true
            );
        }

        $bFake = 0 < \strlen($sFakeRaw);
        $sRaw .= "\r\n";

        if ($this->oLogger && $this->oLogger->IsShowSecter()) {
            $bFake = false;
        }

        if ($bFake) {
            $sFakeRaw .= "\r\n";
        }

        $mResult = @\fwrite($this->rConnect, $sRaw);
        if (false === $mResult) {
            $this->IsConnected(true);

            $this->writeLogException(
                new Exceptions\SocketWriteException(),
                \MailSo\Log\Enumerations\Type::ERROR,
                true
            );
        } else {
            \MailSo\Base\Loader::IncStatistic('NetWrite', $mResult);

            if ($bWriteToLog) {
                $this->writeLogWithCrlf('> '.($bFake ? $sFakeRaw : $sRaw), //.' ['.$iWriteSize.']',
                    $bFake ? \MailSo\Log\Enumerations\Type::SECURE : \MailSo\Log\Enumerations\Type::INFO);
            }
        }
    }

    /**
     * @param mixed $mReadLen = null
     * @param bool $bForceLogin = false
     *
     * @return void
     *
     * @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException
     * @throws \MailSo\Net\Exceptions\SocketReadException
     */
    protected function getNextBuffer($mReadLen = null, $bForceLogin = false)
    {
        if (null === $mReadLen) {
            $this->sResponseBuffer = @\fgets($this->rConnect);
        } else {
            $this->sResponseBuffer = '';
            $iRead = $mReadLen;
            while (0 < $iRead) {
                $sAddRead = @\fread($this->rConnect, $iRead);
                if (false === $sAddRead) {
                    $this->sResponseBuffer = false;
                    break;
                }

                $this->sResponseBuffer .= $sAddRead;
                $iRead -= \strlen($sAddRead);
            }
        }

        if (false === $this->sResponseBuffer) {
            $this->IsConnected(true);
            $this->bUnreadBuffer = true;

            $aSocketStatus = @\stream_get_meta_data($this->rConnect);
            if (isset($aSocketStatus['timed_out']) && $aSocketStatus['timed_out']) {
                $this->writeLogException(
                    new Exceptions\SocketReadTimeoutException(),
                    \MailSo\Log\Enumerations\Type::ERROR,
                    true
                );
            } else {
                //				$this->writeLog('Stream Meta: '.
                //					\print_r($aSocketStatus, true), \MailSo\Log\Enumerations\Type::ERROR);
                $this->writeLogException(
                    new Exceptions\SocketReadException(),
                    \MailSo\Log\Enumerations\Type::ERROR,
                    true
                );
            }
        } else {
            // fixed issue - the disposition field is incorrectly formatted without a space between the quoted value and NIL (e.g., "inline"NIL instead of ("inline" NIL))
            $this->sResponseBuffer = preg_replace('/"([a-zA-Z0-9_\-]+)"NIL/', '"$1" NIL', $this->sResponseBuffer);
            
            $iReadedLen = \strlen($this->sResponseBuffer);
            if (null === $mReadLen || $bForceLogin) {
                $iLimit = 5000; // 5kb
                if ($iLimit < $iReadedLen) {
                    $this->writeLogWithCrlf(
                        '[cutted:'.$iReadedLen.'b] < '.\substr($this->sResponseBuffer.'...', 0, $iLimit),
                        \MailSo\Log\Enumerations\Type::INFO
                    );
                } else {
                    $this->writeLogWithCrlf('< '.$this->sResponseBuffer, //.' ['.$iReadedLen.']',
                        \MailSo\Log\Enumerations\Type::INFO);
                }
            } else {
                $this->writeLog(
                    'Received '.$iReadedLen.'/'.$mReadLen.' bytes.',
                    \MailSo\Log\Enumerations\Type::INFO
                );
            }

            \MailSo\Base\Loader::IncStatistic('NetRead', $iReadedLen);
        }
    }

    /**
     * @return string
     */
    protected function getLogName()
    {
        return 'NET';
    }

    /**
     * @param string $sDesc
     * @param int $iDescType = \MailSo\Log\Enumerations\Type::INFO
     *
     * @return void
     */
    protected function writeLog($sDesc, $iDescType = \MailSo\Log\Enumerations\Type::INFO)
    {
        if ($this->oLogger) {
            $this->oLogger->Write($sDesc, $iDescType, $this->getLogName());
        }
    }

    /**
     * @param string $sDesc
     * @param int $iDescType = \MailSo\Log\Enumerations\Type::INFO
     *
     * @return void
     */
    protected function writeLogWithCrlf($sDesc, $iDescType = \MailSo\Log\Enumerations\Type::INFO)
    {
        $this->writeLog(\strtr($sDesc, array("\r" => '\r', "\n" => '\n')), $iDescType);
    }

    /**
     * @param \Exception $oException
     * @param int $iDescType = \MailSo\Log\Enumerations\Type::NOTICE
     * @param bool $bThrowException = false
     *
     * @return void
     */
    protected function writeLogException(
        $oException,
        $iDescType = \MailSo\Log\Enumerations\Type::NOTICE,
        $bThrowException = false
    ) {
        if ($this->oLogger) {
            if ($oException instanceof Exceptions\SocketCanNotConnectToHostException) {
                $this->oLogger->Write('Socket: ['.$oException->getSocketCode().'] '.$oException->getSocketMessage(), $iDescType, $this->getLogName());
            }

            $this->oLogger->WriteException($oException, $iDescType, $this->getLogName());
        }

        if ($bThrowException) {
            throw $oException;
        }
    }

    /**
     * @param \MailSo\Log\Logger $oLogger
     *
     * @return void
     *
     * @throws \MailSo\Base\Exceptions\InvalidArgumentException
     */
    public function SetLogger($oLogger)
    {
        if (!($oLogger instanceof \MailSo\Log\Logger)) {
            throw new \MailSo\Base\Exceptions\InvalidArgumentException();
        }

        $this->oLogger = $oLogger;
    }

    /**
     * @return \MailSo\Log\Logger|null
     */
    public function Logger()
    {
        return $this->oLogger;
    }
}