diff --git a/CHANGELOG b/CHANGELOG index e281a517e..9fd7bf1a1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ * 1.39.0 (2019-XX-XX) + * made escaping on ternary expressions (?: and ??) more fine-grained * added the possibility to give a nice name to string templates (template_from_string function) * fixed the "with" behavior to always include the globals (for consistency with the "include" and "embed" tags) * fixed "include" with "ignore missing" when an error loading occurs in the included template diff --git a/doc/filters/raw.rst b/doc/filters/raw.rst index e5e5b12ec..23e9287fd 100644 --- a/doc/filters/raw.rst +++ b/doc/filters/raw.rst @@ -13,6 +13,8 @@ if ``raw`` is the last filter applied to it: .. note:: + **This note only applies to Twig before versions 1.39 and 2.8**. + Be careful when using the ``raw`` filter inside expressions: .. code-block:: jinja diff --git a/src/Node/Expression/InlinePrint.php b/src/Node/Expression/InlinePrint.php new file mode 100644 index 000000000..469e73675 --- /dev/null +++ b/src/Node/Expression/InlinePrint.php @@ -0,0 +1,35 @@ + $node], [], $lineno); + } + + public function compile(Compiler $compiler) + { + $compiler + ->raw('print (') + ->subcompile($this->getNode('node')) + ->raw(')') + ; + } +} diff --git a/src/NodeVisitor/EscaperNodeVisitor.php b/src/NodeVisitor/EscaperNodeVisitor.php index 959108f05..16528bc5c 100644 --- a/src/NodeVisitor/EscaperNodeVisitor.php +++ b/src/NodeVisitor/EscaperNodeVisitor.php @@ -15,8 +15,11 @@ use Twig\Environment; use Twig\Node\AutoEscapeNode; use Twig\Node\BlockNode; use Twig\Node\BlockReferenceNode; +use Twig\Node\DoNode; +use Twig\Node\Expression\ConditionalExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\InlinePrint; use Twig\Node\ImportNode; use Twig\Node\ModuleNode; use Twig\Node\Node; @@ -69,8 +72,13 @@ class EscaperNodeVisitor extends AbstractNodeVisitor $this->blocks = []; } elseif ($node instanceof FilterExpression) { return $this->preEscapeFilterNode($node, $env); - } elseif ($node instanceof PrintNode) { - return $this->escapePrintNode($node, $env, $this->needEscaping($env)); + } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping($env)) { + $expression = $node->getNode('expr'); + if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) { + return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine()); + } + + return $this->escapePrintNode($node, $env, $type); } if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) { @@ -82,6 +90,44 @@ class EscaperNodeVisitor extends AbstractNodeVisitor return $node; } + private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, $type) + { + $expr2Safe = $this->isSafeFor($type, $expression->getNode('expr2'), $env); + $expr3Safe = $this->isSafeFor($type, $expression->getNode('expr3'), $env); + + return $expr2Safe !== $expr3Safe; + } + + private function unwrapConditional(ConditionalExpression $expression, Environment $env, $type) + { + // convert "echo a ? b : c" to "a ? echo b : echo c" recursively + $expr2 = $expression->getNode('expr2'); + if ($expr2 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr2, $env, $type)) { + $expr2 = $this->unwrapConditional($expr2, $env, $type); + } else { + $expr2 = $this->escapeInlinePrintNode(new InlinePrint($expr2, $expr2->getTemplateLine()), $env, $type); + } + $expr3 = $expression->getNode('expr3'); + if ($expr3 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr2, $env, $type)) { + $expr3 = $this->unwrapConditional($expr3, $env, $type); + } else { + $expr3 = $this->escapeInlinePrintNode(new InlinePrint($expr3, $expr3->getTemplateLine()), $env, $type); + } + + return new ConditionalExpression($expression->getNode('expr1'), $expr2, $expr3, $expression->getTemplateLine()); + } + + private function escapeInlinePrintNode(InlinePrint $node, Environment $env, $type) + { + $expression = $node->getNode('node'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + return new InlinePrint($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); + } + protected function escapePrintNode(PrintNode $node, Environment $env, $type) { if (false === $type) { @@ -96,10 +142,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor $class = \get_class($node); - return new $class( - $this->getEscaperFilter($type, $expression), - $node->getTemplateLine() - ); + return new $class($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); } protected function preEscapeFilterNode(FilterExpression $filter, Environment $env) diff --git a/test/Twig/Tests/Fixtures/tags/autoescape/literal.test b/test/Twig/Tests/Fixtures/tags/autoescape/literal.test index 1ed2bf35a..8f37e3819 100644 --- a/test/Twig/Tests/Fixtures/tags/autoescape/literal.test +++ b/test/Twig/Tests/Fixtures/tags/autoescape/literal.test @@ -11,19 +11,36 @@ 3. Conditional expression with a variable {{ true ? "
" : someVar }} +{{ false ? "
" : someVar }} +{{ true ? someVar : "
" }} +{{ false ? someVar : "
" }} 4. Nested conditionals with only literals {{ true ? (true ? "
" : "
") : "\n" }} 5. Nested conditionals with a variable {{ true ? (true ? "
" : someVar) : "\n" }} +{{ true ? (false ? "
" : someVar) : "\n" }} +{{ true ? (true ? someVar : "
") : "\n" }} +{{ true ? (false ? someVar : "
") : "\n" }} 6. Nested conditionals with a variable marked safe {{ true ? (true ? "
" : someVar|raw) : "\n" }} +{{ true ? (false ? "
" : someVar|raw) : "\n" }} +{{ true ? (true ? someVar|raw : "
") : "\n" }} +{{ true ? (false ? someVar|raw : "
") : "\n" }} + +7. Without then clause +{{ "
" ?: someVar }} +{{ someFalseVar ?: "
" }} + +8. NullCoalesce +{{ aaaa ?? "
" }} +{{ "
" ?? someVar }} {% endautoescape %} --DATA-- -return [] +return ['someVar' => '
', 'someFalseVar' => false] --EXPECT-- 1. Simple literal @@ -33,13 +50,30 @@ return []
3. Conditional expression with a variable +
<br /> +<br /> +
4. Nested conditionals with only literals
5. Nested conditionals with a variable +
<br /> +<br /> +
6. Nested conditionals with a variable marked safe
+
+
+
+ +7. Without then clause +
+
+ +8. NullCoalesce +
+