mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-09 17:06:30 +00:00
300 lines
10 KiB
PHP
300 lines
10 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of Twig.
|
|
*
|
|
* (c) Fabien Potencier
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Twig\Tests\Resources;
|
|
|
|
use PHPUnit\Framework\Attributes\Group;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Twig\Environment;
|
|
use Twig\Error\RuntimeError;
|
|
use Twig\Extension\SandboxExtension;
|
|
use Twig\Loader\ArrayLoader;
|
|
use Twig\Sandbox\SecurityNotAllowedPropertyError;
|
|
use Twig\Sandbox\SecurityPolicy;
|
|
use Twig\Sandbox\SourcePolicyInterface;
|
|
use Twig\Source;
|
|
use Twig\Template;
|
|
use Twig\TemplateWrapper;
|
|
|
|
require_once __DIR__.'/../../src/Resources/core.php';
|
|
|
|
/**
|
|
* Tests that the deprecated wrappers in src/Resources/core.php still enforce
|
|
* sandbox checks.
|
|
*
|
|
* @group legacy
|
|
*/
|
|
#[Group('legacy')]
|
|
class LegacyCoreTest extends TestCase
|
|
{
|
|
public function testTwigSortFilterEnforcesGlobalSandbox()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(true);
|
|
$template = new LegacyCoreTestTemplate($env, 'index.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
|
|
$template->callLegacySort(['b', 'a'], 'strnatcasecmp');
|
|
}
|
|
|
|
public function testTwigSortFilterRecoversSourceForSourcePolicy()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
|
|
public function enableSandbox(Source $source): bool
|
|
{
|
|
return 'sandboxed.twig' === $source->getName();
|
|
}
|
|
});
|
|
|
|
$sandboxedTemplate = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
|
|
$trustedTemplate = new LegacyCoreTestTemplate($env, 'trusted.twig');
|
|
|
|
// Trusted template: non-Closure callable is allowed (only a deprecation is triggered).
|
|
$this->assertSame(['a', 'b'], array_values($trustedTemplate->callLegacySort(['b', 'a'], 'strnatcasecmp')));
|
|
|
|
// Sandboxed template: non-Closure callable is rejected thanks to Source recovery.
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
$sandboxedTemplate->callLegacySort(['b', 'a'], 'strnatcasecmp');
|
|
}
|
|
|
|
public function testTwigArrayFilterRecoversSourceForSourcePolicy()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
|
|
public function enableSandbox(Source $source): bool
|
|
{
|
|
return 'sandboxed.twig' === $source->getName();
|
|
}
|
|
});
|
|
|
|
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
iterator_to_array($template->callLegacyArrayFilter(['a', 'b'], 'is_string'));
|
|
}
|
|
|
|
public function testTwigArrayMapRecoversSourceForSourcePolicy()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
|
|
public function enableSandbox(Source $source): bool
|
|
{
|
|
return 'sandboxed.twig' === $source->getName();
|
|
}
|
|
});
|
|
|
|
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
$template->callLegacyArrayMap(['a', 'b'], 'strtoupper');
|
|
}
|
|
|
|
public function testTwigArrayColumnEnforcesSandbox()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(true);
|
|
$template = new LegacyCoreTestTemplate($env, 'index.twig');
|
|
|
|
$this->expectException(SecurityNotAllowedPropertyError::class);
|
|
$template->callLegacyArrayColumn([new LegacyColumnObject()], 'bar');
|
|
}
|
|
|
|
public function testTwigArrayReduceRecoversSourceForSourcePolicy()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
|
|
public function enableSandbox(Source $source): bool
|
|
{
|
|
return 'sandboxed.twig' === $source->getName();
|
|
}
|
|
});
|
|
|
|
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
$template->callLegacyArrayReduce([1, 2], 'intval');
|
|
}
|
|
|
|
public function testTwigArraySomeEnforcesGlobalSandbox()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(true);
|
|
$template = new LegacyCoreTestTemplate($env, 'index.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
$template->callLegacyArraySome(['a', 'b'], 'is_string');
|
|
}
|
|
|
|
public function testTwigArraySomeRecoversSourceForSourcePolicy()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
|
|
public function enableSandbox(Source $source): bool
|
|
{
|
|
return 'sandboxed.twig' === $source->getName();
|
|
}
|
|
});
|
|
|
|
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
$template->callLegacyArraySome(['a', 'b'], 'is_string');
|
|
}
|
|
|
|
public function testTwigArrayEveryEnforcesGlobalSandbox()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(true);
|
|
$template = new LegacyCoreTestTemplate($env, 'index.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
$template->callLegacyArrayEvery(['a', 'b'], 'is_string');
|
|
}
|
|
|
|
public function testTwigArrayEveryRecoversSourceForSourcePolicy()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
|
|
public function enableSandbox(Source $source): bool
|
|
{
|
|
return 'sandboxed.twig' === $source->getName();
|
|
}
|
|
});
|
|
|
|
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
$template->callLegacyArrayEvery(['a', 'b'], 'is_string');
|
|
}
|
|
|
|
public function testTwigCheckArrowInSandboxEnforcesGlobalSandbox()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(true);
|
|
$template = new LegacyCoreTestTemplate($env, 'index.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
$template->callLegacyCheckArrowInSandbox('strlen', 'test', 'filter');
|
|
}
|
|
|
|
public function testTwigCheckArrowInSandboxRecoversSourceForSourcePolicy()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
|
|
public function enableSandbox(Source $source): bool
|
|
{
|
|
return 'sandboxed.twig' === $source->getName();
|
|
}
|
|
});
|
|
|
|
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
|
|
$template->callLegacyCheckArrowInSandbox('strlen', 'test', 'filter');
|
|
}
|
|
|
|
public function testTwigCheckArrowInSandboxIsNoopWhenNotSandboxed()
|
|
{
|
|
$env = $this->createSandboxedEnvironment(false);
|
|
$template = new LegacyCoreTestTemplate($env, 'index.twig');
|
|
|
|
$template->callLegacyCheckArrowInSandbox('strlen', 'test', 'filter');
|
|
$this->expectNotToPerformAssertions();
|
|
}
|
|
|
|
private function createSandboxedEnvironment(bool $globallySandboxed, ?SourcePolicyInterface $sourcePolicy = null): Environment
|
|
{
|
|
$env = new Environment(new ArrayLoader([]), ['cache' => false, 'autoescape' => false]);
|
|
$env->addExtension(new SandboxExtension(new SecurityPolicy(), $globallySandboxed, $sourcePolicy));
|
|
|
|
return $env;
|
|
}
|
|
}
|
|
|
|
class LegacyCoreTestTemplate extends Template
|
|
{
|
|
public function __construct(Environment $env, private string $name)
|
|
{
|
|
parent::__construct($env);
|
|
}
|
|
|
|
public function callLegacySort($array, $arrow)
|
|
{
|
|
return twig_sort_filter($this->env, $array, $arrow);
|
|
}
|
|
|
|
public function callLegacyArrayFilter($array, $arrow)
|
|
{
|
|
return twig_array_filter($this->env, $array, $arrow);
|
|
}
|
|
|
|
public function callLegacyArrayMap($array, $arrow)
|
|
{
|
|
return twig_array_map($this->env, $array, $arrow);
|
|
}
|
|
|
|
public function callLegacyArrayReduce($array, $arrow, $initial = null)
|
|
{
|
|
return twig_array_reduce($this->env, $array, $arrow, $initial);
|
|
}
|
|
|
|
public function callLegacyArrayColumn($array, $name, $index = null)
|
|
{
|
|
return twig_array_column($this->env, $array, $name, $index);
|
|
}
|
|
|
|
public function callLegacyArraySome($array, $arrow)
|
|
{
|
|
return twig_array_some($this->env, $array, $arrow);
|
|
}
|
|
|
|
public function callLegacyArrayEvery($array, $arrow)
|
|
{
|
|
return twig_array_every($this->env, $array, $arrow);
|
|
}
|
|
|
|
public function callLegacyCheckArrowInSandbox($arrow, $thing, $type)
|
|
{
|
|
twig_check_arrow_in_sandbox($this->env, $arrow, $thing, $type);
|
|
}
|
|
|
|
public function getTemplateName(): string
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
public function getDebugInfo(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function getSourceContext(): Source
|
|
{
|
|
return new Source('', $this->name);
|
|
}
|
|
|
|
protected function doGetParent(array $context): bool|string|Template|TemplateWrapper
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected function doDisplay(array $context, array $blocks = []): iterable
|
|
{
|
|
return [];
|
|
}
|
|
}
|
|
|
|
class LegacyColumnObject
|
|
{
|
|
public $bar = 'bar';
|
|
}
|