From 77f5c85e58e930fe7527173cf1e7b9609c19a0cc Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 19 May 2013 16:55:52 -0400 Subject: [PATCH] Add http\Input class to provide an input sanitization system. --- http/input.php | 165 ++++++++++++++++++++++++++++++ testing/tests/http/input_test.php | 78 ++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 http/input.php create mode 100644 testing/tests/http/input_test.php diff --git a/http/input.php b/http/input.php new file mode 100644 index 0000000..ba23411 --- /dev/null +++ b/http/input.php @@ -0,0 +1,165 @@ +. + +namespace hoplite\http; + +require_once HOPLITE_ROOT . '/base/filter.php'; + +/*! + The Input class is responsible for sanitizing data from untrusted incoming + HTTP requests. + + This class was designed to be used either in a PHP 5.4 environment, or one in + which both magic_quotes (and all its flavors) and register_globals are Off. +*/ +class Input +{ + /*! @const The raw input value, unsanitized and unsafe. */ + const TYPE_RAW = 1; + /*! @const The value as a string with whitespace trimmed. */ + const TYPE_STR = 2; + /*! @const The value as an integer. */ + const TYPE_INT = 3; + /*! @const The value as an unsigned integer. */ + const TYPE_UINT = 4; + /*! @const The value as a floating point number. */ + const TYPE_FLOAT = 5; + /*! @const The string value parsed as a boolean expression. */ + const TYPE_BOOL = 6; + /*! @const The string sanitized for HTML characters. */ + const TYPE_HTML = 7; + + /*! @const The $_REQUEST array. */ + const SOURCE_REQUEST = 'r'; + /*! @const The $_GET array. */ + const SOURCE_GET = 'g'; + /*! @const The $_POST array. */ + const SOURCE_POST = 'p'; + /*! @const The $_COOKIE array. */ + const SOURCE_COOKIE = 'c'; + + /*! The sanitized input data. */ + public $in = array(); + + /*! + The constructor for the input sanitizer class. By default, this will escape + all the data in _REQUEST into the ->in array using |$default_mode|. + */ + public function __construct($default_mode = self::TYPE_STR) + { + if ($default_mode != self::TYPE_RAW) { + $this->in = $this->_CleanArray($_REQUEST, $default_mode); + } + } + + /*! + Cleans a variable of a given type from the _REQUEST source and returns the + sanitized result. This is the most convenient way to access incoming data. + */ + public function Clean($variable, $type) + { + return $this->InputClean(self::SOURCE_REQUEST, $variable, $type); + } + + /*! + Convenience function that takes an array of variable => type pairs and + calls ::Clean() on each. + */ + public function CleanArray($pairs) + { + $this->InputCleanArray(self::SOURCE_REQUEST, $pairs); + } + + /*! @brief Fully qualified cleaning method. + This method will clean a variable in a specific source array to the + specified type. + */ + public function InputClean($source, $variable, $type) + { + $global = $this->_GetSource($source); + $value = $this->SanitizeScalar($global["$variable"], $type); + $this->in["$variable"] = $value; + return $value; + } + + /*! Convenience function like ::CleanArray but specifies the source array. */ + public function InputCleanArray($source, $pairs) + { + foreach ($pairs as $variable => $type) { + $this->InputClean($source, $variable, $type); + } + } + + /*! Recursive variant of ::InputClean. */ + public function InputCleanDeep($source, $variable, $type) + { + $global = $this->_GetSource($source); + $array = $global["$variable"]; + $this->in["$variable"] = $this->_CleanArray($array, $type); + return $this->in["$variable"]; + } + + private function _CleanArray($array, $type) + { + $out = array(); + foreach ($array as $key => $value) { + if (is_array($value)) { + $out["$key"] = $this->_CleanArray($value, $type); + } else { + $out["$key"] = $this->SanitizeScalar($value, $type); + } + } + return $out; + } + + /*! + Routine that transforms an unclean input to its cleaned output. + */ + public function SanitizeScalar($in, $type) + { + if (is_array($in) || is_object($in)) + throw new InputException('Cannot clean non-scalar value'); + + static $is_true = array('true', 'yes', 'y', '1'); + + switch ($type) { + case self::TYPE_RAW: return $in; + case self::TYPE_STR: return trim($in); + case self::TYPE_INT: return intval($in); + case self::TYPE_UINT: return ($data = intval($in)) < 0 ? 0 : $data; + case self::TYPE_FLOAT: return floatval($in); + case self::TYPE_BOOL: return in_array(strtolower(trim($in)), $is_true); + case self::TYPE_HTML: return \hoplite\base\filter\String($in); + default: + throw new InputException('Cannot clean scalar to unknown type ' . $type); + } + } + + /*! Gets the source array associated with its short type. */ + private function & _GetSource($source) + { + switch ($source) { + case self::SOURCE_REQUEST: return $_REQUEST; + case self::SOURCE_GET: return $_GET; + case self::SOURCE_POST: return $_POST; + case self::SOURCE_COOKIE: return $_COOKIE; + default: + throw new InputException('Unknown source array ' . $source); + } + } +} + +class InputException extends \Exception {} diff --git a/testing/tests/http/input_test.php b/testing/tests/http/input_test.php new file mode 100644 index 0000000..f3149ca --- /dev/null +++ b/testing/tests/http/input_test.php @@ -0,0 +1,78 @@ +. + +namespace hoplite\test; +use hoplite\http\Input; + +require_once HOPLITE_ROOT . '/http/input.php'; + +/** + * @backupGlobals enabled + */ +class InputTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + $_GET = array( + 'gint' => '12', + 'gfloat' => '3.14', + 'gstr' => '"Hello World"', + 'ghtml' => '"Hello World"', + 'gbool' => 'True', + ); + $_POST = array( + 'pstr' => '', + 'pbool' => 'YES', + 'parray' => array('13', 15, '19', '22.23'), + ); + $_REQUEST = array_merge($_GET, $_POST); + } + + public function testDefaultMode() + { + $input = new Input(); + $this->assertEquals('"Hello World"', $input->in['gstr']); + + $input = new Input(Input::TYPE_RAW); + $this->assertArrayNotHasKey('gstr', $input->in); + } + + public function testClean() + { + $input = new Input(); + $this->assertSame(12, $input->Clean('gint', Input::TYPE_INT)); + $this->assertSame(12, $input->in['gint']); + } + + public function testCleanArray() + { + $input = new Input(); + $input->CleanArray(array( + 'gfloat' => Input::TYPE_FLOAT, + 'pbool' => Input::TYPE_BOOL, + )); + $this->assertSame(3.14, $input->in['gfloat']); + $this->assertSame(TRUE, $input->in['pbool']); + } + + public function testInputCleanDeep() + { + $input = new Input(); + $test = $input->InputCleanDeep('p', 'parray', Input::TYPE_UINT); + $this->assertSame(array(13, 15, 19, 22), $test); + $this->assertSame($test, $input->in['parray']); + } +} -- 2.22.5