diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php index 675a6b2..340ee9f 100644 --- a/core/lib/Drupal/Component/Utility/SafeMarkup.php +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php @@ -217,6 +217,36 @@ public static function xssFilter($string, $html_tags = NULL) { } /** + * Safely truncates an HTML string. + * + * The resulting string rendered in a browser will be free of XSS and shorter + * than the specified length. + * + * @param $string + * @param $length + * @param bool|FALSE $wordsafe + * @param bool|FALSE $add_ellipsis + * @param int $min_wordsafe_length + * @param null $html_tags + * @return string + * + * @see \Drupal\Component\Utility\Xss::truncate() + * @see \Drupal\Component\Utility\Xss::filter() + * @see \Drupal\Component\Utility\Xss::getAdminTagList() + * @see \Drupal\Component\Utility\Unicode::truncate() + * @see \Drupal\Component\Utility\SafeMarkup::isSafe() + */ + public static function xssTruncate($string, $length, $wordsafe = FALSE, $add_ellipsis = FALSE, $min_wordsafe_length = 1, $html_tags = NULL) { + if (isset($html_tags)) { + $string = Xss::truncate($string, $length, $wordsafe, $add_ellipsis, $min_wordsafe_length, $html_tags); + } + else { + $string = Xss::truncate($string, $length, $wordsafe, $add_ellipsis, $min_wordsafe_length); + } + return static::set($string); + } + + /** * Gets all strings currently marked as safe. * * This is useful for the batch and form APIs, where it is important to diff --git a/core/lib/Drupal/Component/Utility/Xss.php b/core/lib/Drupal/Component/Utility/Xss.php index a68ca2b..eace954 100644 --- a/core/lib/Drupal/Component/Utility/Xss.php +++ b/core/lib/Drupal/Component/Utility/Xss.php @@ -14,6 +14,17 @@ */ class Xss { + const HTML_TAG_PREG = '% + ( + <(?=[^a-zA-Z!/]) # a lone < + | # or + # a comment + | # or + <[^>]*(?:>|$) # a string that starts with a <, up until the > or the end of the string + | # or + > # just a > + )%x'; + /** * The list of html tags allowed by filterAdmin(). * @@ -88,16 +99,7 @@ public static function filter($string, $html_tags = array('a', 'em', 'strong', ' // for output. All other known XSS vectors have been filtered out by this // point and any HTML tags remaining will have been deliberately allowed, so // it is acceptable to call SafeMarkup::set() on the resultant string. - return preg_replace_callback('% - ( - <(?=[^a-zA-Z!/]) # a lone < - | # or - # a comment - | # or - <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string - | # or - > # just a > - )%x', $splitter, $string); + return preg_replace_callback(static::HTML_TAG_PREG, $splitter, $string); } /** @@ -135,6 +137,55 @@ public static function filterAdmin($string) { } /** + * Safely truncates an HTML string. + * + * The resulting string rendered in a browser will be free of XSS and shorter + * than the specified length. + * + * @param $string + * The string with raw HTML in it. It will be stripped of everything that + * can cause an XSS attack and truncated to $length. + * @param $length + * The maximum length of the rendered string. + * @param bool $wordsafe + * See \Drupal\Component\Unicode::truncate() for this parameter. + * @param bool $add_ellipsis + * See \Drupal\Component\Unicode::truncate() for this parameter. + * @param int $min_wordsafe_length + * See \Drupal\Component\Unicode::truncate() for this parameter. + * @param array $html_tags + * See \Drupal\Component\Xss::filter() for this parameter. + * + * @return string + * The filtered and truncated string. + */ + public static function truncate($string, $length, $wordsafe = FALSE, $add_ellipsis = FALSE, $min_wordsafe_length = 1, $html_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) { + $html_tags = array_flip($html_tags); + $class = get_called_class(); + $pieces = preg_split(static::HTML_TAG_PREG, $string, -1 , PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $final = ''; + $remaining_length = $length; + foreach ($pieces as $piece) { + // If it is a HTML tag, pass it to the normal XSS filter pipeline. + if ($piece[0] == '<' || $piece[0] == '>') { + $piece = static::split($piece, $html_tags, $class); + } + else { + // Would adding this piece of text make the string too long? + $current_length = Unicode::strlen($piece); + if ($current_length > $remaining_length) { + $final .= Unicode::truncate($piece, $remaining_length, $wordsafe, $add_ellipsis, $min_wordsafe_length); + break; + } + $remaining_length -= $current_length; + } + $final .= $piece; + } + // Close the tags. + return Html::normalize($final); + } + + /** * Processes an HTML tag. * * @param string $string diff --git a/core/modules/dblog/src/Controller/DbLogController.php b/core/modules/dblog/src/Controller/DbLogController.php index 3982da6..5aca93e 100644 --- a/core/modules/dblog/src/Controller/DbLogController.php +++ b/core/modules/dblog/src/Controller/DbLogController.php @@ -185,9 +185,7 @@ public function overview() { $message = $this->formatMessage($dblog); if ($message && isset($dblog->wid)) { // Truncate link_text to 56 chars of message. - // @todo Reevaluate the SafeMarkup::set() in - // https://www.drupal.org/node/2399261. - $log_text = SafeMarkup::set(Unicode::truncate(Xss::filter($message, array()), 56, TRUE, TRUE)); + $log_text = SafeMarkup::xssTruncate($message, 56, TRUE, TRUE, 1, []); $message = $this->l($log_text, new Url('dblog.event', array('event_id' => $dblog->wid), array( 'attributes' => array( // Provide a title for the link for useful hover hints.