mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-11 18:06:46 +00:00
feature #3025 Add the possibility to register classes/interface as being safe (fabpot)
This PR was squashed before being merged into the 2.x branch (closes #3025). Discussion ---------- Add the possibility to register classes/interface as being safe closes #2548 To avoid a too big performance impact on the escaper, we aggressively cache the safe classes, which means that changing the. configuration at runtime is not possible (and having different ones on 2 Twig instances is not possible either, this is really *globally* configured). Commits -------fe6503fe-b18733bcadded the possibility to register classes/interfaces as being safe for the escaper
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
* 2.11.0 (2019-XX-XX)
|
||||
|
||||
* added the possibility to register classes/interfaces as being safe for the escaper ("EscaperExtension::addSafeClass()")
|
||||
* deprecated CoreExtension::setEscaper() and CoreExtension::getEscapers() in favor of the same methods on EscaperExtension
|
||||
* macros are now auto-imported in the template they are defined (under the ``_self`` variable)
|
||||
* added support for macros on "is defined" tests
|
||||
|
||||
+18
@@ -415,6 +415,24 @@ The escaping rules are implemented as follows:
|
||||
{% set text = "Twig<br />" %}
|
||||
{{ foo ? text|escape : "<br />Twig" }} {# the result of the expression won't be escaped #}
|
||||
|
||||
* Objects with a ``__toString`` method are converted to strings and
|
||||
escaped. You can mark some classes and/or interfaces as being safe for some
|
||||
strategies via ``EscaperExtension::addSafeClass()``:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
// mark object of class Foo as safe for the HTML strategy
|
||||
$escaper->addSafeClass('Foo', ['html']);
|
||||
|
||||
// mark object of interface Foo as safe for the HTML strategy
|
||||
$escaper->addSafeClass('FooInterface', ['html']);
|
||||
|
||||
// mark object of class Foo as safe for the HTML and JS strategies
|
||||
$escaper->addSafeClass('Foo', ['html', 'js']);
|
||||
|
||||
// mark object of class Foo as safe for all strategies
|
||||
$escaper->addSafeClass('Foo', ['all']);
|
||||
|
||||
* Escaping is applied before printing, after any other filter is applied:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
@@ -20,6 +20,12 @@ final class EscaperExtension extends AbstractExtension
|
||||
private $defaultStrategy;
|
||||
private $escapers = [];
|
||||
|
||||
/** @internal */
|
||||
public $safeClasses = [];
|
||||
|
||||
/** @internal */
|
||||
public $safeLookup = [];
|
||||
|
||||
/**
|
||||
* @param string|false|callable $defaultStrategy An escaping strategy
|
||||
*
|
||||
@@ -104,6 +110,28 @@ final class EscaperExtension extends AbstractExtension
|
||||
{
|
||||
return $this->escapers;
|
||||
}
|
||||
|
||||
public function setSafeClasses(array $safeClasses = [])
|
||||
{
|
||||
$this->safeClasses = [];
|
||||
$this->safeLookup = [];
|
||||
foreach ($safeClasses as $class => $strategies) {
|
||||
$this->addSafeClass($class, $strategies);
|
||||
}
|
||||
}
|
||||
|
||||
public function addSafeClass(string $class, array $strategies)
|
||||
{
|
||||
$class = ltrim($class, '\\');
|
||||
if (!isset($this->safeClasses[$class])) {
|
||||
$this->safeClasses[$class] = [];
|
||||
}
|
||||
$this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies);
|
||||
|
||||
foreach ($strategies as $strategy) {
|
||||
$this->safeLookup[$strategy][$class] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class_alias('Twig\Extension\EscaperExtension', 'Twig_Extension_Escaper');
|
||||
@@ -148,6 +176,25 @@ function twig_escape_filter(Environment $env, $string, $strategy = 'html', $char
|
||||
|
||||
if (!\is_string($string)) {
|
||||
if (\is_object($string) && method_exists($string, '__toString')) {
|
||||
if ($autoescape) {
|
||||
$c = \get_class($string);
|
||||
$ext = $env->getExtension(EscaperExtension::class);
|
||||
if (!isset($ext->safeClasses[$c])) {
|
||||
$ext->safeClasses[$c] = [];
|
||||
foreach (class_parents($string) + class_implements($string) as $class) {
|
||||
if (isset($ext->safeClasses[$class])) {
|
||||
$ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class]));
|
||||
foreach ($ext->safeClasses[$class] as $s) {
|
||||
$ext->safeLookup[$s][$c] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) {
|
||||
return (string) $string;
|
||||
}
|
||||
}
|
||||
|
||||
$string = (string) $string;
|
||||
} elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) {
|
||||
return $string;
|
||||
|
||||
@@ -362,9 +362,42 @@ class Twig_Tests_Extension_EscaperTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
twig_escape_filter(new Environment($this->getMockBuilder(LoaderInterface::class)->getMock()), 'foo', 'bar');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideObjectsForEscaping
|
||||
*/
|
||||
public function testObjectEscaping(string $escapedHtml, string $escapedJs, array $safeClasses)
|
||||
{
|
||||
$obj = new Twig_Tests_Extension_TestClass();
|
||||
$twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock());
|
||||
$twig->getExtension('\Twig\Extension\EscaperExtension')->setSafeClasses($safeClasses);
|
||||
$this->assertSame($escapedHtml, twig_escape_filter($twig, $obj, 'html', null, true));
|
||||
$this->assertSame($escapedJs, twig_escape_filter($twig, $obj, 'js', null, true));
|
||||
}
|
||||
|
||||
public function provideObjectsForEscaping()
|
||||
{
|
||||
return [
|
||||
['<br />', '<br />', ['\Twig_Tests_Extension_TestClass' => ['js']]],
|
||||
['<br />', '\u003Cbr\u0020\/\u003E', ['\Twig_Tests_Extension_TestClass' => ['html']]],
|
||||
['<br />', '<br />', ['\Twig_Tests_Extension_SafeHtmlInterface' => ['js']]],
|
||||
['<br />', '<br />', ['\Twig_Tests_Extension_SafeHtmlInterface' => ['all']]],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function foo_escaper_for_test(Environment $twig, $string, $charset)
|
||||
{
|
||||
return $string.$charset;
|
||||
}
|
||||
|
||||
interface Twig_Tests_Extension_SafeHtmlInterface
|
||||
{
|
||||
}
|
||||
class Twig_Tests_Extension_TestClass implements Twig_Tests_Extension_SafeHtmlInterface
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return '<br />';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user