diff --git a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php index f624720b77755..400a0471e58b8 100644 --- a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php +++ b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php @@ -25,7 +25,7 @@ */ class TwigErrorRenderer implements ErrorRendererInterface { - private HtmlErrorRenderer $fallbackErrorRenderer; + private ErrorRendererInterface $fallbackErrorRenderer; private \Closure|bool $debug; /** @@ -33,7 +33,7 @@ class TwigErrorRenderer implements ErrorRendererInterface */ public function __construct( private Environment $twig, - ?HtmlErrorRenderer $fallbackErrorRenderer = null, + ?ErrorRendererInterface $fallbackErrorRenderer = null, bool|callable $debug = false, ) { $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); diff --git a/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php b/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php index 9febc61e61887..1d4f9f3506cb0 100644 --- a/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php @@ -13,7 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer; -use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\CliErrorRenderer; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Twig\Environment; @@ -21,36 +21,32 @@ class TwigErrorRendererTest extends TestCase { - public function testFallbackToNativeRendererIfDebugOn() + public function tesFallbackRenderer() { - $exception = new \Exception(); - $twig = $this->createMock(Environment::class); - $nativeRenderer = $this->createMock(HtmlErrorRenderer::class); - $nativeRenderer - ->expects($this->once()) - ->method('render') - ->with($exception) - ; - - (new TwigErrorRenderer($twig, $nativeRenderer, true))->render(new \Exception()); + $customRenderer = new class implements ErrorRendererInterface { + public function render(\Throwable $exception): FlattenException + { + return 'This is a custom error renderer.'; + } + }; + + $this->assertSame('This is a custom error renderer.', (new TwigErrorRenderer($twig, $customRenderer, true))->render(new \Exception())); } - public function testFallbackToNativeRendererIfCustomTemplateNotFound() + public function testCliRenderer() { $exception = new NotFoundHttpException(); - $twig = new Environment(new ArrayLoader([])); - $nativeRenderer = $this->createMock(HtmlErrorRenderer::class); - $nativeRenderer - ->expects($this->once()) - ->method('render') - ->with($exception) - ->willReturn(FlattenException::createFromThrowable($exception)) - ; + $exception = (new TwigErrorRenderer($twig, new CliErrorRenderer(), false))->render($exception); + + $exceptionHeaders = $exception->getHeaders(); + if (isset($exceptionHeaders['Content-Type'])) { + $this->assertSame('text/plain', $exceptionHeaders['Content-Type'], 'The exception does not return HTML contents (to prevent potential XSS vulnerabilities)'); + } - (new TwigErrorRenderer($twig, $nativeRenderer, false))->render($exception); + $this->assertStringNotContainsString('', $exception->getAsString(), 'The exception does not include elements of the HTML exception page'); } public function testRenderCustomErrorTemplate() diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ErrorRendererPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ErrorRendererPass.php new file mode 100644 index 0000000000000..59f5d7db3519c --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ErrorRendererPass.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\ErrorHandler\ErrorRenderer\CliErrorRenderer; + +/** + * @author Javier Eguiluz + * @internal + */ +class ErrorRendererPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + // in the 'test' environment, use the CLI error renderer as the default one + if ($container->hasDefinition('test.client')) { + $container->getDefinition('twig.error_renderer.html') + ->setArgument(1, new Definition(CliErrorRenderer::class)); + } + } +} diff --git a/src/Symfony/Bundle/TwigBundle/TwigBundle.php b/src/Symfony/Bundle/TwigBundle/TwigBundle.php index 5ff13b1bc8687..460a1bbe3ebfa 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigBundle.php +++ b/src/Symfony/Bundle/TwigBundle/TwigBundle.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\TwigBundle; +use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\ErrorRendererPass; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\ExtensionPass; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\RuntimeLoaderPass; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigEnvironmentPass; @@ -36,6 +37,7 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass(new TwigEnvironmentPass()); $container->addCompilerPass(new TwigLoaderPass()); $container->addCompilerPass(new RuntimeLoaderPass(), PassConfig::TYPE_BEFORE_REMOVING); + $container->addCompilerPass(new ErrorRendererPass()); } public function registerCommands(Application $application): void diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 3dc0980943820..49196ecc5fb83 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -20,7 +20,7 @@ "composer-runtime-api": ">=2.1", "symfony/config": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", - "symfony/twig-bridge": "^6.4|^7.0", + "symfony/twig-bridge": "^7.2", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "twig/twig": "^3.12|^4.0" diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/CliErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/CliErrorRenderer.php index c414c83077f7f..8a4a3ea5601bd 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/CliErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/CliErrorRenderer.php @@ -40,7 +40,7 @@ protected function supportsColors(): bool } }; - return FlattenException::createFromThrowable($exception) + return FlattenException::createFromThrowable($exception, headers: ['Content-Type' => 'text/plain']) ->setAsString($dumper->dump($cloner->cloneVar($exception), true)); } }