From 123caccc803f733cfecc8f26aa700634acc89f0a Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 6 Nov 2010 03:36:14 +0100 Subject: [PATCH] support for specifying the escaping type of a filter --- lib/Twig/Extension/Core.php | 23 ++++++- lib/Twig/Extension/Escaper.php | 9 ++- lib/Twig/Filter.php | 16 ++++- lib/Twig/NodeVisitor/Escaper.php | 11 ++- lib/Twig/TokenParser/AutoEscape.php | 2 +- test/Twig/Tests/Fixtures/tags/autoescape/type.test | 69 ++++++++++++++++++++ test/Twig/Tests/integrationTest.php | 2 +- 7 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 test/Twig/Tests/Fixtures/tags/autoescape/type.test diff --git a/lib/Twig/Extension/Core.php b/lib/Twig/Extension/Core.php index ee3afc1..8acf214 100644 --- a/lib/Twig/Extension/Core.php +++ b/lib/Twig/Extension/Core.php @@ -46,7 +46,7 @@ class Twig_Extension_Core extends Twig_Extension 'replace' => new Twig_Filter_Function('twig_strtr'), // encoding - 'url_encode' => new Twig_Filter_Function('twig_urlencode_filter', array('is_escaper' => true)), + 'url_encode' => new Twig_Filter_Function('twig_urlencode_filter', array('is_safe' => array('html'))), 'json_encode' => new Twig_Filter_Function('json_encode'), // string filters @@ -72,8 +72,8 @@ class Twig_Extension_Core extends Twig_Extension 'items' => new Twig_Filter_Function('twig_get_array_items_filter'), // escaping - 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_escaper' => true)), - 'e' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_escaper' => true)), + 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), + 'e' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), ); if (function_exists('mb_get_info')) { @@ -245,9 +245,24 @@ function twig_escape_filter(Twig_Environment $env, $string, $type = 'html') return $string; case 'html': - default: return htmlspecialchars($string, ENT_QUOTES, $env->getCharset()); + default: + throw new Exception("Invalid escape type $type"); + } +} + +function twig_escape_filter_is_safe($for, Twig_Node $filterArgs) +{ + $type = 'html'; + foreach($filterArgs as $arg) { + if ($arg instanceof Twig_Node_Expression_Constant) { + $type = $arg->getAttribute('value'); + } else { + $type = null; + } + break; } + return $for == $type; } if (function_exists('iconv')) { diff --git a/lib/Twig/Extension/Escaper.php b/lib/Twig/Extension/Escaper.php index 5476800..7fcb17a 100644 --- a/lib/Twig/Extension/Escaper.php +++ b/lib/Twig/Extension/Escaper.php @@ -45,7 +45,7 @@ class Twig_Extension_Escaper extends Twig_Extension public function getFilters() { return array( - 'raw' => new Twig_Filter_Function('twig_raw_filter', array('is_escaper' => true)), + 'raw' => new Twig_Filter_Function('twig_raw_filter', array('is_safe_callback' => 'twig_raw_filter_is_safe')), ); } @@ -70,3 +70,10 @@ function twig_raw_filter($string) { return $string; } + +// |raw is safe for everything +function twig_raw_filter_is_safe($for, Twig_Node $filterArgs) +{ + return true; +} + diff --git a/lib/Twig/Filter.php b/lib/Twig/Filter.php index 05557dd..529e500 100644 --- a/lib/Twig/Filter.php +++ b/lib/Twig/Filter.php @@ -24,8 +24,12 @@ abstract class Twig_Filter implements Twig_FilterInterface { $this->options = array_merge(array( 'needs_environment' => false, - 'is_escaper' => false, ), $options); + + if (isset($this->options['is_escaper'])) { + $this->options['is_safe'] = array('html'); + unset($this->options['is_escaper']); + } } public function needsEnvironment() @@ -33,8 +37,14 @@ abstract class Twig_Filter implements Twig_FilterInterface return $this->options['needs_environment']; } - public function isEscaper() + public function isSafe($for, Twig_Node $filterArgs) { - return $this->options['is_escaper']; + if (isset($this->options['is_safe'])) { + return in_array($for, $this->options['is_safe']); + } + if (isset($this->options['is_safe_callback'])) { + return call_user_func($this->options['is_safe_callback'], $for, $filterArgs); + } + return false; } } diff --git a/lib/Twig/NodeVisitor/Escaper.php b/lib/Twig/NodeVisitor/Escaper.php index 7190955..e06aaba 100644 --- a/lib/Twig/NodeVisitor/Escaper.php +++ b/lib/Twig/NodeVisitor/Escaper.php @@ -76,11 +76,12 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface if ($expression instanceof Twig_Node_Expression_Filter) { - // don't escape if the last filter in the chain is an "escaper" + // 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'); - if (isset($filterMap[$name]) && $filterMap[$name]->isEscaper()) { + $args = $expression->getNode('filters')->getNode($i+1); + if (isset($filterMap[$name]) && $filterMap[$name]->isSafe($type, $args)) { return $node; } } @@ -108,7 +109,11 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface if (count($this->statusStack)) { return $this->statusStack[count($this->statusStack) - 1]; } else { - return $env->hasExtension('escaper') ? $env->getExtension('escaper')->isGlobal() : false; + if ($env->hasExtension('escaper') && $env->getExtension('escaper')->isGlobal()) { + return 'html'; + } else { + return false; + } } } diff --git a/lib/Twig/TokenParser/AutoEscape.php b/lib/Twig/TokenParser/AutoEscape.php index 08f047b..ac3791b 100644 --- a/lib/Twig/TokenParser/AutoEscape.php +++ b/lib/Twig/TokenParser/AutoEscape.php @@ -24,7 +24,7 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser if (!in_array($value, array('on', 'off'))) { throw new Twig_SyntaxError("Autoescape value must be 'on' or 'off'", $lineno); } - $value = 'on' === $value ? true : false; + $value = 'on' === $value ? 'html' : false; if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE)) { if (false === $value) { diff --git a/test/Twig/Tests/Fixtures/tags/autoescape/type.test b/test/Twig/Tests/Fixtures/tags/autoescape/type.test new file mode 100644 index 0000000..de3f6c3 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/autoescape/type.test @@ -0,0 +1,69 @@ +--TEST-- +escape types +--TEMPLATE-- + +1. autoescape on |escape('js') + +{% autoescape on %} + +{% endautoescape %} + +2. autoescape on html |escape('js') + +{% autoescape on html %} + +{% endautoescape %} + +3. autoescape on js |escape('js') + +{% autoescape on js %} + +{% endautoescape %} + +4. no escape + +{% autoescape off %} + +{% endautoescape %} + +5. |escape('js')|escape('html') + +{% autoescape off %} + +{% endautoescape %} + +6. autoescape on html |escape('js')|escape('html') + +{% autoescape on html %} + +{% endautoescape %} + +--DATA-- +return array('msg' => "<>\n'\"") +--EXPECT-- + +1. autoescape on |escape('js') + + + +2. autoescape on html |escape('js') + + + +3. autoescape on js |escape('js') + + + +4. no escape + + + +5. |escape('js')|escape('html') + + + +6. autoescape on html |escape('js')|escape('html') + + + diff --git a/test/Twig/Tests/integrationTest.php b/test/Twig/Tests/integrationTest.php index ca6ae7d..b0405a3 100644 --- a/test/Twig/Tests/integrationTest.php +++ b/test/Twig/Tests/integrationTest.php @@ -103,7 +103,7 @@ class TestExtension extends Twig_Extension { public function getFilters() { - return array('nl2br' => new Twig_Filter_Method($this, 'nl2br', array('needs_environment' => true, 'is_escaper' => true))); + return array('nl2br' => new Twig_Filter_Method($this, 'nl2br', array('needs_environment' => true, 'is_safe' => array('html')))); } public function nl2br($env, $value, $sep = '
') -- 1.7.2.5