<?php

/**
 * 
 * LICENSE
 * 
 * Copyright (c) 2008 Harald Kampen 
 *
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the Software 
 * is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all 
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 * 
 * Class to convert requests. This class requires PHP 5.
 * It provides security for XSS attacks. The security issues of sql injection
 * and other are not affected.
 * 
 * All $_REQUEST, $_GET and $_POST values are converted. The $_COOKIE array
 * and other globals are not touched.
 * 
 * You must first load the class, then you can use $_REQUEST, $_GET and $_POST
 * with the converted values and the unconverted source from this class.
 * 
 * Load the class (you can use also your own ArrayFilter):
 * <code>HakaRequest::load(new HakaArrayFilter(new HakaArrayConvertChars()));</code>
 *
 * Get a converted parameter:
 * <code>$value = $_REQUEST['x'];</code>
 * or
 * <code>$value = HakaRequest::getInstance()->request('x');</code>
 * 
 * Get an uncoverted parameter:
 * <code>$value = HakaRequest::getInstance()->requestSource['x'];</code>
 * or
 * <code>$value = HakaRequest::getInstance()->request('x', NULL, true);</code>
 * 
 * @copyright Harald Kampen
 * @license MIT License
 * 
 */


/**
 * Request class
 *
 */

class HakaRequest
{
    
/**
     * Current instance
     * @var HakaRequest
     */
    
public static $self NULL;
    
    
/**
     * The unconverted $_REQUEST
     * @var array
     */
    
public $requestSource = array();

    
/**
     * The unconverted $_GET
     * @var array
     */
    
public $getSource = array();
    
    
/**
     * The unconverted $_POST
     * @var array
     */
    
public $postSource = array();
    
    
    
/**
     * Load $_REQUEST
     */
    
protected function __construct() {
        
        
$unsets = array(
            
'_REQUEST''_GET''_POST''_COOKIE'
            
'HTTP_REQUEST_VARS''HTTP_GET_VARS''HTTP_POST_VARS''HTTP_COOKIE_VARS',
            
'_SERVER''_ENV''HTTP_SERVER_VARS''HTTP_ENV_VARS',   
            
'_FILE''HTTP_POST_FILES''_SESSION''HTTP_RAW_POST_DATA'  
        
);
        
        foreach(
$unsets as $s) {
            if(isset(
$_REQUEST[$s])) {
                unset(
$_REQUEST[$s]);
            }
            
            if(isset(
$_GET[$s])) {
                unset(
$_GET[$s]);
            }
            
            if(isset(
$_POST[$s])) {
                unset(
$_POST[$s]);
            }
        }
        
        
$this->requestSource $_REQUEST;
        
$this->getSource $_GET;
        
$this->postSource $_POST;
    }
    
    
/**
     * Deny cloning
     */
    
public function __clone() {
        throw new 
Exception('Nothing to clone');
    }
    
    
/**
     * Load the filter class and create the singleton instance
     * @param HakaArrayFilter $getFilter
     * @param HakaArrayFilter $postFilter Optional, if not set, it is the $getFilter
     */
    
public static function load(HakaArrayFilter $getFilterHakaArrayFilter $postFilter NULL) {
        
self::$self = new self;
        if(!empty(
$_GET)) {
            
$getFilter->setByRef($_GET);
            
$_REQUEST array_merge($_REQUEST$_GET);
        }
        if(!empty(
$_POST)) {
            if(
is_null($postFilter)) {
                
$postFilter $getFilter;
            }
            
$postFilter->setByRef($_POST);
            
$_REQUEST array_merge($_REQUEST$_POST);
        }
        
        if(isset(
$_SERVER['HTTP_GET_VARS'])) {
            
HakaOldServerConfigFilter::filterServerVars();
        }
        
        if(
ini_get('register_globals')) {
            
HakaOldServerConfigFilter::filterRegisterGlobals();
        }
    }
    
    
/**
     * Return this class as object
     * @return HakaRequest
     */
    
public static function getInstance() {
        if(
is_null(self::$self)) {
            throw new 
Exception('You must load this class first.');
        }
        return 
self::$self;
    }
    
    
/**
     * Get a value with a default parameter.
     * @param string $key
     * @param mixed $default
     * @param boolean $source
     * @return mixed
     */
    
public function request($key$default NULL$source false) {
        return isset(
$_REQUEST[$key]) ? 
                
$source $this->requestSource[$key
                : 
$_REQUEST[$key
            : 
$default
    }
}

/**
 * Additional filter for older PHP configurations
 */

class HakaOldServerConfigFilter {
    
    
/**
     * Replaces the HTTP_*_VARS with the converted REQUEST.
     */
    
public static function filterServerVars() {
        
$_SERVER['HTTP_GET_VARS'] = $GLOBALS['HTTP_GET_VARS'] = $_GET;
        
$_SERVER['HTTP_POST_VARS'] = $GLOBALS['HTTP_POST_VARS'] = $_POST;
        
$_SERVER['HTTP_REQUEST_VARS'] = $GLOBALS['HTTP_REQUEST_VARS'] = $_REQUEST;
    }
    
    
/**
     * Replace the loaded vars by register_globals with a modified request parameter.
     * 
     * IMPORTANT NOTES: 
     * This function does not provide for url_fopen attacks! 
     * register_globals attacks are only secure if you make shure that the HakaRequest
     * class is loaded and initialized. 
     */
    
public static function filterRegisterGlobals() {
        foreach(
$_REQUEST as $s => $m) {
            
$GLOBALS[$s] = $m;
        }
    }
}

/**
 * The filter class to execute the filter
 */

class HakaArrayFilter
{
    
/**
     * The array to convert
     * @var array
     */
    
public $array;
    
    
/**
     * The object to convert the array values
     * @var HakaArrayConvert $filterObject
     */
    
protected $filterObject;
    
    
/**
     * Load the filter object
     * @param HakaArrayConvert $filterObject
     */
    
public function __construct(HakaArrayConvert $filterObject) {
        
$this->filterObject $filterObject;
    }
    
    
/**
     * Load and parse an array
     * @param array $array
     */
    
public function set($array) {
        
$this->parse($array);
    }
    
    
/**
     * Load and parse an array by reference
     * @param array $array
     */
    
public function setByRef(&$array) {
        
$this->parse($array);
    }
    
    
/**
     * Executes the parser
     * @param array $array 
     */
    
protected function parse(&$array) {
        
array_walk_recursive($array, array($this->filterObject'convert'));
        
$this->array = &$array;
    }
}

/**
 * The interface for the array converter
 */

interface HakaArrayConvert {
    public function 
convert(&$value, &$key);
}

/**
 * A standard array converter
 */

class HakaArrayConvertChars implements HakaArrayConvert {
    
    
/**
     * Replace parameters
     * @var array $replace
     */
    
protected $replace = array('&' => '&amp;''<' => '&lt;''>' => '&gt;''"' => '&quot;'"'" => '&#039;');
    
    
/**
     * You can overwrite the default replace parameters. It adds the  chars lower 
     * then 32 (without tab, newline and carriage return) to the remove list.
     * @param array $replace
     */
    
public function construct($replace NULL) {
        if(!
is_null($replace)) {
            
$this->replace $replace;
        }
        
        for(
$i 0$i 32$i++) {
            if(!
in_array($i, array(91013))) {
                
$this->replace[chr($i)] = '';
            }
        }
    }
    
    
/**
     * Converts the array values. The array key is not converted by the PHP function
     * array_walk_recursive, but you can e.g. use the key in your convert class to
     * change the conversion by named keys.
     * @param string $value
     * @param string $key
     */
    
    
public function convert(&$value, &$key) {
        
$value str_replace(array_keys($this->replace), array_values($this->replace), $value);
    }
}


?>