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
parent
3884a5867d
commit
0a67ffba04
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.');
|
||||
|
|
|
@ -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.'));
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>"', '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, '<script>', 'Escapes <script> even inside an object that implements MarkupInterface.');
|
||||
$tests[] = array("<script>", '<script>', 'Escapes <script>');
|
||||
$tests[] = array('<>&"\'', '<>&"'', 'Escapes reserved HTML characters.');
|
||||
$specialchars = $this->prophesize(MarkupInterface::class);
|
||||
$specialchars->__toString()->willReturn('<>&"\'');
|
||||
$specialchars = $specialchars->reveal();
|
||||
$tests[] = array($specialchars, '<>&"'', '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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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>"', '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>"', 'Escapes invalid sequence "\xc2\""');
|
||||
$tests[] = array("Fooÿñ", "Fooÿñ", 'Does not escape 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.');
|
||||
$tests[] = array(SafeMarkupTestMarkup::create("<script>"), '<script>', 'Escapes <script> even inside an object that implements MarkupInterface.');
|
||||
$tests[] = array("<script>", '<script>', 'Escapes <script>');
|
||||
$tests[] = array('<>&"\'', '<>&"'', 'Escapes reserved HTML characters.');
|
||||
$tests[] = array(SafeMarkupTestMarkup::create('<>&"\''), '<>&"'', '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;
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/>');
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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([]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue