<?php
    /**
     *	base include file for SimpleTest
     *	@package	SimpleTest
     *	@subpackage	MockObjects
     *	@version	$Id: socket.php 68 2009-07-31 18:23:01Z dlandau $
     */

    /**#@+
     * include SimpleTest files
     */
    require_once(dirname(__FILE__) . '/compatibility.php');
    /**#@-*/

    /**
     *    Stashes an error for later. Useful for constructors
     *    until PHP gets exceptions.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimpleStickyError {
        var $_error = 'Constructor not chained';

        /**
         *    Sets the error to empty.
         *    @access public
         */
        function SimpleStickyError() {
            $this->_clearError();
        }

        /**
         *    Test for an outstanding error.
         *    @return boolean           True if there is an error.
         *    @access public
         */
        function isError() {
            return ($this->_error != '');
        }

        /**
         *    Accessor for an outstanding error.
         *    @return string     Empty string if no error otherwise
         *                       the error message.
         *    @access public
         */
        function getError() {
            return $this->_error;
        }

        /**
         *    Sets the internal error.
         *    @param string       Error message to stash.
         *    @access protected
         */
        function _setError($error) {
            $this->_error = $error;
        }

        /**
         *    Resets the error state to no error.
         *    @access protected
         */
        function _clearError() {
            $this->_setError('');
        }
    }

    /**
     *    Wrapper for TCP/IP socket.
     *    @package SimpleTest
     *    @subpackage WebTester
     */
    class SimpleSocket extends SimpleStickyError {
        var $_handle;
        var $_is_open = false;
        var $_sent = '';
        var $lock_size;

        /**
         *    Opens a socket for reading and writing.
         *    @param string $host          Hostname to send request to.
         *    @param integer $port         Port on remote machine to open.
         *    @param integer $timeout      Connection timeout in seconds.
         *    @param integer $block_size   Size of chunk to read.
         *    @access public
         */
        function SimpleSocket($host, $port, $timeout, $block_size = 255) {
            $this->SimpleStickyError();
            if (! ($this->_handle = $this->_openSocket($host, $port, $error_number, $error, $timeout))) {
                $this->_setError("Cannot open [$host:$port] with [$error] within [$timeout] seconds");
                return;
            }
            $this->_is_open = true;
            $this->_block_size = $block_size;
            SimpleTestCompatibility::setTimeout($this->_handle, $timeout);
        }

        /**
         *    Writes some data to the socket and saves alocal copy.
         *    @param string $message       String to send to socket.
         *    @return boolean              True if successful.
         *    @access public
         */
        function write($message) {
            if ($this->isError() || ! $this->isOpen()) {
                return false;
            }
            $count = fwrite($this->_handle, $message);
            if (! $count) {
                if ($count === false) {
                    $this->_setError('Cannot write to socket');
                    $this->close();
                }
                return false;
            }
            fflush($this->_handle);
            $this->_sent .= $message;
            return true;
        }

        /**
         *    Reads data from the socket. The error suppresion
         *    is a workaround for PHP4 always throwing a warning
         *    with a secure socket.
         *    @return integer/boolean           Incoming bytes. False
         *                                     on error.
         *    @access public
         */
        function read() {
            if ($this->isError() || ! $this->isOpen()) {
                return false;
            }
            $raw = @fread($this->_handle, $this->_block_size);
            if ($raw === false) {
                $this->_setError('Cannot read from socket');
                $this->close();
            }
            return $raw;
        }

        /**
         *    Accessor for socket open state.
         *    @return boolean           True if open.
         *    @access public
         */
        function isOpen() {
            return $this->_is_open;
        }

        /**
         *    Closes the socket preventing further reads.
         *    Cannot be reopened once closed.
         *    @return boolean           True if successful.
         *    @access public
         */
        function close() {
            $this->_is_open = false;
            return fclose($this->_handle);
        }

        /**
         *    Accessor for content so far.
         *    @return string        Bytes sent only.
         *    @access public
         */
        function getSent() {
            return $this->_sent;
        }

        /**
         *    Actually opens the low level socket.
         *    @param string $host          Host to connect to.
         *    @param integer $port         Port on host.
         *    @param integer $error_number Recipient of error code.
         *    @param string $error         Recipoent of error message.
         *    @param integer $timeout      Maximum time to wait for connection.
         *    @access protected
         */
        function _openSocket($host, $port, &$error_number, &$error, $timeout) {
            return @fsockopen($host, $port, $error_number, $error, $timeout);
        }
    }

    /**
     *    Wrapper for TCP/IP socket over TLS.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimpleSecureSocket extends SimpleSocket {

        /**
         *    Opens a secure socket for reading and writing.
         *    @param string $host      Hostname to send request to.
         *    @param integer $port     Port on remote machine to open.
         *    @param integer $timeout  Connection timeout in seconds.
         *    @access public
         */
        function SimpleSecureSocket($host, $port, $timeout) {
            $this->SimpleSocket($host, $port, $timeout);
        }

        /**
         *    Actually opens the low level socket.
         *    @param string $host          Host to connect to.
         *    @param integer $port         Port on host.
         *    @param integer $error_number Recipient of error code.
         *    @param string $error         Recipient of error message.
         *    @param integer $timeout      Maximum time to wait for connection.
         *    @access protected
         */
        function _openSocket($host, $port, &$error_number, &$error, $timeout) {
            return parent::_openSocket("tls://$host", $port, $error_number, $error, $timeout);
        }
    }
?>