diff --git a/core/lib/Drupal/Component/Utility/String.php b/core/lib/Drupal/Component/Utility/String.php
index b39ca5d..fba2eae 100644
--- a/core/lib/Drupal/Component/Utility/String.php
+++ b/core/lib/Drupal/Component/Utility/String.php
@@ -146,5 +146,33 @@ public static function placeholder($text) {
return SafeMarkup::set('' . SafeMarkup::escape($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/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 06f9c4f..c2957f5 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -246,9 +246,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.
@@ -802,7 +801,7 @@ public function generateCachePlaceholder($callback, array &$context) {
'token' => Crypt::randomBytesBase64(55),
];
- return '';
+ return SafeMarkup::set('');
}
}
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 23fd877..1247615 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/Component/Utility/StringTest.php b/core/tests/Drupal/Tests/Component/Utility/StringTest.php
index 307385a..867f0c3 100644
--- a/core/tests/Drupal/Tests/Component/Utility/StringTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/StringTest.php
@@ -148,4 +148,73 @@ public function providerDecodeEntities() {
);
}
+ /**
+ * Tests String::replace().
+ *
+ * @dataProvider providerReplace
+ * @covers ::replace
+ */
+ public function testReplaces($search, $replace, $subject, $expected, $is_safe) {
+ $result = String::replace($search, $replace, $subject);
+ $this->assertEquals($expected, $result);
+ $this->assertEquals($is_safe, SafeMarkup::isSafe($result));
+ }
+
+ /**
+ * Data provider for testReplace().
+ *
+ * @see testReplace()
+ */
+ public function providerReplace() {
+ $tests = [];
+
+ // Subject unsafe
+ $tests[] = [
+ '',
+ SafeMarkup::set('foo'),
+ 'bazqux',
+ 'foobazqux',
+ FALSE,
+ ];
+
+ // All safe
+ $tests[] = [
+ '',
+ SafeMarkup::set('foo'),
+ SafeMarkup::set('barbaz'),
+ 'foobarbaz',
+ TRUE,
+ ];
+
+ // Replacement unsafe safe
+ $tests[] = [
+ '',
+ 'fubar',
+ SafeMarkup::set('barbaz'),
+ 'fubarbarbaz',
+ FALSE,
+ ];
+
+ // Array with all safe
+ $tests[] = [
+ ['', '', ''],
+ [SafeMarkup::set('foo'), SafeMarkup::set('bar'), SafeMarkup::set('baz')],
+ SafeMarkup::set(''),
+ 'foobarbaz',
+ TRUE,
+ ];
+
+ // Array with unsafe replacement
+ $tests[] = [
+ ['', '', '',],
+ [SafeMarkup::set('bar'), SafeMarkup::set('baz'), 'qux'],
+ SafeMarkup::set(''),
+ 'barbazqux',
+ FALSE,
+ ];
+
+ return $tests;
+ }
+
+
}
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php b/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php
index da9f232..a15fe45 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php
@@ -416,7 +416,7 @@ public function testPlaceholder() {
'#prefix' => '',
'#suffix' => '
',
];
- $expected_output = '' . $context['bar'] . '
';
+ $expected_output = '' . $context['bar'] . '
';
// #cache disabled.
$element = $test_element;
@@ -513,7 +513,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 a53155f..4bd7639 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -77,6 +77,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 b4320c9..091c7fc 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php
@@ -207,7 +207,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,