Add http\Input class to provide an input sanitization system.
authorRobert Sesek <rsesek@bluestatic.org>
Sun, 19 May 2013 20:55:52 +0000 (16:55 -0400)
committerRobert Sesek <rsesek@bluestatic.org>
Sun, 19 May 2013 20:55:52 +0000 (16:55 -0400)
http/input.php [new file with mode: 0644]
testing/tests/http/input_test.php [new file with mode: 0644]

diff --git a/http/input.php b/http/input.php
new file mode 100644 (file)
index 0000000..ba23411
--- /dev/null
@@ -0,0 +1,165 @@
+<?php
+// Hoplite
+// Copyright (c) 2013 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program.  If not, see <http://www.gnu.org/licenses/>.
+
+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 (file)
index 0000000..f3149ca
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+// Hoplite
+// Copyright (c) 2013 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program.  If not, see <http://www.gnu.org/licenses/>.
+
+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' => '<blink>"Hello World"</blink>',
+      'gbool' => 'True',
+    );
+    $_POST = array(
+      'pstr' => '<script type="text/javascript"> alert("Hello world"); </script>',
+      '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']);
+  }
+}