PHP Input sanitization class (for GET, POST, COOKIE etc..)

Small, short code snippets that other people may find useful. Do you have a good regex that you would like to share? Share it! Even better, the code can be commented on, and improved.

Moderator: General Moderators

Post Reply
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

PHP Input sanitization class (for GET, POST, COOKIE etc..)

Post by Chris Corbyn »

Sticking this here since I think it may be useful to someone, somewhere. Drafted together as a result of a thread in Theory/Design about spoofing POST data. Added functionality to fetch the raw POST data as per Jcart's suggestion in said thread.

Instead if working directly with $_GET, $_POST etc you pass it an array of items you're expecting to receive on a page, along with their types (int, numeric, float, string, array...). You can optionally pass it a PCRE pattern to match and a specific location to look in (GET, POST...). The default is to look in REQUEST (yuck).

If values do not satisfy the requirements you asked for they are nulled, but can still be accessed by getRaw('variable_name'). Access items normally using getVar('variable_name');

Pass a multidimensional array to expectData() like this:

array (
array( string varName, string varType [, string match [, string requestLocation] ] )
)


This would be easy to convert to PHP4 since the construct does nothing here and the private/public keywords can be adjusted accordingly.

Code: Select all

<?php

/*
 A basic class for sanitizing data being received over HTTP
   in PHP Super Globals.

 Author: d11wtq (Chris Corbyn)
 Date: 2006-04-19

 License: None.  You can use it freely, edit it, sell it, print it,
     burn it, feed it to your cat... I don't care 
 
 */

class htDataHandler
{

	private
	
	$htVars = array(),
	$rawVars = array(),
	$expectVars = array(), //Variable names
	$expectLocs = array(), //POST, GET, COOKIE, REQUEST, SESSION
	$expectTypes = array(), //Data types
	$expectMatches = array(); //PCRE
	
	public function __construct()
	{
		//
	}

	public function expectData($array)
	{
		foreach ($array as $k => $a)
		{
			if (is_array($a) && sizeof($a) >= 2)
			{
				$params = array_values($a);
				$this->addVar($params, $k);
				$this->addLoc($params, $k);
				$this->addMatch($params, $k);
				$this->addType($params, $k);
			}
		}
		$this->validate();
	}

	public function getVar($v)
	{
		if (isset($this->htVars[$v])) return $this->htVars[$v];
	}

	public function getRaw($v)
	{
		if (isset($this->rawVars[$v])) return $this->rawVars[$v];
	}

	private function validate()
	{
		foreach ($this->expectVars as $k => $v)
		{
			//It seems this is needed for variable variable in the superglobal scope
			global ${$this->expectLocs[$k]};
			
			if (isset(${$this->expectLocs[$k]}[$v]))
			{
				$tmp = ${$this->expectLocs[$k]}[$v]; //Read it but don't use it yet
				$this->rawVars[$v] = $tmp;
				
				if ($this->expectTypes[$k] && !$this->checkType($tmp, $this->expectTypes[$k])) $tmp = null;
				if ($this->expectMatches[$k] && !$this->checkMatch($tmp, $this->expectMatches[$k])) $tmp = null;
				
				$this->htVars[$v] = $tmp;
			}
			else
			{
				$this->htVars[$v] = null;
				$this->rawVars[$v] = null;
			}
		}
	}

	private function checkType($v, $type)
	{
		switch (strtolower($type))
		{
			case 'str':
			case 'string': return is_string($v);
			case 'int':
			case 'integer': return is_int($v);
			case 'float': return is_float($v);
			case 'double': return is_double($v);
			case 'array': return is_array($v);
			case 'object': return is_object($v);
			case 'numeric': return is_numeric($v);
		}
		return false;
	}

	private function checkMatch($v, $pattern)
	{
		if (preg_match($pattern, $v)) return true;
		else return false;
	}

	private function addVar($a, $k)
	{
		if (isset($a[0])) $this->expectVars[$k] = $a[0];
		else $this->expectVars[$k] = null;
	}

	private function addType($a, $k)
	{
		if (isset($a[1])) $this->expectTypes[$k] = $a[1];
		else $this->expectTypes[$k] = null;
	}

	private function addMatch($a, $k)
	{
		if (!empty($a[2])) $this->expectMatches[$k] = $a[2];
		else $this->expectMatches[$k] = null;
	}

	private function addLoc($a, $k)
	{
		if (!empty($a[3])) $this->expectLocs[$k] = $this->getLocation($a[3]);
		else $this->expectLocs[$k] = $this->getLocation(0);
	}

