diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 27d9576925eec4c4779ee2d6bf4156488ece5d1d..26a75f604fe9d7d6bf56c808ffb0b69ebec6fd6b 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -8,7 +8,6 @@ use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Environment; use Drupal\Component\Utility\SafeMarkup; -use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; use Drupal\Core\DrupalKernel; use Drupal\Core\Extension\ExtensionDiscovery; @@ -429,12 +428,12 @@ function t($string, array $args = array(), array $options = array()) { /** * Formats a string for HTML display by replacing variable placeholders. * - * @see \Drupal\Component\Utility\String::format() + * @see \Drupal\Component\Utility\SafeMarkup::format() * @see t() * @ingroup sanitization */ function format_string($string, array $args = array()) { - return String::format($string, $args); + return SafeMarkup::format($string, $args); } /** @@ -495,7 +494,7 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia // Use a default value if $message is not set. if (empty($message)) { // The exception message is run through - // \Drupal\Component\Utility\String::checkPlain() by + // \Drupal\Component\Utility\SafeMarkup::checkPlain() by // \Drupal\Core\Utility\Error:decodeException(). $message = '%type: !message in %function (line %line of %file).'; } @@ -1022,10 +1021,10 @@ function drupal_static_reset($name = NULL) { /** * Formats text for emphasized display in a placeholder inside a sentence. * - * @see \Drupal\Component\Utility\String::placeholder() + * @see \Drupal\Component\Utility\SafeMarkup::placeholder() */ function drupal_placeholder($text) { - return String::placeholder($text); + return SafeMarkup::placeholder($text); } /** diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php index 4dd9d276dd85dedb400b4353968511d3d279acda..60b83dfc493037c67399b0b5c6a620cbdee103ad 100644 --- a/core/lib/Drupal/Component/Utility/Html.php +++ b/core/lib/Drupal/Component/Utility/Html.php @@ -359,4 +359,21 @@ public static function escapeCdataElement(\DOMNode $node, $comment_start = '//', } } + /** + * Decodes all HTML entities including numerical ones to regular UTF-8 bytes. + * + * Double-escaped entities will only be decoded once ("&lt;" becomes + * "<", not "<"). Be careful when using this function, as it will revert + * previous sanitization efforts (<script> will become <script>). + * + * @param string $text + * The text to decode entities in. + * + * @return string + * The input $text, with all HTML entities decoded once. + */ + public static function decodeEntities($text) { + return html_entity_decode($text, ENT_QUOTES, 'UTF-8'); + } + } diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php index 56bd518aeac8113950623ba5d6f6fec5e47ab3c1..7a7b8ea6581c20d2bd17967ad86abea3fe6372b4 100644 --- a/core/lib/Drupal/Component/Utility/SafeMarkup.php +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php @@ -15,7 +15,7 @@ * provides a store for known safe strings and methods to manage them * throughout the page request. * - * Strings sanitized by String::checkPlain() or Xss::filter() are automatically + * Strings sanitized by self::checkPlain() or Xss::filter() are automatically * marked safe, as are markup strings created from render arrays via * drupal_render(). * @@ -133,7 +133,7 @@ public static function setMultiple(array $safe_strings) { * self::set(), it won't be escaped again. */ public static function escape($string) { - return static::isSafe($string) ? $string : String::checkPlain($string); + return static::isSafe($string) ? $string : static::checkPlain($string); } /** @@ -165,4 +165,121 @@ public static function getAll() { return static::$safeStrings; } + /** + * Encodes special characters in a plain-text string for display as HTML. + * + * Also validates strings as UTF-8. All processed strings are also + * automatically flagged as safe markup strings for rendering. + * + * @param string $text + * The text to be checked or processed. + * + * @return string + * An HTML safe version of $text, or an empty string if $text is not valid + * UTF-8. + * + * @ingroup sanitization + * + * @see drupal_validate_utf8() + */ + public static function checkPlain($text) { + $string = htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); + static::$safeStrings[$string]['html'] = TRUE; + return $string; + } + + /** + * Formats a string for HTML display by replacing variable placeholders. + * + * This function replaces variable placeholders in a string with the requested + * values and escapes the values so they can be safely displayed as HTML. It + * should be used on any unknown text that is intended to be printed to an + * HTML page (especially text that may have come from untrusted users, since + * in that case it prevents cross-site scripting and other security problems). + * + * In most cases, you should use t() rather than calling this function + * directly, since it will translate the text (on non-English-only sites) in + * addition to formatting it. + * + * @param string $string + * A string containing placeholders. The string itself is not escaped, any + * unsafe content must be in $args and inserted via placeholders. + * @param array $args + * An associative array of replacements to make. Occurrences in $string of + * any key in $args are replaced with the corresponding value, after + * optional sanitization and formatting. The type of sanitization and + * formatting depends on the first character of the key: + * - @variable: Escaped to HTML using self::escape(). Use this as the + * default choice for anything displayed on a page on the site. + * - %variable: Escaped to HTML and formatted using self::placeholder(), + * which makes the following HTML code: + * @code + * <em class="placeholder">text output here.</em> + * @endcode + * - !variable: Inserted as is, with no sanitization or formatting. Only + * use this when the resulting string is being generated for one of: + * - Non-HTML usage, such as a plain-text email. + * - Non-direct HTML output, such as a plain-text variable that will be + * printed as an HTML attribute value and therefore formatted with + * self::checkPlain() as part of that. + * - Some other special reason for suppressing sanitization. + * + * @return string + * The formatted string, which is marked as safe unless sanitization of an + * unsafe argument was suppressed (see above). + * + * @ingroup sanitization + * + * @see t() + */ + public static function format($string, array $args = array()) { + $safe = TRUE; + + // Transform arguments before inserting them. + foreach ($args as $key => $value) { + switch ($key[0]) { + case '@': + // Escaped only. + $args[$key] = static::escape($value); + break; + + case '%': + default: + // Escaped and placeholder. + $args[$key] = static::placeholder($value); + break; + + case '!': + // Pass-through. + if (!static::isSafe($value)) { + $safe = FALSE; + } + } + } + + $output = strtr($string, $args); + if ($safe) { + static::$safeStrings[$output]['html'] = TRUE; + } + + return $output; + } + + /** + * Formats text for emphasized display in a placeholder inside a sentence. + * + * Used automatically by self::format(). + * + * @param string $text + * The text to format (plain-text). + * + * @return string + * The formatted text (html). + */ + public static function placeholder($text) { + $string = '<em class="placeholder">' . static::escape($text) . '</em>'; + static::$safeStrings[$string]['html'] = TRUE; + return $string; + } + } diff --git a/core/lib/Drupal/Component/Utility/String.php b/core/lib/Drupal/Component/Utility/String.php index b39ca5d4dac50604d65c3b3dc26caf220b6d1871..5bacf72af171769dc50b5d886d29d22fb709b065 100644 --- a/core/lib/Drupal/Component/Utility/String.php +++ b/core/lib/Drupal/Component/Utility/String.php @@ -17,9 +17,6 @@ class String { /** * Encodes special characters in a plain-text string for display as HTML. * - * Also validates strings as UTF-8. All processed strings are also - * automatically flagged as safe markup strings for rendering. - * * @param string $text * The text to be checked or processed. * @@ -27,45 +24,32 @@ class String { * An HTML safe version of $text, or an empty string if $text is not * valid UTF-8. * - * @ingroup sanitization - * - * @see drupal_validate_utf8() - * @see \Drupal\Component\Utility\SafeMarkup + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\Component\Utility\SafeMarkup::checkPlain() instead. */ public static function checkPlain($text) { - return SafeMarkup::set(htmlspecialchars($text, ENT_QUOTES, 'UTF-8')); + return SafeMarkup::checkPlain($text); } /** * Decodes all HTML entities including numerical ones to regular UTF-8 bytes. * - * Double-escaped entities will only be decoded once ("&lt;" becomes - * "<", not "<"). Be careful when using this function, as it will revert - * previous sanitization efforts (<script> will become <script>). - * * @param string $text * The text to decode entities in. * * @return string * The input $text, with all HTML entities decoded once. + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\Component\Utility\Html::decodeEntities() instead. */ public static function decodeEntities($text) { - return html_entity_decode($text, ENT_QUOTES, 'UTF-8'); + return Html::decodeEntities($text); } /** * Formats a string for HTML display by replacing variable placeholders. * - * This function replaces variable placeholders in a string with the requested - * values and escapes the values so they can be safely displayed as HTML. It - * should be used on any unknown text that is intended to be printed to an - * HTML page (especially text that may have come from untrusted users, since - * in that case it prevents cross-site scripting and other security problems). - * - * In most cases, you should use t() rather than calling this function - * directly, since it will translate the text (on non-English-only sites) in - * addition to formatting it. - * * @param $string * A string containing placeholders. The string itself is not escaped, any * unsafe content must be in $args and inserted via placeholders. @@ -94,57 +78,27 @@ public static function decodeEntities($text) { * The formatted string, which is marked as safe unless sanitization of an * unsafe argument was suppressed (see above). * - * @ingroup sanitization - * - * @see t() + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\Component\Utility\SafeMarkup::format() instead. */ public static function format($string, array $args = array()) { - $safe = TRUE; - - // Transform arguments before inserting them. - foreach ($args as $key => $value) { - switch ($key[0]) { - case '@': - // Escaped only. - $args[$key] = SafeMarkup::escape($value); - break; - - case '%': - default: - // Escaped and placeholder. - $args[$key] = static::placeholder($value); - break; - - case '!': - // Pass-through. - if (!SafeMarkup::isSafe($value)) { - $safe = FALSE; - } - } - } - - $output = strtr($string, $args); - if ($safe) { - SafeMarkup::set($output); - } - - return $output; + return SafeMarkup::format($string, $args); } /** * Formats text for emphasized display in a placeholder inside a sentence. * - * Used automatically by self::format(). - * * @param string $text * The text to format (plain-text). * * @return string * The formatted text (html). + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\Component\Utility\SafeMarkup::placeholder() instead. */ public static function placeholder($text) { - return SafeMarkup::set('<em class="placeholder">' . SafeMarkup::escape($text) . '</em>'); + return SafeMarkup::placeholder($text); } - } diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationManager.php b/core/lib/Drupal/Core/StringTranslation/TranslationManager.php index cdfc2dff5f8a55fd2934e4c3f2741c646c726845..46012acd535482756908d78d264d18b09fbcf765 100644 --- a/core/lib/Drupal/Core/StringTranslation/TranslationManager.php +++ b/core/lib/Drupal/Core/StringTranslation/TranslationManager.php @@ -8,7 +8,6 @@ namespace Drupal\Core\StringTranslation; use Drupal\Component\Utility\SafeMarkup; -use Drupal\Component\Utility\String; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\StringTranslation\Translator\TranslatorInterface; @@ -144,7 +143,7 @@ public function translate($string, array $args = array(), array $options = array return SafeMarkup::set($string); } else { - return String::format($string, $args); + return SafeMarkup::format($string, $args); } } diff --git a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php index a60ea902a3b70e57d07c7ef941a2a82f95df2da0..18b07f7a754e67236cbb60c9e3a5d63f3c6e0b4e 100644 --- a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php @@ -210,4 +210,45 @@ public function providerTestHtmlGetId() { ); } + /** + * Tests Html::decodeEntities(). + * + * @dataProvider providerDecodeEntities + * @covers ::decodeEntities + */ + public function testDecodeEntities($text, $expected) { + $this->assertEquals($expected, Html::decodeEntities($text)); + } + + /** + * Data provider for testDecodeEntities(). + * + * @see testCheckPlain() + */ + public function providerDecodeEntities() { + return array( + array('Drupal', 'Drupal'), + array('<script>', '<script>'), + array('<script>', '<script>'), + array('<script>', '<script>'), + array('&lt;script&gt;', '<script>'), + array('"', '"'), + array('"', '"'), + array('&#34;', '"'), + array('"', '"'), + array('&quot;', '"'), + array("'", "'"), + array(''', "'"), + array('&#39;', '''), + array('©', '©'), + array('©', '©'), + array('©', '©'), + array('→', '→'), + array('→', '→'), + array('âž¼', 'âž¼'), + array('➼', 'âž¼'), + array('€', '€'), + ); + } + } diff --git a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php index eadc122855ed259adf2e5e4406e8d76555ecd8c6..1e7d355c327ec1f53e3735c8ce87181677767439 100644 --- a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php @@ -45,7 +45,7 @@ public function testSet($text, $message) { */ public function providerSet() { // Checks that invalid multi-byte sequences are rejected. - $tests[] = array("Foo\xC0barbaz", '', 'String::checkPlain() rejects invalid sequence "Foo\xC0barbaz"', TRUE); + $tests[] = array("Foo\xC0barbaz", '', 'SafeMarkup::checkPlain() rejects invalid sequence "Foo\xC0barbaz"', TRUE); $tests[] = array("Fooÿñ", 'SafeMarkup::set() accepts valid sequence "Fooÿñ"'); $tests[] = array(new TextWrapper("Fooÿñ"), 'SafeMarkup::set() accepts valid sequence "Fooÿñ" in an object implementing __toString()'); $tests[] = array("<div>", 'SafeMarkup::set() accepts HTML'); @@ -101,4 +101,91 @@ public function testInvalidSetMultiple() { SafeMarkup::setMultiple($texts); } + /** + * Tests SafeMarkup::checkPlain(). + * + * @dataProvider providerCheckPlain + * @covers ::checkPlain + * + * @param string $text + * The text to provide to SafeMarkup::checkPlain(). + * @param string $expected + * The expected output from the function. + * @param string $message + * The message to provide as output for the test. + * @param bool $ignorewarnings + * Whether or not to ignore PHP 5.3+ invalid multibyte sequence warnings. + */ + function testCheckPlain($text, $expected, $message, $ignorewarnings = FALSE) { + $result = $ignorewarnings ? @SafeMarkup::checkPlain($text) : SafeMarkup::checkPlain($text); + $this->assertEquals($expected, $result, $message); + } + + /** + * Data provider for testCheckPlain(). + * + * @see testCheckPlain() + */ + function providerCheckPlain() { + // Checks that invalid multi-byte sequences are rejected. + $tests[] = array("Foo\xC0barbaz", '', 'SafeMarkup::checkPlain() rejects invalid sequence "Foo\xC0barbaz"', TRUE); + $tests[] = array("\xc2\"", '', 'SafeMarkup::checkPlain() rejects invalid sequence "\xc2\""', TRUE); + $tests[] = array("Fooÿñ", "Fooÿñ", 'SafeMarkup::checkPlain() accepts valid sequence "Fooÿñ"'); + + // Checks that special characters are escaped. + $tests[] = array("<script>", '<script>', 'SafeMarkup::checkPlain() escapes <script>'); + $tests[] = array('<>&"\'', '<>&"'', 'SafeMarkup::checkPlain() escapes reserved HTML characters.'); + + return $tests; + } + + /** + * Tests string formatting with SafeMarkup::format(). + * + * @dataProvider providerFormat + * @covers ::format + * + * @param string $string + * The string to run through SafeMarkup::format(). + * @param string $args + * The arguments to pass into SafeMarkup::format(). + * @param string $expected + * The expected result from calling the function. + * @param string $message + * The message to display as output to the test. + * @param bool $expected_is_safe + * Whether the result is expected to be safe for HTML display. + */ + function testFormat($string, $args, $expected, $message, $expected_is_safe) { + $result = SafeMarkup::format($string, $args); + $this->assertEquals($expected, $result, $message); + $this->assertEquals($expected_is_safe, SafeMarkup::isSafe($result), 'SafeMarkup::format correctly sets the result as safe or not safe.'); + } + + /** + * Data provider for testFormat(). + * + * @see testFormat() + */ + function providerFormat() { + $tests[] = array('Simple text', array(), 'Simple text', 'SafeMarkup::format leaves simple text alone.', TRUE); + $tests[] = array('Escaped text: @value', array('@value' => '<script>'), 'Escaped text: <script>', 'SafeMarkup::format replaces and escapes string.', TRUE); + $tests[] = array('Escaped text: @value', array('@value' => SafeMarkup::set('<span>Safe HTML</span>')), 'Escaped text: <span>Safe HTML</span>', 'SafeMarkup::format does not escape an already safe string.', TRUE); + $tests[] = array('Placeholder text: %value', array('%value' => '<script>'), 'Placeholder text: <em class="placeholder"><script></em>', 'SafeMarkup::format replaces, escapes and themes string.', TRUE); + $tests[] = array('Placeholder text: %value', array('%value' => SafeMarkup::set('<span>Safe HTML</span>')), 'Placeholder text: <em class="placeholder"><span>Safe HTML</span></em>', 'SafeMarkup::format does not escape an already safe string themed as a placeholder.', TRUE); + $tests[] = array('Verbatim text: !value', array('!value' => '<script>'), 'Verbatim text: <script>', 'SafeMarkup::format replaces verbatim string as-is.', FALSE); + $tests[] = array('Verbatim text: !value', array('!value' => SafeMarkup::set('<span>Safe HTML</span>')), 'Verbatim text: <span>Safe HTML</span>', 'SafeMarkup::format replaces verbatim string as-is.', TRUE); + + return $tests; + } + + /** + * Tests SafeMarkup::placeholder(). + * + * @covers ::placeholder + */ + function testPlaceholder() { + $this->assertEquals('<em class="placeholder">Some text</em>', SafeMarkup::placeholder('Some text')); + } + } diff --git a/core/tests/Drupal/Tests/Component/Utility/StringTest.php b/core/tests/Drupal/Tests/Component/Utility/StringTest.php deleted file mode 100644 index 307385a3bf1720d78a1c79b7e34ccb8647120b1b..0000000000000000000000000000000000000000 --- a/core/tests/Drupal/Tests/Component/Utility/StringTest.php +++ /dev/null @@ -1,151 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\Tests\Component\Utility\StringTest. - */ - -namespace Drupal\Tests\Component\Utility; - -use Drupal\Component\Utility\SafeMarkup; -use Drupal\Tests\UnitTestCase; -use Drupal\Component\Utility\String; - -/** - * Tests string filtering. - * - * @group Utility - * - * @coversDefaultClass \Drupal\Component\Utility\String - */ -class StringTest extends UnitTestCase { - - /** - * Tests String::checkPlain(). - * - * @dataProvider providerCheckPlain - * @covers ::checkPlain - * - * @param string $text - * The text to provide to String::checkPlain(). - * @param string $expected - * The expected output from the function. - * @param string $message - * The message to provide as output for the test. - * @param bool $ignorewarnings - * Whether or not to ignore PHP 5.3+ invalid multibyte sequence warnings. - */ - function testCheckPlain($text, $expected, $message, $ignorewarnings = FALSE) { - $result = $ignorewarnings ? @String::checkPlain($text) : String::checkPlain($text); - $this->assertEquals($expected, $result, $message); - } - - /** - * Data provider for testCheckPlain(). - * - * @see testCheckPlain() - */ - function providerCheckPlain() { - // Checks that invalid multi-byte sequences are rejected. - $tests[] = array("Foo\xC0barbaz", '', 'String::checkPlain() rejects invalid sequence "Foo\xC0barbaz"', TRUE); - $tests[] = array("\xc2\"", '', 'String::checkPlain() rejects invalid sequence "\xc2\""', TRUE); - $tests[] = array("Fooÿñ", "Fooÿñ", 'String::checkPlain() accepts valid sequence "Fooÿñ"'); - - // Checks that special characters are escaped. - $tests[] = array("<script>", '<script>', 'String::checkPlain() escapes <script>'); - $tests[] = array('<>&"\'', '<>&"'', 'String::checkPlain() escapes reserved HTML characters.'); - - return $tests; - } - - /** - * Tests string formatting with String::format(). - * - * @dataProvider providerFormat - * @covers ::format - * - * @param string $string - * The string to run through String::format(). - * @param string $args - * The arguments to pass into String::format(). - * @param string $expected - * The expected result from calling the function. - * @param string $message - * The message to display as output to the test. - * @param bool $expected_is_safe - * Whether the result is expected to be safe for HTML display. - */ - function testFormat($string, $args, $expected, $message, $expected_is_safe) { - $result = String::format($string, $args); - $this->assertEquals($expected, $result, $message); - $this->assertEquals($expected_is_safe, SafeMarkup::isSafe($result), 'String::format correctly sets the result as safe or not safe.'); - } - - /** - * Data provider for testFormat(). - * - * @see testFormat() - */ - function providerFormat() { - $tests[] = array('Simple text', array(), 'Simple text', 'String::format leaves simple text alone.', TRUE); - $tests[] = array('Escaped text: @value', array('@value' => '<script>'), 'Escaped text: <script>', 'String::format replaces and escapes string.', TRUE); - $tests[] = array('Escaped text: @value', array('@value' => SafeMarkup::set('<span>Safe HTML</span>')), 'Escaped text: <span>Safe HTML</span>', 'String::format does not escape an already safe string.', TRUE); - $tests[] = array('Placeholder text: %value', array('%value' => '<script>'), 'Placeholder text: <em class="placeholder"><script></em>', 'String::format replaces, escapes and themes string.', TRUE); - $tests[] = array('Placeholder text: %value', array('%value' => SafeMarkup::set('<span>Safe HTML</span>')), 'Placeholder text: <em class="placeholder"><span>Safe HTML</span></em>', 'String::format does not escape an already safe string themed as a placeholder.', TRUE); - $tests[] = array('Verbatim text: !value', array('!value' => '<script>'), 'Verbatim text: <script>', 'String::format replaces verbatim string as-is.', FALSE); - $tests[] = array('Verbatim text: !value', array('!value' => SafeMarkup::set('<span>Safe HTML</span>')), 'Verbatim text: <span>Safe HTML</span>', 'String::format replaces verbatim string as-is.', TRUE); - - return $tests; - } - - /** - * Tests String::placeholder(). - * - * @covers ::placeholder - */ - function testPlaceholder() { - $this->assertEquals('<em class="placeholder">Some text</em>', String::placeholder('Some text')); - } - - /** - * Tests String::decodeEntities(). - * - * @dataProvider providerDecodeEntities - * @covers ::decodeEntities - */ - public function testDecodeEntities($text, $expected) { - $this->assertEquals($expected, String::decodeEntities($text)); - } - - /** - * Data provider for testDecodeEntities(). - * - * @see testCheckPlain() - */ - public function providerDecodeEntities() { - return array( - array('Drupal', 'Drupal'), - array('<script>', '<script>'), - array('<script>', '<script>'), - array('<script>', '<script>'), - array('&lt;script&gt;', '<script>'), - array('"', '"'), - array('"', '"'), - array('&#34;', '"'), - array('"', '"'), - array('&quot;', '"'), - array("'", "'"), - array(''', "'"), - array('&#39;', '''), - array('©', '©'), - array('©', '©'), - array('©', '©'), - array('→', '→'), - array('→', '→'), - array('âž¼', 'âž¼'), - array('➼', 'âž¼'), - array('€', '€'), - ); - } - -}