diff --git a/core/includes/common.inc b/core/includes/common.inc index 86fc44aae9c734fbfabb18c955c0e48daaeee08c..1cf53b647a5e86c3f9a3c17d949280a4678c65b2 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1334,13 +1334,9 @@ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', // Defuse all HTML entities. $string = str_replace('&', '&', $string); - // Change back only well-formed entities in our whitelist: - // Decimal numeric entities. - $string = preg_replace('/&#([0-9]+;)/', '&#\1', $string); - // Hexadecimal numeric entities. - $string = preg_replace('/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string); - // Named entities. - $string = preg_replace('/&([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string); + + // Change back well-formed entities in our whitelist. + $string = partial_decode_entities_safe($string); return preg_replace_callback('% ( @@ -1355,6 +1351,39 @@ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', } /** + * Decodes double-encoded well-formed HTML entities in our whitelist. + * + * During string sanitization html entities can become double-encoded, ie. &foo; + * becomes &foo;. This is legal HTML and is almost always the desired + * behaviour for displaying pure "plain text" strings but can be undesirable + * for HTML strings. For example, filtering HTML against XSS or processing a + * string intended for use as a HTML attribute. + * + * This function reverses double-encoding of HTML strings without reverting + * previous security efforts by only processing entities on a whitelist. + * + * @param string $string + * The double-encoded HTML string to be partially decoded. + * + * @return string + * A partially decoded HTML string. + * + * @see filter_xss() + */ +function partial_decode_entities_safe($string) { + // Do nothing if there are no '&' characters in $string. + if(strpos($string, '&') !== FALSE) { + // Decimal numeric entities. + $string = preg_replace('/&#([0-9]+;)/', '&#\1', $string); + // Hexadecimal numeric entities. + $string = preg_replace('/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string); + // Named entities. + $string = preg_replace('/&([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string); + } + return $string; +} + +/** * Processes an HTML tag. * * @param $m diff --git a/core/lib/Drupal/Core/Template/AttributeString.php b/core/lib/Drupal/Core/Template/AttributeString.php index 9776467f7790581b5ae765e17c1855531f251723..87ab6bd7c13e550324c045915d294e5df1e6082a 100644 --- a/core/lib/Drupal/Core/Template/AttributeString.php +++ b/core/lib/Drupal/Core/Template/AttributeString.php @@ -29,7 +29,12 @@ class AttributeString extends AttributeValueBase { */ public function __toString() { $this->printed = TRUE; - return check_plain($this->value); + $string = check_plain($this->value); + + // Allow well-formed HTML entities on our security whitelist. + $string = partial_decode_entities_safe($string); + + return $string; } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/AttributesUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/AttributesUnitTest.php index 6910f7ad61f6fe3653e85207ca271625ffed3390..0e51b1ebf931529afe7e4ee8c256d44b0ad088b2 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/AttributesUnitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/AttributesUnitTest.php @@ -29,6 +29,27 @@ function testDrupalAttributes() { // Verify that special characters are HTML encoded. $this->assertIdentical((string) new Attribute(array('title' => '&"\'<>')), ' title="&"'<>"', 'HTML encode attribute values.'); + // Verify that decimal NCR characters are preserved. + $this->assertIdentical((string) new Attribute(array('title' => 'Σ')), ' title="Σ"', 'Decimal NCR attribute values are preserved.'); + + // Verify that HTML encoded decimal NCR characters are preserved. + $this->assertIdentical((string) new Attribute(array('title' => '&#931;')), ' title="&#931;"', 'HTML Encoded decimal NCR attribute values are preserved.'); + + // Verify that hexadecimal NCR characters are preserved. + $this->assertIdentical((string) new Attribute(array('title' => 'Σ')), ' title="Σ"', 'Hexadecimal NCR attribute values are preserved.'); + + // Verify that HTML encoded hexadecimal NCR characters are preserved. + $this->assertIdentical((string) new Attribute(array('title' => '&#x03A3;')), ' title="&#x03A3;"', 'HTML Encoded hexadecimal NCR attribute values are preserved.'); + + // Verify that named characters are preserved. + $this->assertIdentical((string) new Attribute(array('title' => '®')), ' title="®"', 'Named character entity attribute values are preserved.'); + + // Verify that HTML encoded named characters are preserved. + $this->assertIdentical((string) new Attribute(array('title' => '&reg;')), ' title="&reg;"', 'HTML Encoded named character entity attribute values are preserved.'); + + // Verify that iconic Unicode characters are preserved. + $this->assertIdentical((string) new Attribute(array('title' => '©')), ' title="©"', 'Iconic Unicode character attribute values are preserved.'); + // Verify multi-value attributes are concatenated with spaces. $attributes = array('class' => array('first', 'last')); $this->assertIdentical((string) new Attribute(array('class' => array('first', 'last'))), ' class="first last"', 'Concatenate multi-value attributes.'); diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php index d4d745106c0ea52993b7290694b3ce365f5b96b8..2a97eb15446d924e94d9ed0cf8c754f137597b1f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php @@ -509,6 +509,8 @@ function testMenuLinkOptions() { 'options' => array( 'attributes' => array( 'title' => 'Test title attribute', + 'data-preserved-entity' => '', + 'data-preserved-encoded-entity' => '&#xf007;', ), 'query' => array( 'testparam' => 'testvalue', @@ -520,6 +522,8 @@ function testMenuLinkOptions() { // Load front page. $this->drupalGet('test-page'); $this->assertRaw('title="Test title attribute"', 'Title attribute of a menu link renders.'); + $this->assertRaw('data-preserved-entity=""', 'HTML entities are preserved in menu link data attributes.'); + $this->assertRaw('data-preserved-encoded-entity="&#xf007;"', 'Double encoded HTML entities are preserved in menu link data attributes.'); $this->assertRaw('testparam=testvalue', 'Query parameter added to menu link.'); }