diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php
index 15d5223..d69daa5 100644
--- a/core/lib/Drupal/Component/Utility/SafeMarkup.php
+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -282,4 +282,37 @@ public static function placeholder($text) {
return $string;
}
+ /**
+ * 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 we have reached this point, then all replacements were safe, and
+ // therefore if the subject was also safe, then the entire output is also
+ // safe, and should be marked as such.
+ 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..7d8daa3 100644
--- a/core/lib/Drupal/Core/Render/Element/HtmlTag.php
+++ b/core/lib/Drupal/Core/Render/Element/HtmlTag.php
@@ -94,7 +94,7 @@ public static function preRenderHtmlTag($element) {
$markup = SafeMarkup::set($markup);
}
if (!empty($element['#noscript'])) {
- $element['#markup'] = '';
+ $element['#markup'] = SafeMarkup::format('', 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 44c79e8..2eb1a2e 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -268,9 +268,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.
@@ -848,7 +847,10 @@ public function generateCachePlaceholder($callback, array &$context) {
'token' => Crypt::randomBytesBase64(55),
];
- return '';
+ return SafeMarkup::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..e148a1b 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,11 @@ public function getInfo() {
* @see _contextual_links_to_id()
*/
public static function preRenderPlaceholder(array $element) {
- $element['#markup'] = '
$element['#id'])) . '>
';
+ // Because the only arguments to this markup will be instance of
+ // \Drupal\Core\Template\AttributeString, which is passed through
+ // \Drupal\Component\Utility\SafeMarkup::checkPlain() before being output
+ // this markup is safe, and is marked as such.
+ $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/rest/src/Plugin/views/style/Serializer.php b/core/modules/rest/src/Plugin/views/style/Serializer.php
index 09e94e6..089b68b 100644
--- a/core/modules/rest/src/Plugin/views/style/Serializer.php
+++ b/core/modules/rest/src/Plugin/views/style/Serializer.php
@@ -7,7 +7,9 @@
namespace Drupal\rest\Plugin\views\style;
+use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\rest\Plugin\views\row\DataFieldRow;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\style\StylePluginBase;
@@ -130,7 +132,16 @@ public function render() {
$content_type = $this->options['formats'] ? reset($this->options['formats']) : 'json';
}
- return $this->serializer->serialize($rows, $content_type);
+ $output = $this->serializer->serialize($rows, $content_type);
+ if ($this->view->rowPlugin instanceof DataFieldRow) {
+ // Individual fields in the DataFieldRow plugin are sanitized in
+ // \Drupal\views\Plugin\views\field\FieldPluginBase::advancedRender() and
+ // we can safely assume that the Serializer does not introduce XSS when
+ // transforming the array into the particular format, hence we can safely
+ // mark the whole serialized string as safe.
+ SafeMarkup::set($output);
+ }
+ return $output;
}
/**
diff --git a/core/modules/rest/src/Tests/Views/StyleSerializerTest.php b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
index 12eb653..908fc25 100644
--- a/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
+++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
@@ -317,4 +317,20 @@ public function testFieldapiField() {
$this->assertEqual($result[0]['body'], $node->body->processed);
}
+ /**
+ * Tests the field row style for XSS using fieldapi fields.
+ */
+ public function testFieldapiFieldXSS() {
+ $this->drupalCreateContentType(array('type' => 'page'));
+ $node = $this->drupalCreateNode();
+ $node_body = '';
+ $node->body = array(
+ 'value' => $node_body,
+ 'format' => filter_default_format(),
+ );
+ $node->save();
+ $result = $this->drupalGetJSON('test/serialize/node-field');
+ $this->assertEqual($result[0]['nid'], $node->id());
+ $this->assertTrue(strpos($this->getRawContent(), " 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 c433e30..7170479 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,