Commit 40d6efef authored by alexpott's avatar alexpott
Browse files

Issue #2454447 by andypost, dawehner, rteijeiro: Split Utility\String class to...

Issue #2454447 by andypost, dawehner, rteijeiro: Split Utility\String class to support PHP 7 (String is a reserved word)
parent 40cc6312
......@@ -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);
}
/**
......
......@@ -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 ("<" becomes
* "&lt;", not "<"). Be careful when using this function, as it will revert
* previous sanitization efforts (&lt;script&gt; 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');
}
}
......@@ -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;
}
}
......@@ -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 ("&amp;lt;" becomes
* "&lt;", not "<"). Be careful when using this function, as it will revert
* previous sanitization efforts (&lt;script&gt; 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);
}
}
......@@ -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);
}
}
......
......@@ -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('&lt;script&gt;', '<script>'),
array('&#60;script&#62;', '<script>'),
array('&amp;lt;script&amp;gt;', '&lt;script&gt;'),
array('"', '"'),
array('&#34;', '"'),
array('&amp;#34;', '&#34;'),
array('&quot;', '"'),
array('&amp;quot;', '&quot;'),
array("'", "'"),
array('&#39;', "'"),
array('&amp;#39;', '&#39;'),
array('©', '©'),
array('&copy;', '©'),
array('&#169;', '©'),
array('→', '→'),
array('&#8594;', '→'),
array('➼', '➼'),
array('&#10172;', '➼'),
array('&euro;', '€'),
);
}
}
......@@ -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>", '&lt;script&gt;', 'SafeMarkup::checkPlain() escapes &lt;script&gt;');
$tests[] = array('<>&"\'', '&lt;&gt;&amp;&quot;&#039;', '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: &lt;script&gt;', '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">&lt;script&gt;</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'));
}
}
<?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>", '&lt;script&gt;', 'String::checkPlain() escapes &lt;script&gt;');
$tests[] = array('<>&"\'', '&lt;&gt;&amp;&quot;&#039;', '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: &lt;script&gt;', '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">&lt;script&gt;</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'));
}