diff --git a/core/lib/Drupal/Component/Utility/Xss.php b/core/lib/Drupal/Component/Utility/Xss.php index 7d7f84b..2078e4b 100644 --- a/core/lib/Drupal/Component/Utility/Xss.php +++ b/core/lib/Drupal/Component/Utility/Xss.php @@ -33,6 +33,25 @@ class Xss { protected static $htmlTags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd'); /** + * The default list of safe attributes untouched by filter(). + * + * @var array + * + * @see \Drupal\Component\Utility\Xss::filter() + */ + protected static $safeAttributes = ['alt', 'title']; + + /** + * The default list of RDFa attributes untouched by filter(). + * + * @var array + * + * @see \Drupal\Component\Utility\Xss::filter() + * @see http://www.w3.org/TR/xhtml-rdfa/ + */ + protected static $rdfaAttributes = ['property', 'typeof', 'rel', 'rev', 'datatype']; + + /** * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities. * * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses. @@ -230,11 +249,7 @@ protected static function attributes($attributes) { // such attributes. // @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol() // @see http://www.w3.org/TR/html4/index/attributes.html - $skip_protocol_filtering = substr($attribute_name, 0, 5) === 'data-' || in_array($attribute_name, array( - 'title', - 'alt', - 'typeof', - )); + $skip_protocol_filtering = substr($attribute_name, 0, 5) === 'data-' || in_array($attribute_name, static::$safeAttributes); $working = $mode = 1; $attributes = preg_replace('/^[-a-zA-Z]+/', '', $attributes); @@ -261,7 +276,12 @@ protected static function attributes($attributes) { case 2: // Attribute value, a URL after href= for instance. if (preg_match('/^"([^"]*)"(\s+|$)/', $attributes, $match)) { - $thisval = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]); + if (in_array($attribute_name, static::$rdfaAttributes)) { + $thisval = ($skip_protocol_filtering || preg_match('/^[[:alnum:]]+\:[[:alnum:]]+$/', $match[1])) ? $match[1] : UrlHelper::filterBadProtocol($match[1]); + } + else { + $thisval = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]); + } if (!$skip) { $attributes_array[] = "$attribute_name=\"$thisval\""; @@ -273,7 +293,12 @@ protected static function attributes($attributes) { } if (preg_match("/^'([^']*)'(\s+|$)/", $attributes, $match)) { - $thisval = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]); + if (in_array($attribute_name, static::$rdfaAttributes)) { + $thisval = ($skip_protocol_filtering || preg_match('/^[[:alnum:]]+\:[[:alnum:]]+$/', $match[1])) ? $match[1] : UrlHelper::filterBadProtocol($match[1]); + } + else { + $thisval = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]); + } if (!$skip) { $attributes_array[] = "$attribute_name='$thisval'"; @@ -284,7 +309,12 @@ protected static function attributes($attributes) { } if (preg_match("%^([^\s\"']+)(\s+|$)%", $attributes, $match)) { - $thisval = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]); + if (in_array($attribute_name, static::$rdfaAttributes)) { + $thisval = ($skip_protocol_filtering || preg_match('/^[[:alnum:]]+\:[[:alnum:]]+$/', $match[1])) ? $match[1] : UrlHelper::filterBadProtocol($match[1]); + } + else { + $thisval = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]); + } if (!$skip) { $attributes_array[] = "$attribute_name=\"$thisval\""; @@ -353,4 +383,5 @@ public static function getAdminTagList() { public static function getHtmlTagList() { return static::$htmlTags; } + } diff --git a/core/tests/Drupal/Tests/Component/Utility/XssTest.php b/core/tests/Drupal/Tests/Component/Utility/XssTest.php index a212112..d838eb5 100644 --- a/core/tests/Drupal/Tests/Component/Utility/XssTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/XssTest.php @@ -516,6 +516,42 @@ public function providerTestAttributes() { 'Image tag with data attribute', array('img') ), + array( + '', + '', + 'Image tag with RDFa with namespaced attribute', + array('img') + ), + array( + '', + '', + 'Image tag with RDFa with bad with namespaced attribute', + array('img') + ), + array( + '', + '', + 'Image tag with non-RDFa attribute', + array('img') + ), + array( + '

The Title

', + '

The Title

', + 'H2 tag with RDFa attribute without namespace', + array('h2') + ), + array( + '

The Title

', + '

The Title

', + 'H2 tag with RDFa attribute with URL', + array('h2') + ), + array( + '

The Title

', + '

The Title

', + 'H2 tag with RDFa attribute with XSS', + array('h2') + ), ); } @@ -544,17 +580,6 @@ public function testFilterXssAdminNotNormalized($value, $expected, $message) { } /** - * Checks that Checks that \Drupal\Component\Utility\Xss::filter() - * doesn't alter attributes. - */ - public function testFilterXSSAttributes() { - $img1 = 'One:Two'; - $this->assertEquals(Xss::filter($img1, ['img']), $img1, 'Attributes OK'); - $img2 = 'One:Two'; - $this->assertEquals(Xss::filter($img2, ['img']), $img2, 'Attributes OK'); - } - - /** * Data provider for testFilterXssAdminNotNormalized(). * * @see testFilterXssAdminNotNormalized()