Add Security class

git-svn-id: http://xe-core.googlecode.com/svn/branches/1.5.0@8815 201d5d3c-b55e-5fd7-737f-ddc643e51545
This commit is contained in:
taggon 2011-08-22 05:33:15 +00:00
parent 3b0660c969
commit 5bfd3e6b61
2 changed files with 250 additions and 0 deletions

View file

@ -0,0 +1,109 @@
<?php
/**
* @class Security
* @brief This class helps to solve security problems.
* @author NHN (developers@xpressengine.com)
**/
class Security
{
/**
* @brief Action target variable. If this value is null, the method will use Context variables
* @protected
**/
var $_targetVar = null;
/**
* @constructor
* @param $var Target context
*/
function init($var = null)
{
$this->_targetVar($var);
}
/**
* @brief Convert special characters to HTML entities for the target variables.
* The results of conversion are equivalent to the results of htmlspecialchars() which is a native function of PHP.
* @params string $varName
* A variable's name to convert
* To process properties of an object or elements of an array,
* separate the owner(object or array) and the item(property or element) using a dot(.)
* @public
*/
function encodeHTML(/*, $varName1, $varName2, ... */)
{
$varNames = func_get_args();
if(count($varNames) < 0) return false;
$use_context = is_null($this->_targetVar);
if(!$use_context) {
if(!count($varNames) || (!is_object($this->_targetVar) && !is_array($this->_targetVar)) ) return $this->_encodeHTML($this->_targetVar);
$is_object = is_object($this->_targetVar);
}
foreach($varNames as $varName) {
$varName = explode('.', $varName);
$varName0 = array_shift($varName);
if($use_context) {
$var = Context::get($varName0);
} else {
$var = $is_object ? $this->_targetVar->{$varName0} : $this->_targetVar[$varName0];
}
$var = $this->_encodeHTML($var, $varName);
if($var !== false) {
if($use_context) {
Context::set($varName0, $var);
} elseif($is_object) {
$this->_targetVar->{$varName0} = $var;
} else {
$this->_targetVar[$varName0] = $var;
}
}
}
}
/**
* @protected
*/
function _encodeHTML($var, $name=array())
{
if(is_string($var)) {
$var = htmlspecialchars($var);
return $var;
}
if(!count($name) || (!is_array($var) && !is_object($var)) ) return false;
$is_object = is_object($var);
$name0 = array_shift($name);
if(strlen($name0)) {
$target = $is_object ? $var->{$name0} : $var[$name0];
$target = $this->_encodeHTML($target, $name);
if($target === false) return $var;
if($is_object) $var->{$name0} = $target;
else $var[$name0] = $target;
return $var;
}
foreach($var as $key=>$target) {
$cloned_name = array_slice($name, 0);
$target = $this->_encodeHTML($target, $name);
$name = $cloned_name;
if($target === false) continue;
if($is_object) $var->{$key} = $target;
else $var[$key] = $target;
}
return $var;
}
}
/* End of file : Security.class.php */

View file

