mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-13 10:56:38 +00:00
[SECURITY] Fix a security issue where escaping was missing when using ??
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# 3.19.0 (2025-XX-XX)
|
||||
|
||||
* Fix a security issue where escaping was missing when using `??`
|
||||
* Deprecate `Token::getType()`, use `Token::test()` instead
|
||||
* Add `Token::toEnglish()`
|
||||
* Add `ForElseNode`
|
||||
|
||||
@@ -19,7 +19,6 @@ use Twig\Node\Expression\OperatorEscapeInterface;
|
||||
use Twig\Node\Expression\Test\DefinedTest;
|
||||
use Twig\Node\Expression\Test\NullTest;
|
||||
use Twig\Node\Expression\Unary\NotUnary;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\TwigTest;
|
||||
|
||||
final class NullCoalesceBinary extends AbstractBinary implements OperatorEscapeInterface
|
||||
@@ -28,47 +27,31 @@ final class NullCoalesceBinary extends AbstractBinary implements OperatorEscapeI
|
||||
{
|
||||
parent::__construct($left, $right, $lineno);
|
||||
|
||||
if (!$left instanceof ContextVariable) {
|
||||
$test = new DefinedTest(clone $left, new TwigTest('defined'), new EmptyNode(), $left->getTemplateLine());
|
||||
// for "block()", we don't need the null test as the return value is always a string
|
||||
if (!$left instanceof BlockReferenceExpression) {
|
||||
$test = new AndBinary(
|
||||
$test,
|
||||
new NotUnary(new NullTest($left, new TwigTest('null'), new EmptyNode(), $left->getTemplateLine()), $left->getTemplateLine()),
|
||||
$left->getTemplateLine(),
|
||||
);
|
||||
}
|
||||
|
||||
$this->setNode('test', $test);
|
||||
} else {
|
||||
$left->setAttribute('always_defined', true);
|
||||
$test = new DefinedTest(clone $left, new TwigTest('defined'), new EmptyNode(), $left->getTemplateLine());
|
||||
// for "block()", we don't need the null test as the return value is always a string
|
||||
if (!$left instanceof BlockReferenceExpression) {
|
||||
$test = new AndBinary(
|
||||
$test,
|
||||
new NotUnary(new NullTest($left, new TwigTest('null'), new EmptyNode(), $left->getTemplateLine()), $left->getTemplateLine()),
|
||||
$left->getTemplateLine(),
|
||||
);
|
||||
}
|
||||
|
||||
$left->setAttribute('always_defined', true);
|
||||
$this->setNode('test', $test);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
/*
|
||||
* This optimizes only one case. PHP 7 also supports more complex expressions
|
||||
* that can return null. So, for instance, if log is defined, log("foo") ?? "..." works,
|
||||
* but log($a["foo"]) ?? "..." does not if $a["foo"] is not defined. More advanced
|
||||
* cases might be implemented as an optimizer node visitor, but has not been done
|
||||
* as benefits are probably not worth the added complexity.
|
||||
*/
|
||||
if ($this->hasNode('test')) {
|
||||
$compiler
|
||||
->raw('((')
|
||||
->subcompile($this->getNode('test'))
|
||||
->raw(') ? (')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(') : (')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw('))')
|
||||
;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
parent::compile($compiler);
|
||||
$compiler
|
||||
->raw('((')
|
||||
->subcompile($this->getNode('test'))
|
||||
->raw(') ? (')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(') : (')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw('))')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
@@ -78,6 +61,6 @@ final class NullCoalesceBinary extends AbstractBinary implements OperatorEscapeI
|
||||
|
||||
public function getOperandNamesToEscape(): array
|
||||
{
|
||||
return $this->hasNode('test') ? ['left', 'right'] : ['right'];
|
||||
return ['left', 'right'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ Twig supports the ternary operator
|
||||
{{ 0 ? 'YES' : 'NO' }}
|
||||
{{ 0 ? 'YES' : (1 ? 'YES1' : 'NO1') }}
|
||||
{{ 0 ? 'YES' : (0 ? 'YES1' : 'NO1') }}
|
||||
{{ 1 == 1 ? 'foo<br />':'' }}
|
||||
{{ 1 == 1 ? 'foo<br />' : '' }}
|
||||
{{ foo ~ (bar ? ('-' ~ bar) : '') }}
|
||||
{{ true ? tag : 'KO' }}
|
||||
--DATA--
|
||||
return ['foo' => 'foo', 'bar' => 'bar']
|
||||
return ['foo' => 'foo', 'bar' => 'bar', 'tag' => '<br>']
|
||||
--EXPECT--
|
||||
YES
|
||||
NO
|
||||
@@ -16,3 +17,4 @@ YES1
|
||||
NO1
|
||||
foo<br />
|
||||
foo-bar
|
||||
<br>
|
||||
|
||||
@@ -3,8 +3,10 @@ Twig supports the ternary operator
|
||||
--TEMPLATE--
|
||||
{{ 1 ? 'YES' }}
|
||||
{{ 0 ? 'YES' }}
|
||||
{{ tag ? tag }}
|
||||
--DATA--
|
||||
return []
|
||||
return ['tag' => '<br>']
|
||||
--EXPECT--
|
||||
YES
|
||||
|
||||
<br>
|
||||
|
||||
@@ -7,8 +7,9 @@ Twig supports the ternary operator
|
||||
{{ 0 ? : 'NO' }}
|
||||
{{ 'YES' ? : 'NO' }}
|
||||
{{ 0 ? : 'NO' }}
|
||||
{{ tag ?: 'KO' }}
|
||||
--DATA--
|
||||
return []
|
||||
return ['tag' => '<br>']
|
||||
--EXPECT--
|
||||
YES
|
||||
NO
|
||||
@@ -16,3 +17,4 @@ YES
|
||||
NO
|
||||
YES
|
||||
NO
|
||||
<br>
|
||||
|
||||
@@ -15,8 +15,9 @@ Twig supports the ?? operator
|
||||
{{ 1 + (nope ?? 3) + (nada ?? 2) }}
|
||||
{{ obj.null() ?? 'OK' }}
|
||||
{{ obj.empty() ?? 'KO' }}
|
||||
{{ tag ?? 'KO' }}
|
||||
--DATA--
|
||||
return ['bar' => 'OK', 'foo' => ['bar' => 'OK'], 'obj' => new Twig\Tests\TwigTestFoo()]
|
||||
return ['bar' => 'OK', 'foo' => ['bar' => 'OK'], 'obj' => new Twig\Tests\TwigTestFoo(), 'tag' => '<br>']
|
||||
--EXPECT--
|
||||
OK
|
||||
OK
|
||||
@@ -31,3 +32,5 @@ OK
|
||||
3
|
||||
6
|
||||
OK
|
||||
|
||||
<br>
|
||||
|
||||
@@ -24,6 +24,6 @@ class NullCoalesceTest extends NodeTestCase
|
||||
$right = new ConstantExpression(2, 1);
|
||||
$node = new NullCoalesceBinary($left, $right, 1);
|
||||
|
||||
return [[$node, "(// line 1\n\$context[\"foo\"] ?? 2)"]];
|
||||
return [[$node, "(((// line 1\narray_key_exists(\"foo\", \$context) && !(null === \$context[\"foo\"]))) ? (\$context[\"foo\"]) : (2))"]];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user