return array();
}
+
+ public function getPreEscape()
+ {
+ if (isset($this->options['pre_escape'])) {
+ return $this->options['pre_escape'];
+ }
+
+ return null;
+ }
}
interface Twig_FilterInterface
{
public function compile();
+ public function needsEnvironment();
+ public function getSafe(Twig_Node $filterArgs);
+ public function getPreEscape();
}
{
if ($node instanceof Twig_Node_AutoEscape) {
$this->statusStack[] = $node->getAttribute('value');
- } elseif ($node instanceof Twig_Node_Print) {
- return $this->escapeNode($node, $env, $this->needEscaping($env));
} elseif ($node instanceof Twig_Node_Block) {
$this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env);
}
*/
public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
{
+ if ($node instanceof Twig_Node_Expression_Filter) {
+ return $this->preEscapeFilterNode($node, $env);
+ } elseif ($node instanceof Twig_Node_Print) {
+ return $this->escapePrintNode($node, $env, $this->needEscaping($env));
+ }
+
if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) {
array_pop($this->statusStack);
} elseif ($node instanceof Twig_Node_BlockReference) {
return $node;
}
- protected function escapeNode(Twig_NodeInterface $node, Twig_Environment $env, $type)
+ protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, $type)
{
if (false === $type) {
return $node;
}
- $expression = $node instanceof Twig_Node_Print ? $node->getNode('expr') : $node;
+ $expression = $node->getNode('expr');
+
+ if ($this->isSafeFor($type, $expression, $env)) {
+ return $node;
+ }
+
+ return new Twig_Node_Print(
+ $this->getEscaperFilter($type, $expression),
+ $node->getLine()
+ );
+ }
+
+ protected function preEscapeFilterNode(Twig_Node_Expression_Filter $filter, Twig_Environment $env)
+ {
+ $filterMap = $env->getFilters();
+ $name = $filter->getNode('filter')->getAttribute('value');
+
+ if (isset($filterMap[$name])) {
+ $type = $filterMap[$name]->getPreEscape();
+ if (null === $type) {
+ return $filter;
+ }
+
+ $node = $filter->getNode('node');
+ if ($this->isSafeFor($type, $node, $env)) {
+ return $filter;
+ }
+
+ $filter->setNode('node', $this->getEscaperFilter($type, $node));
+
+ return $filter;
+ }
+
+ return $filter;
+ }
+ protected function isSafeFor($type, Twig_NodeInterface $expression, $env)
+ {
$safe = $this->safeAnalysis->getSafe($expression);
if (null === $safe) {
$safe = $this->safeAnalysis->getSafe($expression);
}
- if (false !== in_array($type, $safe) || false !== in_array('all', $safe)) {
- return $node;
- }
-
- if ($node instanceof Twig_Node_Print) {
- return new Twig_Node_Print(
- $this->getEscaperFilter($type, $expression),
- $node->getLine()
- );
- }
-
- return $this->getEscaperFilter($type, $node);
+ return in_array($type, $safe) || in_array('all', $safe);
}
protected function needEscaping(Twig_Environment $env)
--TEMPLATE--
{% autoescape on %}
-(nl2br is an escaper filter)
+(escape_and_nl2br is an escaper filter)
1. Don't escape escaper filter output
-( var is escaped by |nl2br, line-breaks are added,
+( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is not escaped )
-{{ var|nl2br }}
+{{ var|escape_and_nl2br }}
2. Don't escape escaper filter output
-( var is escaped by |nl2br, line-breaks are added,
+( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is not escaped, |raw is redundant )
-{{ var|nl2br|raw }}
+{{ var|escape_and_nl2br|raw }}
3. Explicit escape
-( var is escaped by |nl2br, line-breaks are added,
+( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is explicitly escaped by |escape )
-{{ var|nl2br|escape }}
+{{ var|escape_and_nl2br|escape }}
4. Escape non-escaper filter output
( var is upper-cased by |upper,
{{ var|upper }}
5. Escape if last filter is not an escaper
-( var is escaped by |nl2br, line-breaks are added,
+( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is upper-cased by |upper,
the output is auto-escaped as |upper is not an escaper )
-{{ var|nl2br|upper }}
+{{ var|escape_and_nl2br|upper }}
6. Don't escape escaper filter output
( var is upper cased by upper,
- the output is escaped by |nl2br, line-breaks are added,
- the output is not escaped as |nl2br is an escaper )
-{{ var|upper|nl2br }}
+ the output is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is not escaped as |escape_and_nl2br is an escaper )
+{{ var|upper|escape_and_nl2br }}
7. Escape if last filter is not an escaper
( the output of |format is "<b>" ~ var ~ "</b>",
return array('var' => "<Fabien>\nTwig")
--EXPECT--
-(nl2br is an escaper filter)
+(escape_and_nl2br is an escaper filter)
1. Don't escape escaper filter output
-( var is escaped by |nl2br, line-breaks are added,
+( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is not escaped )
<Fabien><br />
Twig
2. Don't escape escaper filter output
-( var is escaped by |nl2br, line-breaks are added,
+( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is not escaped, |raw is redundant )
<Fabien><br />
Twig
3. Explicit escape
-( var is escaped by |nl2br, line-breaks are added,
+( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is explicitly escaped by |escape )
&lt;Fabien&gt;<br />
Twig
TWIG
5. Escape if last filter is not an escaper
-( var is escaped by |nl2br, line-breaks are added,
+( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is upper-cased by |upper,
the output is auto-escaped as |upper is not an escaper )
&LT;FABIEN&GT;<BR />
6. Don't escape escaper filter output
( var is upper cased by upper,
- the output is escaped by |nl2br, line-breaks are added,
- the output is not escaped as |nl2br is an escaper )
+ the output is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is not escaped as |escape_and_nl2br is an escaper )
<FABIEN><br />
TWIG
--- /dev/null
+--TEST--
+"autoescape" tag applies escaping after calling filters, and before calling pre_escape filters
+--TEMPLATE--
+{% autoescape on %}
+
+(nl2br is pre_escaped for "html" and declared safe for "html")
+
+1. Pre-escape and don't post-escape
+( var|escape|nl2br )
+{{ var|nl2br }}
+
+2. Don't double-pre-escape
+( var|escape|nl2br )
+{{ var|escape|nl2br }}
+
+3. Don't escape safe values
+( var|raw|nl2br )
+{{ var|raw|nl2br }}
+
+4. Don't escape safe values
+( var|escape|nl2br|nl2br )
+{{ var|nl2br|nl2br }}
+
+5. Re-escape values that are escaped for an other contexts
+( var|escape_something|escape|nl2br )
+{{ var|escape_something|nl2br }}
+
+6. Still escape when using filters not declared safe
+( var|escape|nl2br|upper|escape )
+{{ var|nl2br|upper }}
+
+{% endautoescape %}
+--DATA--
+return array('var' => "<Fabien>\nTwig")
+--EXPECT--
+
+(nl2br is pre_escaped for "html" and declared safe for "html")
+
+1. Pre-escape and don't post-escape
+( var|escape|nl2br )
+<Fabien><br />
+Twig
+
+2. Don't double-pre-escape
+( var|escape|nl2br )
+<Fabien><br />
+Twig
+
+3. Don't escape safe values
+( var|raw|nl2br )
+<Fabien><br />
+Twig
+
+4. Don't escape safe values
+( var|escape|nl2br|nl2br )
+<Fabien><br /><br />
+Twig
+
+5. Re-escape values that are escaped for an other contexts
+( var|escape_something|escape|nl2br )
+<FABIEN><br />
+TWIG
+
+6. Still escape when using filters not declared safe
+( var|escape|nl2br|upper|escape )
+&LT;FABIEN&GT;<BR />
+TWIG
+
{
public function getFilters()
{
- return array('nl2br' => new Twig_Filter_Method($this, 'nl2br', array('needs_environment' => true, 'is_safe' => array('html'))));
+ return array(
+ 'escape_and_nl2br' => new Twig_Filter_Method($this, 'escape_and_nl2br', array('needs_environment' => true, 'is_safe' => array('html'))),
+ 'nl2br' => new Twig_Filter_Method($this, 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
+ 'escape_something' => new Twig_Filter_Method($this, 'escape_something', array('is_safe' => array('something'))),
+ );
}
- public function nl2br($env, $value, $sep = '<br />')
+ /**
+ * nl2br which also escapes, for testing escaper filters
+ */
+ public function escape_and_nl2br($env, $value, $sep = '<br />')
+ {
+ return $this->nl2br(twig_escape_filter($env, $value, 'html'), $sep);
+ }
+
+ /**
+ * nl2br only, for testing filters with pre_escape
+ */
+ public function nl2br($value, $sep = '<br />')
+ {
+ // not secure if $value contains html tags (not only entities)
+ // don't use
+ return str_replace("\n", "$sep\n", $value);
+ }
+
+ public function escape_something($value)
{
- return str_replace("\n", $sep."\n", twig_escape_filter($env, $value, 'html'));
+ return strtoupper($value);
}
public function getName()