diff --git a/core/lib/Drupal/Core/Utility/token.api.php b/core/lib/Drupal/Core/Utility/token.api.php index fce47a6..1763c0a 100644 --- a/core/lib/Drupal/Core/Utility/token.api.php +++ b/core/lib/Drupal/Core/Utility/token.api.php @@ -95,7 +95,7 @@ function hook_tokens($type, $tokens, array $data, array $options, \Drupal\Core\R break; case 'title': - $replacements[$original] = Html::escape($node->getTitle()); + $replacements[$original] = $node->getTitle(); break; case 'edit-url': @@ -105,7 +105,7 @@ function hook_tokens($type, $tokens, array $data, array $options, \Drupal\Core\R // Default values for the chained tokens handled below. case 'author': $account = $node->getOwner() ? $node->getOwner() : User::load(0); - $replacements[$original] = Html::escape($account->label()); + $replacements[$original] = $account->label(); $bubbleable_metadata->addCacheableDependency($account); break; diff --git a/core/modules/action/src/Plugin/Action/MessageAction.php b/core/modules/action/src/Plugin/Action/MessageAction.php index c87b606..0e4a170 100644 --- a/core/modules/action/src/Plugin/Action/MessageAction.php +++ b/core/modules/action/src/Plugin/Action/MessageAction.php @@ -12,6 +12,8 @@ use Drupal\Core\Action\ConfigurableActionBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Render\Renderer; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Utility\Token; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -28,16 +30,33 @@ class MessageAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface { /** + * The renderer. + * + * We depend on the XSS filtering in + * \Drupal\Core\Render|Renderer::renderPlain(), so we cannot type hint on + * \Drupal\Core\Render\RendererInterface. + * + * @var \Drupal\Core\Render\Renderer + */ + protected $renderer; + + /** * @var \Drupal\Core\Utility\Token */ protected $token; /** * Constructs a MessageAction object. + * + * @param \Drupal\Core\Render\Renderer $renderer + * The renderer. We depend on the XSS filtering in + * \Drupal\Core\Render|Renderer::renderPlain(), so we cannot type hint on + * \Drupal\Core\Render\RendererInterface. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, Token $token) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, Token $token, Renderer $renderer) { parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->renderer = $renderer; $this->token = $token; } @@ -45,7 +64,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static($configuration, $plugin_id, $plugin_definition, $container->get('token')); + return new static($configuration, $plugin_id, $plugin_definition, $container->get('token'), $container->get('renderer')); } /** @@ -55,7 +74,15 @@ public function execute($entity = NULL) { if (empty($this->configuration['node'])) { $this->configuration['node'] = $entity; } - $message = $this->token->replace(Xss::filterAdmin($this->configuration['message']), $this->configuration); + + // Depend on \Drupal\Core\Render\Renderer::renderPlain()'s sanitization of + // the message, so it won't be fully escaped during the main rendering + // process. + $build = [ + '#markup' => $this->token->replace($this->configuration['message'], $this->configuration), + ]; + $message = $this->renderer->renderPlain($build); + drupal_set_message($message); } diff --git a/core/modules/comment/comment.tokens.inc b/core/modules/comment/comment.tokens.inc index 5cf40ff..3293c57 100644 --- a/core/modules/comment/comment.tokens.inc +++ b/core/modules/comment/comment.tokens.inc @@ -134,7 +134,7 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM // Poster identity information for comments. case 'hostname': - $replacements[$original] = Html::escape($comment->getHostname()); + $replacements[$original] = $comment->getHostname(); break; case 'mail': @@ -144,15 +144,15 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM if ($comment->getOwnerId()) { $bubbleable_metadata->addCacheableDependency($comment->getOwner()); } - $replacements[$original] = Html::escape($mail); + $replacements[$original] = $mail; break; case 'homepage': - $replacements[$original] = UrlHelper::filterBadProtocol($comment->getHomepage()); + $replacements[$original] = $comment->getHomepage(); break; case 'title': - $replacements[$original] = Xss::filter($comment->getSubject()); + $replacements[$original] = $comment->getSubject(); break; case 'body': @@ -160,7 +160,7 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM break; case 'langcode': - $replacements[$original] = Html::escape($comment->language()->getId()); + $replacements[$original] = $comment->language()->getId(); break; // Comment related URLs. @@ -181,14 +181,14 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM if ($comment->getOwnerId()) { $bubbleable_metadata->addCacheableDependency($comment->getOwner()); } - $replacements[$original] = Xss::filter($name); + $replacements[$original] = $name; break; case 'parent': if ($comment->hasParentComment()) { $parent = $comment->getParentComment(); $bubbleable_metadata->addCacheableDependency($parent); - $replacements[$original] = Xss::filter($parent->getSubject()); + $replacements[$original] = $parent->getSubject(); } break; @@ -208,7 +208,7 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM $entity = $comment->getCommentedEntity(); $bubbleable_metadata->addCacheableDependency($entity); $title = $entity->label(); - $replacements[$original] = Xss::filter($title); + $replacements[$original] = $title; break; } } diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 45376dd..df216d6 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -951,7 +951,6 @@ function file_tokens($type, $tokens, array $data, array $options, BubbleableMeta else { $langcode = NULL; } - $sanitize = !empty($options['sanitize']); $replacements = array(); @@ -968,15 +967,15 @@ function file_tokens($type, $tokens, array $data, array $options, BubbleableMeta // Essential file data case 'name': - $replacements[$original] = $sanitize ? Html::escape($file->getFilename()) : $file->getFilename(); + $replacements[$original] = $file->getFilename(); break; case 'path': - $replacements[$original] = $sanitize ? Html::escape($file->getFileUri()) : $file->getFileUri(); + $replacements[$original] = $file->getFileUri(); break; case 'mime': - $replacements[$original] = $sanitize ? Html::escape($file->getMimeType()) : $file->getMimeType(); + $replacements[$original] = $file->getMimeType(); break; case 'size': @@ -984,7 +983,7 @@ function file_tokens($type, $tokens, array $data, array $options, BubbleableMeta break; case 'url': - $replacements[$original] = $sanitize ? Html::escape(file_create_url($file->getFileUri())) : file_create_url($file->getFileUri()); + $replacements[$original] = file_create_url($file->getFileUri()); break; // These tokens are default variations on the chained tokens handled below. @@ -1004,7 +1003,7 @@ function file_tokens($type, $tokens, array $data, array $options, BubbleableMeta $owner = $file->getOwner(); $bubbleable_metadata->addCacheableDependency($owner); $name = $owner->label(); - $replacements[$original] = $sanitize ? Html::escape($name) : $name; + $replacements[$original] = $name; break; } } diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php index 7722e66..5d0d1a0 100644 --- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php +++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php @@ -271,6 +271,8 @@ public function getUploadLocation($data = array()) { // Replace tokens. $destination = \Drupal::token()->replace($destination, $data); + // @todo Is any valid URI always safe output? If so, handle invalid URIs + // here, and certainly do not return them. return $settings['uri_scheme'] . '://' . $destination; } diff --git a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php index e0974f1..a57c8b8 100644 --- a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php +++ b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php @@ -188,7 +188,6 @@ public function viewElements(FieldItemListInterface $items) { // If the title field value is available, use it for the link text. if (empty($settings['url_only']) && !empty($item->title)) { - // @todo fix this $link_title = \Drupal::token()->replace($item->title, array($entity->getEntityTypeId() => $entity), array('clear' => TRUE)); } diff --git a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php index f597825..26d2547 100644 --- a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php +++ b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php @@ -54,7 +54,6 @@ public function viewElements(FieldItemListInterface $items) { // If the link text field value is available, use it for the text. if (empty($settings['url_only']) && !empty($item->title)) { - // @todo fix this $link_title = \Drupal::token()->replace($item->title, array($entity->getEntityTypeId() => $entity), array('clear' => TRUE)); } diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc index fdc326a..68a516c 100644 --- a/core/modules/node/node.tokens.inc +++ b/core/modules/node/node.tokens.inc @@ -114,16 +114,16 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta break; case 'type': - $replacements[$original] = Html::escape($node->getType()); + $replacements[$original] = $node->getType(); break; case 'type-name': $type_name = node_get_type_label($node); - $replacements[$original] = Html::escape($type_name); + $replacements[$original] = $type_name; break; case 'title': - $replacements[$original] = Html::escape($node->getTitle()); + $replacements[$original] = $node->getTitle(); break; case 'body': @@ -162,7 +162,7 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta break; case 'langcode': - $replacements[$original] = Html::escape($node->language()->getId()); + $replacements[$original] = $node->language()->getId(); break; case 'url': @@ -177,7 +177,7 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta case 'author': $account = $node->getOwner() ? $node->getOwner() : User::load(0); $bubbleable_metadata->addCacheableDependency($account); - $replacements[$original] = Html::escape($account->label()); + $replacements[$original] = $account->label(); break; case 'created': diff --git a/core/modules/system/system.tokens.inc b/core/modules/system/system.tokens.inc index 222e49f..a3af309 100644 --- a/core/modules/system/system.tokens.inc +++ b/core/modules/system/system.tokens.inc @@ -109,14 +109,14 @@ function system_tokens($type, $tokens, array $data, array $options, BubbleableMe $config = \Drupal::config('system.site'); $bubbleable_metadata->addCacheableDependency($config); $site_name = $config->get('name'); - $replacements[$original] = Html::escape($site_name); + $replacements[$original] = $site_name; break; case 'slogan': $config = \Drupal::config('system.site'); $bubbleable_metadata->addCacheableDependency($config); $slogan = $config->get('slogan'); - $replacements[$original] = Xss::filterAdmin($slogan); + $replacements[$original] = $slogan; break; case 'mail': @@ -176,7 +176,7 @@ function system_tokens($type, $tokens, array $data, array $options, BubbleableMe break; case 'raw': - $replacements[$original] = Html::escape($date); + $replacements[$original] = $date; break; } } diff --git a/core/modules/taxonomy/taxonomy.tokens.inc b/core/modules/taxonomy/taxonomy.tokens.inc index 1406cc1..6c33f57 100644 --- a/core/modules/taxonomy/taxonomy.tokens.inc +++ b/core/modules/taxonomy/taxonomy.tokens.inc @@ -108,7 +108,7 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable break; case 'name': - $replacements[$original] = Html::escape($term->getName()); + $replacements[$original] = $term->getName(); break; case 'description': @@ -130,14 +130,14 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable case 'vocabulary': $vocabulary = Vocabulary::load($term->bundle()); $bubbleable_metadata->addCacheableDependency($vocabulary); - $replacements[$original] = Html::escape($vocabulary->label()); + $replacements[$original] = $vocabulary->label(); break; case 'parent': if ($parents = $taxonomy_storage->loadParents($term->id())) { $parent = array_pop($parents); $bubbleable_metadata->addCacheableDependency($parent); - $replacements[$original] = Html::escape($parent->getName()); + $replacements[$original] = $parent->getName(); } break; } @@ -164,11 +164,11 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable break; case 'name': - $replacements[$original] = Html::escape($vocabulary->label()); + $replacements[$original] = $vocabulary->label(); break; case 'description': - $replacements[$original] = Xss::filter($vocabulary->getDescription()); + $replacements[$original] = $vocabulary->getDescription(); break; case 'term-count': diff --git a/core/modules/tour/src/Plugin/tour/tip/TipPluginText.php b/core/modules/tour/src/Plugin/tour/tip/TipPluginText.php index bedb4bd..f525d83 100644 --- a/core/modules/tour/src/Plugin/tour/tip/TipPluginText.php +++ b/core/modules/tour/src/Plugin/tour/tip/TipPluginText.php @@ -121,7 +121,7 @@ public function getAttributes() { */ public function getOutput() { $output = '

' . Html::escape($this->getLabel()) . '

'; - $output .= '

' . Xss::filterAdmin($this->token->replace($this->getBody())) . '

'; + $output .= '

' . $this->token->replace($this->getBody()) . '

'; return array('#markup' => $output); } diff --git a/core/modules/user/user.tokens.inc b/core/modules/user/user.tokens.inc index 455cc6b..c8b88e6 100644 --- a/core/modules/user/user.tokens.inc +++ b/core/modules/user/user.tokens.inc @@ -95,11 +95,11 @@ function user_tokens($type, $tokens, array $data, array $options, BubbleableMeta if ($account->isAnonymous()) { $bubbleable_metadata->addCacheableDependency(\Drupal::config('user.settings')); } - $replacements[$original] = Html::escape($name); + $replacements[$original] = $name; break; case 'mail': - $replacements[$original] = Html::escape($account->getEmail()); + $replacements[$original] = $account->getEmail(); break; case 'url': diff --git a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/area/TestExample.php b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/area/TestExample.php index 1afb7d4..c02272d 100644 --- a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/area/TestExample.php +++ b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/area/TestExample.php @@ -7,6 +7,7 @@ namespace Drupal\views_test_data\Plugin\views\area; +use Drupal\Component\Utility\Xss; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; use Drupal\views\Plugin\views\area\AreaPluginBase; @@ -52,7 +53,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { public function render($empty = FALSE) { if (!$empty || !empty($this->options['empty'])) { return array( - '#markup' => $this->globalTokenReplace($this->options['string']), + '#markup' => Xss::filter($this->globalTokenReplace($this->options['string'])), ); } return array(); diff --git a/core/modules/views/views.tokens.inc b/core/modules/views/views.tokens.inc index f4ddca7..cbdfea9 100644 --- a/core/modules/views/views.tokens.inc +++ b/core/modules/views/views.tokens.inc @@ -85,11 +85,11 @@ function views_tokens($type, $tokens, array $data, array $options, BubbleableMet foreach ($tokens as $name => $original) { switch ($name) { case 'label': - $replacements[$original] = Html::escape($view->storage->label()); + $replacements[$original] = $view->storage->label(); break; case 'description': - $replacements[$original] = Html::escape($view->storage->get('description')); + $replacements[$original] = $view->storage->get('description'); break; case 'id': @@ -98,7 +98,7 @@ function views_tokens($type, $tokens, array $data, array $options, BubbleableMet case 'title': $title = $view->getTitle(); - $replacements[$original] = Html::escape($title); + $replacements[$original] = $title; break; case 'url':