Issue #2575615 by alexpott, pwolanin, stefan.r, catch, dawehner, effulgentsia, xjm, David_Rothstein, iMiksu, lauriii, joelpittet: Introduce HtmlEscapedText and remove SafeMarkup::setMultiple() and SafeMarkup::getAll() and remove the static safeStrings list

8.0.x
effulgentsia 2015-10-02 16:19:24 -07:00
parent 3884a5867d
commit 0a67ffba04
18 changed files with 200 additions and 391 deletions

View File

@ -47,14 +47,7 @@ function _batch_page(Request $request) {
return new RedirectResponse(\Drupal::url('<front>', [], ['absolute' => TRUE]));
}
}
// Restore safe strings from previous batches.
// This is safe because we are passing through the known safe values from
// SafeMarkup::getAll(). See _batch_shutdown().
// @todo Ensure we are not storing an excessively large string list in:
// https://www.drupal.org/node/2295823
if (!empty($batch['safe_strings'])) {
SafeMarkup::setMultiple($batch['safe_strings']);
}
// Register database update for the end of processing.
drupal_register_shutdown_function('_batch_shutdown');
@ -521,10 +514,6 @@ function _batch_finished() {
*/
function _batch_shutdown() {
if ($batch = batch_get()) {
// Update safe strings.
// @todo Ensure we are not storing an excessively large string list in:
// https://www.drupal.org/node/2295823
$batch['safe_strings'] = SafeMarkup::getAll();
\Drupal::service('batch.storage')->update($batch);
}
}

View File

@ -6,7 +6,6 @@
*/
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Database\Database;
@ -701,8 +700,6 @@ function template_preprocess_form_element_label(&$variables) {
* - css: Array of paths to CSS files to be used on the progress page.
* - url_options: options passed to url() when constructing redirect URLs for
* the batch.
* - safe_strings: Internal use only. Used to store and retrieve strings
* marked as safe between requests.
* - progressive: A Boolean that indicates whether or not the batch needs to
* run progressively. TRUE indicates that the batch will run in more than
* one run. FALSE (default) indicates that the batch will finish in a single
@ -854,11 +851,6 @@ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = N
$request->query->remove('destination');
}
// Store safe strings.
// @todo Ensure we are not storing an excessively large string list in:
// https://www.drupal.org/node/2295823
$batch['safe_strings'] = SafeMarkup::getAll();
// Store the batch.
\Drupal::service('batch.storage')->create($batch);

View File

@ -0,0 +1,61 @@
<?php
/**
* @file
* Contains \Drupal\Component\Render\HtmlEscapedText.
*/
namespace Drupal\Component\Render;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
/**
* Escapes HTML syntax characters to HTML entities for display in markup.
*
* This class can be used to provide theme engine-like late escaping
* functionality.
*
* @ingroup sanitization
*/
class HtmlEscapedText implements MarkupInterface {
/**
* The string to escape.
*
* @var string
*/
protected $string;
/**
* Constructs an HtmlEscapedText object.
*
* @param $string
* The string to escape. This value will be cast to a string.
*/
public function __construct($string) {
$this->string = (string) $string;
}
/**
* {@inheritdoc}
*/
public function __toString() {
return Html::escape($this->string);
}
/**
* {@inheritdoc}
*/
public function count() {
return Unicode::strlen($this->string);
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() {
return $this->__toString();
}
}

View File

@ -31,6 +31,9 @@ namespace Drupal\Component\Render;
* @see \Drupal\Component\Render\FormattableMarkup
* @see \Drupal\Core\StringTranslation\TranslatableMarkup
* @see \Drupal\views\Render\ViewsRenderPipelineMarkup
* @see twig_render_template()
* @see sanitization
* @see theme_render
*/
interface MarkupInterface extends \JsonSerializable {

View File

@ -7,26 +7,16 @@
namespace Drupal\Component\Utility;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Render\MarkupInterface;
/**
* Manages known safe strings for rendering at the theme layer.
* Contains deprecated functionality related to sanitization of markup.
*
* The Twig theme engine autoescapes string variables in the template, so it
* is possible for a string of markup to become double-escaped. SafeMarkup
* provides a store for known safe strings and methods to manage them
* throughout the page request.
*
* Strings sanitized by self::checkPlain() and self::escape() are automatically
* marked safe, as are markup strings created from @link theme_render render
* arrays @endlink via drupal_render().
*
* This class should be limited to internal use only. Module developers should
* instead use the appropriate
* @link sanitization sanitization functions @endlink or the
* @link theme_render theme and render systems @endlink so that the output can
* can be themed, escaped, and altered properly.
* @deprecated Will be removed before Drupal 9.0.0. Use the appropriate
* @link sanitization sanitization functions @endlink or the @link theme_render theme and render systems @endlink
* so that the output can can be themed, escaped, and altered properly.
*
* @see TwigExtension::escapeFilter()
* @see twig_render_template()
@ -35,100 +25,23 @@ use Drupal\Component\Render\MarkupInterface;
*/
class SafeMarkup {
/**
* The list of safe strings.
*
* Strings in this list are marked as secure for the entire page render, not
* just the code or element that set it. Therefore, only valid HTML should be
* marked as safe (never partial markup). For example, you should never mark
* string such as '<' or '<script>' safe.
*
* @var array
*/
protected static $safeStrings = array();
/**
* Checks if a string is safe to output.
*
* @param string|\Drupal\Component\Render\MarkupInterface $string
* The content to be checked.
* @param string $strategy
* The escaping strategy. Defaults to 'html'. Two escaping strategies are
* supported by default:
* - 'html': (default) The string is safe for use in HTML code.
* - 'all': The string is safe for all use cases.
* See the
* @link http://twig.sensiolabs.org/doc/filters/escape.html Twig escape documentation @endlink
* for more information on escaping strategies in Twig.
* (optional) This value is ignored.
*
* @return bool
* TRUE if the string has been marked secure, FALSE otherwise.
*
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
* Instead, you should just check if a variable is an instance of
* \Drupal\Component\Render\MarkupInterface.
*/
public static function isSafe($string, $strategy = 'html') {
// Do the instanceof checks first to save unnecessarily casting the object
// to a string.
return $string instanceOf MarkupInterface || isset(static::$safeStrings[(string) $string][$strategy]) ||
isset(static::$safeStrings[(string) $string]['all']);
}
/**
* Adds previously retrieved known safe strings to the safe string list.
*
* This method is for internal use. Do not use it to prevent escaping of
* markup; instead, use the appropriate
* @link sanitization sanitization functions @endlink or the
* @link theme_render theme and render systems @endlink so that the output
* can be themed, escaped, and altered properly.
*
* This marks strings as secure for the entire page render, not just the code
* or element that set it. Therefore, only valid HTML should be
* marked as safe (never partial markup). For example, you should never do:
* @code
* SafeMarkup::setMultiple(['<' => ['html' => TRUE]]);
* @endcode
* or:
* @code
* SafeMarkup::setMultiple(['<script>' => ['all' => TRUE]]);
* @endcode
* @param array $safe_strings
* A list of safe strings as previously retrieved by self::getAll().
* Every string in this list will be represented by a multidimensional
* array in which the keys are the string and the escaping strategy used for
* this string, and in which the value is the boolean TRUE.
* See self::isSafe() for the list of supported escaping strategies.
*
* @throws \UnexpectedValueException
*
* @internal This is called by FormCache, StringTranslation and the Batch API.
* It should not be used anywhere else.
*/
public static function setMultiple(array $safe_strings) {
foreach ($safe_strings as $string => $strategies) {
foreach ($strategies as $strategy => $value) {
$string = (string) $string;
if ($value === TRUE) {
static::$safeStrings[$string][$strategy] = TRUE;
}
else {
// Danger - something is very wrong.
throw new \UnexpectedValueException('Only the value TRUE is accepted for safe strings');
}
}
}
}
/**
* Gets all strings currently marked as safe.
*
* This is useful for the batch and form APIs, where it is important to
* preserve the safe markup state across page requests.
*
* @return array
* An array of strings currently marked safe.
*/
public static function getAll() {
return static::$safeStrings;
return $string instanceOf MarkupInterface;
}
/**
@ -140,13 +53,10 @@ class SafeMarkup {
* @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.
* @return \Drupal\Component\Render\HtmlEscapedText
* An HtmlEscapedText object that escapes when rendered to string.
*
* @ingroup sanitization
*
* @deprecated Will be removed before Drupal 8.0.0. Rely on Twig's
* @deprecated Will be removed before Drupal 9.0.0. Rely on Twig's
* auto-escaping feature, or use the @link theme_render #plain_text @endlink
* key when constructing a render array that contains plain text in order to
* use the renderer's auto-escaping feature. If neither of these are
@ -156,9 +66,7 @@ class SafeMarkup {
* @see drupal_validate_utf8()
*/
public static function checkPlain($text) {
$string = Html::escape($text);
static::$safeStrings[$string]['html'] = TRUE;
return $string;
return new HtmlEscapedText($text);
}
/**
@ -176,8 +84,6 @@ class SafeMarkup {
* The formatted string, which is an instance of MarkupInterface unless
* sanitization of an unsafe argument was suppressed (see above).
*
* @ingroup sanitization
*
* @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
* @see \Drupal\Component\Render\FormattableMarkup
*

View File

@ -169,14 +169,6 @@ class FormCache implements FormCacheInterface {
require_once $this->root . '/' . $file;
}
}
// Retrieve the list of safe strings and store it for this request. The
// safety of these strings has already been determined in ::setCache().
// @todo Ensure we are not storing an excessively large string list
// in: https://www.drupal.org/node/2295823
$build_info += ['safe_strings' => []];
SafeMarkup::setMultiple($build_info['safe_strings']);
unset($build_info['safe_strings']);
$form_state->setBuildInfo($build_info);
}
}
@ -206,11 +198,6 @@ class FormCache implements FormCacheInterface {
$this->keyValueExpirableFactory->get('form')->setWithExpire($form_build_id, $form, $expire);
}
// Store the known list of safe strings for form re-use.
// @todo Ensure we are not storing an excessively large string list in:
// https://www.drupal.org/node/2295823
$form_state->addBuildInfo('safe_strings', SafeMarkup::getAll());
if ($data = $form_state->getCacheableArray()) {
$this->keyValueExpirableFactory->get('form_state')->setWithExpire($form_build_id, $data, $expire);
}

View File

@ -7,8 +7,8 @@
namespace Drupal\Core\Utility;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheBackendInterface;
@ -207,7 +207,7 @@ class Token {
// Escape the tokens, unless they are explicitly markup.
foreach ($replacements as $token => $value) {
$replacements[$token] = SafeMarkup::isSafe($value) ? $value : Html::escape($value);
$replacements[$token] = $value instanceof MarkupInterface ? $value : new HtmlEscapedText($value);
}
// Optionally alter the list of replacement values.

View File

@ -54,12 +54,14 @@ class ProcessingTest extends WebTestBase {
// Batch 0: no operation.
$edit = array('batch' => 'batch_0');
$this->drupalPostForm('batch-test', $edit, 'Submit');
$this->assertNoEscaped('<', 'No escaped markup is present.');
$this->assertBatchMessages($this->_resultMessages('batch_0'), 'Batch with no operation performed successfully.');
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
// Batch 1: several simple operations.
$edit = array('batch' => 'batch_1');
$this->drupalPostForm('batch-test', $edit, 'Submit');
$this->assertNoEscaped('<', 'No escaped markup is present.');
$this->assertBatchMessages($this->_resultMessages('batch_1'), 'Batch with simple operations performed successfully.');
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), 'Execution order was correct.');
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
@ -67,6 +69,7 @@ class ProcessingTest extends WebTestBase {
// Batch 2: one multistep operation.
$edit = array('batch' => 'batch_2');
$this->drupalPostForm('batch-test', $edit, 'Submit');
$this->assertNoEscaped('<', 'No escaped markup is present.');
$this->assertBatchMessages($this->_resultMessages('batch_2'), 'Batch with multistep operation performed successfully.');
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_2'), 'Execution order was correct.');
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
@ -74,6 +77,7 @@ class ProcessingTest extends WebTestBase {
// Batch 3: simple + multistep combined.
$edit = array('batch' => 'batch_3');
$this->drupalPostForm('batch-test', $edit, 'Submit');
$this->assertNoEscaped('<', 'No escaped markup is present.');
$this->assertBatchMessages($this->_resultMessages('batch_3'), 'Batch with simple and multistep operations performed successfully.');
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_3'), 'Execution order was correct.');
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
@ -81,6 +85,7 @@ class ProcessingTest extends WebTestBase {
// Batch 4: nested batch.
$edit = array('batch' => 'batch_4');
$this->drupalPostForm('batch-test', $edit, 'Submit');
$this->assertNoEscaped('<', 'No escaped markup is present.');
$this->assertBatchMessages($this->_resultMessages('batch_4'), 'Nested batch performed successfully.');
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_4'), 'Execution order was correct.');
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
@ -91,6 +96,7 @@ class ProcessingTest extends WebTestBase {
*/
function testBatchFormMultistep() {
$this->drupalGet('batch-test/multistep');
$this->assertNoEscaped('<', 'No escaped markup is present.');
$this->assertText('step 1', 'Form is displayed in step 1.');
// First step triggers batch 1.
@ -98,12 +104,14 @@ class ProcessingTest extends WebTestBase {
$this->assertBatchMessages($this->_resultMessages('batch_1'), 'Batch for step 1 performed successfully.');
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), 'Execution order was correct.');
$this->assertText('step 2', 'Form is displayed in step 2.');
$this->assertNoEscaped('<', 'No escaped markup is present.');
// Second step triggers batch 2.
$this->drupalPostForm(NULL, array(), 'Submit');
$this->assertBatchMessages($this->_resultMessages('batch_2'), 'Batch for step 2 performed successfully.');
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_2'), 'Execution order was correct.');
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
$this->assertNoEscaped('<', 'No escaped markup is present.');
}
/**

View File

@ -40,7 +40,7 @@ class DrupalSetMessageTest extends WebTestBase {
// Ensure Markup objects are rendered as expected.
$this->assertRaw('Markup with <em>markup!</em>');
$this->assertUniqueText('Markup with markup!');
$this->assertRaw('SafeString2 with <em>markup!</em>');
$this->assertRaw('Markup2 with <em>markup!</em>');
// Ensure when the same message is of different types it is not duplicated.
$this->assertUniqueText('Non duplicate Markup / string.');

View File

@ -119,7 +119,7 @@ class SystemTestController extends ControllerBase {
// Test duplicate Markup messages.
drupal_set_message(Markup::create('Markup with <em>markup!</em>'));
// Ensure that multiple Markup messages work.
drupal_set_message(Markup::create('SafeString2 with <em>markup!</em>'));
drupal_set_message(Markup::create('Markup2 with <em>markup!</em>'));
// Test mixing of types.
drupal_set_message(Markup::create('Non duplicate Markup / string.'));

View File

@ -46,8 +46,6 @@
<listeners>
<listener class="\Drupal\Tests\Listeners\DrupalStandardsListener">
</listener>
<listener class="\Drupal\Tests\Listeners\SafeMarkupSideEffects">
</listener>
</listeners>
<!-- Filter for coverage reports. -->
<filter>

View File

@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Render\HtmlEscapedTextTest.
*/
namespace Drupal\Tests\Component\Render;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Tests\UnitTestCase;
/**
* Tests the HtmlEscapedText class.
*
* @coversDefaultClass \Drupal\Component\Render\HtmlEscapedText
* @group utility
*/
class HtmlEscapedTextTest extends UnitTestCase {
/**
* @covers ::__toString
* @covers ::jsonSerialize
*
* @dataProvider providerToString
*/
public function testToString($text, $expected, $message) {
$escapeable_string = new HtmlEscapedText($text);
$this->assertEquals($expected, (string) $escapeable_string, $message);
$this->assertEquals($expected, $escapeable_string->jsonSerialize());
}
/**
* Data provider for testToString().
*
* @see testToString()
*/
function providerToString() {
// Checks that invalid multi-byte sequences are escaped.
$tests[] = array("Foo\xC0barbaz", 'Foo<6F>barbaz', 'Escapes invalid sequence "Foo\xC0barbaz"');
$tests[] = array("\xc2\"", '<27>&quot;', 'Escapes invalid sequence "\xc2\""');
$tests[] = array("Fooÿñ", "Fooÿñ", 'Does not escape valid sequence "Fooÿñ"');
// Checks that special characters are escaped.
$script_tag = $this->prophesize(MarkupInterface::class);
$script_tag->__toString()->willReturn('<script>');
$script_tag = $script_tag->reveal();
$tests[] = array($script_tag, '&lt;script&gt;', 'Escapes &lt;script&gt; even inside an object that implements MarkupInterface.');
$tests[] = array("<script>", '&lt;script&gt;', 'Escapes &lt;script&gt;');
$tests[] = array('<>&"\'', '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters.');
$specialchars = $this->prophesize(MarkupInterface::class);
$specialchars->__toString()->willReturn('<>&"\'');
$specialchars = $specialchars->reveal();
$tests[] = array($specialchars, '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters even inside an object that implements MarkupInterface.');
return $tests;
}
/**
* @covers ::count
*/
public function testCount() {
$string = 'Can I please have a <em>kitten</em>';
$escapeable_string = new HtmlEscapedText($string);
$this->assertEquals(strlen($string), $escapeable_string->count());
}
}

View File

@ -7,6 +7,7 @@
namespace Drupal\Tests\Component\Utility;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Render\MarkupTrait;
@ -30,85 +31,6 @@ class SafeMarkupTest extends UnitTestCase {
UrlHelper::setAllowedProtocols(['http', 'https']);
}
/**
* Helper function to add a string to the safe list for testing.
*
* @param string $string
* The content to be marked as secure.
* @param string $strategy
* The escaping strategy used for this string. Two values are supported
* by default:
* - 'html': (default) The string is safe for use in HTML code.
* - 'all': The string is safe for all use cases.
* See the
* @link http://twig.sensiolabs.org/doc/filters/escape.html Twig escape documentation @endlink
* for more information on escaping strategies in Twig.
*
* @return string
* The input string that was marked as safe.
*/
protected function safeMarkupSet($string, $strategy = 'html') {
$reflected_class = new \ReflectionClass('\Drupal\Component\Utility\SafeMarkup');
$reflected_property = $reflected_class->getProperty('safeStrings');
$reflected_property->setAccessible(true);
$current_value = $reflected_property->getValue();
$current_value[$string][$strategy] = TRUE;
$reflected_property->setValue($current_value);
return $string;
}
/**
* Tests SafeMarkup::isSafe() with different providers.
*
* @covers ::isSafe
*/
public function testStrategy() {
$returned = $this->safeMarkupSet('string0', 'html');
$this->assertTrue(SafeMarkup::isSafe($returned), 'String set with "html" provider is safe for default (html)');
$returned = $this->safeMarkupSet('string1', 'all');
$this->assertTrue(SafeMarkup::isSafe($returned), 'String set with "all" provider is safe for default (html)');
$returned = $this->safeMarkupSet('string2', 'css');
$this->assertFalse(SafeMarkup::isSafe($returned), 'String set with "css" provider is not safe for default (html)');
$returned = $this->safeMarkupSet('string3');
$this->assertFalse(SafeMarkup::isSafe($returned, 'all'), 'String set with "html" provider is not safe for "all"');
}
/**
* Data provider for testSet().
*/
public function providerSet() {
// Checks that invalid multi-byte sequences are escaped.
$tests[] = array(
'Foo<6F>barbaz',
'SafeMarkup::setMarkup() functions with valid sequence "Foo<6F>barbaz"',
TRUE
);
$tests[] = array(
"Fooÿñ",
'SafeMarkup::setMarkup() functions with valid sequence "Fooÿñ"'
);
$tests[] = array("<div>", 'SafeMarkup::setMultiple() does not escape HTML');
return $tests;
}
/**
* Tests SafeMarkup::setMultiple().
* @dataProvider providerSet
*
* @param string $text
* The text or object to provide to SafeMarkup::setMultiple().
* @param string $message
* The message to provide as output for the test.
*
* @covers ::setMultiple
*/
public function testSet($text, $message) {
SafeMarkup::setMultiple([$text => ['html' => TRUE]]);
$this->assertTrue(SafeMarkup::isSafe($text), $message);
}
/**
* Tests SafeMarkup::isSafe() with different objects.
*
@ -121,38 +43,6 @@ class SafeMarkupTest extends UnitTestCase {
$this->assertFalse(SafeMarkup::isSafe($string_object));
}
/**
* Tests SafeMarkup::setMultiple().
*
* @covers ::setMultiple
*/
public function testSetMultiple() {
$texts = array(
'multistring0' => array('html' => TRUE),
'multistring1' => array('all' => TRUE),
);
SafeMarkup::setMultiple($texts);
foreach ($texts as $string => $providers) {
$this->assertTrue(SafeMarkup::isSafe($string), 'The value has been marked as safe for html');
}
}
/**
* Tests SafeMarkup::setMultiple().
*
* Only TRUE may be passed in as the value.
*
* @covers ::setMultiple
*
* @expectedException \UnexpectedValueException
*/
public function testInvalidSetMultiple() {
$texts = array(
'invalidstring0' => array('html' => 1),
);
SafeMarkup::setMultiple($texts);
}
/**
* Tests SafeMarkup::checkPlain().
*
@ -165,28 +55,49 @@ class SafeMarkupTest extends UnitTestCase {
* 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);
function testCheckPlain($text, $expected, $message) {
$result = SafeMarkup::checkPlain($text);
$this->assertTrue($result instanceof HtmlEscapedText);
$this->assertEquals($expected, $result, $message);
}
/**
* Data provider for testCheckPlain().
* Tests Drupal\Component\Render\HtmlEscapedText.
*
* Verifies that the result of SafeMarkup::checkPlain() is the same as using
* HtmlEscapedText directly.
*
* @dataProvider providerCheckPlain
*
* @param string $text
* The text to provide to the HtmlEscapedText constructor.
* @param string $expected
* The expected output from the function.
* @param string $message
* The message to provide as output for the test.
*/
function testHtmlEscapedText($text, $expected, $message) {
$result = new HtmlEscapedText($text);
$this->assertEquals($expected, $result, $message);
}
/**
* Data provider for testCheckPlain() and testEscapeString().
*
* @see testCheckPlain()
*/
function providerCheckPlain() {
// Checks that invalid multi-byte sequences are escaped.
$tests[] = array("Foo\xC0barbaz", 'Foo<6F>barbaz', 'SafeMarkup::checkPlain() escapes invalid sequence "Foo\xC0barbaz"', TRUE);
$tests[] = array("\xc2\"", '<27>&quot;', 'SafeMarkup::checkPlain() escapes invalid sequence "\xc2\""', TRUE);
$tests[] = array("Fooÿñ", "Fooÿñ", 'SafeMarkup::checkPlain() does not escape valid sequence "Fooÿñ"');
$tests[] = array("Foo\xC0barbaz", 'Foo<6F>barbaz', 'Escapes invalid sequence "Foo\xC0barbaz"');
$tests[] = array("\xc2\"", '<27>&quot;', 'Escapes invalid sequence "\xc2\""');
$tests[] = array("Fooÿñ", "Fooÿñ", 'Does not escape 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.');
$tests[] = array(SafeMarkupTestMarkup::create("<script>"), '&lt;script&gt;', 'Escapes &lt;script&gt; even inside an object that implements MarkupInterface.');
$tests[] = array("<script>", '&lt;script&gt;', 'Escapes &lt;script&gt;');
$tests[] = array('<>&"\'', '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters.');
$tests[] = array(SafeMarkupTestMarkup::create('<>&"\''), '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters even inside an object that implements MarkupInterface.');
return $tests;
}
@ -266,10 +177,7 @@ class SafeMarkupTestString {
}
/**
* Marks text as safe.
*
* SafeMarkupTestMarkup is used to mark text as safe because
* SafeMarkup::$safeStrings is a global static that affects all tests.
* Marks an object's __toString() method as returning markup.
*/
class SafeMarkupTestMarkup implements MarkupInterface {
use MarkupTrait;

View File

@ -321,34 +321,6 @@ class FormCacheTest extends UnitTestCase {
$this->formCache->getCache($form_build_id, $form_state);
}
/**
* @covers ::loadCachedFormState
*/
public function testLoadCachedFormStateWithSafeStrings() {
$this->assertEmpty(SafeMarkup::getAll());
$form_build_id = 'the_form_build_id';
$form_state = new FormState();
$cached_form = ['#cache_token' => NULL];
$this->formCacheStore->expects($this->once())
->method('get')
->with($form_build_id)
->willReturn($cached_form);
$this->account->expects($this->once())
->method('isAnonymous')
->willReturn(TRUE);
$cached_form_state = ['build_info' => ['safe_strings' => [
'a_safe_string' => ['html' => TRUE],
]]];
$this->formStateCacheStore->expects($this->once())
->method('get')
->with($form_build_id)
->willReturn($cached_form_state);
$this->formCache->getCache($form_build_id, $form_state);
}
/**
* @covers ::setCache
*/
@ -364,7 +336,6 @@ class FormCacheTest extends UnitTestCase {
->with($form_build_id, $form, $this->isType('int'));
$form_state_data = $form_state->getCacheableArray();
$form_state_data['build_info']['safe_strings'] = [];
$this->formStateCacheStore->expects($this->once())
->method('setWithExpire')
->with($form_build_id, $form_state_data, $this->isType('int'));
@ -384,7 +355,6 @@ class FormCacheTest extends UnitTestCase {
->method('setWithExpire');
$form_state_data = $form_state->getCacheableArray();
$form_state_data['build_info']['safe_strings'] = [];
$this->formStateCacheStore->expects($this->once())
->method('setWithExpire')
->with($form_build_id, $form_state_data, $this->isType('int'));
@ -408,7 +378,6 @@ class FormCacheTest extends UnitTestCase {
->with($form_build_id, $form_data, $this->isType('int'));
$form_state_data = $form_state->getCacheableArray();
$form_state_data['build_info']['safe_strings'] = [];
$this->formStateCacheStore->expects($this->once())
->method('setWithExpire')
->with($form_build_id, $form_state_data, $this->isType('int'));
@ -423,34 +392,6 @@ class FormCacheTest extends UnitTestCase {
$this->formCache->setCache($form_build_id, $form, $form_state);
}
/**
* @covers ::setCache
*/
public function testSetCacheWithSafeStrings() {
SafeMarkup::setMultiple([
'a_safe_string' => ['html' => TRUE],
]);
$form_build_id = 'the_form_build_id';
$form = [
'#form_id' => 'the_form_id'
];
$form_state = new FormState();
$this->formCacheStore->expects($this->once())
->method('setWithExpire')
->with($form_build_id, $form, $this->isType('int'));
$form_state_data = $form_state->getCacheableArray();
$form_state_data['build_info']['safe_strings'] = [
'a_safe_string' => ['html' => TRUE],
];
$this->formStateCacheStore->expects($this->once())
->method('setWithExpire')
->with($form_build_id, $form_state_data, $this->isType('int'));
$this->formCache->setCache($form_build_id, $form, $form_state);
}
/**
* @covers ::setCache
*/

View File

@ -35,18 +35,6 @@ class RendererTest extends RendererTestBase {
'#children' => '',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Reset the static list of SafeStrings to prevent bleeding between tests.
$reflected_class = new \ReflectionClass('\Drupal\Component\Utility\SafeMarkup');
$reflected_property = $reflected_class->getProperty('safeStrings');
$reflected_property->setAccessible(true);
$reflected_property->setValue([]);
}
/**
* @covers ::render
* @covers ::doRender

View File

@ -7,9 +7,9 @@
namespace Drupal\Tests\Core\Template;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Render\RenderableInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Template\Loader\StringLoader;
use Drupal\Core\Template\TwigEnvironment;
use Drupal\Core\Template\TwigExtension;
@ -161,14 +161,14 @@ class TwigExtensionTest extends UnitTestCase {
$twig_extension = new TwigExtension($renderer);
$twig_environment = $this->prophesize(TwigEnvironment::class)->reveal();
// Simulate t().
$string = '<em>will be markup</em>';
SafeMarkup::setMultiple([$string => ['html' => TRUE]]);
$markup = $this->prophesize(TranslatableMarkup::class);
$markup->__toString()->willReturn('<em>will be markup</em>');
$markup = $markup->reveal();
$items = [
'<em>will be escaped</em>',
$string,
$markup,
['#markup' => '<strong>will be rendered</strong>']
];
$result = $twig_extension->safeJoin($twig_environment, $items, '<br/>');

View File

@ -1,35 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Listeners\SafeMarkupSideEffects.
*
* Listener for PHPUnit tests, to enforce that data providers don't add to the
* SafeMarkup static safe string list.
*/
namespace Drupal\Tests\Listeners;
use Drupal\Component\Utility\SafeMarkup;
/**
* Listens for PHPUnit tests and fails those with SafeMarkup side effects.
*/
class SafeMarkupSideEffects extends \PHPUnit_Framework_BaseTestListener {
/**
* {@inheritdoc}
*/
public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) {
// Use a static so we only do this test once after all the data providers
// have run.
static $tested = FALSE;
if ($suite->getName() !== '' && !$tested) {
$tested = TRUE;
if (!empty(SafeMarkup::getAll())) {
throw new \RuntimeException('SafeMarkup string list polluted by data providers');
}
}
}
}

View File

@ -51,12 +51,6 @@ abstract class UnitTestCase extends \PHPUnit_Framework_TestCase {
FileCacheFactory::setConfiguration(['default' => ['class' => '\Drupal\Component\FileCache\NullFileCache']]);
$this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
// Reset the static list of SafeStrings to prevent bleeding between tests.
$reflected_class = new \ReflectionClass('\Drupal\Component\Utility\SafeMarkup');
$reflected_property = $reflected_class->getProperty('safeStrings');
$reflected_property->setAccessible(true);
$reflected_property->setValue([]);
}
/**