	private function getLocation($loc)
	{
		switch (strtolower($loc))
		{
			case 'get':
			case '$_get':
			case '_get': return '_GET';
			//
			case 'post':
			case '$_post':
			case '_post': return '_POST';
			//
			case 'cookie':
			case '$_cookie':
			case '_cookie': return '_COOKIE';
			//
			case 'session':
			case '$_session':
			case '_session': return '_SESSION';
			//
			case 'request':
			case '$_request':
			case '_request':
			default: return '_REQUEST';
		}
	}

}

?>
Usage:

Code: Select all

/*
 array (
        array( string varName, string varType [, string match [, string requestLocation] ] )
 )
 */
$a = array(
        array('foo', 'string', '/^foo\d{2}$/i', 'get'),
        array('bar', 'numeric')
);

$handler = new htDataHandler;

$handler->expectData($a);

echo $handler->getVar('foo');
echo $handler->getVar('bar');

echo $handler->getRaw('foo');
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Nice. This looks like a middle ground between something like Zend's Input_Filter and the Request/FilterChain/Validator style.

Input_Filter is procedural something like:

Code: Select all

$filter = new Zend_InputFilter($_REQUEST);
$foo = $filter->testString('foo');
$foo = $filter->testPattern('foo', '/^foo\d{2}$/i');
$bar = $filter->testNumeric('bar');
Your Data Handler:

Code: Select all

a = array(
        array('foo', 'string', '/^foo\d{2}$/i', 'get'),
        array('bar', 'numeric')
);

$handler = new htDataHandler;

$handler->expectData($a);

echo $handler->getVar('foo');
echo $handler->getVar('bar');

echo $handler->getRaw('foo');
And the Request/FilterChain/Validator style:

Code: Select all

$request = new Request();

$filterchain = new FilterChain();
$filterchain->add('foo', new FilterString());
$filterchain->add('foo', new FilterNumeric());
$filterchain->run($request);

$validator = new Validator ();
$validator->add('foo', new RuleRegexp('/^foo\d{2}$/i'));
$validator->run($request);
if ($validator->isValid()) {
// request ok
    echo $request->get('foo');
    echo $request->get('bar');
} else {
// request errors
    echo $validator->getErrorMessages()
}
(#10850)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

arborint wrote:Nice. This looks like a middle ground between something like Zend's Input_Filter and the Request/FilterChain/Validator style.
How interesting. I hadn't actually looked at those before... I guess it's quite a natural way to handle this in any case :)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

