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) {
<body>!html</body>
</html>
EOD;
// PHP's \DOMDocument serialization adds straw whitespace in case the markup
// of the wrapping document contains newlines, so ensure to remove all
// newlines before injecting the actual HTML body to process.
// PHP's \DOMDocument serialization adds extra whitespace when the markup
// of the wrapping document contains newlines, so ensure we remove all
// newlines before injecting the actual HTML body to be processed.
$document = strtr($document, array("\n" => '', '!html' => $html));
$dom = new \DOMDocument();
......
......@@ -221,6 +221,11 @@
// the feature that was just added or removed. Not every feature has
// such metadata.
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');
if (!featuresMetadata[featureName]) {
featuresMetadata[featureName] = new Drupal.EditorFeature(featureName);
......@@ -301,7 +306,6 @@
broadcastConfigurationChanges: function ($ckeditorToolbar) {
var view = this;
var hiddenEditorConfig = this.model.get('hiddenEditorConfig');
var featuresMetadata = this.model.get('featuresMetadata');
var getFeatureForButton = this.getFeatureForButton.bind(this);
var getCKEditorFeatures = this.getCKEditorFeatures.bind(this);
$ckeditorToolbar
......@@ -335,6 +339,7 @@
getCKEditorFeatures(hiddenEditorConfig, function (features) {
// Trigger a standardized text editor configuration event for each
// feature that was modified by the configuration changes.
var featuresMetadata = view.model.get('featuresMetadata');
for (var name in features) {
if (features.hasOwnProperty(name)) {
var feature = features[name];
......
......@@ -516,6 +516,13 @@ protected function generateACFSettings(Editor $editor) {
}
// Tell CKEditor the tag is allowed, along with some tags.
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
// allowed "class" attribute values.
// CKEditor only allows specific values for the "class" and "style"
......@@ -580,6 +587,9 @@ protected function generateACFSettings(Editor $editor) {
}
}
ksort($allowed);
ksort($disallowed);
return array($allowed, $disallowed);
}
}
......
......@@ -54,7 +54,7 @@ protected function setUp() {
'filter_html' => array(
'status' => 1,
'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() {
);
$expected_config = $this->castSafeStrings($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.');
// Customize the configuration: add button, have two contextually enabled
......@@ -122,12 +123,13 @@ function testGetJSSettings() {
// Change the allowed HTML tags; the "allowedContent" and "format_tags"
// settings for CKEditor should automatically be updated as well.
$format = $editor->getFilterFormat();
$format->filters('filter_html')->settings['allowed_html'] .= '<pre> <h3>';
$format->filters('filter_html')->settings['allowed_html'] .= '<pre> <h1>';
$format->save();
$expected_config['allowedContent']['pre'] = array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE);
$expected_config['allowedContent']['h3'] = array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE);
$expected_config['format_tags'] = 'p;h2;h3;h4;h5;h6;pre';
$expected_config['allowedContent']['pre'] = array('attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE);
$expected_config['allowedContent']['h1'] = array('attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE);
$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.');
// Disable the filter_html filter: allow *all *tags.
......@@ -179,14 +181,17 @@ function testGetJSSettings() {
),
'a' => array(
'attributes' => 'href,rel,class,target',
'styles' => FALSE,
'classes' => 'external',
),
'span' => array(
'attributes' => 'class,property,rel,style',
'styles' => 'font-size',
'classes' => FALSE,
),
'*' => array(
'attributes' => 'class,data-*',
'styles' => FALSE,
'classes' => 'is-a-hipster-llama,and-more',
),
'del' => array(
......@@ -206,6 +211,8 @@ function testGetJSSettings() {
);
$expected_config['format_tags'] = 'p';
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.');
}
......@@ -421,17 +428,18 @@ protected function getDefaultInternalConfig() {
}
protected function getDefaultAllowedContentConfig() {
return array(
'h2' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'h3' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'h4' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'h5' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'h6' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'p' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'br' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'strong' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
'a' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
);
return [
'h2' => ['attributes' => 'id', 'styles' => FALSE, 'classes' => FALSE],
'h3' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'h4' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'h5' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'h6' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'p' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'br' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'strong' => ['attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE],
'a' => ['attributes' => 'href,hreflang', 'styles' => FALSE, 'classes' => FALSE],
'*' => ['attributes' => 'lang,dir', 'styles' => FALSE, 'classes' => FALSE],
];
}
protected function getDefaultDisallowedContentConfig() {
......
......@@ -821,17 +821,32 @@
* @see Drupal.FilterStatus
*/
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: [],
allow: null,
// Apply restrictions to properties set on tags.
restrictedTags: {
tags: [],
allowed: {attributes: [], styles: [], classes: []},
forbidden: {attributes: [], styles: [], classes: []}
}
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 @@
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Template\Attribute;
use Drupal\filter\Entity\FilterFormat;
use Drupal\filter\FilterFormatInterface;
/**
......@@ -450,25 +449,6 @@ function template_preprocess_filter_tips(&$variables) {
* 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.
*
......
......@@ -34,7 +34,9 @@ process:
- filter_url
- filter_htmlcorrector
- filter_html_escape
settings: settings
settings:
plugin: filter_settings
source: settings
status:
plugin: default_value
default_value: true
......
......@@ -19,7 +19,9 @@ process:
source: name
map:
php_code: filter_null
settings: settings
settings:
plugin: filter_settings
source: settings
status:
plugin: default_value
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() {
$filtered_html_format = entity_load('filter_format', 'filtered_html');
$this->assertIdentical(
$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.'
);
$this->assertIdentical(
......@@ -164,7 +172,7 @@ function testFilterFormatAPI() {
'filter_html' => array(
'status' => 1,
'settings' => array(
'allowed_html' => '<p> <br> <a> <strong>',
'allowed_html' => '<p> <br> <a href> <strong>',
),
),
'filter_test_restrict_tags_and_attributes' => array(
......@@ -185,7 +193,14 @@ function testFilterFormatAPI() {
$very_restricted_html_format->save();
$this->assertIdentical(
$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.'
);
$this->assertIdentical(
......
......@@ -45,7 +45,7 @@ protected function setUp() {
'filter_html' => array(
'status' => 1,
'settings' => array(
'allowed_html' => '<img> <a>',
'allowed_html' => '<img src testattribute> <a>',
),
),
'filter_autop' => array(
......
......@@ -9,6 +9,7 @@
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Language\Language;
use Drupal\Core\Render\RenderContext;
use Drupal\editor\EditorXssFilter\Standard;
use Drupal\filter\Entity\FilterFormat;
......@@ -190,7 +191,7 @@ function testCaptionFilter() {
$html_filter = $this->filters['filter_html'];
$html_filter->setConfiguration(array(
'settings' => array(
'allowed_html' => '<img>',
'allowed_html' => '<img src data-align data-caption>',
'filter_html_help' => 1,
'filter_html_nofollow' => 0,
)
......@@ -399,7 +400,7 @@ function testHtmlFilter() {
$filter = $this->filters['filter_html'];
$filter->setConfiguration(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_nofollow' => 0,
)
......@@ -407,41 +408,78 @@ function testHtmlFilter() {
// HTML filter is not able to secure some tags, these should never be
// allowed.
$f = _filter_html('<script />', $filter);
$this->assertNoNormalized($f, 'script', 'HTML filter should always remove script tags.');
$f = (string) $filter->process('<script />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertIdentical($f, '', 'HTML filter should remove script tags.');
$f = _filter_html('<iframe />', $filter);
$this->assertNoNormalized($f, 'iframe', 'HTML filter should always remove iframe tags.');
$f = (string) $filter->process('<iframe />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertIdentical($f, '', 'HTML filter should remove iframe tags.');
$f = _filter_html('<object />', $filter);
$this->assertNoNormalized($f, 'object', 'HTML filter should always remove object tags.');
$f = (string) $filter->process('<object />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertIdentical($f, '', 'HTML filter should remove object tags.');
$f = _filter_html('<style />', $filter);
$this->assertNoNormalized($f, 'style', 'HTML filter should always remove style tags.');
$f = (string) $filter->process('<style />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertIdentical($f, '', 'HTML filter should remove style tags.');
// Some tags make CSRF attacks easier, let the user take the risk herself.
$f = _filter_html('<img />', $filter);
$this->assertNoNormalized($f, 'img', 'HTML filter should remove img tags on default.');
$f = (string) $filter->process('<img />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertIdentical($f, '', 'HTML filter should remove img tags by default.');
$f = _filter_html('<input />', $filter);
$this->assertNoNormalized($f, 'img', 'HTML filter should remove input tags on default.');
$f = (string) $filter->process('<input />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertIdentical($f, '', 'HTML filter should remove input tags by default.');
// Filtering content of some attributes is infeasible, these shouldn't be
// allowed too.
$f = _filter_html('<p style="display: none;" />', $filter);
$this->assertNoNormalized($f, 'style', 'HTML filter should remove style attribute on default.');
$f = (string) $filter->process('<p style="display: none;" />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'style', 'HTML filter should remove style attributes.');
$this->assertIdentical($f, '<p></p>');
$f = _filter_html('<p onerror="alert(0);" />', $filter);
$this->assertNoNormalized($f, 'onerror', 'HTML filter should remove on* attributes on default.');
$f = (string) $filter->process('<p onerror="alert(0);"></p>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNoNormalized($f, 'onerror', 'HTML filter should remove on* attributes.');
$this->assertIdentical($f, '<p></p>');
$f = _filter_html('<code onerror>&nbsp;</code>', $filter);
$this->assertNoNormalized($f, 'onerror', 'HTML filter should remove empty on* attributes on default.');
$f = (string) $filter->process('<code onerror>&nbsp;</code>', Language::LANGCODE_NOT_SPECIFIED);
$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);
$this->assertNormalized($f, '<br>', 'HTML filter should allow line breaks.');
$f = (string) $filter->process('<br>', Language::LANGCODE_NOT_SPECIFIED);
$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.');
// 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() {
$filter = $this->filters['filter_html'];
$filter->setConfiguration(array(
'settings' => array(
'allowed_html' => '<a>',
'allowed_html' => '<a href>',
'filter_html_help' => 1,
'filter_html_nofollow' => 1,
)
......@@ -460,19 +498,19 @@ function testNoFollowFilter() {
// Test if the rel="nofollow" attribute is added, even if we try to prevent
// 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.');
$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.');
$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.');
$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.');
$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->assertNormalized($f, 'rel="nofollow"', 'Spam deterrent evasion -- with rel set - rel="nofollow" added.');
}
......
......@@ -44,7 +44,7 @@ public function testFilterFormat() {
$this->assertFalse(isset($filters['filter_html_image_secure']));
// 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']);
$this->assertIdentical(FALSE, $filters['filter_html']['settings']['filter_html_nofollow']);
$this->assertIdentical(72, $filters['filter_url']['settings']['filter_url_length']);
......
......@@ -68,7 +68,7 @@ public function testFilterFormat() {
/** @var \Drupal\filter\FilterFormatInterface $format */
$format = FilterFormat::load('filtered_html');
$config = $format->filters('filter_html')->getConfiguration();
$this->assertIdentical('<div> <span> <ul> <li>', $config['settings']['allowed_html']);
$this->assertIdentical('<div> <span> <ul type> <li> <ol start type> <a href hreflang> <img src alt height width>', $config['settings']['allowed_html']);
$config = $format->filters('filter_url')->getConfiguration();
$this->assertIdentical(128, $config['settings']['filter_url_length']);
......
......@@ -12,4 +12,4 @@ filters:
provider: filter
status: true
settings:
allowed_html: '<p> <br> <strong> <a>'
allowed_html: '<p> <br> <strong> <a href hreflang>'
<?php
/**
* @file
* Contains \Drupal\Tests\filter\Unit\FilterHtmlTest.
*/
namespace Drupal\Tests\filter\Unit;
use Drupal\Tests\UnitTestCase;
use Drupal\filter\Plugin\Filter\FilterHtml;
/**
* @coversDefaultClass \Drupal\filter\Plugin\Filter\FilterHtml
* @group filter
*/
class FilterHtmlTest extends UnitTestCase {
/**
* @var \Drupal\filter\Plugin\Filter\FilterHtml
*/
protected $filter;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$configuration['settings'] = [
'allowed_html' => '<a href> <p> <em> <strong> <cite> <blockquote> <code class="pretty boring align-*"> <ul alpaca-*="wooly-* strong"> <ol llama-*> <li> <dl> <dt> <dd> <br> <h3 id>',
'filter_html_help' => 1,
'filter_html_nofollow' => 0,
];
$this->filter = new FilterHtml($configuration, 'filter_html', ['provider' => 'test']);
$this->filter->setStringTranslation($this->getStringTranslationStub());
}
/**
* @covers ::filterAttributes
*
* @dataProvider providerFilterAttributes
*
* @param string $html
* Input HTML.
* @param array $expected
* The expected output string.
*/
public function testfilterAttributes($html, $expected) {
$this->assertSame($expected, $this->filter->filterAttributes($html));
}
/**
* Provides data for testfilterAttributes.
*
* @return array
* An array of test data.
*/
public function providerFilterAttributes() {
return [
['<a href="/blog" title="Blog">Blog</a>', '<a href="/blog">Blog</a>'],
['<p dir="rtl" />', '<p dir="rtl"></p>'],
['<p dir="bogus" />', '<p></p>'],
['<p id="first" />', '<p></p>'],
// The addition of xml:lang isn't especially desired, but is still valid
// HTML5. See https://www.drupal.org/node/1333730.
['<p id="first" lang="en">text</p>', '<p lang="en" xml:lang="en">text</p>'],
['<p style="display: none;" />', '<p></p>'],
['<code class="pretty invalid">foreach ($a as $b) {}</code>', '<code class="pretty">foreach ($a as $b) {}</code>'],
['<code class="boring pretty">foreach ($a as $b) {}</code>', '<code class="boring pretty">foreach ($a as $b) {}</code>'],
['<code class="boring pretty ">foreach ($a as $b) {}</code>', '<code class="boring pretty">foreach ($a as $b) {}</code>'],
['<code class="invalid alpaca">foreach ($a as $b) {}</code>', '<code>foreach ($a as $b) {}</code>'],
['<h3 class="big">a heading</h3>', '<h3>a heading</h3>'],
['<h3 id="first">a heading</h3>', '<h3 id="first">a heading</h3>'],
// Wilcard value. Case matters, so upper case doesn't match.
['<code class="align-left bold">foreach ($a as $b) {}</code>', '<code class="align-left">foreach ($a as $b) {}</code>'],
['<code class="align-right ">foreach ($a as $b) {}</code>', '<code class="align-right">foreach ($a as $b) {}</code>'],
['<code class="Align-right ">foreach ($a as $b) {}</code>', '<code>foreach ($a as $b) {}</code>'],
// Wilcard name, case is ignored.
['<ol style="display: none;" llama-wim="noble majestic"></ol>', '<ol llama-wim="noble majestic"></ol>'],
['<ol style="display: none;" LlamA-Wim="majestic"></ol>', '<ol llama-wim="majestic"></ol>'],
['<ol style="display: none;" llama-="noble majestic"></ol>', '<ol llama-="noble majestic"></ol>'],
// Both wildcard names and values.
['<ul style="display: none;" alpaca-wool="wooly-warm strong majestic"></ul>', '<ul alpaca-wool="wooly-warm strong"></ul>'],
];
}
}
......@@ -119,7 +119,7 @@ public function load() {
'name' => 'filter_html',
'weight' => '1',
'status' => '1',
'settings' => 'a:3:{s:12:"allowed_html";s:22:"<div> <span> <ul> <li>";s:16:"filter_html_help";i:1;s:20:"filter_html_nofollow";i:0;}',
'settings' => 'a:3:{s:12:"allowed_html";s:37:"<div> <span> <ul> <li> <ol> <a> <img>";s:16:"filter_html_help";i:1;s:20:"filter_html_nofollow";i:0;}',
))->values(array(
'format' => 'filtered_html',
'module' => 'filter',
......@@ -257,4 +257,5 @@ public function load() {
}
}
#7f8ea668d5deed8ce2d6c782dad2bcd5
#e0bd772d07df589752fa9372705aaa9d
<?php
/**
* @file
* Contains \Drupal\system\Tests\Update\FilterHtmlUpdateTest.
*/
namespace Drupal\system\Tests\Update;
use Drupal\filter\Entity\FilterFormat;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests that the allowed html configutations are updated with attributes.
*
* @group Entity
*/
class FilterHtmlUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
public function setDatabaseDumpFiles() {