Commit f09046af authored by catch's avatar catch

Issue #2549077 by pwolanin, Wim Leers, phenaproxima, lauriii: Allow the "Limit...

Issue #2549077 by pwolanin, Wim Leers, phenaproxima, lauriii: Allow the "Limit allowed HTML tags" filter to also restrict HTML attributes, and only allow a small whitelist of attributes by default
parent eac02c74
...@@ -262,9 +262,9 @@ public static function load($html) { ...@@ -262,9 +262,9 @@ public static function load($html) {
<body>!html</body> <body>!html</body>
</html> </html>
EOD; EOD;
// PHP's \DOMDocument serialization adds straw whitespace in case the markup // PHP's \DOMDocument serialization adds extra whitespace when the markup
// of the wrapping document contains newlines, so ensure to remove all // of the wrapping document contains newlines, so ensure we remove all
// newlines before injecting the actual HTML body to process. // newlines before injecting the actual HTML body to be processed.
$document = strtr($document, array("\n" => '', '!html' => $html)); $document = strtr($document, array("\n" => '', '!html' => $html));
$dom = new \DOMDocument(); $dom = new \DOMDocument();
......
...@@ -221,6 +221,11 @@ ...@@ -221,6 +221,11 @@
// the feature that was just added or removed. Not every feature has // the feature that was just added or removed. Not every feature has
// such metadata. // such metadata.
var featureName = this.model.get('buttonsToFeatures')[button.toLowerCase()]; var featureName = this.model.get('buttonsToFeatures')[button.toLowerCase()];
// Features without an associated command do not have a 'feature name' by
// default, so we use the lowercased button name instead.
if (!featureName) {
featureName = button.toLowerCase();
}
var featuresMetadata = this.model.get('featuresMetadata'); var featuresMetadata = this.model.get('featuresMetadata');
if (!featuresMetadata[featureName]) { if (!featuresMetadata[featureName]) {
featuresMetadata[featureName] = new Drupal.EditorFeature(featureName); featuresMetadata[featureName] = new Drupal.EditorFeature(featureName);
...@@ -301,7 +306,6 @@ ...@@ -301,7 +306,6 @@
broadcastConfigurationChanges: function ($ckeditorToolbar) { broadcastConfigurationChanges: function ($ckeditorToolbar) {
var view = this; var view = this;
var hiddenEditorConfig = this.model.get('hiddenEditorConfig'); var hiddenEditorConfig = this.model.get('hiddenEditorConfig');
var featuresMetadata = this.model.get('featuresMetadata');
var getFeatureForButton = this.getFeatureForButton.bind(this); var getFeatureForButton = this.getFeatureForButton.bind(this);
var getCKEditorFeatures = this.getCKEditorFeatures.bind(this); var getCKEditorFeatures = this.getCKEditorFeatures.bind(this);
$ckeditorToolbar $ckeditorToolbar
...@@ -335,6 +339,7 @@ ...@@ -335,6 +339,7 @@
getCKEditorFeatures(hiddenEditorConfig, function (features) { getCKEditorFeatures(hiddenEditorConfig, function (features) {
// Trigger a standardized text editor configuration event for each // Trigger a standardized text editor configuration event for each
// feature that was modified by the configuration changes. // feature that was modified by the configuration changes.
var featuresMetadata = view.model.get('featuresMetadata');
for (var name in features) { for (var name in features) {
if (features.hasOwnProperty(name)) { if (features.hasOwnProperty(name)) {
var feature = features[name]; var feature = features[name];
......
...@@ -516,6 +516,13 @@ protected function generateACFSettings(Editor $editor) { ...@@ -516,6 +516,13 @@ protected function generateACFSettings(Editor $editor) {
} }
// Tell CKEditor the tag is allowed, along with some tags. // Tell CKEditor the tag is allowed, along with some tags.
elseif (is_array($attributes)) { elseif (is_array($attributes)) {
// Set defaults (these will be overridden below if more specific
// values are present).
$allowed[$tag] = array(
'attributes' => FALSE,
'styles' => FALSE,
'classes' => FALSE,
);
// Configure allowed attributes, allowed "style" attribute values and // Configure allowed attributes, allowed "style" attribute values and
// allowed "class" attribute values. // allowed "class" attribute values.
// CKEditor only allows specific values for the "class" and "style" // CKEditor only allows specific values for the "class" and "style"
...@@ -580,6 +587,9 @@ protected function generateACFSettings(Editor $editor) { ...@@ -580,6 +587,9 @@ protected function generateACFSettings(Editor $editor) {
} }
} }
ksort($allowed);
ksort($disallowed);
return array($allowed, $disallowed); return array($allowed, $disallowed);
} }
} }
......
...@@ -54,7 +54,7 @@ protected function setUp() { ...@@ -54,7 +54,7 @@ protected function setUp() {
'filter_html' => array( 'filter_html' => array(
'status' => 1, 'status' => 1,
'settings' => array( 'settings' => array(
'allowed_html' => '<h2> <h3> <h4> <h5> <h6> <p> <br> <strong> <a>', 'allowed_html' => '<h2 id> <h3> <h4> <h5> <h6> <p> <br> <strong> <a href hreflang>',
) )
), ),
), ),
...@@ -96,6 +96,7 @@ function testGetJSSettings() { ...@@ -96,6 +96,7 @@ function testGetJSSettings() {
); );
$expected_config = $this->castSafeStrings($expected_config); $expected_config = $this->castSafeStrings($expected_config);
ksort($expected_config); ksort($expected_config);
ksort($expected_config['allowedContent']);
$this->assertIdentical($expected_config, $this->castSafeStrings($this->ckeditor->getJSSettings($editor)), 'Generated JS settings are correct for default configuration.'); $this->assertIdentical($expected_config, $this->castSafeStrings($this->ckeditor->getJSSettings($editor)), 'Generated JS settings are correct for default configuration.');
// Customize the configuration: add button, have two contextually enabled // Customize the configuration: add button, have two contextually enabled
...@@ -122,12 +123,13 @@ function testGetJSSettings() { ...@@ -122,12 +123,13 @@ function testGetJSSettings() {
// Change the allowed HTML tags; the "allowedContent" and "format_tags" // Change the allowed HTML tags; the "allowedContent" and "format_tags"
// settings for CKEditor should automatically be updated as well. // settings for CKEditor should automatically be updated as well.
$format = $editor->getFilterFormat(); $format = $editor->getFilterFormat();
$format->filters('filter_html')->settings['allowed_html'] .= '<pre> <h3>'; $format->filters('filter_html')->settings['allowed_html'] .= '<pre> <h1>';
$format->save(); $format->save();
$expected_config['allowedContent']['pre'] = array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE); $expected_config['allowedContent']['pre'] = array('attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE);
$expected_config['allowedContent']['h3'] = array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE); $expected_config['allowedContent']['h1'] = array('attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE);
$expected_config['format_tags'] = 'p;h2;h3;h4;h5;h6;pre'; $expected_config['format_tags'] = 'p;h1;h2;h3;h4;h5;h6;pre';
ksort($expected_config['allowedContent']);
$this->assertIdentical($expected_config, $this->castSafeStrings($this->ckeditor->getJSSettings($editor)), 'Generated JS settings are correct for customized configuration.'); $this->assertIdentical($expected_config, $this->castSafeStrings($this->ckeditor->getJSSettings($editor)), 'Generated JS settings are correct for customized configuration.');
// Disable the filter_html filter: allow *all *tags. // Disable the filter_html filter: allow *all *tags.
...@@ -179,14 +181,17 @@ function testGetJSSettings() { ...@@ -179,14 +181,17 @@ function testGetJSSettings() {
), ),
'a' => array( 'a' => array(
'attributes' => 'href,rel,class,target', 'attributes' => 'href,rel,class,target',
'styles' => FALSE,
'classes' => 'external', 'classes' => 'external',
), ),
'span' => array( 'span' => array(
'attributes' => 'class,property,rel,style', 'attributes' => 'class,property,rel,style',
'styles' => 'font-size', 'styles' => 'font-size',
'classes' => FALSE,
), ),
'*' => array( '*' => array(
'attributes' => 'class,data-*', 'attributes' => 'class,data-*',
'styles' => FALSE,
'classes' => 'is-a-hipster-llama,and-more', 'classes' => 'is-a-hipster-llama,and-more',
), ),
'del' => array( 'del' => array(
...@@ -206,6 +211,8 @@ function testGetJSSettings() { ...@@ -206,6 +211,8 @@ function testGetJSSettings() {
); );
$expected_config['format_tags'] = 'p'; $expected_config['format_tags'] = 'p';
ksort($expected_config); ksort($expected_config);
ksort($expected_config['allowedContent']);
ksort($expected_config['disallowedContent']);
$this->assertIdentical($expected_config, $this->castSafeStrings($this->ckeditor->getJSSettings($editor)), 'Generated JS settings are correct for customized configuration.'); $this->assertIdentical($expected_config, $this->castSafeStrings($this->ckeditor->getJSSettings($editor)), 'Generated JS settings are correct for customized configuration.');
} }
...@@ -421,17 +428,18 @@ protected function getDefaultInternalConfig() { ...@@ -421,17 +428,18 @@ protected function getDefaultInternalConfig() {
} }
protected function getDefaultAllowedContentConfig() { protected function getDefaultAllowedContentConfig() {
return array( return [
'h2' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE), 'h2' => ['attributes' => 'id', 'styles' => FALSE, 'classes' => FALSE],
'h3' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE), 'h3' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'h4' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE), 'h4' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'h5' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE), 'h5' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'h6' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE), 'h6' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'p' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE), 'p' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'br' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE), 'br' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'strong' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE), 'strong' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'a' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE), 'a' => ['attributes' => 'href,hreflang', 'styles' => FALSE, 'classes' => FALSE],
); '*' => ['attributes' => 'lang,dir', 'styles' => FALSE, 'classes' => FALSE],
];
} }
protected function getDefaultDisallowedContentConfig() { protected function getDefaultDisallowedContentConfig() {
......
...@@ -821,17 +821,32 @@ ...@@ -821,17 +821,32 @@
* @see Drupal.FilterStatus * @see Drupal.FilterStatus
*/ */
Drupal.FilterHTMLRule = function () { Drupal.FilterHTMLRule = function () {
return { // Allow or forbid tags.
// Allow or forbid tags. this.tags = [];
this.allow = null;
// Apply restrictions to properties set on tags.
this.restrictedTags = {
tags: [], tags: [],
allow: null, allowed: {attributes: [], styles: [], classes: []},
// Apply restrictions to properties set on tags. forbidden: {attributes: [], styles: [], classes: []}
restrictedTags: {
tags: [],
allowed: {attributes: [], styles: [], classes: []},
forbidden: {attributes: [], styles: [], classes: []}
}
}; };
return this;
};
Drupal.FilterHTMLRule.prototype.clone = function () {
var clone = new Drupal.FilterHTMLRule();
clone.tags = this.tags.slice(0);
clone.allow = this.allow;
clone.restrictedTags.tags = this.restrictedTags.tags.slice(0);
clone.restrictedTags.allowed.attributes = this.restrictedTags.allowed.attributes.slice(0);
clone.restrictedTags.allowed.styles = this.restrictedTags.allowed.styles.slice(0);
clone.restrictedTags.allowed.classes = this.restrictedTags.allowed.classes.slice(0);
clone.restrictedTags.forbidden.attributes = this.restrictedTags.forbidden.attributes.slice(0);
clone.restrictedTags.forbidden.styles = this.restrictedTags.forbidden.styles.slice(0);
clone.restrictedTags.forbidden.classes = this.restrictedTags.forbidden.classes.slice(0);
return clone;
}; };
/** /**
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Template\Attribute; use Drupal\Core\Template\Attribute;
use Drupal\filter\Entity\FilterFormat;
use Drupal\filter\FilterFormatInterface; use Drupal\filter\FilterFormatInterface;
/** /**
...@@ -450,25 +449,6 @@ function template_preprocess_filter_tips(&$variables) { ...@@ -450,25 +449,6 @@ function template_preprocess_filter_tips(&$variables) {
* Filters implemented by the Filter module. * Filters implemented by the Filter module.
*/ */
/**
* Provides filtering of input into accepted HTML.
*/
function _filter_html($text, $filter) {
$allowed_tags = preg_split('/\s+|<|>/', $filter->settings['allowed_html'], -1, PREG_SPLIT_NO_EMPTY);
$text = Xss::filter($text, $allowed_tags);
if ($filter->settings['filter_html_nofollow']) {
$html_dom = Html::load($text);
$links = $html_dom->getElementsByTagName('a');
foreach ($links as $link) {
$link->setAttribute('rel', 'nofollow');
}
$text = Html::serialize($html_dom);
}
return trim($text);
}
/** /**
* Converts text into hyperlinks automatically. * Converts text into hyperlinks automatically.
* *
......
...@@ -34,7 +34,9 @@ process: ...@@ -34,7 +34,9 @@ process:
- filter_url - filter_url
- filter_htmlcorrector - filter_htmlcorrector
- filter_html_escape - filter_html_escape
settings: settings settings:
plugin: filter_settings
source: settings
status: status:
plugin: default_value plugin: default_value
default_value: true default_value: true
......
...@@ -19,7 +19,9 @@ process: ...@@ -19,7 +19,9 @@ process:
source: name source: name
map: map:
php_code: filter_null php_code: filter_null
settings: settings settings:
plugin: filter_settings
source: settings
status: status:
plugin: default_value plugin: default_value
default_value: true default_value: true
......
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\FilterSettings.
*/
namespace Drupal\filter\Plugin\migrate\process;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* Adds the default allowed attributes to filter_html's allowed_html setting.
*
* E.g. map '<a>' to '<a href hreflang dir>'.
*
* @MigrateProcessPlugin(
* id = "filter_settings",
* handle_multiples = TRUE
* )
*/
class FilterSettings extends ProcessPluginBase {
/**
* Default attributes for migrating filter_html's 'allowed_html' setting.
*
* @var string[]
*/
protected $allowedHtmlDefaultAttributes = [
'<a>' => '<a href hreflang>',
'<blockquote>' => '<blockquote cite>',
'<ol>' => '<ol start type>',
'<ul>' => '<ul type>',
'<img>' => '<img src alt height width>',
'<h2>' => '<h2 id>',
'<h3>' => '<h3 id>',
'<h4>' => '<h4 id>',
'<h5>' => '<h5 id>',
'<h6>' => '<h6 id>',
];
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
// Only the filter_html filter's settings have a changed format.
if ($row->getDestinationProperty('id') === 'filter_html') {
$value['allowed_html'] = str_replace(array_keys($this->allowedHtmlDefaultAttributes), array_values($this->allowedHtmlDefaultAttributes), $value['allowed_html']);
}
return $value;
}
}
...@@ -108,7 +108,15 @@ function testFilterFormatAPI() { ...@@ -108,7 +108,15 @@ function testFilterFormatAPI() {
$filtered_html_format = entity_load('filter_format', 'filtered_html'); $filtered_html_format = entity_load('filter_format', 'filtered_html');
$this->assertIdentical( $this->assertIdentical(
$filtered_html_format->getHtmlRestrictions(), $filtered_html_format->getHtmlRestrictions(),
array('allowed' => array('p' => TRUE, 'br' => TRUE, 'strong' => TRUE, 'a' => TRUE, '*' => array('style' => FALSE, 'on*' => FALSE))), array(
'allowed' => array(
'p' => FALSE,
'br' => FALSE,
'strong' => FALSE,
'a' => array('href' => TRUE, 'hreflang' => TRUE),
'*' => array('style' => FALSE, 'on*' => FALSE, 'lang' => TRUE, 'dir' => array('ltr' => TRUE, 'rtl' => TRUE)),
),
),
'FilterFormatInterface::getHtmlRestrictions() works as expected for the filtered_html format.' 'FilterFormatInterface::getHtmlRestrictions() works as expected for the filtered_html format.'
); );
$this->assertIdentical( $this->assertIdentical(
...@@ -164,7 +172,7 @@ function testFilterFormatAPI() { ...@@ -164,7 +172,7 @@ function testFilterFormatAPI() {
'filter_html' => array( 'filter_html' => array(
'status' => 1, 'status' => 1,
'settings' => array( 'settings' => array(
'allowed_html' => '<p> <br> <a> <strong>', 'allowed_html' => '<p> <br> <a href> <strong>',
), ),
), ),
'filter_test_restrict_tags_and_attributes' => array( 'filter_test_restrict_tags_and_attributes' => array(
...@@ -185,7 +193,14 @@ function testFilterFormatAPI() { ...@@ -185,7 +193,14 @@ function testFilterFormatAPI() {
$very_restricted_html_format->save(); $very_restricted_html_format->save();
$this->assertIdentical( $this->assertIdentical(
$very_restricted_html_format->getHtmlRestrictions(), $very_restricted_html_format->getHtmlRestrictions(),
array('allowed' => array('p' => TRUE, 'br' => FALSE, 'a' => array('href' => TRUE), '*' => array('style' => FALSE, 'on*' => FALSE))), array(
'allowed' => array(
'p' => FALSE,
'br' => FALSE,
'a' => array('href' => TRUE),
'*' => array('style' => FALSE, 'on*' => FALSE, 'lang' => TRUE, 'dir' => array('ltr' => TRUE, 'rtl' => TRUE)),
),
),
'FilterFormatInterface::getHtmlRestrictions() works as expected for the very_restricted_html format.' 'FilterFormatInterface::getHtmlRestrictions() works as expected for the very_restricted_html format.'
); );
$this->assertIdentical( $this->assertIdentical(
......
...@@ -45,7 +45,7 @@ protected function setUp() { ...@@ -45,7 +45,7 @@ protected function setUp() {
'filter_html' => array( 'filter_html' => array(
'status' => 1, 'status' => 1,
'settings' => array( 'settings' => array(
'allowed_html' => '<img> <a>', 'allowed_html' => '<img src testattribute> <a>',
), ),
), ),
'filter_autop' => array( 'filter_autop' => array(
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Language\Language;
use Drupal\Core\Render\RenderContext; use Drupal\Core\Render\RenderContext;
use Drupal\editor\EditorXssFilter\Standard; use Drupal\editor\EditorXssFilter\Standard;
use Drupal\filter\Entity\FilterFormat; use Drupal\filter\Entity\FilterFormat;
...@@ -190,7 +191,7 @@ function testCaptionFilter() { ...@@ -190,7 +191,7 @@ function testCaptionFilter() {
$html_filter = $this->filters['filter_html']; $html_filter = $this->filters['filter_html'];
$html_filter->setConfiguration(array( $html_filter->setConfiguration(array(
'settings' => array( 'settings' => array(
'allowed_html' => '<img>', 'allowed_html' => '<img src data-align data-caption>',
'filter_html_help' => 1, 'filter_html_help' => 1,
'filter_html_nofollow' => 0, 'filter_html_nofollow' => 0,
) )
...@@ -399,7 +400,7 @@ function testHtmlFilter() { ...@@ -399,7 +400,7 @@ function testHtmlFilter() {
$filter = $this->filters['filter_html']; $filter = $this->filters['filter_html'];
$filter->setConfiguration(array( $filter->setConfiguration(array(
'settings' => array( 'settings' => array(
'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <br>', 'allowed_html' => '<a> <p> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <br>',
'filter_html_help' => 1, 'filter_html_help' => 1,
'filter_html_nofollow' => 0, 'filter_html_nofollow' => 0,
) )
...@@ -407,41 +408,78 @@ function testHtmlFilter() { ...@@ -407,41 +408,78 @@ function testHtmlFilter() {
// HTML filter is not able to secure some tags, these should never be // HTML filter is not able to secure some tags, these should never be
// allowed. // allowed.
$f = _filter_html('<script />', $filter); $f = (string) $filter->process('<script />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'script', 'HTML filter should always remove script tags.'); $this->assertIdentical($f, '', 'HTML filter should remove script tags.');
$f = _filter_html('<iframe />', $filter); $f = (string) $filter->process('<iframe />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'iframe', 'HTML filter should always remove iframe tags.'); $this->assertIdentical($f, '', 'HTML filter should remove iframe tags.');
$f = _filter_html('<object />', $filter); $f = (string) $filter->process('<object />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'object', 'HTML filter should always remove object tags.'); $this->assertIdentical($f, '', 'HTML filter should remove object tags.');
$f = _filter_html('<style />', $filter); $f = (string) $filter->process('<style />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'style', 'HTML filter should always remove style tags.'); $this->assertIdentical($f, '', 'HTML filter should remove style tags.');
// Some tags make CSRF attacks easier, let the user take the risk herself. // Some tags make CSRF attacks easier, let the user take the risk herself.
$f = _filter_html('<img />', $filter); $f = (string) $filter->process('<img />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'img', 'HTML filter should remove img tags on default.'); $this->assertIdentical($f, '', 'HTML filter should remove img tags by default.');
$f = _filter_html('<input />', $filter); $f = (string) $filter->process('<input />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'img', 'HTML filter should remove input tags on default.'); $this->assertIdentical($f, '', 'HTML filter should remove input tags by default.');
// Filtering content of some attributes is infeasible, these shouldn't be // Filtering content of some attributes is infeasible, these shouldn't be
// allowed too. // allowed too.
$f = _filter_html('<p style="display: none;" />', $filter); $f = (string) $filter->process('<p style="display: none;" />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'style', 'HTML filter should remove style attribute on default.'); $this->assertNoNormalized($f, 'style', 'HTML filter should remove style attributes.');
$this->assertIdentical($f, '<p></p>');
$f = _filter_html('<p onerror="alert(0);" />', $filter); $f = (string) $filter->process('<p onerror="alert(0);"></p>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'onerror', 'HTML filter should remove on* attributes on default.'); $this->assertNoNormalized($f, 'onerror', 'HTML filter should remove on* attributes.');
$this->assertIdentical($f, '<p></p>');
$f = _filter_html('<code onerror>&nbsp;</code>', $filter); $f = (string) $filter->process('<code onerror>&nbsp;</code>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'onerror', 'HTML filter should remove empty on* attributes on default.'); $this->assertNoNormalized($f, 'onerror', 'HTML filter should remove empty on* attributes.');
// Note - this string has a decoded &nbsp; character.
$this->assertIdentical($f, '<code> </code>');
$f = _filter_html('<br>', $filter); $f = (string) $filter->process('<br>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, '<br>', 'HTML filter should allow line breaks.'); $this->assertNormalized($f, '<br />', 'HTML filter should allow line breaks.');
$f = _filter_html('<br />', $filter); $f = (string) $filter->process('<br />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, '<br />', 'HTML filter should allow self-closing line breaks.'); $this->assertNormalized($f, '<br />', 'HTML filter should allow self-closing line breaks.');
// All attributes of whitelisted tags are stripped by default.
$f = (string) $filter->process('<a kitten="cute" llama="awesome">link</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, '<a>link</a>', 'HTML filter should remove attributes that are not explicitly allowed.');
// Now whitelist the "llama" attribute on <a>.
$filter->setConfiguration(array(
'settings' => array(
'allowed_html' => '<a href llama> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <br>',
'filter_html_help' => 1,
'filter_html_nofollow' => 0,
)
));
$f = (string) $filter->process('<a kitten="cute" llama="awesome">link</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, '<a llama="awesome">link</a>', 'HTML filter keeps explicitly allowed attributes, and removes attributes that are not explicitly allowed.');
// Restrict the whitelisted "llama" attribute on <a> to only allow the value
// "majestical", or "epic".
$filter->setConfiguration(array(
'settings' => array(
'allowed_html' => '<a href llama="majestical epic"> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <br>',
'filter_html_help' => 1,
'filter_html_nofollow' => 0,
)
));
$f = (string) $filter->process('<a kitten="cute" llama="awesome">link</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertIdentical($f, '<a>link</a>', 'HTML filter removes allowed attributes that do not have an explicitly allowed value.');
$f = (string) $filter->process('<a kitten="cute" llama="majestical">link</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertIdentical($f, '<a llama="majestical">link</a>', 'HTML filter keeps explicitly allowed attributes with an attribute value that is also explicitly allowed.');
$f = (string) $filter->process('<a kitten="cute" llama="awesome">link</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, '<a>link</a>', 'HTML filter removes allowed attributes that have a not explicitly allowed value.');
$f = (string) $filter->process('<a href="/beautiful-animals" kitten="cute" llama="epic majestical">link</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertIdentical($f, '<a href="/beautiful-animals" llama="epic majestical">link</a>', 'HTML filter keeps explicitly allowed attributes with an attribute value that is also explicitly allowed.');
} }
/** /**
...@@ -452,7 +490,7 @@ function testNoFollowFilter() { ...@@ -452,7 +490,7 @@ function testNoFollowFilter() {
$filter = $this->filters['filter_html']; $filter = $this->filters['filter_html'];
$filter->setConfiguration(array( $filter->setConfiguration(array(
'settings' => array( 'settings' => array(
'allowed_html' => '<a>', 'allowed_html' => '<a href>',
'filter_html_help' => 1, 'filter_html_help' => 1,
'filter_html_nofollow' => 1, 'filter_html_nofollow' => 1,
) )
...@@ -460,19 +498,19 @@ function testNoFollowFilter() { ...@@ -460,19 +498,19 @@ function testNoFollowFilter() {
// Test if the rel="nofollow" attribute is added, even if we try to prevent // Test if the rel="nofollow" attribute is added, even if we try to prevent
// it. // it.
$f = _filter_html('<a href="http://www.example.com/">text</a>', $filter); $f = (string) $filter->process('<a href="http://www.example.com/">text</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent -- no evasion.'); $this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent -- no evasion.');
$f = _filter_html('<A href="http://www.example.com/">text</a>', $filter); $f = (string) $filter->process('<A href="http://www.example.com/">text</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- capital A.'); $this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- capital A.');
$f = _filter_html("<a/href=\"http://www.example.com/\">text</a>", $filter); $f = (string) $filter->process("<a/href=\"http://www.example.com/\">text</a>", Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- non whitespace character after tag name.'); $this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- non whitespace character after tag name.');
$f = _filter_html("<\0a\0 href=\"http://www.example.com/\">text</a>", $filter); $f = (string) $filter->process("<\0a\0 href=\"http://www.example.com/\">text</a>", Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- some nulls.'); $this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- some nulls.');
$f = _filter_html('<a href="http://www.example.com/" rel="follow">text</a>', $filter); $f = (string) $filter->process('<a href="http://www.example.com/" rel="follow">text</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'rel="follow"', 'Spam deterrent evasion -- with rel set - rel="follow" removed.'); $this->assertNoNormalized($f, 'rel="follow"', 'Spam deterrent evasion -- with rel set - rel="follow" removed.');
$this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- with rel set - rel="nofollow" added.'); $this->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- with rel set - rel="nofollow" added.');
} }
......
...@@ -44,7 +44,7 @@ public function testFilterFormat() { ...@@ -44,7 +44,7 @@ public function testFilterFormat() {
$this->assertFalse(isset($filters['filter_html_image_secure'])); $this->assertFalse(isset($filters['filter_html_image_secure']));
// Check variables migrated into filter. // Check variables migrated into filter.
$this->assertIdentical('<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>', $filters['filter_html']['settings']['allowed_html']); $this->assertIdentical('<a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd>', $filters['filter_html']['settings']['allowed_html']);
$this->assertIdentical(TRUE, $filters['filter_html']['settings']['filter_html_help']);