diff --git a/common/framework/filters/htmlfilter.php b/common/framework/filters/htmlfilter.php
index 06a1f854e..e11da5b21 100644
--- a/common/framework/filters/htmlfilter.php
+++ b/common/framework/filters/htmlfilter.php
@@ -2,6 +2,7 @@
namespace Rhymix\Framework\Filters;
+use Rhymix\Framework\Config;
use Rhymix\Framework\Security;
use Rhymix\Framework\Storage;
@@ -11,9 +12,9 @@ use Rhymix\Framework\Storage;
class HTMLFilter
{
/**
- * HTMLPurifier instance is cached here.
+ * HTMLPurifier instances are cached here.
*/
- protected static $_htmlpurifier;
+ protected static $_instances = array();
/**
* Pre-processing and post-processing filters are stored here.
@@ -69,18 +70,26 @@ class HTMLFilter
* Filter HTML content to block XSS attacks.
*
* @param string $input
+ * @param bool $allow_editor_components (optional)
+ * @param bool $allow_widgets (optional)
* @return string
*/
- public static function clean($input)
+ public static function clean($input, $allow_editor_components = true, $allow_widgets = false)
{
foreach (self::$_preproc as $callback)
{
$input = $callback($input);
}
- $input = self::_preprocess($input);
- $output = self::getHTMLPurifier()->purify($input);
- $output = self::_postprocess($output);
+ $allowed_classes = Config::get('mediafilter.classes') ?: array();
+ if ($allow_widgets)
+ {
+ $allowed_classes[] = 'zbxe_widget_output';
+ }
+
+ $input = self::_preprocess($input, $allow_editor_components, $allow_widgets);
+ $output = self::getHTMLPurifier($allowed_classes)->purify($input);
+ $output = self::_postprocess($output, $allow_editor_components, $allow_widgets);
foreach (self::$_postproc as $callback)
{
@@ -93,18 +102,24 @@ class HTMLFilter
/**
* Get an instance of HTMLPurifier.
*
+ * @param array $allowed_classes (optional)
* @return object
*/
- public static function getHTMLPurifier()
+ public static function getHTMLPurifier($allowed_classes = array())
{
+ // Keep separate instances for different sets of allowed classes.
+ $allowed_classes = array_unique($allowed_classes);
+ sort($allowed_classes);
+ $key = sha1(serialize($allowed_classes));
+
// Create an instance with reasonable defaults.
- if (self::$_htmlpurifier === null)
+ if (!isset(self::$_instances[$key]))
{
// Get the default configuration.
$config = \HTMLPurifier_Config::createDefault();
// Customize the default configuration.
- $config->set('Attr.AllowedClasses', array());
+ $config->set('Attr.AllowedClasses', $allowed_classes);
$config->set('Attr.AllowedFrameTargets', array('_blank'));
$config->set('Attr.DefaultImageAlt', '');
$config->set('Attr.EnableID', true);
@@ -144,11 +159,11 @@ class HTMLFilter
self::_supportCSS3($config);
// Cache our instance of HTMLPurifier.
- self::$_htmlpurifier = new \HTMLPurifier($config);
+ self::$_instances[$key] = new \HTMLPurifier($config);
}
// Return the cached instance.
- return self::$_htmlpurifier;
+ return self::$_instances[$key];
}
/**
@@ -379,12 +394,17 @@ class HTMLFilter
* Rhymix-specific preprocessing method.
*
* @param string $content
+ * @param bool $allow_editor_components (optional)
+ * @param bool $allow_widgets (optional)
* @return string
*/
- protected static function _preprocess($content)
+ protected static function _preprocess($content, $allow_editor_components = true, $allow_widgets = false)
{
// Encode widget and editor component properties so that they are not removed by HTMLPurifier.
- $content = self::_encodeWidgetsAndEditorComponents($content);
+ if ($allow_editor_components || $allow_widgets)
+ {
+ $content = self::_encodeWidgetsAndEditorComponents($content, $allow_editor_components, $allow_widgets);
+ }
return $content;
}
@@ -392,9 +412,11 @@ class HTMLFilter
* Rhymix-specific postprocessing method.
*
* @param string $content
+ * @param bool $allow_editor_components (optional)
+ * @param bool $allow_widgets (optional)
* @return string
*/
- protected static function _postprocess($content)
+ protected static function _postprocess($content, $allow_editor_components = true, $allow_widgets = false)
{
// Define acts to allow and deny.
$allow_acts = array('procFileDownload');
@@ -436,7 +458,7 @@ class HTMLFilter
}, $content);
// Restore widget and editor component properties.
- $content = self::_decodeWidgetsAndEditorComponents($content);
+ $content = self::_decodeWidgetsAndEditorComponents($content, $allow_editor_components, $allow_widgets);
return $content;
}
@@ -444,11 +466,27 @@ class HTMLFilter
* Encode widgets and editor components before processing.
*
* @param string $content
+ * @param bool $allow_editor_components (optional)
+ * @param bool $allow_widgets (optional)
* @return string
*/
- protected static function _encodeWidgetsAndEditorComponents($content)
+ protected static function _encodeWidgetsAndEditorComponents($content, $allow_editor_components = true, $allow_widgets = false)
{
- return preg_replace_callback('!<(div|img)([^>]*)(editor_component="[^"]+"|class="zbxe_widget_output")([^>]*)>!i', function($match) {
+ $regexp = array();
+ if ($allow_editor_components)
+ {
+ $regexp[] = 'editor_component="[^"]+"';
+ }
+ if ($allow_widgets)
+ {
+ $regexp[] = 'class="zbxe_widget_output"';
+ }
+ if (!count($regexp))
+ {
+ return $content;
+ }
+
+ return preg_replace_callback('!<(div|img)([^>]*)(' . implode('|', $regexp) . ')([^>]*)>!i', function($match) {
$tag = strtolower($match[1]);
$attrs = array();
$html = preg_replace_callback('!([a-zA-Z0-9_-]+)="([^"]+)"!', function($attr) use($tag, &$attrs) {
@@ -477,10 +515,25 @@ class HTMLFilter
* Decode widgets and editor components after processing.
*
* @param string $content
+ * @param bool $allow_editor_components (optional)
+ * @param bool $allow_widgets (optional)
* @return string
*/
- protected static function _decodeWidgetsAndEditorComponents($content)
+ protected static function _decodeWidgetsAndEditorComponents($content, $allow_editor_components = true, $allow_widgets = false)
{
+ if (!$allow_editor_components)
+ {
+ $content = preg_replace('!(<(?:div|img)[^>]*)\s(editor_component="(?:[^"]+)")!i', '$1', $content);
+ }
+ if (!$allow_widgets)
+ {
+ $content = preg_replace('!(<(?:div|img)[^>]*)\s(widget="(?:[^"]+)")!i', '$1blocked-$2', $content);
+ }
+ if (!$allow_editor_components && !$allow_widgets)
+ {
+ return $content;
+ }
+
return preg_replace_callback('!<(div|img)([^>]*)(\srx_encoded_properties="([^"]+)")!i', function($match) {
$attrs = array();
$decoded_properties = Security::decrypt($match[4]);