diff options
Diffstat (limited to 'modules/gallery')
25 files changed, 693 insertions, 184 deletions
diff --git a/modules/gallery/controllers/admin_advanced_settings.php b/modules/gallery/controllers/admin_advanced_settings.php index 64007fdb..d727b654 100644 --- a/modules/gallery/controllers/admin_advanced_settings.php +++ b/modules/gallery/controllers/admin_advanced_settings.php @@ -46,7 +46,7 @@ class Admin_Advanced_Settings_Controller extends Admin_Controller { module::set_var($module_name, $var_name, Input::instance()->post("value")); message::success( t("Saved value for %var (%module_name)", - array("var" => p::clean($var_name), "module_name" => $module_name))); + array("var" => SafeString::of($var_name), "module_name" => $module_name))); print json_encode(array("result" => "success")); } diff --git a/modules/gallery/controllers/albums.php b/modules/gallery/controllers/albums.php index cdfa823d..ec3eb426 100644 --- a/modules/gallery/controllers/albums.php +++ b/modules/gallery/controllers/albums.php @@ -112,7 +112,7 @@ class Albums_Controller extends Items_Controller { log::success("content", "Created an album", html::anchor("albums/$new_album->id", "view album")); message::success( - t("Created album %album_title", array("album_title" => p::clean($new_album->title)))); + t("Created album %album_title", array("album_title" => $new_album->title))); print json_encode( array("result" => "success", @@ -145,7 +145,7 @@ class Albums_Controller extends Items_Controller { log::success("content", "Added a photo", html::anchor("photos/$photo->id", "view photo")); message::success( - t("Added photo %photo_title", array("photo_title" => p::clean($photo->title)))); + t("Added photo %photo_title", array("photo_title" => $photo->title))); print json_encode( array("result" => "success", @@ -194,7 +194,7 @@ class Albums_Controller extends Items_Controller { log::success("content", "Updated album", "<a href=\"albums/$album->id\">view</a>"); message::success( - t("Saved album %album_title", array("album_title" => p::clean($album->title)))); + t("Saved album %album_title", array("album_title" => $album->title))); print json_encode( array("result" => "success", diff --git a/modules/gallery/controllers/movies.php b/modules/gallery/controllers/movies.php index c8227d74..09b16759 100644 --- a/modules/gallery/controllers/movies.php +++ b/modules/gallery/controllers/movies.php @@ -93,7 +93,7 @@ class Movies_Controller extends Items_Controller { log::success("content", "Updated photo", "<a href=\"photos/$photo->id\">view</a>"); message::success( - t("Saved photo %photo_title", array("photo_title" => p::clean($photo->title)))); + t("Saved photo %photo_title", array("photo_title" => $photo->title))); print json_encode( array("result" => "success", diff --git a/modules/gallery/controllers/photos.php b/modules/gallery/controllers/photos.php index 8ee24da8..3447b4c6 100644 --- a/modules/gallery/controllers/photos.php +++ b/modules/gallery/controllers/photos.php @@ -86,7 +86,7 @@ class Photos_Controller extends Items_Controller { log::success("content", "Updated photo", "<a href=\"photos/$photo->id\">view</a>"); message::success( - t("Saved photo %photo_title", array("photo_title" => p::clean($photo->title)))); + t("Saved photo %photo_title", array("photo_title" => $photo->title))); print json_encode( array("result" => "success", diff --git a/modules/gallery/controllers/quick.php b/modules/gallery/controllers/quick.php index 82176e02..8fddb563 100644 --- a/modules/gallery/controllers/quick.php +++ b/modules/gallery/controllers/quick.php @@ -75,7 +75,7 @@ class Quick_Controller extends Controller { access::required("view", $item->parent()); access::required("edit", $item->parent()); - $msg = t("Made <b>%title</b> this album's cover", array("title" => p::purify($item->title))); + $msg = t("Made <b>%title</b> this album's cover", array("title" => SafeString::purify($item->title))); item::make_album_cover($item); message::success($msg); @@ -91,10 +91,10 @@ class Quick_Controller extends Controller { if ($item->is_album()) { print t( "Delete the album <b>%title</b>? All photos and movies in the album will also be deleted.", - array("title" => p::purify($item->title))); + array("title" => SafeString::purify($item->title))); } else { print t("Are you sure you want to delete <b>%title</b>?", - array("title" => p::purify($item->title))); + array("title" => SafeString::purify($item->title))); } $form = item::get_delete_form($item); @@ -108,9 +108,9 @@ class Quick_Controller extends Controller { access::required("edit", $item); if ($item->is_album()) { - $msg = t("Deleted album <b>%title</b>", array("title" => p::purify($item->title))); + $msg = t("Deleted album <b>%title</b>", array("title" => SafeString::purify($item->title))); } else { - $msg = t("Deleted photo <b>%title</b>", array("title" => p::purify($item->title))); + $msg = t("Deleted photo <b>%title</b>", array("title" => SafeString::purify($item->title))); } $parent = $item->parent(); diff --git a/modules/gallery/helpers/MY_url.php b/modules/gallery/helpers/MY_url.php index c4967c52..6092a9d8 100644 --- a/modules/gallery/helpers/MY_url.php +++ b/modules/gallery/helpers/MY_url.php @@ -30,7 +30,8 @@ class url extends url_Core { if ($parts[0] == "albums" || $parts[0] == "photos") { $uri = model_cache::get("item", $parts[1])->relative_path(); } - return parent::site($uri . $query, $protocol); + $url = parent::site($uri . $query, $protocol); + return SafeString::of_safe_html($url); } static function parse_url() { @@ -99,4 +100,25 @@ class url extends url_Core { static function abs_current($qs=false) { return self::abs_site(url::current($qs)); } + + public static function base($index=false, $protocol=false) { + $url = parent::base($index, $protocol); + return SafeString::of_safe_html($url); + } + + public static function current($qs=false) { + $url = parent::current($qs); + return SafeString::of_safe_html($url); + } + + public static function file($file, $index=false) { + $url = parent::file($file, $index); + return SafeString::of_safe_html($url); + } + + public static function merge(array $arguments) { + $url = parent::merge($arguments); + return SafeString::of_safe_html($url); + } + } diff --git a/modules/gallery/helpers/gallery_rss.php b/modules/gallery/helpers/gallery_rss.php index 8e887368..affb3101 100644 --- a/modules/gallery/helpers/gallery_rss.php +++ b/modules/gallery/helpers/gallery_rss.php @@ -53,9 +53,9 @@ class gallery_rss_Core { ->descendants($limit, $offset, array("type" => "photo")); $feed->max_pages = ceil( $item->viewable()->descendants_count(array("type" => "photo")) / $limit); - $feed->title = p::purify($item->title); + $feed->title = SafeString::purify($item->title); $feed->link = url::abs_site("albums/{$item->id}"); - $feed->description = nl2br(p::purify($item->description)); + $feed->description = nl2br(SafeString::purify($item->description)); return $feed; } diff --git a/modules/gallery/helpers/gallery_task.php b/modules/gallery/helpers/gallery_task.php index 9edc3acd..8c0e8aa8 100644 --- a/modules/gallery/helpers/gallery_task.php +++ b/modules/gallery/helpers/gallery_task.php @@ -64,10 +64,10 @@ class gallery_task_Core { if (!$success) { $ignored[$item->id] = 1; $errors[] = t("Unable to rebuild images for '%title'", - array("title" => p::purify($item->title))); + array("title" => SafeString::purify($item->title))); } else { $errors[] = t("Successfully rebuilt images for '%title'", - array("title" => p::purify($item->title))); + array("title" => SafeString::purify($item->title))); } } diff --git a/modules/gallery/helpers/p.php b/modules/gallery/helpers/p.php deleted file mode 100644 index 862c769b..00000000 --- a/modules/gallery/helpers/p.php +++ /dev/null @@ -1,39 +0,0 @@ -<?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 p_Core { - private static $_purifier = null; - static function clean($dirty_html) { - return html::specialchars($dirty_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); - } -} diff --git a/modules/gallery/libraries/I18n.php b/modules/gallery/libraries/I18n.php index d0531b9a..c3336052 100644 --- a/modules/gallery/libraries/I18n.php +++ b/modules/gallery/libraries/I18n.php @@ -89,6 +89,12 @@ class I18n_Core { /** * 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 @@ -115,7 +121,7 @@ class I18n_Core { $entry = $this->interpolate($locale, $entry, $values); - return $entry; + return SafeString::of_safe_html($entry); } private function lookup($locale, $message) { @@ -184,17 +190,19 @@ class I18n_Core { 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) { @@ -419,4 +427,4 @@ class I18n_Core { return $count == 1 ? 'one' : 'other'; } } -}
\ No newline at end of file +} diff --git a/modules/gallery/libraries/MY_ORM.php b/modules/gallery/libraries/MY_ORM.php index de8adc1d..2c9ad1d7 100644 --- a/modules/gallery/libraries/MY_ORM.php +++ b/modules/gallery/libraries/MY_ORM.php @@ -43,6 +43,10 @@ class ORM extends ORM_Core { $this->original = clone $this; } + if ($value instanceof SafeString) { + $value = $value->unescaped(); + } + return parent::__set($column, $value); } diff --git a/modules/gallery/libraries/SafeString.php b/modules/gallery/libraries/SafeString.php new file mode 100644 index 00000000..9614a213 --- /dev/null +++ b/modules/gallery/libraries/SafeString.php @@ -0,0 +1,177 @@ +<?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; + protected $_is_purified_html = false; + + private static $_purifier = null; + + /** Constructor */ + function __construct($string) { + if ($string instanceof SafeString) { + $this->_is_safe_html = $string->_is_safe_html; + $this->_is_purified_html = $string->_is_purified_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); + } + + /** + * Factory method returning a new SafeString instance after HTML purifying + * the given string. + */ + static function purify($string) { + if ($string instanceof SafeString) { + $string = $string->unescaped(); + } + $safe_string = self::of_safe_html(self::_purify_for_html($string)); + $safe_string->_is_purified_html = true; + return $safe_string; + } + + /** + * Factory method returning a new SafeString instance which won't HTML escape. + */ + static function of_safe_html($string) { + $safe_string = new SafeString($string); + $safe_string->_is_safe_html = true; + return $safe_string; + } + + /** + * 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 in HTML element attributes. + * + * Assumes that the HTML element attribute is already + * delimited by single or double quotes + * + * Example:<pre> + * <a title="<?= $php_var->for_html_attr() ?>">; + * </script> + * </pre> + * @return the string escaped for use in HTML attributes. + */ + function for_html_attr() { + $string = (string) $this->for_html(); + return strtr($string, + array("'"=>"'", + '"'=>'"')); + } + + /** + * 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_purified_html) { + return $this; + } else { + return self::purify($this); + } + } + + /** + * 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); + } +} diff --git a/modules/gallery/tests/SafeString_Test.php b/modules/gallery/tests/SafeString_Test.php new file mode 100644 index 00000000..0fc7f6f3 --- /dev/null +++ b/modules/gallery/tests/SafeString_Test.php @@ -0,0 +1,121 @@ +<?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 toString_escapes_for_html_test() { + $safe_string = new SafeString("hello <p>world</p>"); + $this->assert_equal("hello <p>world</p>", + $safe_string); + } + + public function toString_for_safe_string_test() { + $safe_string = SafeString::of_safe_html("hello <p>world</p>"); + $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 <p>world</p>", + $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 <p>world</p>", $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 for_html_attr_test() { + $safe_string = new SafeString('"<em>Foo</em>\'s bar"'); + $attr_string = $safe_string->for_html_attr(); + $this->assert_equal('"<em>Foo</em>'s bar"', + $attr_string); + } + + public function for_html_attr_with_safe_html_test() { + $safe_string = SafeString::of_safe_html('"<em>Foo</em>\'s bar"'); + $attr_string = $safe_string->for_html_attr(); + $this->assert_equal('"<em>Foo</em>'s bar"', + $attr_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 <p>world</p>"; + $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_safe_html("hello <p>world</p>"); + $this->assert_equal("hello <p>world</p>", $safe_string->for_html()); + } + + public function purify_test() { + $safe_string = SafeString::purify("hello <p >world</p>"); + $this->assert_equal("hello <p>world</p>", $safe_string); + } + + 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_safe_html("hello's <p>world</p>"); + $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_safe_html("hello's <p>world</p>"); + $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_html($safe_string); + $this->assert_equal("hello <p>world</p>", $safe_string_2); + } +} diff --git a/modules/gallery/tests/Xss_Security_Test.php b/modules/gallery/tests/Xss_Security_Test.php index 9bde11dc..690dc760 100644 --- a/modules/gallery/tests/Xss_Security_Test.php +++ b/modules/gallery/tests/Xss_Security_Test.php @@ -19,87 +19,304 @@ */ class Xss_Security_Test extends Unit_Test_Case { public function find_unescaped_variables_in_views_test() { + $found = array(); foreach (glob("*/*/views/*.php") as $view) { - $expr = null; - $level = 0; - $php = 0; - $str = null; - $in_p_clean = 0; + // List of all tokens without whitespace, simplifying parsing. + $tokens = array(); foreach (token_get_all(file_get_contents($view)) as $token) { - if (false /* useful for debugging */) { - if (is_array($token)) { - printf("[$str] [$in_p_clean] %-15s %s\n", token_name($token[0]), $token[1]); - } else { - printf("[$str] [$in_p_clean] %-15s %s\n", "<char>", $token); - } - } - - // If we find a "(" after a "p::clean" then start counting levels of parens and assume - // that we're inside a p::clean() call until we find the matching close paren. - if ($token[0] == "(" && ($str == "p::clean" || $str == "p::purify")) { - $in_p_clean = 1; - } else if ($token[0] == "(" && $in_p_clean) { - $in_p_clean++; - } else if ($token[0] == ")" && $in_p_clean) { - $in_p_clean--; - } - - // Concatenate runs of strings for convenience, which we use above to figure out if we're - // inside a p::clean() call or not - if ($token[0] == T_STRING || $token[0] == T_DOUBLE_COLON) { - $str .= $token[1]; - } else { - $str = null; - } - - // Scan for any occurrences of < ? = $variable ? > and store it in $expr - if ($token[0] == T_OPEN_TAG_WITH_ECHO) { - $php++; - } else if ($php && $token[0] == T_CLOSE_TAG) { - $php--; - } else if ($php && $token[0] == T_VARIABLE) { - if (!$expr) { - $entry = array($token[2], $in_p_clean); - } - $expr .= $token[1]; - } else if ($expr) { - if ($token[0] == T_OBJECT_OPERATOR) { - $expr .= $token[1]; - } else if ($token[0] == T_STRING) { - $expr .= $token[1]; - } else if ($token == "(") { - $expr .= $token; - $level++; - } else if ($level > 0 && $token == ")") { - $expr .= $token; - $level--; - } else if ($level > 0) { - $expr .= is_array($token) ? $token[1] : $token; - } else { - $entry[] = $expr; - $found[$view][] = $entry; - $expr = null; - $entry = null; - } - } + if (!is_array($token) || ($token[0] != T_WHITESPACE)) { + $tokens[] = $token; + } + } + + $frame = null; + $script_block = 0; + $in_script_block = false; + + for ($token_number = 0; $token_number < count($tokens); $token_number++) { + $token = $tokens[$token_number]; + + // Are we in a <script> ... </script> block? + if (is_array($token) && $token[0] == T_INLINE_HTML) { + $inline_html = $token[1]; + // T_INLINE_HTML blocks can be split. Need to handle the case + // where one token has "<scr" and the next has "ipt" + while (self::_token_matches(array(T_INLINE_HTML), $tokens, $token_number + 1)) { + $token_number++; + $token = $tokens[$token_number]; + $inline_html .= $token[1]; + } + + if ($frame) { + $frame->expr_append($inline_html); + } + + // Note: This approach won't catch <script src="..."> blocks if the src + // URL is generated via < ? = url::site() ? > or some other PHP. + // Assume that all such script blocks with a src URL have an + // empty element body. + // But we'll catch closing tags for such blocks, so don't keep track + // of opening / closing tag count since it would be meaningless. + + // Handle multiple start / end blocks on the same line? + $opening_script_pos = $closing_script_pos = 0; + if (preg_match_all('{</script>}i', $inline_html, $matches, PREG_OFFSET_CAPTURE)) { + $last_match = array_pop($matches[0]); + if (is_array($last_match)) { + $closing_script_pos = $last_match[1]; + } else { + $closing_script_pos = $last_match; + } + } + if (preg_match('{<script\b[^>]*>}i', $inline_html, $matches, PREG_OFFSET_CAPTURE)) { + $last_match = array_pop($matches[0]); + if (is_array($last_match)) { + $opening_script_pos = $last_match[1]; + } else { + $opening_script_pos = $last_match; + } + } + if ($opening_script_pos != $closing_script_pos) { + $in_script_block = $opening_script_pos > $closing_script_pos; + } + } + + // Look and report each instance of < ? = ... ? > + if (!is_array($token)) { + // A single char token, e.g: ; ( ) + if ($frame) { + $frame->expr_append($token); + } + } else if ($token[0] == T_OPEN_TAG_WITH_ECHO) { + // No need for a stack here - assume < ? = cannot be nested. + $frame = self::_create_frame($token, $in_script_block); + } else if ($frame && $token[0] == T_CLOSE_TAG) { + // Store the < ? = ... ? > block that just ended here. + $found[$view][] = $frame; + $frame = null; + } else if ($frame && $token[0] == T_VARIABLE) { + $frame->expr_append($token[1]); + } else if ($frame && $token[0] == T_STRING) { + $frame->expr_append($token[1]); + // t() and t2() are special in that they're guaranteed to return a SafeString(). + if (in_array($token[1], array("t", "t2"))) { + if (self::_token_matches("(", $tokens, $token_number + 1)) { + $frame->is_safestring(true); + $frame->expr_append("("); + + $token_number++; + $token = $tokens[$token_number]; + } + } else if ($token[1] == "SafeString") { + // Looking for SafeString::of(... + if (self::_token_matches(array(T_DOUBLE_COLON, "::"), $tokens, $token_number + 1) && + self::_token_matches(array(T_STRING), $tokens, $token_number + 2) && + in_array($tokens[$token_number + 2][1], array("of", "of_safe_html", "purify")) && + self::_token_matches("(", $tokens, $token_number + 3)) { + $frame->is_safestring(true); + + $method = $tokens[$token_number + 2][1]; + $frame->expr_append("::$method("); + + $token_number += 3; + $token = $tokens[$token_number]; + } + } else if ($token[1] == "json_encode") { + if (self::_token_matches("(", $tokens, $token_number + 1)) { + $frame->json_encode_called(true); + $frame->expr_append("("); + + $token_number++; + $token = $tokens[$token_number]; + } + } else if ($token[1] == "url") { + // url methods return a SafeString + if (self::_token_matches(array(T_DOUBLE_COLON, "::"), $tokens, $token_number + 1) && + self::_token_matches(array(T_STRING), $tokens, $token_number + 2) && + in_array($tokens[$token_number + 2][1], + array("site", "current", "base", "file", "abs_site", "abs_current", + "abs_file", "merge")) && + self::_token_matches("(", $tokens, $token_number + 3)) { + $frame->is_safestring(true); + + $method = $tokens[$token_number + 2][1]; + $frame->expr_append("::$method("); + + $token_number += 3; + $token = $tokens[$token_number]; + } + } + } else if ($frame && $token[0] == T_OBJECT_OPERATOR) { + $frame->expr_append($token[1]); + + if (self::_token_matches(array(T_STRING), $tokens, $token_number + 1) && + in_array($tokens[$token_number + 1][1], + array("for_js", "for_html", "purified_html")) && + self::_token_matches("(", $tokens, $token_number + 2)) { + + $method = $tokens[$token_number + 1][1]; + $frame->expr_append("$method("); + + $token_number += 2; + $token = $tokens[$token_number]; + + if ("for_js" == $method) { + $frame->for_js_called(true); + } else if ("for_html" == $method) { + $frame->for_html_called(true); + } else if ("purified_html" == $method) { + $frame->purified_html_called(true); + } + } + } else if ($frame) { + $frame->expr_append($token[1]); + } } } - $canonical = MODPATH . "gallery/tests/xss_data.txt"; + /* + * Generate the report + * + * States for uses of < ? = X ? >: + * DIRTY_JS: + * In <script> block + * X can be anything without calling ->for_js() + * DIRTY: + * Outside <script> block: + * X can be anything without a call to ->for_html() or ->purified_html() + * CLEAN: + * Outside <script> block: + * X = is SafeString (t(), t2(), url::site()) + * X = * and for_html() or purified_html() is called + * Inside <script> block: + * X = * with ->for_js() or json_encode(...) + */ $new = TMPPATH . "xss_data.txt"; $fd = fopen($new, "wb"); ksort($found); - foreach ($found as $view => $entries) { - foreach ($entries as $entry) { - fwrite($fd, - sprintf("%-60s %-3s %-5s %s\n", - $view, $entry[0], $entry[1] ? "" : "DIRTY", $entry[2])); + foreach ($found as $view => $frames) { + foreach ($frames as $frame) { + $state = "DIRTY"; + if ($frame->in_script_block()) { + $state = "DIRTY_JS"; + if ($frame->for_js_called() || $frame->json_encode_called()) { + $state = "CLEAN"; + } + } else { + if ($frame->is_safestring() || $frame->purified_html_called() || $frame->for_html_called()) { + $state = "CLEAN"; + } + } + + if ("CLEAN" == $state) { + // Don't print CLEAN instances - No need to update the golden + // file when adding / moving clean instances. + continue; + } + + fprintf($fd, "%-60s %-3s %-8s %s\n", + $view, $frame->line(), $state, $frame->expr()); } } fclose($fd); + // Compare with the expected report from our golden file. + $canonical = MODPATH . "gallery/tests/xss_data.txt"; exec("diff $canonical $new", $output, $return_value); $this->assert_false( $return_value, "XSS golden file mismatch. Output:\n" . implode("\n", $output) ); } + + private static function _create_frame($token, $in_script_block) { + return new Xss_Security_Test_Frame($token[2], $in_script_block); + } + + private static function _token_matches($expected_token, &$tokens, $token_number) { + if (!isset($tokens[$token_number])) { + return false; + } + + $token = $tokens[$token_number]; + + if (is_array($expected_token)) { + for ($i = 0; $i < count($expected_token); $i++) { + if ($expected_token[$i] != $token[$i]) { + return false; + } + } + return true; + } else { + return $expected_token == $token; + } + } +} + +class Xss_Security_Test_Frame { + private $_expr = ""; + private $_in_script_block = false; + private $_is_safestring = false; + private $_for_js_called = false; + private $_for_html_called = false; + private $_purified_html_called = false; + private $_json_encode_called = false; + private $_line; + + function __construct($line_number, $in_script_block) { + $this->_line = $line_number; + $this->in_script_block($in_script_block); + } + + function expr() { + return $this->_expr; + } + + function expr_append($append_value) { + return $this->_expr .= $append_value; + } + + function in_script_block($new_val=NULL) { + if ($new_val !== NULL) { + $this->_in_script_block = (bool) $new_val; + } + return $this->_in_script_block; + } + + function is_safestring($new_val=NULL) { + if ($new_val !== NULL) { + $this->_is_safestring = (bool) $new_val; + } + return $this->_is_safestring; + } + + function json_encode_called($new_val=NULL) { + if ($new_val !== NULL) { + $this->_json_encode_called = (bool) $new_val; + } + return $this->_json_encode_called; + } + + function for_js_called($new_val=NULL) { + if ($new_val !== NULL) { + $this->_for_js_called = (bool) $new_val; + } + return $this->_for_js_called; + } + + function for_html_called($new_val=NULL) { + if ($new_val !== NULL) { + $this->_for_html_called = (bool) $new_val; + } + return $this->_for_html_called; + } + + function purified_html_called($new_val=NULL) { + if ($new_val !== NULL) { + $this->_purified_html_called = (bool) $new_val; + } + return $this->_purified_html_called; + } + + function line() { + return $this->_line; + } } diff --git a/modules/gallery/views/admin_advanced_settings.html.php b/modules/gallery/views/admin_advanced_settings.html.php index b37c1c73..adc15b91 100644 --- a/modules/gallery/views/admin_advanced_settings.html.php +++ b/modules/gallery/views/admin_advanced_settings.html.php @@ -20,13 +20,13 @@ <? if ($var->module_name == "gallery" && $var->name == "_cache") continue ?> <tr class="setting"> <td> <?= $var->module_name ?> </td> - <td> <?= p::clean($var->name) ?> </td> + <td> <?= SafeString::of($var->name) ?> </td> <td> - <a href="<?= url::site("admin/advanced_settings/edit/$var->module_name/" . p::clean($var->name)) ?>" + <a href="<?= url::site("admin/advanced_settings/edit/$var->module_name/" . SafeString::of($var->name)) ?>" class="gDialogLink" - title="<?= t("Edit %var (%module_name)", array("var" => p::clean($var->name), "module_name" => $var->module_name)) ?>"> + title="<?= t("Edit %var (%module_name)", array("var" => $var->name, "module_name" => $var->module_name)) ?>"> <? if ($var->value): ?> - <?= p::clean($var->value) ?> + <?= SafeString::of($var->value) ?> <? else: ?> <i> <?= t("empty") ?> </i> <? endif ?> diff --git a/modules/gallery/views/admin_block_log_entries.html.php b/modules/gallery/views/admin_block_log_entries.html.php index 44c1657f..b7afb22d 100644 --- a/modules/gallery/views/admin_block_log_entries.html.php +++ b/modules/gallery/views/admin_block_log_entries.html.php @@ -2,7 +2,7 @@ <ul> <? foreach ($entries as $entry): ?> <li class="<?= log::severity_class($entry->severity) ?>" style="direction: ltr"> - <a href="<?= url::site("user/$entry->user_id") ?>"><?= p::clean($entry->user->name) ?></a> + <a href="<?= url::site("user/$entry->user_id") ?>"><?= SafeString::of($entry->user->name) ?></a> <?= gallery::date_time($entry->timestamp) ?> <?= $entry->message ?> <?= $entry->html ?> diff --git a/modules/gallery/views/admin_block_photo_stream.html.php b/modules/gallery/views/admin_block_photo_stream.html.php index 1e1329d1..732bdc38 100644 --- a/modules/gallery/views/admin_block_photo_stream.html.php +++ b/modules/gallery/views/admin_block_photo_stream.html.php @@ -2,9 +2,9 @@ <ul> <? foreach ($photos as $photo): ?> <li class="gItem gPhoto"> - <a href="<?= url::site("photos/$photo->id") ?>" title="<?= p::clean($photo->title) ?>"> + <a href="<?= url::site("photos/$photo->id") ?>" title="<?= SafeString::of($photo->title) ?>"> <img <?= photo::img_dimensions($photo->width, $photo->height, 72) ?> - src="<?= $photo->thumb_url() ?>" alt="<?= p::clean($photo->title) ?>" /> + src="<?= $photo->thumb_url() ?>" alt="<?= SafeString::of($photo->title) ?>" /> </a> </li> <? endforeach ?> diff --git a/modules/gallery/views/admin_maintenance.html.php b/modules/gallery/views/admin_maintenance.html.php index 3649ea58..a0a6a19e 100644 --- a/modules/gallery/views/admin_maintenance.html.php +++ b/modules/gallery/views/admin_maintenance.html.php @@ -93,7 +93,7 @@ <?= $task->status ?> </td> <td> - <?= p::clean($task->owner()->name) ?> + <?= SafeString::of($task->owner()->name) ?> </td> <td> <? if ($task->state == "stalled"): ?> diff --git a/modules/gallery/views/admin_maintenance_show_log.html.php b/modules/gallery/views/admin_maintenance_show_log.html.php index 9d850986..209aef03 100644 --- a/modules/gallery/views/admin_maintenance_show_log.html.php +++ b/modules/gallery/views/admin_maintenance_show_log.html.php @@ -12,7 +12,7 @@ appendTo('body').submit().remove(); <div id="gTaskLogDialog"> <h1> <?= $task->name ?> </h1> <div class="gTaskLog"> - <pre><?= p::purify($task->get_log()) ?></pre> + <pre><?= SafeString::purify($task->get_log()) ?></pre> </div> <button id="gCloseButton" class="ui-state-default ui-corner-all" onclick="dismiss()"><?= t("Close") ?></button> <button id="gSaveButton" class="ui-state-default ui-corner-all" onclick="download()"><?= t("Save") ?></button> diff --git a/modules/gallery/views/after_install.html.php b/modules/gallery/views/after_install.html.php index bfce46f0..b77a1707 100644 --- a/modules/gallery/views/after_install.html.php +++ b/modules/gallery/views/after_install.html.php @@ -8,7 +8,7 @@ </p> <p> - <?= t("You're logged in to the <b>%user_name</b> account. The very first thing you should do is to change your password to something that you'll remember.", array("user_name" => p::clean($user->name))) ?> + <?= t("You're logged in to the <b>%user_name</b> account. The very first thing you should do is to change your password to something that you'll remember.", array("user_name" => $user->name)) ?> </p> <p> diff --git a/modules/gallery/views/l10n_client.html.php b/modules/gallery/views/l10n_client.html.php index 5ee7eca3..520fd79e 100644 --- a/modules/gallery/views/l10n_client.html.php +++ b/modules/gallery/views/l10n_client.html.php @@ -72,8 +72,8 @@ </div> </div> <script type="text/javascript"> - var MSG_TRANSLATE_TEXT = "<?= t("Translate Text") ?>"; - var MSG_CLOSE_X = "<?= t("X") ?>"; + var MSG_TRANSLATE_TEXT = "<?= t("Translate Text")->for_js() ?>"; + var MSG_CLOSE_X = "<?= t("X")->for_js() ?>"; var l10n_client_data = <?= json_encode($string_list) ?>; var plural_forms = <?= json_encode($plural_forms) ?>; </script> diff --git a/modules/gallery/views/move_tree.html.php b/modules/gallery/views/move_tree.html.php index 5f70cf67..7818a42a 100644 --- a/modules/gallery/views/move_tree.html.php +++ b/modules/gallery/views/move_tree.html.php @@ -1,18 +1,18 @@ <?php defined("SYSPATH") or die("No direct script access.") ?> <?= $parent->thumb_img(array(), 25); ?> <? if (!access::can("edit", $parent) || $source->is_descendant($parent)): ?> -<a href="javascript:load_tree('<?= $parent->id ?>',1)"> <?= p::clean($parent->title) ?> <?= t("(locked)") ?> </a> +<a href="javascript:load_tree('<?= $parent->id ?>',1)"> <?= SafeString::of($parent->title) ?> <?= t("(locked)") ?> </a> <? else: ?> -<a href="javascript:load_tree('<?= $parent->id ?>',0)"> <?= p::clean($parent->title) ?></a> +<a href="javascript:load_tree('<?= $parent->id ?>',0)"> <?= SafeString::of($parent->title) ?></a> <? endif ?> <ul id="tree_<?= $parent->id ?>"> <? foreach ($children as $child): ?> <li id="node_<?= $child->id ?>" class="node"> <?= $child->thumb_img(array(), 25); ?> <? if (!access::can("edit", $child) || $source->is_descendant($child)): ?> - <a href="javascript:load_tree('<?= $child->id ?>',1)"> <?= p::clean($child->title) ?> <?= t("(locked)") ?></a> + <a href="javascript:load_tree('<?= $child->id ?>',1)"> <?= SafeString::of($child->title) ?> <?= t("(locked)") ?></a> <? else: ?> - <a href="javascript:load_tree('<?= $child->id ?>',0)"> <?= p::clean($child->title) ?> </a> + <a href="javascript:load_tree('<?= $child->id ?>',0)"> <?= SafeString::of($child->title) ?> </a> <? endif ?> </li> <? endforeach ?> diff --git a/modules/gallery/views/permissions_browse.html.php b/modules/gallery/views/permissions_browse.html.php index f990896c..90970112 100644 --- a/modules/gallery/views/permissions_browse.html.php +++ b/modules/gallery/views/permissions_browse.html.php @@ -33,21 +33,21 @@ </ul> <? endif ?> - <p>Edit permissions for album:</p> + <p><?= t("Edit permissions for album:") ?></p> <ul class="gBreadcrumbs"> <? foreach ($parents as $parent): ?> <li id="item-<?= $parent->id ?>"> <a href="javascript:show(<?= $parent->id ?>)"> - <?= p::purify($parent->title) ?> + <?= SafeString::purify($parent->title) ?> </a> </li> <? endforeach ?> <li class="active" id="item-<?= $item->id ?>"> <a href="javascript:show(<?= $item->id ?>)"> - <?= p::purify($item->title) ?></li> - </a> - </li> + <?= SafeString::purify($item->title) ?> + </a> + </li> </ul> <div id="gEditPermissionForm"> diff --git a/modules/gallery/views/permissions_form.html.php b/modules/gallery/views/permissions_form.html.php index ee5e3a24..adc0496f 100644 --- a/modules/gallery/views/permissions_form.html.php +++ b/modules/gallery/views/permissions_form.html.php @@ -6,7 +6,7 @@ <tr> <th> </th> <? foreach ($groups as $group): ?> - <th> <?= p::clean($group->name) ?> </th> + <th> <?= SafeString::of($group->name) ?> </th> <? endforeach ?> </tr> diff --git a/modules/gallery/views/simple_uploader.html.php b/modules/gallery/views/simple_uploader.html.php index 29a0dfe8..1f185780 100644 --- a/modules/gallery/views/simple_uploader.html.php +++ b/modules/gallery/views/simple_uploader.html.php @@ -6,7 +6,7 @@ <!-- hack to set the title for the dialog --> <form id="gAddPhotosForm" action="<?= url::site("simple_uploader/finish?csrf=$csrf") ?>"> <fieldset> - <legend> <?= t("Add photos to %album_title", array("album_title" => p::purify($item->title))) ?> </legend> + <legend> <?= t("Add photos to %album_title", array("album_title" => SafeString::purify($item->title))) ?> </legend> </fieldset> </form> @@ -26,9 +26,9 @@ </p> <ul class="gBreadcrumbs"> <? foreach ($item->parents() as $parent): ?> - <li> <?= p::clean($parent->title) ?> </li> + <li> <?= SafeString::of($parent->title) ?> </li> <? endforeach ?> - <li class="active"> <?= p::purify($item->title) ?> </li> + <li class="active"> <?= SafeString::purify($item->title) ?> </li> </ul> <p> @@ -82,27 +82,26 @@ <script type="text/javascript"> var swfu = new SWFUpload({ - flash_url: "<?= url::file("lib/swfupload/swfupload.swf") ?>", - upload_url: "<?= url::site("simple_uploader/add_photo/$item->id") ?>", - post_params: { - "g3sid": "<?= Session::instance()->id() ?>", - "user_agent": "<?= Input::instance()->server("HTTP_USER_AGENT") ?>", - "csrf": "<?= $csrf ?>" - }, - file_size_limit: "<?= ini_get("upload_max_filesize") ? num::convert_to_bytes(ini_get("upload_max_filesize"))."B" : "100MB" ?>", + flash_url: "<?= url::file("lib/swfupload/swfupload.swf")->for_js() ?>", + upload_url: "<?= url::site("simple_uploader/add_photo/$item->id")->for_js() ?>", + post_params: <?= json_encode(array( + "g3sid" => Session::instance()->id(), + "user_agent" => Input::instance()->server("HTTP_USER_AGENT"), + "csrf" => $csrf)) ?>, + file_size_limit: "<?= SafeString::of(ini_get("upload_max_filesize") ? num::convert_to_bytes(ini_get("upload_max_filesize"))."B" : "100MB")->for_js() ?>", file_types: "*.gif;*.jpg;*.jpeg;*.png;*.flv;*.mp4;*.GIF;*.JPG;*.JPEG;*.PNG;*.FLV;*.MP4", - file_types_description: "<?= t("Photos and Movies") ?>", + file_types_description: "<?= t("Photos and Movies")->for_js() ?>", file_upload_limit: 1000, file_queue_limit: 0, custom_settings: { }, debug: false, // Button settings - button_image_url: "<?= url::file("themes/default/images/select-photos-backg.png") ?>", + button_image_url: "<?= url::file("themes/default/images/select-photos-backg.png")->for_js() ?>", button_width: "202", button_height: "45", button_placeholder_id: "gChooseFilesButtonPlaceholder", - button_text: '<span class="swfUploadFont"><?= t("Select photos...") ?></span>', + button_text: <?= json_encode('<span class="swfUploadFont">' . t("Select photos...") . '</span>') ?>, button_text_style: ".swfUploadFont { color: #2E6E9E; font-size: 16px; font-family: Lucida Grande,Lucida Sans,Arial,sans-serif; font-weight: bold; }", button_text_left_padding: 30, button_text_top_padding: 10, @@ -146,13 +145,13 @@ function file_queued(file) { var fp = new File_Progress(file); fp.title.html(file.name); - fp.set_status("pending", "<?= t("Pending...") ?>"); + fp.set_status("pending", "<?= t("Pending...")->for_js() ?>"); // @todo add cancel button to call this.cancelUpload(file.id) } function file_queue_error(file, error_code, message) { if (error_code === SWFUpload.QUEUE_ERROR.QUEUE_LIMIT_EXCEEDED) { - alert("<?= t("You have attempted to queue too many files.") ?>"); + alert("<?= t("You have attempted to queue too many files.")->for_js() ?>"); return; } @@ -160,20 +159,20 @@ switch (error_code) { case SWFUpload.QUEUE_ERROR.FILE_EXCEEDS_SIZE_LIMIT: fp.title.html(file.name); - fp.set_status("error", "<?= t("<strong>File is too big.</strong> A likely error source is a too low value for <em>upload_max_filesize</em> (%upload_max_filesize) in your <em>php.ini</em>.", array("upload_max_filesize" => ini_get("upload_max_filesize"))) ?>"); + fp.set_status("error", "<?= t("<strong>File is too big.</strong> A likely error source is a too low value for <em>upload_max_filesize</em> (%upload_max_filesize) in your <em>php.ini</em>.", array("upload_max_filesize" => ini_get("upload_max_filesize")))->for_js() ?>"); break; case SWFUpload.QUEUE_ERROR.ZERO_BYTE_FILE: fp.title.html(file.name); - fp.set_status("error", "<?= t("Cannot upload empty files.") ?>"); + fp.set_status("error", "<?= t("Cannot upload empty files.")->for_js() ?>"); break; case SWFUpload.QUEUE_ERROR.INVALID_FILETYPE: fp.title.html(file.name); - fp.set_status("error", "<?= t("Invalid file type.") ?>"); + fp.set_status("error", "<?= t("Invalid file type.")->for_js() ?>"); break; default: if (file !== null) { fp.title.html(file.name); - fp.set_status("error", "<?= t("Unknown error") ?>"); + fp.set_status("error", "<?= t("Unknown error")->for_js() ?>"); } break; } @@ -194,7 +193,7 @@ // no uploadProgress events are called (limitation in the Linux Flash VM). var fp = new File_Progress(file); fp.title.html(file.name); - fp.set_status("uploading", "<?= t("Uploading...") ?>"); + fp.set_status("uploading", "<?= t("Uploading...")->for_js() ?>"); $("#gAddPhotosCanvas").scrollTo(fp.box, 1000); return true; // @todo add cancel button to call this.cancelUpload(file.id) @@ -203,7 +202,7 @@ function upload_progress(file, bytes_loaded, bytes_total) { var percent = Math.ceil((bytes_loaded / bytes_total) * 100); var fp = new File_Progress(file); - fp.set_status("uploading", "<?= t("Uploading...") ?>"); + fp.set_status("uploading", "<?= t("Uploading...")->for_js() ?>"); fp.progress_bar.css("visibility", "visible"); fp.progress_bar.progressbar("value", percent); } @@ -211,42 +210,42 @@ function upload_success(file, serverData) { var fp = new File_Progress(file); fp.progress_bar.progressbar("value", 100); - fp.set_status("complete", "<?= t("Complete.") ?>"); + fp.set_status("complete", "<?= t("Complete.")->for_js() ?>"); } function upload_error(file, error_code, message) { var fp = new File_Progress(file); switch (error_code) { case SWFUpload.UPLOAD_ERROR.HTTP_ERROR: - fp.set_status("error", "<?= t("Upload error: bad image file") ?>"); + fp.set_status("error", "<?= t("Upload error: bad image file")->for_js() ?>"); break; case SWFUpload.UPLOAD_ERROR.UPLOAD_FAILED: - fp.set_status("error", "<?= t("Upload failed") ?>"); + fp.set_status("error", "<?= t("Upload failed")->for_js() ?>"); break; case SWFUpload.UPLOAD_ERROR.IO_ERROR: - fp.set_status("error", "<?= t("Server error") ?>"); + fp.set_status("error", "<?= t("Server error")->for_js() ?>"); break; case SWFUpload.UPLOAD_ERROR.SECURITY_ERROR: - fp.set_status("error", "<?= t("Security error") ?>"); + fp.set_status("error", "<?= t("Security error")->for_js() ?>"); break; case SWFUpload.UPLOAD_ERROR.UPLOAD_LIMIT_EXCEEDED: - fp.set_status("error", "<?= t("Upload limit exceeded") ?>"); + fp.set_status("error", "<?= t("Upload limit exceeded")->for_js() ?>"); break; case SWFUpload.UPLOAD_ERROR.FILE_VALIDATION_FAILED: - fp.set_status("error", "<?= t("Failed validation. File skipped") ?>"); + fp.set_status("error", "<?= t("Failed validation. File skipped")->for_js() ?>"); break; case SWFUpload.UPLOAD_ERROR.FILE_CANCELLED: // If there aren't any files left (they were all cancelled) disable the cancel button if (this.getStats().files_queued === 0) { $("#gUploadCancel").hide(); } - fp.set_status("error", "<?= t("Cancelled") ?>"); + fp.set_status("error", "<?= t("Cancelled")->for_js() ?>"); break; case SWFUpload.UPLOAD_ERROR.UPLOAD_STOPPED: - fp.set_status("error", "<?= t("Stopped") ?>"); + fp.set_status("error", "<?= t("Stopped")->for_js() ?>"); break; default: - fp.set_status("error", "<?= t("Unknown error: ") ?>" + error_code); + fp.set_status("error", "<?= t("Unknown error: ")->for_js() ?>" + error_code); break; } } @@ -260,7 +259,7 @@ } function get_completed_status_msg(stats) { - var msg = "<?= t("Upload Queue (completed %completed of %total)", array("completed" => "__COMPLETED__", "total" => "__TOTAL__")) ?>"; + var msg = "<?= t("Upload Queue (completed %completed of %total)", array("completed" => "__COMPLETED__", "total" => "__TOTAL__"))->for_js() ?>"; msg = msg.replace("__COMPLETED__", stats.successful_uploads); msg = msg.replace("__TOTAL__", stats.files_queued + stats.successful_uploads + stats.upload_errors + stats.upload_cancelled + stats.queue_errors); @@ -269,7 +268,7 @@ // This event comes from the Queue Plugin function queue_complete(num_files_uploaded) { - var status_msg = "<?= t("Uploaded: __COUNT__") ?>"; + var status_msg = "<?= t("Uploaded: __COUNT__")->for_js() ?>"; $("#gUploadStatus").html(status_msg.replace("__COUNT__", num_files_uploaded)); } </script> |