<?php
    /**
     *	base include file for SimpleTest
     *	@package	SimpleTest
     *	@subpackage	WebTester
     *	@version	$Id$
     */
     
    /**#@+
     *	include other SimpleTest class files
     */
    require_once(dirname(__FILE__) . '/socket.php');
    /**#@-*/

    /**
     *    Single post parameter.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimpleEncodedPair {
        var $_key;
        var $_value;
        
        /**
         *    Stashes the data for rendering later.
         *    @param string $key       Form element name.
         *    @param string $value     Data to send.
         */
        function SimpleEncodedPair($key, $value) {
            $this->_key = $key;
            $this->_value = $value;
        }
        
        /**
         *    The pair as a single string.
         *    @return string        Encoded pair.
         *    @access public
         */
        function asRequest() {
            return urlencode($this->_key) . '=' . urlencode($this->_value);
        }
        
        /**
         *    The MIME part as a string.
         *    @return string        MIME part encoding.
         *    @access public
         */
        function asMime() {
            $part = 'Content-Disposition: form-data; ';
            $part .= "name=\"" . $this->_key . "\"\r\n";
            $part .= "\r\n" . $this->_value;
            return $part;
        }
        
        /**
         *    Is this the value we are looking for?
         *    @param string $key    Identifier.
         *    @return boolean       True if matched.
         *    @access public
         */
        function isKey($key) {
            return $key == $this->_key;
        }
        
        /**
         *    Is this the value we are looking for?
         *    @return string       Identifier.
         *    @access public
         */
        function getKey() {
            return $this->_key;
        }
        
        /**
         *    Is this the value we are looking for?
         *    @return string       Content.
         *    @access public
         */
        function getValue() {
            return $this->_value;
        }
    }

    /**
     *    Single post parameter.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimpleAttachment {
        var $_key;
        var $_content;
        var $_filename;
        
        /**
         *    Stashes the data for rendering later.
         *    @param string $key          Key to add value to.
         *    @param string $content      Raw data.
         *    @param hash $filename       Original filename.
         */
        function SimpleAttachment($key, $content, $filename) {
            $this->_key = $key;
            $this->_content = $content;
            $this->_filename = $filename;
        }
        
        /**
         *    The pair as a single string.
         *    @return string        Encoded pair.
         *    @access public
         */
        function asRequest() {
            return '';
        }
        
        /**
         *    The MIME part as a string.
         *    @return string        MIME part encoding.
         *    @access public
         */
        function asMime() {
            $part = 'Content-Disposition: form-data; ';
            $part .= 'name="' . $this->_key . '"; ';
            $part .= 'filename="' . $this->_filename . '"';
            $part .= "\r\nContent-Type: " . $this->_deduceMimeType();
            $part .= "\r\n\r\n" . $this->_content;
            return $part;
        }
        
        /**
         *    Attempts to figure out the MIME type from the
         *    file extension and the content.
         *    @return string        MIME type.
         *    @access private
         */
        function _deduceMimeType() {
            if ($this->_isOnlyAscii($this->_content)) {
                return 'text/plain';
            }
            return 'application/octet-stream';
        }
        
        /**
         *    Tests each character is in the range 0-127.
         *    @param string $ascii    String to test.
         *    @access private
         */
        function _isOnlyAscii($ascii) {
            for ($i = 0, $length = strlen($ascii); $i < $length; $i++) {
                if (ord($ascii[$i]) > 127) {
                    return false;
                }
            }
            return true;
        }
        
        /**
         *    Is this the value we are looking for?
         *    @param string $key    Identifier.
         *    @return boolean       True if matched.
         *    @access public
         */
        function isKey($key) {
            return $key == $this->_key;
        }
        
        /**
         *    Is this the value we are looking for?
         *    @return string       Identifier.
         *    @access public
         */
        function getKey() {
            return $this->_key;
        }
        
        /**
         *    Is this the value we are looking for?
         *    @return string       Content.
         *    @access public
         */
        function getValue() {
            return $this->_filename;
        }
    }

    /**
     *    Bundle of GET/POST parameters. Can include
     *    repeated parameters.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimpleEncoding {
        var $_request;
        
        /**
         *    Starts empty.
         *    @param array $query       Hash of parameters.
         *                              Multiple values are
         *                              as lists on a single key.
         *    @access public
         */
        function SimpleEncoding($query = false) {
            if (! $query) {
                $query = array();
            }
            $this->clear();
            $this->merge($query);
        }
        
        /**
         *    Empties the request of parameters.
         *    @access public
         */
        function clear() {
            $this->_request = array();
        }
        
        /**
         *    Adds a parameter to the query.
         *    @param string $key            Key to add value to.
         *    @param string/array $value    New data.
         *    @access public
         */
        function add($key, $value) {
            if ($value === false) {
                return;
            }
            if (is_array($value)) {
                foreach ($value as $item) {
                    $this->_addPair($key, $item);
                }
            } else {
                $this->_addPair($key, $value);
            }
        }
        
        /**
         *    Adds a new value into the request.
         *    @param string $key            Key to add value to.
         *    @param string/array $value    New data.
         *    @access private
         */
        function _addPair($key, $value) {
            $this->_request[] = new SimpleEncodedPair($key, $value);
        }
        
        /**
         *    Adds a MIME part to the query. Does nothing for a
         *    form encoded packet.
         *    @param string $key          Key to add value to.
         *    @param string $content      Raw data.
         *    @param hash $filename       Original filename.
         *    @access public
         */
        function attach($key, $content, $filename) {
            $this->_request[] = new SimpleAttachment($key, $content, $filename);
        }
        
        /**
         *    Adds a set of parameters to this query.
         *    @param array/SimpleQueryString $query  Multiple values are
         *                                           as lists on a single key.
         *    @access public
         */
        function merge($query) {
            if (is_object($query)) {
                $this->_request = array_merge($this->_request, $query->getAll());
            } elseif (is_array($query)) {
                foreach ($query as $key => $value) {
                    $this->add($key, $value);
                }
            }
        }
        
        /**
         *    Accessor for single value.
         *    @return string/array    False if missing, string
         *                            if present and array if
         *                            multiple entries.
         *    @access public
         */
        function getValue($key) {
            $values = array();
            foreach ($this->_request as $pair) {
                if ($pair->isKey($key)) {
                    $values[] = $pair->getValue();
                }
            }
            if (count($values) == 0) {
                return false;
            } elseif (count($values) == 1) {
                return $values[0];
            } else {
                return $values;
            }
        }
        
        /**
         *    Accessor for listing of pairs.
         *    @return array        All pair objects.
         *    @access public
         */
        function getAll() {
            return $this->_request;
        }
        
        /**
         *    Renders the query string as a URL encoded
         *    request part.
         *    @return string        Part of URL.
         *    @access protected
         */
        function _encode() {
            $statements = array();
            foreach ($this->_request as $pair) {
                if ($statement = $pair->asRequest()) {
                    $statements[] = $statement;
                }
            }
            return implode('&', $statements);
        }
    }
    
    /**
     *    Bundle of GET parameters. Can include
     *    repeated parameters.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimpleGetEncoding extends SimpleEncoding {
        
        /**
         *    Starts empty.
         *    @param array $query       Hash of parameters.
         *                              Multiple values are
         *                              as lists on a single key.
         *    @access public
         */
        function SimpleGetEncoding($query = false) {
            $this->SimpleEncoding($query);
        }
        
        /**
         *    HTTP request method.
         *    @return string        Always GET.
         *    @access public
         */
        function getMethod() {
            return 'GET';
        }
        
        /**
         *    Writes no extra headers.
         *    @param SimpleSocket $socket        Socket to write to.
         *    @access public
         */
        function writeHeadersTo(&$socket) {
        }
        
        /**
         *    No data is sent to the socket as the data is encoded into
         *    the URL.
         *    @param SimpleSocket $socket        Socket to write to.
         *    @access public
         */
        function writeTo(&$socket) {
        }
        
        /**
         *    Renders the query string as a URL encoded
         *    request part for attaching to a URL.
         *    @return string        Part of URL.
         *    @access public
         */
        function asUrlRequest() {
            return $this->_encode();
        }
    }
    
    /**
     *    Bundle of URL parameters for a HEAD request.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimpleHeadEncoding extends SimpleGetEncoding {
        
        /**
         *    Starts empty.
         *    @param array $query       Hash of parameters.
         *                              Multiple values are
         *                              as lists on a single key.
         *    @access public
         */
        function SimpleHeadEncoding($query = false) {
            $this->SimpleGetEncoding($query);
        }
        
        /**
         *    HTTP request method.
         *    @return string        Always HEAD.
         *    @access public
         */
        function getMethod() {
            return 'HEAD';
        }
    }
    
    /**
     *    Bundle of POST parameters. Can include
     *    repeated parameters.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimplePostEncoding extends SimpleEncoding {
        
        /**
         *    Starts empty.
         *    @param array $query       Hash of parameters.
         *                              Multiple values are
         *                              as lists on a single key.
         *    @access public
         */
        function SimplePostEncoding($query = false) {
            $this->SimpleEncoding($query);
        }
        
        /**
         *    HTTP request method.
         *    @return string        Always POST.
         *    @access public
         */
        function getMethod() {
            return 'POST';
        }
        
        /**
         *    Dispatches the form headers down the socket.
         *    @param SimpleSocket $socket        Socket to write to.
         *    @access public
         */
        function writeHeadersTo(&$socket) {
            $socket->write("Content-Length: " . (integer)strlen($this->_encode()) . "\r\n");
            $socket->write("Content-Type: application/x-www-form-urlencoded\r\n");
        }
        
        /**
         *    Dispatches the form data down the socket.
         *    @param SimpleSocket $socket        Socket to write to.
         *    @access public
         */
        function writeTo(&$socket) {
            $socket->write($this->_encode());
        }
        
        /**
         *    Renders the query string as a URL encoded
         *    request part for attaching to a URL.
         *    @return string        Part of URL.
         *    @access public
         */
        function asUrlRequest() {
            return '';
        }
    }
    
    /**
     *    Bundle of POST parameters in the multipart
     *    format. Can include file uploads.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimpleMultipartEncoding extends SimplePostEncoding {
        var $_boundary;
        
        /**
         *    Starts empty.
         *    @param array $query       Hash of parameters.
         *                              Multiple values are
         *                              as lists on a single key.
         *    @access public
         */
        function SimpleMultipartEncoding($query = false, $boundary = false) {
            $this->SimplePostEncoding($query);
            $this->_boundary = ($boundary === false ? uniqid('st') : $boundary);
        }
        
        /**
         *    Dispatches the form headers down the socket.
         *    @param SimpleSocket $socket        Socket to write to.
         *    @access public
         */
        function writeHeadersTo(&$socket) {
            $socket->write("Content-Length: " . (integer)strlen($this->_encode()) . "\r\n");
            $socket->write("Content-Type: multipart/form-data, boundary=" . $this->_boundary . "\r\n");
        }
        
        /**
         *    Dispatches the form data down the socket.
         *    @param SimpleSocket $socket        Socket to write to.
         *    @access public
         */
        function writeTo(&$socket) {
            $socket->write($this->_encode());
        }
        
        /**
         *    Renders the query string as a URL encoded
         *    request part.
         *    @return string        Part of URL.
         *    @access public
         */
        function _encode() {
            $stream = '';
            foreach ($this->_request as $pair) {
                $stream .= "--" . $this->_boundary . "\r\n";
                $stream .= $pair->asMime() . "\r\n";
            }
            $stream .= "--" . $this->_boundary . "--\r\n";
            return $stream;
        }
    }
?>