Newer
Older
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Utility\SafeMarkupTest.
*/
namespace Drupal\Tests\Component\Utility;
Alex Bronstein
committed
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Utility\SafeMarkup;
Alex Bronstein
committed
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Render\MarkupTrait;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Tests\UnitTestCase;
/**
* Tests marking strings as safe.
*
Alex Pott
committed
* @group Utility
* @coversDefaultClass \Drupal\Component\Utility\SafeMarkup
*/
class SafeMarkupTest extends UnitTestCase {
Alex Pott
committed
/**
* The error message of the last error in the error handler.
*
* @var string
*/
protected $lastErrorMessage;
/**
* The error number of the last error in the error handler.
*
* @var int
*/
protected $lastErrorNumber;
/**
* {@inheritdoc}
*/
protected function tearDown() {
parent::tearDown();
UrlHelper::setAllowedProtocols(['http', 'https']);
}
/**
* Tests SafeMarkup::isSafe() with different objects.
*
* @covers ::isSafe
*/
public function testIsSafe() {
Alex Bronstein
committed
$safe_string = $this->getMock('\Drupal\Component\Render\MarkupInterface');
$this->assertTrue(SafeMarkup::isSafe($safe_string));
$string_object = new SafeMarkupTestString('test');
$this->assertFalse(SafeMarkup::isSafe($string_object));
}
/**
Alex Bronstein
committed
* Tests SafeMarkup::checkPlain().
*
Alex Bronstein
committed
* @dataProvider providerCheckPlain
* @covers ::checkPlain
*
Alex Bronstein
committed
* @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.
*/
Alex Bronstein
committed
function testCheckPlain($text, $expected, $message) {
$result = SafeMarkup::checkPlain($text);
$this->assertTrue($result instanceof HtmlEscapedText);
$this->assertEquals($expected, $result, $message);
}
Alex Pott
committed
/**
Alex Bronstein
committed
* Tests Drupal\Component\Render\HtmlEscapedText.
*
* Verifies that the result of SafeMarkup::checkPlain() is the same as using
* HtmlEscapedText directly.
Alex Pott
committed
*
* @dataProvider providerCheckPlain
*
* @param string $text
Alex Bronstein
committed
* The text to provide to the HtmlEscapedText constructor.
Alex Pott
committed
* @param string $expected
* The expected output from the function.
* @param string $message
* The message to provide as output for the test.
*/
Alex Bronstein
committed
function testHtmlEscapedText($text, $expected, $message) {
$result = new HtmlEscapedText($text);
Alex Pott
committed
$this->assertEquals($expected, $result, $message);
}
/**
Alex Bronstein
committed
* Data provider for testCheckPlain() and testEscapeString().
Alex Pott
committed
*
* @see testCheckPlain()
*/
function providerCheckPlain() {
Alex Pott
committed
// Checks that invalid multi-byte sequences are escaped.
Alex Bronstein
committed
$tests[] = array("Foo\xC0barbaz", 'Foo�barbaz', 'Escapes invalid sequence "Foo\xC0barbaz"');
$tests[] = array("\xc2\"", '�"', 'Escapes invalid sequence "\xc2\""');
$tests[] = array("Fooÿñ", "Fooÿñ", 'Does not escape valid sequence "Fooÿñ"');
Alex Pott
committed
// Checks that special characters are escaped.
Alex Bronstein
committed
$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.');
Alex Pott
committed
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
Alex Pott
committed
* 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.
*/
public function testFormat($string, array $args, $expected, $message, $expected_is_safe) {
UrlHelper::setAllowedProtocols(['http', 'https', 'mailto']);
Alex Pott
committed
$result = SafeMarkup::format($string, $args);
$this->assertEquals($expected, $result, $message);
Scott Reeves
committed
$this->assertEquals($expected_is_safe, $result instanceof MarkupInterface, 'SafeMarkup::format correctly sets the result as safe or not safe.');
foreach ($args as $arg) {
Alex Bronstein
committed
$this->assertSame($arg instanceof SafeMarkupTestMarkup, SafeMarkup::isSafe($arg));
Alex Pott
committed
}
/**
* 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);
Alex Bronstein
committed
$tests[] = array('Escaped text: @value', array('@value' => SafeMarkupTestMarkup::create('<span>Safe HTML</span>')), 'Escaped text: <span>Safe HTML</span>', 'SafeMarkup::format does not escape an already safe string.', TRUE);
Alex Pott
committed
$tests[] = array('Placeholder text: %value', array('%value' => '<script>'), 'Placeholder text: <em class="placeholder"><script></em>', 'SafeMarkup::format replaces, escapes and themes string.', TRUE);
Alex Bronstein
committed
$tests[] = array('Placeholder text: %value', array('%value' => SafeMarkupTestMarkup::create('<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);
Alex Pott
committed
$tests['javascript-protocol-url'] = ['Simple text <a href=":url">giraffe</a>', [':url' => 'javascript://example.com?foo&bar'], 'Simple text <a href="//example.com?foo&bar">giraffe</a>', 'Support for filtering bad protocols', TRUE];
$tests['external-url'] = ['Simple text <a href=":url">giraffe</a>', [':url' => 'http://example.com?foo&bar'], 'Simple text <a href="http://example.com?foo&bar">giraffe</a>', 'Support for filtering bad protocols', TRUE];
$tests['relative-url'] = ['Simple text <a href=":url">giraffe</a>', [':url' => '/node/1?foo&bar'], 'Simple text <a href="/node/1?foo&bar">giraffe</a>', 'Support for filtering bad protocols', TRUE];
$tests['fragment-with-special-chars'] = ['Simple text <a href=":url">giraffe</a>', [':url' => 'http://example.com/#<'], 'Simple text <a href="http://example.com/#&lt;">giraffe</a>', 'Support for filtering bad protocols', TRUE];
$tests['mailto-protocol'] = ['Hey giraffe <a href=":url">MUUUH</a>', [':url' => 'mailto:test@example.com'], 'Hey giraffe <a href="mailto:test@example.com">MUUUH</a>', '', TRUE];
$tests['js-with-fromCharCode'] = ['Hey giraffe <a href=":url">MUUUH</a>', [':url' => "javascript:alert(String.fromCharCode(88,83,83))"], 'Hey giraffe <a href="alert(String.fromCharCode(88,83,83))">MUUUH</a>', '', TRUE];
// Test some "URL" values that are not RFC 3986 compliant URLs. The result
// of SafeMarkup::format() should still be valid HTML (other than the
// value of the "href" attribute not being a valid URL), and not
// vulnerable to XSS.
$tests['non-url-with-colon'] = ['Hey giraffe <a href=":url">MUUUH</a>', [':url' => "llamas: they are not URLs"], 'Hey giraffe <a href=" they are not URLs">MUUUH</a>', '', TRUE];
$tests['non-url-with-html'] = ['Hey giraffe <a href=":url">MUUUH</a>', [':url' => "<span>not a url</span>"], 'Hey giraffe <a href="<span>not a url</span>">MUUUH</a>', '', TRUE];
Alex Pott
committed
return $tests;
}
/**
Alex Pott
committed
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
* Custom error handler that saves the last error.
*
* We need this custom error handler because we cannot rely on the error to
* exception conversion as __toString is never allowed to leak any kind of
* exception.
*
* @param int $error_number
* The error number.
* @param string $error_message
* The error message.
*/
public function errorHandler($error_number, $error_message) {
$this->lastErrorNumber = $error_number;
$this->lastErrorMessage = $error_message;
}
/**
* String formatting with SafeMarkup::format() and an unsupported placeholder.
*
* When you call SafeMarkup::format() with an unsupported placeholder, an
* InvalidArgumentException should be thrown.
*/
public function testUnexpectedFormat() {
// We set a custom error handler because of https://github.com/sebastianbergmann/phpunit/issues/487
set_error_handler([$this, 'errorHandler']);
// We want this to trigger an error.
$error = SafeMarkup::format('Broken placeholder: ~placeholder', ['~placeholder' => 'broken'])->__toString();
restore_error_handler();
$this->assertEquals(E_USER_ERROR, $this->lastErrorNumber);
$this->assertEquals('Invalid placeholder (~placeholder) in string: Broken placeholder: ~placeholder', $this->lastErrorMessage);
Alex Pott
committed
}
Alex Pott
committed
}
class SafeMarkupTestString {
protected $string;
public function __construct($string) {
$this->string = $string;
}
public function __toString() {
return $this->string;
}
}
/**
Alex Bronstein
committed
* Marks an object's __toString() method as returning markup.
*/
Alex Bronstein
committed
class SafeMarkupTestMarkup implements MarkupInterface {
use MarkupTrait;