<?php 
/** 
 * This file is part of the PHP Generics package. 
 * 
 * @package Generics 
 */ 
namespace Generics\Socket; 
 
use Composer\CaBundle\CaBundle; 
use Generics\ResetException; 
use Generics\Streams\SocketStream; 
use Countable; 
use Exception; 
 
/** 
 * This abstract class provides basic secure socket functionality 
 * 
 * @author Maik Greubel <[email protected]> 
 */ 
abstract class SecureSocket implements SocketStream 
{ 
 
    /** 
     * The socket handle 
     * 
     * @var resource 
     */ 
    protected $handle; 
 
    /** 
     * The socket endpoint 
     * 
     * @var Endpoint 
     */ 
    protected $endpoint; 
 
    /** 
     * The stream context 
     * 
     * @var resource 
     */ 
    private $streamContext; 
 
    /** 
     * Create a new socket 
     * 
     * @param Endpoint $endpoint 
     *            The endpoint for the socket 
     */ 
    public function __construct(Endpoint $endpoint) 
    { 
        $this->endpoint = $endpoint; 
        $this->open(); 
    } 
 
    /** 
     * 
     * {@inheritdoc} 
     * @see \Generics\Streams\InputStream::read() 
     */ 
    public function read($length = 1, $offset = null): string 
    { 
        return stream_get_contents($this->handle, $length, $offset === null ? - 1 : intval($offset)); 
    } 
 
    /** 
     * 
     * {@inheritdoc} 
     * @see \Generics\Streams\Stream::isOpen() 
     */ 
    public function isOpen(): bool 
    { 
        return is_resource($this->handle) && ! feof($this->handle); 
    } 
 
    /** 
     * 
     * {@inheritdoc} 
     * @see \Generics\Streams\OutputStream::flush() 
     */ 
    public function flush() 
    { 
        // flush not available on streams 
    } 
 
    /** 
     * 
     * {@inheritdoc} 
     * @see \Generics\Streams\Stream::ready() 
     */ 
    public function ready(): bool 
    { 
        if (! is_resource($this->handle)) { 
            return false; 
        } 
         
        $read = array( 
            $this->handle 
        ); 
        $write = null; 
        $except = null; 
         
        $num = @stream_select($read, $write, $except, 0); 
         
        if ($num === false) { 
            throw new SocketException("Could not determine the stream client status"); 
        } 
         
        if ($num < 1) { 
            return false; 
        } 
         
        if (! in_array($this->handle, $read)) { 
            return false; 
        } 
         
        return true; 
    } 
 
    /** 
     * 
     * {@inheritdoc} 
     * @see Countable::count() 
     */ 
    public function count() 
    { 
        $meta = stream_get_meta_data($this->handle); 
         
        foreach ($meta as $data) { 
            if (strstr($data, 'Content-Length:')) { 
                return intval(trim(substr($data, 15))); 
            } 
        } 
        throw new SocketException("Cannot count elements of stream client"); 
    } 
 
    /** 
     * 
     * {@inheritdoc} 
     * @see \Generics\Resettable::reset() 
     */ 
    public function reset() 
    { 
        try { 
            $this->close(); 
            $this->open(); 
        } catch (Exception $ex) { 
            throw new ResetException($ex->getMessage(), array(), $ex->getCode(), $ex); 
        } 
    } 
 
    /** 
     * 
     * {@inheritdoc} 
     * @see \Generics\Streams\Stream::close() 
     */ 
    public function close() 
    { 
        if (is_resource($this->handle)) { 
            fclose($this->handle); 
            $this->handle = null; 
        } 
    } 
 
    /** 
     * 
     * {@inheritdoc} 
     * @see \Generics\Streams\OutputStream::write() 
     */ 
    public function write($buffer) 
    { 
        if (!$this->isWriteable()) { 
            throw new SocketException("Stream is not ready for writing"); 
        } 
        $len = strlen($buffer); 
        $written = 0; 
        do { 
            $bytes = fwrite($this->handle, $buffer); 
            if ($bytes === false) { 
                throw new SocketException("Could not write {len} bytes to stream (at least {written} written)", array( 
                    'len' => $len, 
                    'written' => $written 
                )); 
            } 
            $written += $bytes; 
        } while ($written != $len); 
    } 
 
    /** 
     * 
     * {@inheritdoc} 
     * @see \Generics\Streams\OutputStream::isWriteable() 
     */ 
    public function isWriteable(): bool 
    { 
        if (! is_resource($this->handle)) { 
            return false; 
        } 
         
        $read = null; 
        $write = array( 
            $this->handle 
        ); 
        $except = null; 
         
        $num = @stream_select($read, $write, $except, 0, 0); 
         
        if ($num === false) { 
            throw new SocketException("Could not determine the stream client status"); 
        } 
         
        if ($num < 1) { 
            return false; 
        } 
         
        if (! in_array($this->handle, $write)) { 
            return false; 
        } 
         
        return true; 
    } 
 
    private function open() 
    { 
        $this->prepareStreamContext(); 
         
        $this->handle = stream_socket_client( 
            sprintf('ssl://%s:%d', $this->endpoint->getAddress(), $this->endpoint->getPort()), // 
            $error, 
            $errorString, 
            2, 
            STREAM_CLIENT_CONNECT, 
            $this->streamContext 
        ); 
         
        if ($error > 0) { 
            throw new SocketException($errorString, array(), $error); 
        } 
    } 
 
    private function prepareStreamContext() 
    { 
        $opts = array( 
            'http' => array( 
                'method' => "GET" 
            ) 
        ); 
         
        $caPath = CaBundle::getSystemCaRootBundlePath(); 
         
        if (is_dir($caPath)) { 
            $opts['ssl']['capath'] = $caPath; 
        } else { 
            $opts['ssl']['cafile'] = $caPath; 
        } 
         
        $this->streamContext = stream_context_create($opts); 
    } 
 
    /** 
     * Retrieve end point object 
     * 
     * @return Endpoint 
     */ 
    public function getEndPoint(): Endpoint 
    { 
        return $this->endpoint; 
    } 
} 
 
 |