<?php
    /**
     *	Base include file for SimpleTest
     *	@package	SimpleTest
     *	@subpackage	WebTester
     *	@version	$Id: authentication.php,v 1.10 2005/07/26 01:27:18 lastcraft Exp $
     */
    /**
     *	include http class
     */
    require_once(dirname(__FILE__) . '/http.php');
    
    /**
     *    Represents a single security realm's identity.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimpleRealm {
        protected $_type;
        protected $_root;
        protected $_username;
        protected $_password;
        
        /**
         *    Starts with the initial entry directory.
         *    @param string $type      Authentication type for this
         *                             realm. Only Basic authentication
         *                             is currently supported.
         *    @param SimpleUrl $url    Somewhere in realm.
         *    @access public
         */
        function SimpleRealm($type, $url) {
            $this->_type = $type;
            $this->_root = $url->getBasePath();
            $this->_username = false;
            $this->_password = false;
        }
        
        /**
         *    Adds another location to the realm.
         *    @param SimpleUrl $url    Somewhere in realm.
         *    @access public
         */
        function stretch($url) {
            $this->_root = $this->_getCommonPath($this->_root, $url->getPath());
        }
        
        /**
         *    Finds the common starting path.
         *    @param string $first        Path to compare.
         *    @param string $second       Path to compare.
         *    @return string              Common directories.
         *    @access private
         */
        function _getCommonPath($first, $second) {
            $first = explode('/', $first);
            $second = explode('/', $second);
            for ($i = 0; $i < min(count($first), count($second)); $i++) {
                if ($first[$i] != $second[$i]) {
                    return implode('/', array_slice($first, 0, $i)) . '/';
                }
            }
            return implode('/', $first) . '/';
        }
        
        /**
         *    Sets the identity to try within this realm.
         *    @param string $username    Username in authentication dialog.
         *    @param string $username    Password in authentication dialog.
         *    @access public
         */
        function setIdentity($username, $password) {
            $this->_username = $username;
            $this->_password = $password;
        }
        
        /**
         *    Accessor for current identity.
         *    @return string        Last succesful username.
         *    @access public
         */
        function getUsername() {
            return $this->_username;
        }
        
        /**
         *    Accessor for current identity.
         *    @return string        Last succesful password.
         *    @access public
         */
        function getPassword() {
            return $this->_password;
        }
        
        /**
         *    Test to see if the URL is within the directory
         *    tree of the realm.
         *    @param SimpleUrl $url    URL to test.
         *    @return boolean          True if subpath.
         *    @access public
         */
        function isWithin($url) {
            if ($this->_isIn($this->_root, $url->getBasePath())) {
                return true;
            }
            if ($this->_isIn($this->_root, $url->getBasePath() . $url->getPage() . '/')) {
                return true;
            }
            return false;
        }
        
        /**
         *    Tests to see if one string is a substring of
         *    another.
         *    @param string $part        Small bit.
         *    @param string $whole       Big bit.
         *    @return boolean            True if the small bit is
         *                               in the big bit.
         *    @access private
         */
        function _isIn($part, $whole) {
            return strpos($whole, $part) === 0;
        }
    }
    
    /**
     *    Manages security realms.
	 *    @package SimpleTest
	 *    @subpackage WebTester
     */
    class SimpleAuthenticator {
        protected $_realms;
        
        /**
         *    Clears the realms.
         *    @access public
         */
        function SimpleAuthenticator() {
            $this->restartSession();
        }
        
        /**
         *    Starts with no realms set up.
         *    @access public
         */
        function restartSession() {
            $this->_realms = array();
        }
        
        /**
         *    Adds a new realm centered the current URL.
         *    Browsers vary wildly on their behaviour in this
         *    regard. Mozilla ignores the realm and presents
         *    only when challenged, wasting bandwidth. IE
         *    just carries on presenting until a new challenge
         *    occours. SimpleTest tries to follow the spirit of
         *    the original standards committee and treats the
         *    base URL as the root of a file tree shaped realm.
         *    @param SimpleUrl $url    Base of realm.
         *    @param string $type      Authentication type for this
         *                             realm. Only Basic authentication
         *                             is currently supported.
         *    @param string $realm     Name of realm.
         *    @access public
         */
        function addRealm($url, $type, $realm) {
            $this->_realms[$url->getHost()][$realm] = new SimpleRealm($type, $url);
        }
        
        /**
         *    Sets the current identity to be presented
         *    against that realm.
         *    @param string $host        Server hosting realm.
         *    @param string $realm       Name of realm.
         *    @param string $username    Username for realm.
         *    @param string $password    Password for realm.
         *    @access public
         */
        function setIdentityForRealm($host, $realm, $username, $password) {
            if (isset($this->_realms[$host][$realm])) {
                $this->_realms[$host][$realm]->setIdentity($username, $password);
            }
        }
        
        /**
         *    Finds the name of the realm by comparing URLs.
         *    @param SimpleUrl $url        URL to test.
         *    @return SimpleRealm          Name of realm.
         *    @access private
         */
        function _findRealmFromUrl($url) {
            if (! isset($this->_realms[$url->getHost()])) {
                return false;
            }
            foreach ($this->_realms[$url->getHost()] as $name => $realm) {
                if ($realm->isWithin($url)) {
                    return $realm;
                }
            }
            return false;
        }
        
        /**
         *    Presents the appropriate headers for this location.
         *    @param SimpleHttpRequest $request  Request to modify.
         *    @param SimpleUrl $url              Base of realm.
         *    @access public
         */
        function addHeaders($request, $url) {
            if ($url->getUsername() && $url->getPassword()) {
                $username = $url->getUsername();
                $password = $url->getPassword();
            } elseif ($realm = $this->_findRealmFromUrl($url)) {
                $username = $realm->getUsername();
                $password = $realm->getPassword();
            } else {
                return;
            }
            $this->addBasicHeaders($request, $username, $password);
        }
        
        /**
         *    Presents the appropriate headers for this
         *    location for basic authentication.
         *    @param SimpleHttpRequest $request  Request to modify.
         *    @param string $username            Username for realm.
         *    @param string $password            Password for realm.
         *    @access public
         *    @static
         */
        function addBasicHeaders($request, $username, $password) {
            if ($username && $password) {
                $request->addHeaderLine(
                        'Authorization: Basic ' . base64_encode("$username:$password"));
            }
        }
    }
?>