diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index fd7e9c0..118caf6 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -13,7 +13,6 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Template\Attribute; -use Drupal\filter\Plugin\FilterBase; use Drupal\filter\FilterFormatInterface; /** diff --git a/core/modules/filter/src/Plugin/Filter/FilterHtml.php b/core/modules/filter/src/Plugin/Filter/FilterHtml.php index c780f87..e1afef9 100644 --- a/core/modules/filter/src/Plugin/Filter/FilterHtml.php +++ b/core/modules/filter/src/Plugin/Filter/FilterHtml.php @@ -81,8 +81,13 @@ public function setConfiguration(array $configuration) { */ public function process($text, $langcode) { $restrictions = $this->getHtmlRestrictions(); + // Split the work into two parts. For filtering HTML tags out of the content + // we rely on the well-tested Xss::filter() code. Since there is no '*' tag + // that needs to be removed from the list. unset($restrictions['allowed']['*']); $text = Xss::filter($text, array_keys($restrictions['allowed'])); + // After we've done tag filtering, we do attribute and attribute value + // filtering as the second part. return new FilterProcessResult($this->filterAttributes($text)); } @@ -104,35 +109,36 @@ public function filterAttributes($text) { $html_dom = Html::load($text); $xpath = new \DOMXPath($html_dom); foreach ($restrictions['allowed'] as $allowed_tag => $tag_attributes) { - if ($tag_attributes === FALSE) { - $tag_attributes = []; - } // By default, no attributes are allowed for a tag, but due to the // globally whitelisted attributes, it is impossible for a tag to actually // completely disallow attributes. - $allowed_attributes = ['?' => []]; + if ($tag_attributes === FALSE) { + $tag_attributes = []; + } + $allowed_attributes = ['exact' => [], 'prefix' => []]; foreach (($global_allowed_attributes + $tag_attributes) as $name => $values) { // A trailing * indicates wildcard, but it must have some prefix. if (substr($name, -1) === '*' && $name[0] !== '*') { - $allowed_attributes['?'][str_replace('*', '', $name)] = $this->prepareAttributeValues($values); + $allowed_attributes['prefix'][str_replace('*', '', $name)] = $this->prepareAttributeValues($values); } else { - $allowed_attributes[$name] = $this->prepareAttributeValues($values); + $allowed_attributes['exact'][$name] = $this->prepareAttributeValues($values); } } - krsort($allowed_attributes['?']); + krsort($allowed_attributes['prefix']); - // Find all nodes that have any attributes and filter the attributes. + // Find all nodes that have any attributes and filter the attributes by + // name and value. foreach ($xpath->query('//' . $allowed_tag . '[@*]') as $node) { $modified_attributes = []; foreach($node->attributes as $name => $attribute) { - // Remove attributes not in the white list. + // Remove attributes not in the whitelist. $allowed_value = $this->findAllowedValue($allowed_attributes, $name); if (empty($allowed_value)) { $modified_attributes[$name] = FALSE; } elseif ($allowed_value !== TRUE) { - // Check the attribute values white list. + // Check the attribute values whitelist. $attribute_values = preg_split('/\s+/', $attribute->value, -1, PREG_SPLIT_NO_EMPTY); $modified_attributes[$name] = []; foreach ($attribute_values as $value) { @@ -177,11 +183,11 @@ public function filterAttributes($text) { * @return bool|array */ protected function findAllowedValue(array $allowed, $name) { - if (isset($allowed[$name])) { - return $allowed[$name]; + if (isset($allowed['exact'][$name])) { + return $allowed['exact'][$name]; } // Handle prefix (wildcard) matches. - foreach ($allowed['?'] as $prefix => $value) { + foreach ($allowed['prefix'] as $prefix => $value) { if (strpos($name, $prefix) === 0) { return $value; } @@ -190,7 +196,11 @@ protected function findAllowedValue(array $allowed, $name) { } /** - * Helper function to prepare attribute values. + * Helper function to prepare attribute values including wildcards. + * + * Splits the values into two lists, one for values that must match exactly + * and the other for values that are wildcard prefixes. + * * * @param bool|array $attribute_values * TRUE, FALSE, or an array of allowed values. @@ -201,17 +211,17 @@ protected function prepareAttributeValues($attribute_values) { if ($attribute_values === TRUE || $attribute_values === FALSE) { return $attribute_values; } - $result = ['?' => []]; + $result = ['exact' => [], 'prefix' => []]; foreach ($attribute_values as $name => $allowed) { // A trailing * indicates wildcard, but it must have some prefix. if (substr($name, -1) === '*' && $name[0] !== '*') { - $result['?'][str_replace('*', '', $name)] = $allowed; + $result['prefix'][str_replace('*', '', $name)] = $allowed; } else { - $result[$name] = $allowed; + $result['exact'][$name] = $allowed; } } - krsort($result['?']); + krsort($result['prefix']); return $result; } @@ -248,8 +258,8 @@ public function getHTMLRestrictions() { foreach ($node->attributes as $name => $attribute) { // Put back any trailing * on wildcard attribute name. $name = str_replace($star_protector, '*', $name); - // Fourth, if the attribute value is not the empty string, this means an - // actual attribute value is assigned, mark each of the specified + // Fourth, if the attribute value is not the empty string, this means + // an actual attribute value is assigned, mark each of the specified // attribute values as allowed. if ($attribute->value === '') { $restrictions['allowed'][$tag][$name] = TRUE;