'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
'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')) {
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')) {
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')),
);
}
{
return $string;
}
+
+// |raw is safe for everything
+function twig_raw_filter_is_safe($for, Twig_Node $filterArgs)
+{
+ return true;
+}
+
{
$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()
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;
}
}
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;
}
}
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;
+ }
}
}
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) {
--- /dev/null
+--TEST--
+escape types
+--TEMPLATE--
+
+1. autoescape on |escape('js')
+
+{% autoescape on %}
+<a onclick="alert("{{ msg|escape('js') }}")"></a>
+{% endautoescape %}
+
+2. autoescape on html |escape('js')
+
+{% autoescape on html %}
+<a onclick="alert("{{ msg|escape('js') }}")"></a>
+{% endautoescape %}
+
+3. autoescape on js |escape('js')
+
+{% autoescape on js %}
+<a onclick="alert("{{ msg|escape('js') }}")"></a>
+{% endautoescape %}
+
+4. no escape
+
+{% autoescape off %}
+<a onclick="alert("{{ msg }}")"></a>
+{% endautoescape %}
+
+5. |escape('js')|escape('html')
+
+{% autoescape off %}
+<a onclick="alert("{{ msg|escape('js')|escape('html') }}")"></a>
+{% endautoescape %}
+
+6. autoescape on html |escape('js')|escape('html')
+
+{% autoescape on html %}
+<a onclick="alert("{{ msg|escape('js')|escape('html') }}")"></a>
+{% endautoescape %}
+
+--DATA--
+return array('msg' => "<>\n'\"")
+--EXPECT--
+
+1. autoescape on |escape('js')
+
+<a onclick="alert("\x3c\x3e\x0a\x27\x22")"></a>
+
+2. autoescape on html |escape('js')
+
+<a onclick="alert("\x3c\x3e\x0a\x27\x22")"></a>
+
+3. autoescape on js |escape('js')
+
+<a onclick="alert("\x3c\x3e\x0a\x27\x22")"></a>
+
+4. no escape
+
+<a onclick="alert("<>
+'"")"></a>
+
+5. |escape('js')|escape('html')
+
+<a onclick="alert("\x3c\x3e\x0a\x27\x22")"></a>
+
+6. autoescape on html |escape('js')|escape('html')
+
+<a onclick="alert("\x3c\x3e\x0a\x27\x22")"></a>
+
{
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 = '<br />')