}
}
-function twig_escape_filter_is_safe($for, Twig_Node $filterArgs)
+function twig_escape_filter_is_safe(Twig_Node $filterArgs)
{
- $type = 'html';
foreach($filterArgs as $arg) {
if ($arg instanceof Twig_Node_Expression_Constant) {
- $type = $arg->getAttribute('value');
+ return array($arg->getAttribute('value'));
} else {
- $type = null;
+ return array();
}
break;
}
- return $for == $type;
+ return array('html');
}
if (function_exists('iconv')) {
public function getFilters()
{
return array(
- 'raw' => new Twig_Filter_Function('twig_raw_filter', array('is_safe_callback' => 'twig_raw_filter_is_safe')),
+ 'raw' => new Twig_Filter_Function('twig_raw_filter', array('is_safe' => array('all'))),
);
}
return $string;
}
-// |raw is safe for everything
-function twig_raw_filter_is_safe($for, Twig_Node $filterArgs)
-{
- return true;
-}
-
return $this->options['needs_environment'];
}
- public function isSafe($for, Twig_Node $filterArgs)
+ public function getSafe(Twig_Node $filterArgs)
{
if (isset($this->options['is_safe'])) {
- return in_array($for, $this->options['is_safe']);
+ return $this->options['is_safe'];
}
if (isset($this->options['is_safe_callback'])) {
- return call_user_func($this->options['is_safe_callback'], $for, $filterArgs);
+ return call_user_func($this->options['is_safe_callback'], $filterArgs);
}
- return false;
+ return array();
}
}
protected $statusStack = array();
protected $blocks = array();
+ protected $safeAnalysis;
+ protected $traverser;
+
+ function __construct()
+ {
+ $this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis;
+ }
+
/**
* Called before child nodes are visited.
*
$expression = $node instanceof Twig_Node_Print ? $node->getNode('expr') : $node;
- if ($expression instanceof Twig_Node_Expression_Constant) {
+ $safe = $this->safeAnalysis->getSafe($expression);
- return $node;
+ if ($safe === null) {
+ if ($this->traverser === null) {
+ $this->traverser = new Twig_NodeTraverser($env, array($this->safeAnalysis));
+ }
+ $this->traverser->traverse($expression);
+ $safe = $this->safeAnalysis->getSafe($expression);
}
- if ($expression instanceof Twig_Node_Expression_Filter) {
-
- // don't escape if the last filter in the chain is safe for $type
- $filterMap = $env->getFilters();
- $i = count($expression->getNode('filters')) - 2;
- $name = $expression->getNode('filters')->getNode($i)->getAttribute('value');
- $args = $expression->getNode('filters')->getNode($i+1);
- if (isset($filterMap[$name]) && $filterMap[$name]->isSafe($type, $args)) {
- return $node;
- }
+ if (in_array($type, $safe) !== false || in_array('all', $safe) !== false) {
+ return $node;
}
// escape
--- /dev/null
+<?php
+
+class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface
+{
+ protected $data = array();
+
+ public function getSafe(Twig_NodeInterface $node)
+ {
+ $hash = spl_object_hash($node);
+ return isset($this->data[$hash]) ? $this->data[$hash] : null;
+ }
+
+ protected function setSafe(Twig_NodeInterface $node, array $safe)
+ {
+ $hash = spl_object_hash($node);
+ $this->data[$hash] = $safe;
+ }
+
+ protected function intersectSafe(array $a = null, array $b = null)
+ {
+ if ($a === null || $b === null) {
+ return array();
+ }
+ if (in_array('all', $a)) {
+ return $b;
+ }
+ if (in_array('all', $b)) {
+ return $a;
+ }
+ return array_intersect($a, $b);
+ }
+
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
+ {
+ return $node;
+ }
+
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
+ {
+
+ // constants are marked safe for all
+
+ if ($node instanceof Twig_Node_Expression_Constant) {
+
+ $this->setSafe($node, array('all'));
+
+ // instersect safeness of both operands
+
+ } else if ($node instanceof Twig_Node_Expression_Conditional) {
+
+ $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3')));
+ $this->setSafe($node, $safe);
+
+ // filter expression is safe when the last filter is safe
+
+ } else if ($node instanceof Twig_Node_Expression_Filter) {
+
+ $filterMap = $env->getFilters();
+ $filters = $node->getNode('filters');
+ $i = count($filters) - 2;
+ $name = $filters->getNode($i)->getAttribute('value');
+ $args = $filters->getNode($i+1);
+ if (isset($filterMap[$name])) {
+ $this->setSafe($node, $filterMap[$name]->getSafe($args));
+ } else {
+ $this->setSafe($node, array());
+ }
+
+ } else {
+
+ $this->setSafe($node, array());
+ }
+
+ return $node;
+ }
+}
"autoescape" tag does not apply escaping on literals
--TEMPLATE--
{% autoescape on %}
+
+1. Simple literal
{{ "<br />" }}
+
+2. Conditional expression with only literals
+{{ true ? "<br />" : "<br>" }}
+
+3. Conditonal expression with a variable
+{{ true ? "<br />" : someVar }}
+
+4. Nested conditionals with only literals
+{{ true ? (true ? "<br />" : "<br>") : "\n" }}
+
+5. Nested conditionals with a variable
+{{ true ? (true ? "<br />" : someVar) : "\n" }}
+
+6. Nested conditionals with a variable marked safe
+{{ true ? (true ? "<br />" : someVar|raw) : "\n" }}
+
{% endautoescape %}
--DATA--
return array()
--EXPECT--
+
+1. Simple literal
+<br />
+
+2. Conditional expression with only literals
+<br />
+
+3. Conditonal expression with a variable
+<br />
+
+4. Nested conditionals with only literals
+<br />
+
+5. Nested conditionals with a variable
+<br />
+
+6. Nested conditionals with a variable marked safe
<br />