@ -0,0 +1,141 @@
<?php
define('__DEBUG__', 1);
$xe_path = realpath(dirname(__FILE__).'/../../../');
require "{$xe_path}/classes/security/Security.class.php";
error_reporting(E_ALL & ~E_NOTICE);
class SecurityTest extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
/**
* Setup mock data
**/
// string
Context::set('content1', '<strong>Hello, world</strong>');
Context::set('content2', 'Wow, >_< !');
// object
$args = new stdClass;
$args->prop1 = 'Normal string';
$args->prop2 = 'He said, "Very nice!"';
$args->prop3 = '<strong>Strong</strong> Baby';
Context::set('object1', $args);
// array
$arr = array();
$arr[] = '<span class="first">F</span>irst';
$arr[] = '<u>S</u>econd';
$arr[] = '<b>T</b>hird';
Context::set('array1', $arr);
// associative array
$aarr = array();
$aarr['elem1'] = 'One <ins>1</ins>';
$aarr['elem2'] = 'Two <del>2</del>';
$aarr['elem3'] = 'Three <addr>3</addr>';
Context::set('array2', $aarr);
}
public function testEncodeHTML_DefaultContext()
{
$security = new Security();
// normal string - one
$this->setUp();
$this->assertEquals('<strong>Hello, world</strong>', Context::get('content1'));
$security->encodeHTML('content1');
$this->assertEquals('&lt;strong&gt;Hello, world&lt;/strong&gt;', Context::get('content1'));
// normal string - two
$this->setUp();
$this->assertEquals('<strong>Hello, world</strong>', Context::get('content1'));
$this->assertEquals('Wow, >_< !', Context::get('content2'));
$security->encodeHTML('content1','content2');
$this->assertEquals('&lt;strong&gt;Hello, world&lt;/strong&gt;', Context::get('content1'));
$this->assertEquals('Wow, &gt;_&lt; !', Context::get('content2'));
// array
$this->assertEquals(Context::get('array1'), array('<span class="first">F</span>irst','<u>S</u>econd','<b>T</b>hird'));
$security->encodeHTML('array1'); // should ignore this
$this->assertEquals(Context::get('array1'), array('<span class="first">F</span>irst','<u>S</u>econd','<b>T</b>hird'));
$security->encodeHTML('array1.0'); // affect only first element
$this->assertEquals(Context::get('array1'), array('&lt;span class=&quot;first&quot;&gt;F&lt;/span&gt;irst','<u>S</u>econd','<b>T</b>hird'));
$security->encodeHTML('array1.2'); // affects only third element
$this->assertEquals(Context::get('array1'), array('&lt;span class=&quot;first&quot;&gt;F&lt;/span&gt;irst','<u>S</u>econd','&lt;b&gt;T&lt;/b&gt;hird'));
$this->setUp(); // reset;
$this->assertEquals(Context::get('array1'), array('<span class="first">F</span>irst','<u>S</u>econd','<b>T</b>hird'));
$security->encodeHTML('array1.'); // affects all items
$this->assertEquals(Context::get('array1'), array('&lt;span class=&quot;first&quot;&gt;F&lt;/span&gt;irst','&lt;u&gt;S&lt;/u&gt;econd','&lt;b&gt;T&lt;/b&gt;hird'));
// associated array
$this->assertEquals(Context::get('array2'), array('elem1'=>'One <ins>1</ins>','elem2'=>'Two <del>2</del>','elem3'=>'Three <addr>3</addr>'));
$security->encodeHTML('array2'); // should ignore this
$this->assertEquals(Context::get('array2'), array('elem1'=>'One <ins>1</ins>','elem2'=>'Two <del>2</del>','elem3'=>'Three <addr>3</addr>'));
$security->encodeHTML('array2.0'); // should ignore this
$this->assertEquals(Context::get('array2'), array('elem1'=>'One <ins>1</ins>','elem2'=>'Two <del>2</del>','elem3'=>'Three <addr>3</addr>'));
$security->encodeHTML('array2.elem2'); // affects only 'elem2'
$this->assertEquals(Context::get('array2'), array('elem1'=>'One <ins>1</ins>','elem2'=>'Two &lt;del&gt;2&lt;/del&gt;','elem3'=>'Three <addr>3</addr>'));
$this->setUp(); // reset;
$this->assertEquals(Context::get('array2'), array('elem1'=>'One <ins>1</ins>','elem2'=>'Two <del>2</del>','elem3'=>'Three <addr>3</addr>'));
$security->encodeHTML('array2.'); // affects all items
$this->assertEquals(Context::get('array2'), array('elem1'=>'One &lt;ins&gt;1&lt;/ins&gt;','elem2'=>'Two &lt;del&gt;2&lt;/del&gt;','elem3'=>'Three &lt;addr&gt;3&lt;/addr&gt;'));
// object
$obj = new stdClass;
$obj->prop1 = 'Normal string';
$obj->prop2 = 'He said, "Very nice!"';
$obj->prop3 = '<strong>Strong</strong> Baby';
$this->assertEquals(Context::get('object1'), $obj);
$security->encodeHTML('object1'); // should ignore this
$this->assertEquals(Context::get('object1'), $obj);
$security->encodeHTML('object1.0'); // should ignore this
$this->assertEquals(Context::get('object1'), $obj);
$security->encodeHTML('object1.prop1'); // affects only 'prop1' property - no changes
$this->assertEquals(Context::get('object1'), $obj);
$security->encodeHTML('object1.prop3'); // affects only 'prop3' property
$obj->prop3 = '&lt;strong&gt;Strong&lt;/strong&gt; Baby';
$this->assertEquals(Context::get('object1'), $obj);
$this->setUp(); // reset
$obj->prop3 = '<strong>Strong</strong> Baby';
$this->assertEquals(Context::get('object1'), $obj);
$security->encodeHTML('object1.'); // affects all properties
$obj->prop2 = 'He said, &quot;Very nice!&quot;';
$obj->prop3 = '&lt;strong&gt;Strong&lt;/strong&gt; Baby';
$this->assertEquals(Context::get('object1'), $obj);
}
}
$mock_vars = array();
class Context
{
public function gets() {
global $mock_vars;
$args = func_get_args();
$output = new stdClass;
foreach($args as $name) {
$output->{$name} = $mock_vars[$name];
}
return $output;
}
public function get($name) {
global $mock_vars;
return array_key_exists($name, $mock_vars)?$mock_vars[$name]:'';
}
public function set($name, $value) {
global $mock_vars;
$mock_vars[$name] = $value;
}
}