diff --git a/core/lib/Drupal/Component/Utility/String.php b/core/lib/Drupal/Component/Utility/String.php index df4ba54..46485de 100644 --- a/core/lib/Drupal/Component/Utility/String.php +++ b/core/lib/Drupal/Component/Utility/String.php @@ -101,4 +101,33 @@ public static function placeholder($text) { return SafeMarkup::placeholder($text); } + /** + * Replace all occurrences of the search string with the replacement string. + * + * Functions identically to str_replace, but marks the returned output as safe + * if all the inputs and the subject have also been marked as safe. + */ + public static function replace($search, $replace, $subject) { + $output = str_replace($search, $replace, $subject); + + if (!is_array($replace)) { + if (!SafeMarkup::isSafe($replace)) { + return $output; + } + } + else { + foreach ($replace as $replacement) { + if (!SafeMarkup::isSafe($replacement)) { + return $output; + } + } + } + + if (SafeMarkup::isSafe($subject)) { + return SafeMarkup::set($output); + } + else { + return $output; + } + } } diff --git a/core/lib/Drupal/Core/Render/Element/HtmlTag.php b/core/lib/Drupal/Core/Render/Element/HtmlTag.php index 553767f..83eef44 100644 --- a/core/lib/Drupal/Core/Render/Element/HtmlTag.php +++ b/core/lib/Drupal/Core/Render/Element/HtmlTag.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Template\Attribute; +use Drupal\Component\Utility\String; /** * Provides a render element for any HTML tag, with properties and value. @@ -94,7 +95,13 @@ public static function preRenderHtmlTag($element) { $markup = SafeMarkup::set($markup); } if (!empty($element['#noscript'])) { - $element['#markup'] = ''; + $element['#markup'] = array( + '#type' => 'inline_template', + '#inline_template' => '', + '#context' => array( + 'markup' => $markup, + ), + ); } else { $element['#markup'] = $markup; diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 5041a78..637d7b1 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -16,6 +16,7 @@ use Drupal\Core\Controller\ControllerResolverInterface; use Drupal\Core\Theme\ThemeManagerInterface; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Component\Utility\String; use Symfony\Component\HttpFoundation\RequestStack; /** @@ -271,9 +272,8 @@ protected function doRender(&$elements, $is_root_call = FALSE) { $elements['#children'] = ''; } - // @todo Simplify after https://drupal.org/node/2273925 if (isset($elements['#markup'])) { - $elements['#markup'] = SafeMarkup::set($elements['#markup']); + $elements['#markup'] = SafeMarkup::checkAdminXss($elements['#markup']); } // Assume that if #theme is set it represents an implemented hook. @@ -832,7 +832,10 @@ public function generateCachePlaceholder($callback, array &$context) { 'token' => Crypt::randomBytesBase64(55), ]; - return ''; + return String::format('', array( + '@callback' => $callback, + '@token' => $context['token'], + )); } } diff --git a/core/modules/contextual/src/Element/ContextualLinksPlaceholder.php b/core/modules/contextual/src/Element/ContextualLinksPlaceholder.php index d10078b..a2f6aa0 100644 --- a/core/modules/contextual/src/Element/ContextualLinksPlaceholder.php +++ b/core/modules/contextual/src/Element/ContextualLinksPlaceholder.php @@ -9,6 +9,7 @@ use Drupal\Core\Template\Attribute; use Drupal\Core\Render\Element\RenderElement; +use Drupal\Component\Utility\SafeMarkup; /** * Provides a contextual_links_placeholder element. @@ -47,7 +48,7 @@ public function getInfo() { * @see _contextual_links_to_id() */ public static function preRenderPlaceholder(array $element) { - $element['#markup'] = ' $element['#id'])) . '>'; + $element['#markup'] = SafeMarkup::set(' $element['#id'])) . '>'); return $element; } diff --git a/core/modules/filter/src/Element/ProcessedText.php b/core/modules/filter/src/Element/ProcessedText.php index d007b5f..aedc714 100644 --- a/core/modules/filter/src/Element/ProcessedText.php +++ b/core/modules/filter/src/Element/ProcessedText.php @@ -8,6 +8,7 @@ namespace Drupal\filter\Element; use Drupal\Component\Utility\NestedArray; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Cache\Cache; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Render\Element\RenderElement; @@ -120,7 +121,7 @@ public static function preRenderText($element) { // Filtering done, store in #markup, set the updated bubbleable rendering // metadata, and set the text format's cache tag. - $element['#markup'] = $text; + $element['#markup'] = SafeMarkup::set($text); $metadata->applyTo($element); $element['#cache']['tags'] = Cache::mergeTags($element['#cache']['tags'], $format->getCacheTags()); diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 25c62f8..a140b49 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -674,7 +674,7 @@ function views_pre_render_views_form_views_form($element) { } // Apply substitutions to the rendered output. - $element['output'] = array('#markup' => str_replace($search, $replace, drupal_render($element['output']))); + $element['output'] = array('#markup' => String::replace($search, $replace, drupal_render($element['output']))); // Sort, render and add remaining form fields. $children = Element::children($element, TRUE); diff --git a/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php b/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php index 99a639e..09d5aa1 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php @@ -432,7 +432,7 @@ public function testPlaceholder() { '#prefix' => '
',
       '#suffix' => '
', ]; - $expected_output = '
' . $context['bar'] . '
'; + $expected_output = '
' . $context['bar'] . '
'; // #cache disabled. $element = $test_element; @@ -530,7 +530,7 @@ public function testChildElementPlaceholder() { '#suffix' => '' ], ]; - $expected_output = '
' . $context['bar'] . '
' . "\n"; + $expected_output = '
' . $context['bar'] . '
' . "\n"; // #cache disabled. $element = $test_element; diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php index 56d9db1..723d183 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php @@ -81,6 +81,10 @@ public function providerTestRenderBasic() { $data[] = [[ 'child' => ['#markup' => 'bar'], ], 'bar']; + // XSS filtering test. + $data[] = [[ + 'child' => ['#markup' => 'This is test'], + ], 'This is alert(\'XSS\') test']; // #children set but empty, and renderable children. $data[] = [[ '#children' => '', diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php index 7f24130..65cf310 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php @@ -245,7 +245,7 @@ public static function callback(array $element, array $context) { public static function placeholder(array $element, array $context) { $placeholder = \Drupal::service('renderer')->generateCachePlaceholder(__NAMESPACE__ . '\\PostRenderCache::placeholder', $context); $replace_element = array( - '#markup' => '' . $context['bar'] . '', + '#markup' => '' . $context['bar'] . '', '#attached' => array( 'drupalSettings' => [ 'common_test' => $context,