diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 009bb33..53aeea7 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1254,6 +1254,10 @@ function template_preprocess_html(&$variables) {
$site_config = \Drupal::config('system.site');
// Construct page title.
+ if (isset($variables['page']['#title']) && is_array($variables['page']['#title'])) {
+ // Do an early render if the title is a render array.
+ $variables['page']['#title'] = (string) \Drupal::service('renderer')->render($variables['page']['#title']);
+ }
if (!empty($variables['page']['#title'])) {
$head_title = array(
'title' => trim(strip_tags($variables['page']['#title'])),
diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php
index 2fb5152..91a2f1e 100644
--- a/core/lib/Drupal/Component/Utility/SafeMarkup.php
+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -15,9 +15,9 @@
* provides a store for known safe strings and methods to manage them
* throughout the page request.
*
- * Strings sanitized by self::checkPlain() and self::escape() or
- * self::xssFilter() are automatically marked safe, as are markup strings
- * created from @link theme_render render arrays @endlink via drupal_render().
+ * Strings sanitized by self::checkPlain() and self::escape() are automatically
+ * marked safe, as are markup strings created from @link theme_render render
+ * arrays @endlink via drupal_render().
*
* This class should be limited to internal use only. Module developers should
* instead use the appropriate
@@ -139,60 +139,6 @@ public static function escape($string) {
}
/**
- * Filters HTML for XSS vulnerabilities and marks the result as safe.
- *
- * Calling this method unnecessarily will result in bloating the safe string
- * list and increases the chance of unintended side effects.
- *
- * If Twig receives a value that is not marked as safe then it will
- * automatically encode special characters in a plain-text string for display
- * as HTML. Therefore, SafeMarkup::xssFilter() should only be used when the
- * string might contain HTML that needs to be rendered properly by the
- * browser.
- *
- * If you need to filter for admin use, like Xss::filterAdmin(), then:
- * - If the string is used as part of a @link theme_render render array @endlink,
- * use #markup to allow the render system to filter by the admin tag list
- * automatically.
- * - Otherwise, use the SafeMarkup::xssFilter() with tag list provided by
- * Xss::getAdminTagList() instead.
- *
- * This method should only be used instead of Xss::filter() when the result is
- * being added to a render array that is constructed before rendering begins.
- *
- * In the rare instance that the caller does not want to filter strings that
- * are marked safe already, it needs to check SafeMarkup::isSafe() itself.
- *
- * @param $string
- * The string with raw HTML in it. It will be stripped of everything that
- * can cause an XSS attack. The string provided will always be escaped
- * regardless of whether the string is already marked as safe.
- * @param array $html_tags
- * (optional) An array of HTML tags. If omitted, it uses the default tag
- * list defined by \Drupal\Component\Utility\Xss::filter().
- *
- * @return string
- * An XSS-safe version of $string, or an empty string if $string is not
- * valid UTF-8. The string is marked as safe.
- *
- * @ingroup sanitization
- *
- * @see \Drupal\Component\Utility\Xss::filter()
- * @see \Drupal\Component\Utility\Xss::filterAdmin()
- * @see \Drupal\Component\Utility\Xss::getAdminTagList()
- * @see \Drupal\Component\Utility\SafeMarkup::isSafe()
- */
- public static function xssFilter($string, $html_tags = NULL) {
- if (is_null($html_tags)) {
- $string = Xss::filter($string);
- }
- else {
- $string = Xss::filter($string, $html_tags);
- }
- return static::set($string);
- }
-
- /**
* Gets all strings currently marked as safe.
*
* This is useful for the batch and form APIs, where it is important to
diff --git a/core/lib/Drupal/Component/Utility/Xss.php b/core/lib/Drupal/Component/Utility/Xss.php
index a68ca2b..6fdeb68 100644
--- a/core/lib/Drupal/Component/Utility/Xss.php
+++ b/core/lib/Drupal/Component/Utility/Xss.php
@@ -15,7 +15,7 @@
class Xss {
/**
- * The list of html tags allowed by filterAdmin().
+ * The list of HTML tags allowed by filterAdmin().
*
* @var array
*
@@ -24,18 +24,20 @@ class Xss {
protected static $adminTags = array('a', 'abbr', 'acronym', 'address', 'article', 'aside', 'b', 'bdi', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'command', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'mark', 'menu', 'meter', 'nav', 'ol', 'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'small', 'span', 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr', 'tt', 'u', 'ul', 'var', 'wbr');
/**
+ * The default list of HTML tags allowed by filter().
+ *
+ * @var array
+ *
+ * @see \Drupal\Component\Utility\Xss::filter()
+ */
+ protected static $htmlTags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd');
+
+ /**
* Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
*
* Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
* For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
*
- * This method is preferred to
- * \Drupal\Component\Utility\SafeMarkup::xssFilter() when the result is not
- * being used directly in the rendering system (for example, when its result
- * is being combined with other strings before rendering). This avoids
- * bloating the safe string list with partial strings if the whole result will
- * be marked safe.
- *
* This code does four things:
* - Removes characters and constructs that can trick browsers.
* - Makes sure all HTML entities are well-formed.
@@ -54,11 +56,13 @@ class Xss {
* valid UTF-8.
*
* @see \Drupal\Component\Utility\Unicode::validateUtf8()
- * @see \Drupal\Component\Utility\SafeMarkup::xssFilter()
*
* @ingroup sanitization
*/
- public static function filter($string, $html_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
+ public static function filter($string, array $html_tags = NULL) {
+ if (is_null($html_tags)) {
+ $html_tags = static::$htmlTags;
+ }
// Only operate on valid UTF-8 strings. This is necessary to prevent cross
// site scripting issues on Internet Explorer 6.
if (!Unicode::validateUtf8($string)) {
@@ -108,13 +112,6 @@ public static function filter($string, $html_tags = array('a', 'em', 'strong', '
* is desired (so \Drupal\Component\Utility\SafeMarkup::checkPlain() is
* not acceptable).
*
- * This method is preferred to
- * \Drupal\Component\Utility\SafeMarkup::xssFilter() when the result is
- * not being used directly in the rendering system (for example, when its
- * result is being combined with other strings before rendering). This avoids
- * bloating the safe string list with partial strings if the whole result will
- * be marked safe.
- *
* Allows all tags that can be used inside an HTML body, save
* for scripts and styles.
*
@@ -126,7 +123,6 @@ public static function filter($string, $html_tags = array('a', 'em', 'strong', '
*
* @ingroup sanitization
*
- * @see \Drupal\Component\Utility\SafeMarkup::xssFilter()
* @see \Drupal\Component\Utility\Xss::getAdminTagList()
*
*/
@@ -338,13 +334,22 @@ protected static function needsRemoval($html_tags, $elem) {
}
/**
- * Gets the list of html tags allowed by Xss::filterAdmin().
+ * Gets the list of HTML tags allowed by Xss::filterAdmin().
*
* @return array
- * The list of html tags allowed by filterAdmin().
+ * The list of HTML tags allowed by filterAdmin().
*/
public static function getAdminTagList() {
return static::$adminTags;
}
+ /**
+ * Gets the standard list of HTML tags allowed by Xss::filter().
+ *
+ * @return array
+ * The list of HTML tags allowed by Xss::filter().
+ */
+ public static function getHtmlTagList() {
+ return static::$htmlTags;
+ }
}
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 2ef11d1..fb5e066 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -7,6 +7,7 @@
namespace Drupal\Core\Render;
+use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
@@ -415,7 +416,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
if (!empty($elements['#markup'])) {
// @todo Decide how to support non-HTML in the render API in
// https://www.drupal.org/node/2501313.
- $elements['#markup'] = $this->xssFilterAdminIfUnsafe($elements['#markup']);
+ $elements['#markup'] = $this->ensureMarkupIsSafe($elements);
}
// Assume that if #theme is set it represents an implemented hook.
@@ -737,6 +738,54 @@ public function addCacheableDependency(array &$elements, $dependency) {
}
/**
+ * Escapes or filters #markup as required.
+ *
+ * Drupal uses Twig's auto-escape feature to improve security. This feature
+ * automatically escapes any HTML that is not known to be safe. Due to this
+ * the render system needs to ensure that all markup it generates is marked
+ * safe so that Twig does not do any additional escaping.
+ *
+ * By default all #markup is filtered to protect against XSS using the admin
+ * tag list. Render arrays can alter the list of tags allowed by the filter
+ * using the #markup_allowed_tags property. This value should be an array of
+ * tags that Xss::filter() would accept. Render arrays can escape #markup
+ * instead of XSS filtering by setting the #markup_safe_strategy property to
+ * RendererInterface::MARKUP_SAFE_STRATEGY_ESCAPE. If the escaping strategy is
+ * used #markup_allowed_tags is ignored.
+ *
+ * @param array $elements
+ * A render array with #markup set.
+ *
+ * @return \Drupal\Component\Utility\SafeStringInterface|string
+ * The escaped markup wrapped in a SafeString object. If
+ * SafeMarkup::isSafe($elements['#markup']) returns TRUE, it won't be
+ * escaped or filtered again.
+ *
+ * @see \Drupal\Component\Utility\Xss::filter()
+ * @see \Drupal\Component\Utility\Xss::adminFilter()
+ * @see htmlspecialchars()
+ * @see \Drupal\Core\Render\RendererInterface::MARKUP_SAFE_STRATEGY_FILTER
+ * @see \Drupal\Core\Render\RendererInterface::MARKUP_SAFE_STRATEGY_ESCAPE
+ */
+ protected function ensureMarkupIsSafe(array $elements) {
+ $strategy = isset($elements['#markup_safe_strategy']) ? $elements['#markup_safe_strategy'] : static::MARKUP_SAFE_STRATEGY_FILTER;
+ if (SafeMarkup::isSafe($elements['#markup'])) {
+ // Nothing to do as #markup is already marked as safe.
+ return $elements['#markup'];
+ }
+ elseif ($strategy == static::MARKUP_SAFE_STRATEGY_ESCAPE) {
+ $markup = Html::escape($elements['#markup']);
+ }
+ else {
+ // The default behaviour is to XSS filter using the admin tag list.
+ $tags = isset($elements['#markup_allowed_tags']) ? $elements['#markup_allowed_tags'] : Xss::getAdminTagList();
+ $markup = Xss::filter($elements['#markup'], $tags);
+ }
+
+ return SafeString::create($markup);
+ }
+
+ /**
* Applies a very permissive XSS/HTML filter for admin-only use.
*
* Note: This method only filters if $string is not marked safe already. This
diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php
index 155eec4..acad41f 100644
--- a/core/lib/Drupal/Core/Render/RendererInterface.php
+++ b/core/lib/Drupal/Core/Render/RendererInterface.php
@@ -13,6 +13,22 @@
interface RendererInterface {
/**
+ * #markup_safe_strategy indicating #markup should be filtered.
+ *
+ * @see \Drupal\Core\Render\ensureMarkupIsSafe::ensureSafeMarkup()
+ * @see \Drupal\Component\Utility\Xss::filter()
+ */
+ const MARKUP_SAFE_STRATEGY_FILTER = 'xss';
+
+ /**
+ * #markup_safe_strategy indicating #markup should be escaped.
+ *
+ * @see \Drupal\Core\Render\Renderer::ensureMarkupIsSafe()
+ * @see htmlspecialchars()
+ */
+ const MARKUP_SAFE_STRATEGY_ESCAPE = 'escape';
+
+ /**
* Renders final HTML given a structured array tree.
*
* Calls ::render() in such a way that placeholders are replaced.
diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php
index 689dce0..a7c89fd 100644
--- a/core/lib/Drupal/Core/Render/theme.api.php
+++ b/core/lib/Drupal/Core/Render/theme.api.php
@@ -271,10 +271,25 @@
* vectors. (I.e, test"],
], "This is alert('XSS') test"];
+ // XSS filtering test.
+ $data[] = [[
+ 'child' => ['#markup' => "This is test", '#markup_allowed_tags' => ['script']],
+ ], "This is test"];
+ // XSS filtering test.
+ $data[] = [[
+ 'child' => ['#markup' => "This is test", '#markup_allowed_tags' => ['em', 'strong']],
+ ], "This is alert('XSS') test"];
+ // Html escaping test.
+ $data[] = [[
+ 'child' => ['#markup' => "This is test", '#markup_safe_strategy' => RendererInterface::MARKUP_SAFE_STRATEGY_ESCAPE],
+ ], "This is <script><em>alert('XSS')</em></script> <strong>test</strong>"];
+ // XSS filtering by default test.
+ $data[] = [[
+ 'child' => ['#markup' => "This is test", '#markup_safe_strategy' => 'nonsense'],
+ ], "This is alert('XSS') test"];
// Ensure non-XSS tags are not filtered out.
$data[] = [[
'child' => ['#markup' => "This is test"],