diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index a9ea984..62a60e8 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -16,6 +16,7 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Site\Settings; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Utility\Title; use Drupal\Core\Utility\Error; use Symfony\Component\ClassLoader\ApcClassLoader; @@ -1186,7 +1187,10 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) } if ($repeat || !in_array($message, $_SESSION['messages'][$type])) { - $_SESSION['messages'][$type][] = $message; + $_SESSION['messages'][$type][] = array( + 'safe' => SafeMarkup::is($message), + 'message' => $message, + ); } // Mark this page as being uncacheable. @@ -1224,6 +1228,14 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) */ function drupal_get_messages($type = NULL, $clear_queue = TRUE) { if ($messages = drupal_set_message()) { + foreach ($messages as $message_type => $message_typed_messages) { + foreach ($message_typed_messages as $key => $message) { + if ($message['safe']) { + $message['message'] = SafeMarkup::create($message['message']); + } + $messages[$message_type][$key] = $message['message']; + } + } if ($type) { if ($clear_queue) { unset($_SESSION['messages'][$type]); diff --git a/core/includes/common.inc b/core/includes/common.inc index 3f0fd1d..73f3c2d 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -29,6 +29,7 @@ use Drupal\Core\EventSubscriber\HtmlViewSubscriber; use Drupal\Core\Routing\GeneratorNotInitializedException; use Drupal\Core\Template\Attribute; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Render\Element; use Drupal\Core\Session\AnonymousUserSession; @@ -485,7 +486,7 @@ function format_xml_elements($array) { $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : String::checkPlain($value)) . "$key>\n"; } } - return $output; + return SafeMarkup::create($output); } /** @@ -886,8 +887,7 @@ function l($text, $path, array $options = array()) { // Sanitize the link text if necessary. $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']); - - return '' . $text . ''; + return SafeMarkup::create('' . $text . ''); } /** @@ -2829,6 +2829,7 @@ function drupal_pre_render_conditional_comments($elements) { * - meta: To provide meta information, such as a page refresh. * - link: To refer to stylesheets and other contextual information. * - script: To load JavaScript. + * This is not HTML escaped, do not pass in user input. * - #attributes: (optional) An array of HTML attributes to apply to the * tag. * - #value: (optional) A string containing tag content, such as inline @@ -2841,7 +2842,9 @@ function drupal_pre_render_conditional_comments($elements) { function drupal_pre_render_html_tag($element) { $attributes = isset($element['#attributes']) ? new Attribute($element['#attributes']) : ''; if (!isset($element['#value'])) { - $markup = '<' . $element['#tag'] . $attributes . " />\n"; + // Attributes are safe and we are assuming people don't use this function + // and second they don't pass unsafe variables to #tag. + $markup = SafeMarkup::create('<' . $element['#tag'] . $attributes . " />\n"); } else { $markup = '<' . $element['#tag'] . $attributes . '>'; @@ -2853,6 +2856,8 @@ function drupal_pre_render_html_tag($element) { $markup .= $element['#value_suffix']; } $markup .= '' . $element['#tag'] . ">\n"; + // @todo Creating safe markup, avoid if possible! + $markup = SafeMarkup::create($markup); } if (!empty($element['#noscript'])) { $element['#markup'] = ''; @@ -3292,6 +3297,7 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) { if (!$is_recursive_call) { _drupal_render_process_post_render_cache($elements); } + $elements['#markup'] = SafeMarkup::create($elements['#markup']); return $elements['#markup']; } } @@ -3336,6 +3342,12 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) { $elements['#children'] = ''; } + // @todo Simplify after https://drupal.org/node/2208061 and + // https://drupal.org/node/2273925 + if (isset($elements['#markup'])) { + $elements['#markup'] = SafeMarkup::create($elements['#markup']); + } + // Assume that if #theme is set it represents an implemented hook. $theme_is_implemented = isset($elements['#theme']); @@ -3359,6 +3371,7 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) { foreach ($children as $key) { $elements['#children'] .= drupal_render($elements[$key], TRUE); } + $elements['#children'] = SafeMarkup::create($elements['#children']); } // If #theme is not implemented and the element has raw #markup as a @@ -3369,7 +3382,7 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) { // required. Eventually #theme_wrappers will expect both #markup and // #children to be a single string as #children. if (!$theme_is_implemented && isset($elements['#markup'])) { - $elements['#children'] = $elements['#markup'] . $elements['#children']; + $elements['#children'] = SafeMarkup::create($elements['#markup'] . $elements['#children']); } // Let the theme functions in #theme_wrappers add markup around the rendered @@ -3453,6 +3466,7 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) { } $elements['#printed'] = TRUE; + $elements['#markup'] = SafeMarkup::create($elements['#markup']); return $elements['#markup']; } @@ -3474,13 +3488,13 @@ function drupal_render_children(&$element, $children_keys = NULL) { if ($children_keys === NULL) { $children_keys = Element::children($element); } - $output = ''; + $markups = array(); foreach ($children_keys as $key) { if (!empty($element[$key])) { - $output .= drupal_render($element[$key]); + $markups[] = drupal_render($element[$key]); } } - return $output; + return SafeMarkup::implode('', $markups); } /** diff --git a/core/includes/errors.inc b/core/includes/errors.inc index 8b0b9e4..b2816b2 100644 --- a/core/includes/errors.inc +++ b/core/includes/errors.inc @@ -8,6 +8,7 @@ use Drupal\Component\Utility\Xss; use Drupal\Core\Page\DefaultHtmlPageRenderer; use Drupal\Core\Utility\Error; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\String; use Symfony\Component\HttpFoundation\Response; @@ -212,7 +213,7 @@ function _drupal_log_error($error, $fatal = FALSE) { // Generate a backtrace containing only scalar argument values. $message .= '
' . format_backtrace($backtrace) . ''; } - drupal_set_message($message, $class, TRUE); + drupal_set_message(SafeMarkup::create($message), $class, TRUE); } if ($fatal) { diff --git a/core/includes/form.inc b/core/includes/form.inc index 5deb493..6c9b323 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -14,6 +14,7 @@ use Drupal\Core\Form\OptGroup; use Drupal\Core\Render\Element; use Drupal\Core\Template\Attribute; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Utility\Color; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -997,7 +998,7 @@ function form_select_options($element, $choices = NULL) { $options .= ''; } } - return $options; + return SafeMarkup::create($options); } /** @@ -1665,7 +1666,7 @@ function theme_tableselect($variables) { // A header can span over multiple cells and in this case the cells // are passed in an array. The order of this array determines the // order in which they are added. - if (!isset($element['#options'][$key][$fieldname]['data']) && is_array($element['#options'][$key][$fieldname])) { + if (is_array($element['#options'][$key][$fieldname]) && !isset($element['#options'][$key][$fieldname]['data'])) { foreach ($element['#options'][$key][$fieldname] as $cell) { $row['data'][] = $cell; } @@ -2011,13 +2012,13 @@ function form_process_machine_name($element, &$form_state) { $element['#machine_name']['suffix'] = '#' . $suffix_id; if ($element['#machine_name']['standalone']) { - $element['#suffix'] .= ' '; + $element['#suffix'] = SafeMarkup::create($element['#suffix'] . ' '); } else { // Append a field suffix to the source form element, which will contain // the live preview of the machine name. $source += array('#field_suffix' => ''); - $source['#field_suffix'] .= ' '; + $source['#field_suffix'] = SafeMarkup::create($source['#field_suffix'] . ' '); $parents = array_merge($element['#machine_name']['source'], array('#field_suffix')); NestedArray::setValue($form_state['complete_form'], $parents, $source['#field_suffix']); diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index f7550b1..f88a994 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -14,6 +14,7 @@ use Drupal\Core\Page\DefaultHtmlPageRenderer; use Drupal\Core\Site\Settings; use Drupal\Core\StringTranslation\Translator\FileTranslation; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -1711,10 +1712,10 @@ function install_finished(&$install_state) { // @todo Temporary hack to satisfy PIFR. // @see https://drupal.org/node/1317548 $pifr_assertion = ' '; - - drupal_set_message(t('Congratulations, you installed @drupal!', array( + $success_message = t('Congratulations, you installed @drupal!', array( '@drupal' => drupal_install_profile_distribution_name(), - )) . $pifr_assertion); + )); + drupal_set_message(SafeMarkup::create($success_message . $pifr_assertion)); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index fbcc619..59a0af5 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -20,6 +20,7 @@ use Drupal\Core\Page\MetaElement; use Drupal\Core\Template\Attribute; use Drupal\Core\Template\RenderWrapper; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Theme\ThemeSettings; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Render\Element; @@ -578,7 +579,7 @@ function _theme($hook, $variables = array()) { $output = ''; if (isset($info['function'])) { if (function_exists($info['function'])) { - $output = $info['function']($variables); + $output = SafeMarkup::create($info['function']($variables)); } } else { @@ -2000,7 +2001,7 @@ function template_preprocess_html(&$variables) { // Construct page title. if ($page->hasTitle()) { $head_title = array( - 'title' => trim(strip_tags($page->getTitle())), + 'title' => SafeMarkup::create(trim(strip_tags($page->getTitle()))), 'name' => String::checkPlain($site_config->get('name')), ); } @@ -2020,7 +2021,7 @@ function template_preprocess_html(&$variables) { } $variables['head_title_array'] = $head_title; - $variables['head_title'] = implode(' | ', $head_title); + $variables['head_title'] = SafeMarkup::implode(' | ', $head_title); // @todo Remove drupal_*_html_head() and refactor accordingly. $html_heads = drupal_get_html_head(FALSE); @@ -2052,7 +2053,10 @@ function template_preprocess_html(&$variables) { // Wrap function calls in an object so they can be called when printed. $variables['head'] = new RenderWrapper(function() use ($page) { - return implode("\n", $page->getMetaElements()) . implode("\n", $page->getLinkElements()); + // HeadElement itself is safe and MetaElement and LinkElement both extend + // it so each element are safe. The result of concatenating a lot of them + // together is also safe. + return SafeMarkup::create(implode("\n", $page->getMetaElements()) . implode("\n", $page->getLinkElements())); }); $variables['styles'] = new RenderWrapper('drupal_get_css'); $variables['scripts'] = new RenderWrapper('drupal_get_js'); diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc index 8a19a49..9bced2e 100644 --- a/core/includes/theme.maintenance.inc +++ b/core/includes/theme.maintenance.inc @@ -7,6 +7,7 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Site\Settings; +use Drupal\Component\Utility\SafeMarkup; /** * Sets up the theming system for maintenance page. @@ -110,6 +111,8 @@ function _drupal_maintenance_theme() { * @param $variables * An associative array containing: * - items: An associative array of maintenance tasks. + * It's the caller's responsibility to ensure this array's items contain no + * dangerous HTML such as SCRIPT tags. * - active: The key for the currently active maintenance task. * * @ingroup themeable @@ -187,6 +190,8 @@ function theme_authorize_report($variables) { * @param $variables * An associative array containing: * - message: The log message. + * It's the caller's responsibility to ensure this string contains no + * dangerous HTML such as SCRIPT tags. * - success: A boolean indicating failure or success. * * @ingroup themeable diff --git a/core/lib/Drupal/Component/Diff/Engine/HWLDFWordAccumulator.php b/core/lib/Drupal/Component/Diff/Engine/HWLDFWordAccumulator.php index 384593b..80dc911 100644 --- a/core/lib/Drupal/Component/Diff/Engine/HWLDFWordAccumulator.php +++ b/core/lib/Drupal/Component/Diff/Engine/HWLDFWordAccumulator.php @@ -4,6 +4,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; +use Drupal\Component\Utility\SafeMarkup; /** * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3 @@ -46,7 +47,9 @@ protected function _flushGroup($new_tag) { protected function _flushLine($new_tag) { $this->_flushGroup($new_tag); if ($this->line != '') { - array_push($this->lines, $this->line); + // @todo - this is probably not the right place to do this. To be + // addressed in https://drupal.org/node/2280963 + array_push($this->lines, SafeMarkup::create($this->line)); } else { // make empty lines visible by inserting an NBSP diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php new file mode 100644 index 0000000..f2fc295 --- /dev/null +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php @@ -0,0 +1,57 @@ + $string) { + if (!isset(static::$safeStrings[$string])) { + $array[$key] = String::checkPlain($string); + } + } + return SafeMarkup::create(implode($delimiter, $array)); + } + +} diff --git a/core/lib/Drupal/Component/Utility/String.php b/core/lib/Drupal/Component/Utility/String.php index 9796a5f..a4068d5 100644 --- a/core/lib/Drupal/Component/Utility/String.php +++ b/core/lib/Drupal/Component/Utility/String.php @@ -7,6 +7,8 @@ namespace Drupal\Component\Utility; +use Drupal\Component\Utility\SafeMarkup; + /** * Provides helpers to operate on strings. * @@ -31,7 +33,7 @@ class String { * @see drupal_validate_utf8() */ public static function checkPlain($text) { - return htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); + return SafeMarkup::create(htmlspecialchars($text, ENT_QUOTES, 'UTF-8')); } /** @@ -65,7 +67,8 @@ public static function decodeEntities($text) { * addition to formatting it. * * @param $string - * A string containing placeholders. + * A string containing placeholders. The string itself is not escaped, any + * unsafe content must be in $args and inserted via placeholders. * @param $args * An associative array of replacements to make. Occurrences in $string of * any key in $args are replaced with the corresponding value, after @@ -108,7 +111,7 @@ public static function format($string, array $args = array()) { // Pass-through. } } - return strtr($string, $args); + return SafeMarkup::create(strtr($string, $args)); } /** @@ -123,7 +126,8 @@ public static function format($string, array $args = array()) { * The formatted text (html). */ public static function placeholder($text) { - return '' . static::checkPlain($text) . ''; + return SafeMarkup::create('' . static::checkPlain($text) . ''); } + } diff --git a/core/lib/Drupal/Component/Utility/Xss.php b/core/lib/Drupal/Component/Utility/Xss.php index dc49913..d709cca 100644 --- a/core/lib/Drupal/Component/Utility/Xss.php +++ b/core/lib/Drupal/Component/Utility/Xss.php @@ -7,6 +7,8 @@ namespace Drupal\Component\Utility; +use Drupal\Component\Utility\SafeMarkup; + /** * Provides helper to filter for cross-site scripting. * @@ -90,7 +92,7 @@ public static function filter($string, $html_tags = array('a', 'em', 'strong', ' $splitter = function ($matches) use ($html_tags, $mode) { return static::split($matches[1], $html_tags, $mode); }; - return preg_replace_callback('% + return SafeMarkup::create(preg_replace_callback('% ( <(?=[^a-zA-Z!/]) # a lone < | # or @@ -99,7 +101,7 @@ public static function filter($string, $html_tags = array('a', 'em', 'strong', ' <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string | # or > # just a > - )%x', $splitter, $string); + )%x', $splitter, $string)); } /** diff --git a/core/lib/Drupal/Core/Controller/ExceptionController.php b/core/lib/Drupal/Core/Controller/ExceptionController.php index 320cb06..41f0b0f 100644 --- a/core/lib/Drupal/Core/Controller/ExceptionController.php +++ b/core/lib/Drupal/Core/Controller/ExceptionController.php @@ -18,6 +18,7 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\HttpKernelInterface; use Drupal\Component\Utility\String; +use Drupal\Component\Utility\SafeMarkup; use Symfony\Component\Debug\Exception\FlattenException; use Drupal\Core\ContentNegotiation; use Drupal\Core\Utility\Error; @@ -312,7 +313,7 @@ public function on500Html(FlattenException $exception, Request $request) { // Generate a backtrace containing only scalar argument values. $message .= '
' . Error::formatFlattenedBacktrace($backtrace) . ''; } - drupal_set_message($message, $class, TRUE); + drupal_set_message(SafeMarkup::create($message), $class, TRUE); } $content = $this->t('The website has encountered an error. Please try again later.'); diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index c72cafc..dbd1966 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -95,8 +95,6 @@ public static function registerTwig(ContainerBuilder $container) { // When in the installer, twig_cache must be FALSE until we know the // files folder is writable. 'cache' => drupal_installation_attempted() ? FALSE : Settings::get('twig_cache', TRUE), - // @todo Remove in followup issue - // @see http://drupal.org/node/1712444. 'autoescape' => FALSE, 'debug' => Settings::get('twig_debug', FALSE), 'auto_reload' => Settings::get('twig_auto_reload', NULL), diff --git a/core/lib/Drupal/Core/Page/HeadElement.php b/core/lib/Drupal/Core/Page/HeadElement.php index 85055ad..8bd2889 100644 --- a/core/lib/Drupal/Core/Page/HeadElement.php +++ b/core/lib/Drupal/Core/Page/HeadElement.php @@ -8,9 +8,12 @@ namespace Drupal\Core\Page; use Drupal\Core\Template\Attribute; +use Drupal\Component\Utility\SafeMarkup; /** * This class represents an HTML element that appears in the HEAD tag. + * + * @see template_preprocess_html() */ class HeadElement { @@ -52,7 +55,7 @@ public function __toString() { if ($this->noScript) { $string = ""; } - return $string; + return SafeMarkup::create($string); } /** diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationManager.php b/core/lib/Drupal/Core/StringTranslation/TranslationManager.php index 23deb8c..d2652db 100644 --- a/core/lib/Drupal/Core/StringTranslation/TranslationManager.php +++ b/core/lib/Drupal/Core/StringTranslation/TranslationManager.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\StringTranslation\Translator\TranslatorInterface; +use Drupal\Component\Utility\SafeMarkup; /** * Defines a chained translation implementation combining multiple translators. @@ -140,7 +141,7 @@ public function translate($string, array $args = array(), array $options = array $string = $translation === FALSE ? $string : $translation; if (empty($args)) { - return $string; + return SafeMarkup::create($string); } else { return String::format($string, $args); @@ -160,7 +161,7 @@ public function formatPlural($count, $singular, $plural, array $args = array(), $translated_array = explode(LOCALE_PLURAL_DELIMITER, $translated_strings); if ($count == 1) { - return $translated_array[0]; + return SafeMarkup::create($translated_array[0]); } // Get the plural index through the gettext formula. @@ -168,20 +169,21 @@ public function formatPlural($count, $singular, $plural, array $args = array(), $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1; if ($index == 0) { // Singular form. - return $translated_array[0]; + $return = $translated_array[0]; } else { if (isset($translated_array[$index])) { // N-th plural form. - return $translated_array[$index]; + $return = $translated_array[$index]; } else { // If the index cannot be computed or there's no translation, use // the second plural form as a fallback (which allows for most flexiblity // with the replaceable @count value). - return $translated_array[1]; + $return = $translated_array[1]; } } + return SafeMarkup::create($return); } /** diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php index ead5d05..56aa58a 100644 --- a/core/lib/Drupal/Core/Template/Attribute.php +++ b/core/lib/Drupal/Core/Template/Attribute.php @@ -6,8 +6,7 @@ */ namespace Drupal\Core\Template; - -use Drupal\Component\Utility\String; +use Drupal\Component\Utility\SafeMarkup; /** * A class that can be used for collecting then rendering HTML attributtes. @@ -117,7 +116,7 @@ public function __toString() { $return .= ' ' . $rendered; } } - return $return; + return SafeMarkup::create($return); } /** diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 4cb31ca..b59f902 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -47,6 +47,9 @@ public function getFilters() { // @see TwigNodeTrans::compileString() new \Twig_SimpleFilter('passthrough', 'twig_raw_filter'), new \Twig_SimpleFilter('placeholder', 'twig_raw_filter'), + new \Twig_SimpleFilter('safe', '\Drupal\Core\Template\SafeMarkup::create'), + // Replace twig's raw filter with our own. + new \Twig_SimpleFilter('twig_raw', 'twig_raw'), // Array filters. new \Twig_SimpleFilter('without', 'twig_without'), diff --git a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php index a24ee50..46bf511 100644 --- a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php +++ b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php @@ -39,6 +39,12 @@ function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env) { $line ); } + // Change the 'raw' filter to our own 'twig_raw' filter. + else if ($node instanceof \Twig_Node_Expression_Filter && + 'raw' == $node->getNode('filter')->getAttribute('value')) { + // Use our own twig_raw filter that returns a Twig_Markup object. + $node->getNode('filter')->setAttribute('value', 'twig_raw'); + } return $node; } diff --git a/core/lib/Drupal/Core/Utility/Error.php b/core/lib/Drupal/Core/Utility/Error.php index e3b084f..cc46d9a 100644 --- a/core/lib/Drupal/Core/Utility/Error.php +++ b/core/lib/Drupal/Core/Utility/Error.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Xss; +use Drupal\Component\Utility\SafeMarkup; /** * Drupal error utility class. @@ -101,7 +102,7 @@ public static function renderExceptionSafe(\Exception $exception) { // no longer function correctly (as opposed to a user-triggered error), so // we assume that it is safe to include a verbose backtrace. $output .= '
' . static::formatBacktrace($backtrace) . ''; - return $output; + return SafeMarkup::create($output); } /** diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php index b547d95..34aab97 100644 --- a/core/lib/Drupal/Core/Utility/LinkGenerator.php +++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php @@ -11,8 +11,9 @@ use Drupal\Component\Utility\String; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Path\AliasManagerInterface; -use Drupal\Core\Template\Attribute; use Drupal\Core\Routing\UrlGeneratorInterface; +use Drupal\Core\Template\Attribute; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Url; /** @@ -122,8 +123,7 @@ public function generateFromUrl($text, Url $url) { // Sanitize the link text if necessary. $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']); - - return '' . $text . ''; + return SafeMarkup::create('' . $text . ''); } /** diff --git a/core/modules/book/book.admin.inc b/core/modules/book/book.admin.inc index 4379582..fc9ec66 100644 --- a/core/modules/book/book.admin.inc +++ b/core/modules/book/book.admin.inc @@ -6,6 +6,7 @@ */ use Drupal\Core\Render\Element; +use Drupal\Component\Utility\SafeMarkup; /** * Returns HTML for a book administration form. @@ -36,9 +37,9 @@ function theme_book_admin_table($variables) { $indentation = array('#theme' => 'indentation', '#size' => $form[$key]['depth']['#value'] - 2); $data = array( - drupal_render($indentation) . drupal_render($form[$key]['title']), + SafeMarkup::create(drupal_render($indentation) . drupal_render($form[$key]['title'])), drupal_render($form[$key]['weight']), - drupal_render($form[$key]['pid']) . drupal_render($form[$key]['nid']), + SafeMarkup::create(drupal_render($form[$key]['pid']) . drupal_render($form[$key]['nid'])), ); $links = array(); $links['view'] = array( diff --git a/core/modules/book/src/BookExport.php b/core/modules/book/src/BookExport.php index f28f855..c447ad4 100644 --- a/core/modules/book/src/BookExport.php +++ b/core/modules/book/src/BookExport.php @@ -8,6 +8,7 @@ namespace Drupal\book; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Component\Utility\SafeMarkup; use Drupal\node\NodeInterface; /** @@ -105,18 +106,16 @@ protected function exportTraverse(array $tree, $callable) { // If there is no valid callable, use the default callback. $callable = !empty($callable) ? $callable : array($this, 'bookNodeExport'); - $output = ''; + $build = array(); foreach ($tree as $data) { // Note- access checking is already performed when building the tree. if ($node = $this->nodeStorage->load($data['link']['nid'])) { $children = $data['below'] ? $this->exportTraverse($data['below'], $callable) : ''; - - $callable_output = call_user_func($callable, $node, $children); - $output .= drupal_render($callable_output); + $build[] = call_user_func($callable, $node, $children); } } - return $output; + return drupal_render($build); } /** diff --git a/core/modules/color/color.module b/core/modules/color/color.module index 335e0ad..56d617d 100644 --- a/core/modules/color/color.module +++ b/core/modules/color/color.module @@ -8,6 +8,7 @@ use Drupal\Component\Utility\Bytes; use Drupal\Component\Utility\Environment; use Drupal\Component\Utility\String; +use Drupal\Component\Utility\SafeMarkup; use Symfony\Component\HttpFoundation\Request; /** @@ -275,7 +276,7 @@ function template_preprocess_color_scheme_form(&$variables) { // Attempt to load preview HTML if the theme provides it. $preview_html_path = DRUPAL_ROOT . '/' . (isset($info['preview_html']) ? drupal_get_path('theme', $theme) . '/' . $info['preview_html'] : drupal_get_path('module', 'color') . '/preview.html'); - $variables['html_preview'] = file_get_contents($preview_html_path); + $variables['html_preview'] = SafeMarkup::create(file_get_contents($preview_html_path)); } /** diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 8fc5537..d634f4b 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -980,7 +980,7 @@ function comment_node_update_index(EntityInterface $node, $langcode) { } } - $return = ''; + $build = array(); if ($index_comments) { foreach (\Drupal::service('comment.manager')->getFields('node') as $field_name => $info) { @@ -994,12 +994,11 @@ function comment_node_update_index(EntityInterface $node, $langcode) { if ($node->get($field_name)->status && $cids = comment_get_thread($node, $field_name, $mode, $comments_per_page)) { $comments = entity_load_multiple('comment', $cids); comment_prepare_thread($comments); - $build = comment_view_multiple($comments); - $return .= drupal_render($build); + $build[] = comment_view_multiple($comments); } } } - return $return; + return drupal_render($build); } /** diff --git a/core/modules/config/src/Tests/ConfigImportUITest.php b/core/modules/config/src/Tests/ConfigImportUITest.php index b95a666..164a506 100644 --- a/core/modules/config/src/Tests/ConfigImportUITest.php +++ b/core/modules/config/src/Tests/ConfigImportUITest.php @@ -383,7 +383,8 @@ function testImportErrorLog() { // Attempt to import configuration and verify that an error message appears. $this->drupalPostForm(NULL, array(), t('Import all')); - $this->assertText(String::format('Deleted and replaced configuration entity "@name"', array('@name' => $name_secondary))); + // @todo Revisit for a better solution. + $this->assertText(String::checkPlain(String::format('Deleted and replaced configuration entity "@name"', array('@name' => $name_secondary)))); $this->assertText(t('The configuration was imported with errors.')); $this->assertNoText(t('The configuration was imported successfully.')); $this->assertText(t('There are no configuration changes.')); diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 4e0157d..5f272f3 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -9,6 +9,7 @@ use Drupal\Core\Config\ConfigImporter; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Extension\Extension; +use Drupal\Component\Utility\SafeMarkup; use Symfony\Component\HttpFoundation\Request; /* @@ -266,7 +267,7 @@ function field_entity_bundle_delete($entity_type, $bundle) { * UTF-8. */ function field_filter_xss($string) { - return Html::normalize(Xss::filter($string, _field_filter_xss_allowed_tags())); + return SafeMarkup::create(Html::normalize(Xss::filter($string, _field_filter_xss_allowed_tags()))); } /** diff --git a/core/modules/field/src/Plugin/views/field/Field.php b/core/modules/field/src/Plugin/views/field/Field.php index d336db6..b70e913 100644 --- a/core/modules/field/src/Plugin/views/field/Field.php +++ b/core/modules/field/src/Plugin/views/field/Field.php @@ -7,12 +7,14 @@ namespace Drupal\field\Plugin\views\field; +use Drupal\Component\Utility\String; use Drupal\Component\Utility\Xss; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Field\FieldDefinition; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Render\Element; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Entity\ContentEntityDatabaseStorage; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FormatterPluginManager; @@ -687,11 +689,11 @@ public function submitGroupByForm(&$form, &$form_state) { protected function renderItems($items) { if (!empty($items)) { if (!$this->options['group_rows']) { - return implode('', $items); + return SafeMarkup::implode('', $items); } if ($this->options['multi_type'] == 'separator') { - return implode(Xss::filterAdmin($this->options['separator']), $items); + return SafeMarkup::create(implode(Xss::filterAdmin($this->options['separator']), $items)); } else { $item_list = array( diff --git a/core/modules/field_ui/src/DisplayOverviewBase.php b/core/modules/field_ui/src/DisplayOverviewBase.php index 2a6f46f..757332a 100644 --- a/core/modules/field_ui/src/DisplayOverviewBase.php +++ b/core/modules/field_ui/src/DisplayOverviewBase.php @@ -13,6 +13,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; +use Drupal\Component\Utility\SafeMarkup; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -398,7 +399,7 @@ protected function buildFieldRow(FieldDefinitionInterface $field_definition, Ent if (!empty($summary)) { $field_row['settings_summary'] = array( - '#markup' => '
Theme hook suggestions:
-{{ theme_hook_suggestions|join("
") }}
Theme hook suggestions:
-{{ theme_hook_suggestions|join("
") }}
' . $data . '
'; - return $output; + return SafeMarkup::create($output); } $header = array(); @@ -267,7 +271,7 @@ function theme_update_report($variables) { $row_key = isset($project['title']) ? drupal_strtolower($project['title']) : drupal_strtolower($project['name']); $rows[$project['project_type']][$row_key] = array( 'class' => array($class), - 'data' => array($row), + 'data' => array(SafeMarkup::create($row)), ); } @@ -303,7 +307,7 @@ function theme_update_report($variables) { ); drupal_render($assets); - return $output; + return SafeMarkup::create($output); } /** diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index 5eb5765..8c52f96 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -11,6 +11,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\UrlHelper; use Drupal\Component\Utility\Xss; +use Drupal\Component\Utility\SafeMarkup; use Drupal\views\Plugin\views\HandlerBase; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\ResultRow; @@ -890,7 +891,7 @@ public function buildOptionsForm(&$form, &$form_state) { $form['alter']['help'] = array( '#type' => 'details', '#title' => t('Replacement patterns'), - '#value' => $output, + '#value' => SafeMarkup::create($output), '#states' => array( 'visible' => array( array( @@ -1172,6 +1173,9 @@ public function advancedRender(ResultRow $values) { $this->last_render = $this->renderText($alter); } } + // @TODO: this is very dicey! + $this->last_render = SafeMarkup::create($this->last_render); + return $this->last_render; } diff --git a/core/modules/views/src/Plugin/views/style/Rss.php b/core/modules/views/src/Plugin/views/style/Rss.php index 0a6f8cb..2ef3c96 100644 --- a/core/modules/views/src/Plugin/views/style/Rss.php +++ b/core/modules/views/src/Plugin/views/style/Rss.php @@ -6,6 +6,7 @@ */ namespace Drupal\views\Plugin\views\style; +use Drupal\Component\Utility\SafeMarkup; /** * Default style plugin to render an RSS feed. @@ -138,7 +139,7 @@ public function render() { '#theme' => $this->themeFunctions(), '#view' => $this->view, '#options' => $this->options, - '#rows' => $rows, + '#rows' => SafeMarkup::create($rows), ); unset($this->view->row_index); return drupal_render($build); diff --git a/core/modules/views/templates/views-view-summary-unformatted.html.twig b/core/modules/views/templates/views-view-summary-unformatted.html.twig index 9120dd4..99c84ce 100644 --- a/core/modules/views/templates/views-view-summary-unformatted.html.twig +++ b/core/modules/views/templates/views-view-summary-unformatted.html.twig @@ -20,7 +20,7 @@ */ #} {% for row in rows %} - {{ options.inline ? ' + {{ options.inline ? ' {% if row.separator -%} {{ row.separator }} {%- endif %} diff --git a/core/modules/views/views.module b/core/modules/views/views.module index cb9024b..3fbfb11 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -14,6 +14,7 @@ use Drupal\Core\Database\Query\AlterableInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Render\Element; +use Drupal\Component\Utility\SafeMarkup; use Drupal\views\Plugin\Derivative\ViewsLocalTask; use Drupal\Core\Template\AttributeArray; use Drupal\views\ViewExecutable; diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc index 7d1060c..ddaeac4 100644 --- a/core/modules/views/views.theme.inc +++ b/core/modules/views/views.theme.inc @@ -8,6 +8,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Xss; use Drupal\Core\Template\Attribute; +use Drupal\Component\Utility\SafeMarkup; use Drupal\views\Form\ViewsForm; use Drupal\views\ViewExecutable; @@ -541,7 +542,8 @@ function template_preprocess_views_view_table(&$variables) { '#theme' => 'tablesort_indicator', '#style' => $initial, ); - $label .= drupal_render($tablesort_indicator); + $markup = drupal_render($tablesort_indicator); + $label = SafeMarkup::concat($label, $markup); } $query['order'] = $field; @@ -632,7 +634,7 @@ function template_preprocess_views_view_table(&$variables) { $field_output = $handler->getField($num, $field); $element_type = $fields[$field]->elementType(TRUE, TRUE); if ($element_type) { - $field_output = '<' . $element_type . '>' . $field_output . '' . $element_type . '>'; + $field_output = SafeMarkup::concat(SafeMarkup::create('<' . $element_type . '>'), $field_output, SafeMarkup::create('' . $element_type . '>')); } // Only bother with separators and stuff if the field shows up. @@ -640,13 +642,13 @@ function template_preprocess_views_view_table(&$variables) { // Place the field into the column, along with an optional separator. if (!empty($column_reference['content'])) { if (!empty($options['info'][$column]['separator'])) { - $column_reference['content'] .= Xss::filterAdmin($options['info'][$column]['separator']); + $column_reference['content'] = SafeMarkup::concat($column_reference['content'], Xss::filterAdmin($options['info'][$column]['separator'])); } } else { $column_reference['content'] = ''; } - $column_reference['content'] .= $field_output; + $column_reference['content'] = SafeMarkup::concat($column_reference['content'], $field_output); } } $column_reference['attributes'] = new Attribute($column_reference['attributes']); diff --git a/core/modules/views_ui/src/Controller/ViewsUIController.php b/core/modules/views_ui/src/Controller/ViewsUIController.php index a09b063..72aff09 100644 --- a/core/modules/views_ui/src/Controller/ViewsUIController.php +++ b/core/modules/views_ui/src/Controller/ViewsUIController.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\Controller\ControllerBase; +use Drupal\Component\Utility\SafeMarkup; use Drupal\views\ViewExecutable; use Drupal\views\ViewStorageInterface; use Drupal\views\Views; @@ -92,7 +93,7 @@ public function reportFields() { foreach ($views as $view) { $rows[$field_name]['data'][1][] = $this->l($view, 'views_ui.edit', array('view' => $view)); } - $rows[$field_name]['data'][1] = implode(', ', $rows[$field_name]['data'][1]); + $rows[$field_name]['data'][1] = SafeMarkup::implode(', ', $rows[$field_name]['data'][1]); } // Sort rows by field name. @@ -120,7 +121,7 @@ public function reportPlugins() { foreach ($row['views'] as $row_name => $view) { $row['views'][$row_name] = $this->l($view, 'views_ui.edit', array('view' => $view)); } - $row['views'] = implode(', ', $row['views']); + $row['views'] = SafeMarkup::implode(', ', $row['views']); } // Sort rows by field name. diff --git a/core/modules/views_ui/src/ViewListBuilder.php b/core/modules/views_ui/src/ViewListBuilder.php index a80d33a..610be5a 100644 --- a/core/modules/views_ui/src/ViewListBuilder.php +++ b/core/modules/views_ui/src/ViewListBuilder.php @@ -13,6 +13,7 @@ use Drupal\Core\Config\Entity\ConfigEntityListBuilder; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Component\Utility\SafeMarkup; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -96,7 +97,7 @@ public function buildRow(EntityInterface $view) { 'class' => array('views-table-filter-text-source'), ), 'tag' => $view->get('tag'), - 'path' => implode(', ', $this->getDisplayPaths($view)), + 'path' => SafeMarkup::implode(', ', $this->getDisplayPaths($view)), 'operations' => $row['operations'], ), 'title' => $this->t('Machine name: @name', array('@name' => $view->id())), diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 3b9cb81..d560051 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Timer; use Drupal\Component\Utility\Xss; +use Drupal\Component\Utility\SafeMarkup; use Drupal\views\Views; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\views\ViewExecutable; @@ -202,7 +203,7 @@ public function set($property_name, $value, $notify = TRUE) { } public static function getDefaultAJAXMessage() { - return '' . String::checkPlain(strtr($query_string, $quoted)) . ''); + $rows['query'][] = array( + SafeMarkup::create('' . t('Query') . ''), + SafeMarkup::create('
' . String::checkPlain(strtr($query_string, $quoted)) . ''), + ); if (!empty($this->additionalQueries)) { $queries = '' . t('These queries were run during view rendering:') . ''; foreach ($this->additionalQueries as $query) { @@ -688,18 +692,24 @@ public function renderPreview($display_id, $args = array()) { $queries .= t('[@time ms] @query', array('@time' => round($query['time'] * 100000, 1) / 100000.0, '@query' => $query_string)); } - $rows['query'][] = array('' . t('Other queries') . '', '
' . $queries . ''); + $rows['query'][] = array( + SafeMarkup::create('' . t('Other queries') . ''), + SafeMarkup::create('
' . $queries . ''), + ); } } if ($show_info) { - $rows['query'][] = array('' . t('Title') . '', Xss::filterAdmin($this->executable->getTitle())); + $rows['query'][] = array( + SafeMarkup::create('' . t('Title') . ''), + Xss::filterAdmin($this->executable->getTitle()), + ); if (isset($path)) { $path = l($path, $path); } else { $path = t('This display has no path.'); } - $rows['query'][] = array('' . t('Path') . '', $path); + $rows['query'][] = array(SafeMarkup::create('' . t('Path') . ''), $path); } if ($show_stats) { @@ -714,10 +724,10 @@ public function renderPreview($display_id, $args = array()) { // No query was run. Display that information in place of either the // query or the performance statistics, whichever comes first. if ($combined || ($show_location === 'above')) { - $rows['query'] = array(array('' . t('Query') . '', t('No query was run'))); + $rows['query'] = array(array(SafeMarkup::create('' . t('Query') . ''), t('No query was run'))); } else { - $rows['statistics'] = array(array('' . t('Query') . '', t('No query was run'))); + $rows['statistics'] = array(array(SafeMarkup::create('' . t('Query') . ''), t('No query was run'))); } } } diff --git a/core/modules/views_ui/templates/views-ui-display-tab-setting.html.twig b/core/modules/views_ui/templates/views-ui-display-tab-setting.html.twig index 1c67469..641d2aa 100644 --- a/core/modules/views_ui/templates/views-ui-display-tab-setting.html.twig +++ b/core/modules/views_ui/templates/views-ui-display-tab-setting.html.twig @@ -20,6 +20,7 @@ {{ description }} {%- endif %} {% if settings_links %} - {{ settings_links|join(' | ') }} + {# @todo - identify a way to remove |safe #} + {{ settings_links|join(' | ')|safe }} {% endif %} '; + return SafeMarkup::create(' '); } /** @@ -677,7 +678,10 @@ public function renderPreview($display_id, $args = array()) { } } } - $rows['query'][] = array('' . t('Query') . '', '