Skip to content

Commit

Permalink
Adding SafeString which is going to replace p::clean() and p::purify().
Browse files Browse the repository at this point in the history
Refactoring of Xss_Security_Test.
t() and t2() return a SafeString instance.

TODO:
 - Update all code to use SafeString where appropriate.
 - Update golden fole of Xss_Security_Test
 - Stop reporting CLEAN vars in Xss_Security_Test
  • Loading branch information
andyst committed Aug 29, 2009
1 parent a2e2a21 commit 020281d
Show file tree
Hide file tree
Showing 7 changed files with 535 additions and 89 deletions.
16 changes: 3 additions & 13 deletions modules/gallery/helpers/p.php
Expand Up @@ -18,22 +18,12 @@
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class p_Core {
private static $_purifier = null;
static function clean($dirty_html) {
return html::specialchars($dirty_html);
return new SafeString($dirty_html);
}

// Deprecated: Please use p::clean($var).purified_html()
static function purify($dirty_html) {
if (empty(self::$_purifier)) {
require_once(dirname(__file__) . "/../lib/HTMLPurifier/HTMLPurifier.auto.php");
$config = HTMLPurifier_Config::createDefault();
foreach (Kohana::config('purifier') as $category => $key_value) {
foreach ($key_value as $key => $value) {
$config->set("$category.$key", $value);
}
}
self::$_purifier = new HTMLPurifier($config);
}
return self::$_purifier->purify($dirty_html);
return SafeString::of($dirty_html)->purified_html();
}
}
20 changes: 14 additions & 6 deletions modules/gallery/libraries/I18n.php
Expand Up @@ -84,6 +84,12 @@ public function locale($locale=null) {

/**
* Translates a localizable message.
*
* Security:
* The returned string is safe for use in HTML (it contains a safe subset of HTML and
* interpolation parameters are converted to HTML entities).
* For use in JavaScript, please call ->for_js() on it.
*
* @param $message String|array The message to be translated. E.g. "Hello world"
* or array("one" => "One album", "other" => "%count albums")
* @param $options array (optional) Options array for key value pairs which are used
Expand All @@ -110,7 +116,7 @@ public function translate($message, $options=array()) {

$entry = $this->interpolate($locale, $entry, $values);

return $entry;
return SafeString::of($entry)->mark_html_safe();
}

private function lookup($locale, $message) {
Expand Down Expand Up @@ -179,17 +185,19 @@ static function is_plural_message($message) {
return is_array($message);
}

private function interpolate($locale, $string, $values) {
private function interpolate($locale, $string, $key_values) {
// TODO: Handle locale specific number formatting.

// Replace x_y before replacing x.
krsort($values, SORT_STRING);
krsort($key_values, SORT_STRING);

$keys = array();
foreach (array_keys($values) as $key) {
$values = array();
foreach ($key_values as $key => $value) {
$keys[] = "%$key";
$values[] = new SafeString($value);
}
return str_replace($keys, array_values($values), $string);
return str_replace($keys, $values, $string);
}

private function pluralize($locale, $entry, $count) {
Expand Down Expand Up @@ -414,4 +422,4 @@ private static function get_plural_key($locale, $count) {
return $count == 1 ? 'one' : 'other';
}
}
}
}
4 changes: 4 additions & 0 deletions modules/gallery/libraries/MY_ORM.php
Expand Up @@ -43,6 +43,10 @@ public function __set($column, $value) {
$this->original = clone $this;
}

if ($value instanceof SafeString) {
$value = $value->unescaped();
}

return parent::__set($column, $value);
}

Expand Down
142 changes: 142 additions & 0 deletions modules/gallery/libraries/SafeString.php
@@ -0,0 +1,142 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2009 Bharat Mediratta
*
* 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 2 of the License, or (at
* your option) 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/

/**
* Safe string representation (regarding security - cross site scripting).
*/
class SafeString_Core {
private $_raw_string;
protected $_is_safe_html = false;

private static $_purifier = null;

/** Constructor */
function __construct($string) {
if ($string instanceof SafeString) {
$this->_is_safe_html = $string->_is_safe_html;
$string = $string->unescaped();
}
$this->_raw_string = (string) $string;
}

/**
* Factory method returning a new SafeString instance for the given string.
*/
static function of($string) {
return new SafeString($string);
}

/**
* Marks this string as safe to be used in HTML without any escaping.
*/
function mark_html_safe() {
$this->_is_safe_html = true;
return $this;
}

/**
* Safe for use in HTML.
* @see #for_html()
*/
function __toString() {
if ($this->_is_safe_html) {
return $this->_raw_string;
} else {
return self::_escape_for_html($this->_raw_string);
}
}

/**
* Safe for use in HTML.
*
* Example:<pre>
* <div><?= $php_var ?>
* </pre>
* @return the string escaped for use in HTML.
*/
function for_html() {
return $this;
}

/**
* Safe for use in JavaScript.
*
* Example:<pre>
* <script type="text/javascript>"
* var some_js_var = "<?= $php_var->for_js() ?>";
* </script>
* </pre>
* @return the string escaped for use in JavaScript.
*/
function for_js() {
return self::_escape_for_js($this->_raw_string);
}

/**
* Safe for use HTML (purified HTML)
*
* Example:<pre>
* <div><?= $php_var->purified_html() ?>
* </pre>
* @return the string escaped for use in HTML.
*/
function purified_html() {
if ($this->_is_safe_html) {
return $this;
} else {
return SafeString::of(self::_purify_for_html($this->_raw_string), true);
}
}

/**
* Returns the raw, unsafe string. Do not use lightly.
*/
function unescaped() {
return $this->_raw_string;
}

// Escapes special HTML chars ("<", ">", "&", etc.) to HTML entities.
private static function _escape_for_html($dirty_html) {
return html::specialchars($dirty_html);
}

// Escapes special chars (quotes, backslash, etc.) with a backslash sequence.
private static function _escape_for_js($string) {
// From Smarty plugins/modifier.escape.php
// Might want to be stricter here.
return strtr($string,
array('\\'=>'\\\\',"'"=>"\\'",'"'=>'\\"',"\r"=>'\\r',"\n"=>'\\n','</'=>'<\/'));
}

// Purifies the string, removing any potentially malicious or unsafe HTML / JavaScript.
private static function _purify_for_html($dirty_html) {
if (empty(self::$_purifier)) {
require_once(dirname(__file__) . "/../lib/HTMLPurifier/HTMLPurifier.auto.php");
$config = HTMLPurifier_Config::createDefault();
foreach (Kohana::config('purifier') as $category => $key_value) {
foreach ($key_value as $key => $value) {
$config->set("$category.$key", $value);
}
}
self::$_purifier = new HTMLPurifier($config);
}
return self::$_purifier->purify($dirty_html);
}
}
111 changes: 111 additions & 0 deletions modules/gallery/tests/SafeString_Test.php
@@ -0,0 +1,111 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2009 Bharat Mediratta
*
* 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 2 of the License, or (at
* your option) 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class SafeString_Test extends Unit_Test_Case {
public function p_clean_returns_safestring_instance_test() {
$safe_string = p::clean("hello <p>world</p>");
$this->assert_true($safe_string instanceof SafeString);
$this->assert_equal("hello <p>world</p>",
$safe_string->unescaped());
}

public function toString_escapes_for_html_test() {
$safe_string = new SafeString("hello <p>world</p>");
$this->assert_equal("hello &lt;p&gt;world&lt;/p&gt;",
$safe_string);
}

public function toString_for_safe_string_test() {
$safe_string = new SafeString("hello <p>world</p>");
$safe_string->mark_html_safe();
$this->assert_equal("hello <p>world</p>",
$safe_string);
}

public function for_html_test() {
$safe_string = new SafeString("hello <p>world</p>");
$this->assert_equal("hello &lt;p&gt;world&lt;/p&gt;",
$safe_string->for_html());
}

public function safestring_of_safestring_test() {
$safe_string = new SafeString("hello <p>world</p>");
$safe_string_2 = new SafeString($safe_string);
$this->assert_true($safe_string_2 instanceof SafeString);
$raw_string = $safe_string_2->unescaped();
$this->assert_false(is_object($raw_string));
$this->assert_equal("hello <p>world</p>", $raw_string);
$this->assert_equal("hello &lt;p&gt;world&lt;/p&gt;", $safe_string_2);
}

public function for_js_test() {
$safe_string = new SafeString('"<em>Foo</em>\'s bar"');
$js_string = $safe_string->for_js();
$this->assert_equal('\\"<em>Foo<\\/em>\\\'s bar\\"',
$js_string);
}

public function string_safestring_equality_test() {
$safe_string = new SafeString("hello <p>world</p>");
$this->assert_equal("hello <p>world</p>",
$safe_string->unescaped());
$escaped_string = "hello &lt;p&gt;world&lt;/p&gt;";
$this->assert_equal($escaped_string, $safe_string);

$this->assert_true($escaped_string == $safe_string);
$this->assert_false($escaped_string === $safe_string);
$this->assert_false("meow" == $safe_string);
}

public function of_test() {
$safe_string = SafeString::of("hello <p>world</p>");
$this->assert_equal("hello <p>world</p>", $safe_string->unescaped());
}

public function of_safe_html_test() {
$safe_string = SafeString::of("hello <p>world</p>")->mark_html_safe();
$this->assert_equal("hello <p>world</p>", $safe_string->for_html());
}

public function of_fluid_api_test() {
$escaped_string = SafeString::of("Foo's bar")->for_js();
$this->assert_equal("Foo\\'s bar", $escaped_string);
}

public function safestring_of_safestring_preserves_safe_status_test() {
$safe_string = SafeString::of("hello's <p>world</p>")->mark_html_safe();
$safe_string_2 = new SafeString($safe_string);
$this->assert_equal("hello's <p>world</p>", $safe_string_2);
$this->assert_equal("hello\\'s <p>world<\\/p>", $safe_string_2->for_js());
}

public function safestring_of_safestring_preserves_html_safe_status_test() {
$safe_string = SafeString::of("hello's <p>world</p>")
->mark_html_safe();
$safe_string_2 = new SafeString($safe_string);
$this->assert_equal("hello's <p>world</p>", $safe_string_2);
$this->assert_equal("hello\\'s <p>world<\\/p>", $safe_string_2->for_js());
}

public function safestring_of_safestring_safe_status_override_test() {
$safe_string = new SafeString("hello <p>world</p>");
$safe_string_2 = SafeString::of($safe_string)->mark_html_safe();
$this->assert_equal("hello <p>world</p>", $safe_string_2);
}
}

0 comments on commit 020281d

Please sign in to comment.