Restrict callables to closures in filters

This commit is contained in:
Jérémy Derussé
2020-04-02 23:47:53 +02:00
committed by Fabien Potencier
parent 7e08a97e0a
commit d8259519c0
3 changed files with 43 additions and 6 deletions
+1
View File
@@ -9,6 +9,7 @@
* Fix PHP 8 compatibility
* Drop PHP 5.5 5.6, and 7.0 support
* Fix ambiguous syntax parsing
* In sandbox, the `filter`, `map` and `reduce` filters require Closures in `arrow` parameter
# 1.42.5 (2020-02-11)
+18 -6
View File
@@ -194,9 +194,9 @@ class CoreExtension extends AbstractExtension
new TwigFilter('sort', 'twig_sort_filter'),
new TwigFilter('merge', 'twig_array_merge'),
new TwigFilter('batch', 'twig_array_batch'),
new TwigFilter('filter', 'twig_array_filter'),
new TwigFilter('map', 'twig_array_map'),
new TwigFilter('reduce', 'twig_array_reduce'),
new TwigFilter('filter', 'twig_array_filter', ['needs_environment' => true]),
new TwigFilter('map', 'twig_array_map', ['needs_environment' => true]),
new TwigFilter('reduce', 'twig_array_reduce', ['needs_environment' => true]),
// string/array filters
new TwigFilter('reverse', 'twig_reverse_filter', ['needs_environment' => true]),
@@ -1693,12 +1693,16 @@ function twig_array_batch($items, $size, $fill = null, $preserveKeys = true)
return $result;
}
function twig_array_filter($array, $arrow)
function twig_array_filter(Environment $env, $array, $arrow)
{
if (!twig_test_iterable($array)) {
throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array)));
}
if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) {
throw new RuntimeError('The callable passed to "filter" filter must be a Closure in sandbox mode.');
}
if (\is_array($array)) {
if (\PHP_VERSION_ID >= 50600) {
return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH);
@@ -1711,8 +1715,12 @@ function twig_array_filter($array, $arrow)
return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow);
}
function twig_array_map($array, $arrow)
function twig_array_map(Environment $env, $array, $arrow)
{
if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) {
throw new RuntimeError('The callable passed to the "map" filter must be a Closure in sandbox mode.');
}
$r = [];
foreach ($array as $k => $v) {
$r[$k] = $arrow($v, $k);
@@ -1721,8 +1729,12 @@ function twig_array_map($array, $arrow)
return $r;
}
function twig_array_reduce($array, $arrow, $initial = null)
function twig_array_reduce(Environment $env, $array, $arrow, $initial = null)
{
if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) {
throw new RuntimeError('The callable passed to the "reduce" filter must be a Closure in sandbox mode.');
}
if (!\is_array($array)) {
$array = iterator_to_array($array);
}
+24
View File
@@ -315,6 +315,30 @@ EOF
$this->assertFalse($twig->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed(), 'Sandboxed include() function call should not leave Sandbox enabled when an error occurs.');
}
public function testSandboxWithNoClosureFilter()
{
$this->expectException('\Twig\Error\RuntimeError');
$this->expectExceptionMessage('The callable passed to "filter" filter must be a Closure in sandbox mode in "index" at line 1.');
$twig = $this->getEnvironment(true, ['autoescape' => 'html'], ['index' => <<<EOF
{{ ["foo", "bar", ""]|filter("trim")|join(", ") }}
EOF
], [], ['escape', 'filter', 'join']);
$twig->load('index')->render([]);
}
public function testSandboxWithClosureFilter()
{
$twig = $this->getEnvironment(true, ['autoescape' => 'html'], ['index' => <<<EOF
{{ ["foo", "bar", ""]|filter(v => v != "")|join(", ") }}
EOF
], [], ['escape', 'filter', 'join']);
$this->assertSame('foo, bar', $twig->load('index')->render([]));
}
protected function getEnvironment($sandboxed, $options, $templates, $tags = [], $filters = [], $methods = [], $properties = [], $functions = [])
{
$loader = new ArrayLoader($templates);