In looking at it again (I just typed it in earlier without thinking much about it) you can see the actual data move from vars, to properties, to a container. Three levels of complexity -- all filtering which is a Good Thing.
(#10850)
User avatar
JayBird
Admin
Posts: 4524
Joined: Wed Aug 13, 2003 7:02 am
Location: York, UK
Contact:

Post by JayBird »

Looks d11wtq!
User avatar
Technocrat
Forum Contributor
Posts: 127
Joined: Thu Oct 20, 2005 7:01 pm

Post by Technocrat »

I took your code and made some minor changes to it. What do you think?

Code: Select all

/*
    Usage:
        $var_handler->pushData('var', 'type', 'where', 'legnth', 'regex');
        $var_handler->pushData('foo', 'string', 'get', '4', '/^foo\d{2}$/i');
        $var_handler->getData();
        $var_handler->getVar('foo');
*/

//Orginal Author: d11wtq (Chris Corbyn)
class variables
{

    var $Vars           = array();
    var $rawVars        = array();
    var $expectVars     = array(); //Variable names
    var $expectLocs     = array(); //POST, GET, COOKIE, REQUEST, SESSION
    var $expectTypes    = array(); //Data types
    var $expectMatches  = array(); //PCRE
    var $expectLength   = array(); //Lengthy
    var $dataArray      = array();
   
    //Constructor
    function variables() { }

    function getData() {
        if (is_array($this->dataArray)) {
            foreach ($this->dataArray as $k => $a) {
                if (is_array($a) && sizeof($a) >= 2) {
                    $params = array_values($a);
                    $this->_addVar($params, $k);
                    $this->_addLoc($params, $k);
                    $this->_addMatch($params, $k);
                    $this->_addType($params, $k);
                    $this->_addLen($params, $k);
                }
            }
            $this->_validate();
        } else {
            die('getData() Error');
        }
    }

    function getRaw($v) {
        if (isset($this->rawVars[$v])) return $this->rawVars[$v];
    }
    
    function getVar($v) {
        if (isset($this->Vars[$v])) return $this->Vars[$v];
    }
    
    function pushData($var, $type, $loc, $len='', $regex='') {
        if (!empty($var)) {
            $this->dataArray[] = array($var, $type, $regex, $loc, $len);
        } else {
            die('pushData() Error');
        }
    }
    
    function fixQuotes($string) {
        if (!is_numeric($string)) {
            $string = str_replace('%27', "'", $string);
            return (version_compare(phpversion(), '4.3.0', '>=')) ? mysql_real_escape_string($string) : mysql_escape_string($string);
        }
        return $string;
    }

   function _addLen($a, $k) {
        if (isset($a[4]) && (!empty($a[4]) || $a[4] == 0) && $this->_checkType($a[0], 'int')) {
            $this->expectLength[$k] = $a[4];
        } else {
            $this->expectLength[$k] = null;
        }
    }

    function _addLoc($a, $k) {
        if (isset($a[3]) && (!empty($a[3]))) {
            $this->expectLocs[$k] = $this->_getLocation($a[3]);
        } else {
            $this->expectLocs[$k] = $this->_getLocation(0);
        }
    }
    
    function _addMatch($a, $k) {
        if (isset($a[2]) && (!empty($a[2]))) {
            $this->expectMatches[$k] = $a[2];
        } else {
            $this->expectMatches[$k] = null;
        }
    }
    
    function _addType($a, $k) {
        if (isset($a[1]) && (isset($a[1]))) {
            $this->expectTypes[$k] = $a[1];
        } else {
            $this->expectTypes[$k] = null;
        }
    }
    
    function _addVar($a, $k) {
        if (isset($a[0]) && (isset($a[0]))) {
            $this->expectVars[$k] = $a[0];
        } else {
            $this->expectVars[$k] = null;
        }
    }
    
    function _checkType($v, $type) {
        switch (strtolower($type)) {
            case 'str':
            case 'string':  return is_string($v);
            //
            case 'int':
            case 'integer': return (is_numeric($v) ? ((int)$v) == $v : false);
            //
            case 'double':
            case 'float':   return (is_numeric($v) ? ((float)$v) == $v : false);
            //
            case 'array':   return is_array($v);
            case 'object':  return is_object($v);
            case 'numeric': return is_numeric($v);
        }
        return false;
    }

    function _checkMatch($v, $pattern) {
        return (preg_match($pattern, $v)) ? true : false;
    }
    
    function _checkLen($v, $len) {
        return (strlen($v) <= ((int) $len) ) ? true : false;
    }

    function _getLocation($loc)
    {
        switch (strtolower($loc))
        {
            case 'get':
            case '$_get':
            case '_get':        return '_GET';
            //
            case 'post':
            case '$_post':
            case '_post':       return '_POST';
            //
            case 'cookie':
            case '$_cookie':
            case '_cookie':     return '_COOKIE';
            //
            case 'session':
            case '$_session':
            case '_session':    return '_SESSION';
            //
            case 'request':
            case '$_request':
            case '_request':
            default:            return '_REQUEST';
        }
    }
    
    function _stripslashes_array($data) {
        return is_array($data) ? array_map('stripslashes', $data) : stripslashes($data);
    }

    function _undoMagic($data) {
        static $magic_quotes;
        
        if (empty($data)) return null;
        
        //Replace with STRIP
        if (!isset($magic_quotes)) $magic_quotes = get_magic_quotes_gpc();
        return ($magic_quotes) ? $this->_stripslashes_array($data) : $data;
    }
    
    function _validate() {
        if (is_array($this->expectVars)) {
            foreach ($this->expectVars as $k => $v) {
                //It seems this is needed for variable variable in the superglobal scope
                global ${$this->expectLocs[$k]};
                
                if (isset(${$this->expectLocs[$k]}[$v])) {
                    $tmp = ${$this->expectLocs[$k]}[$v]; //Read it but don't use it yet
                    $this->rawVars[$v] = $tmp;
                    
                    if ($this->expectTypes[$k] && !$this->_checkType($tmp, $this->expectTypes[$k])) $tmp = null;
                    if ($this->expectMatches[$k] && !$this->_checkMatch($tmp, $this->expectMatches[$k])) $tmp = null;
                    if ($this->expectLength[$k] && !$this->_checkLen($tmp, $this->expectLength[$k])) $tmp = null;
                    
                    $this->Vars[$v] = $this->_undoMagic($tmp);
                } else {
                    $this->Vars[$v] = null;
                    $this->rawVars[$v] = null;
                }
            }
        } else {
            die('_validate() Error');
        }
    }
}
Post Reply