From c1e7be0ad4e24b0275d3a463b98f8af16c4b8a9e Mon Sep 17 00:00:00 2001 From: Damien Fernandes Date: Fri, 11 Oct 2024 17:37:30 +0200 Subject: [PATCH 1/4] [WebProfilerBundle] show debugbar on StreamedResponse --- .../Bundle/WebProfilerBundle/CHANGELOG.md | 1 + .../EventListener/WebDebugToolbarListener.php | 69 ++++++--- .../EventListener/MockedStreamedResponse.php | 38 +++++ .../WebDebugToolbarListenerTest.php | 145 ++++++++++++------ 4 files changed, 191 insertions(+), 62 deletions(-) create mode 100644 src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/MockedStreamedResponse.php diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index 539d814d2a438..109f947e3e28d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG --- * Add support for displaying profiles of multiple serializer instances + * Show debug bar when using a streamed response 7.1 --- diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index de7bb7b001ca0..510e4c7a51c3c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -109,7 +110,6 @@ public function onKernelResponse(ResponseEvent $event): void // keep current flashes for one more request if using AutoExpireFlashBag $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } - $response->setContent($this->twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location'), 'host' => $request->getSchemeAndHttpHost()])); $response->setStatusCode(200); $response->headers->remove('Location'); @@ -133,26 +133,59 @@ public function onKernelResponse(ResponseEvent $event): void */ protected function injectToolbar(Response $response, Request $request, array $nonces): void { - $content = $response->getContent(); - $pos = strripos($content, ''); - - if (false !== $pos) { - $toolbar = "\n".str_replace("\n", '', $this->twig->render( - '@WebProfiler/Profiler/toolbar_js.html.twig', - [ - 'full_stack' => class_exists(FullStack::class), - 'excluded_ajax_paths' => $this->excludedAjaxPaths, - 'token' => $response->headers->get('X-Debug-Token'), - 'request' => $request, - 'csp_script_nonce' => $nonces['csp_script_nonce'] ?? null, - 'csp_style_nonce' => $nonces['csp_style_nonce'] ?? null, - ] - ))."\n"; - $content = substr($content, 0, $pos).$toolbar.substr($content, $pos); - $response->setContent($content); + if ($response instanceof StreamedResponse) { + $callback = $response->getCallback(); + if (false !== strripos($response->headers->get('Content-Type'), 'text/html')) { + $toolbarHTMLContent = $this->getToolbarHTML($request, $response->headers->get('X-Debug-Token'), $nonces); + $injectedCallback = static function () use ($toolbarHTMLContent, $callback): void { + ob_start(function (string $buffer, int $phase) use ($toolbarHTMLContent): string { + $pos = strripos($buffer, ''); + if (false !== $pos) { + $buffer = substr($buffer, 0, $pos).$toolbarHTMLContent.substr($buffer, $pos); + } + + return $buffer; + }, 8); // length of '' + + ($callback)(); + ob_end_flush(); + }; + $response->setCallback($injectedCallback); + } + } else { + $content = $response->getContent(); + $pos = strripos($content, ''); + + if (false !== $pos) { + $response->setContent( + $this->renderToolbarInContent($content, $pos, $response->headers->get('X-Debug-Token'), $request, $nonces) + ); + } } } + protected function renderToolbarInContent(string $content, int $pos, ?string $debugToken, Request $request, array $nonces): string + { + $toolbar = "\n".str_replace("\n", '', $this->getToolbarHTML($request, $debugToken, $nonces))."\n"; + + return substr($content, 0, $pos).$toolbar.substr($content, $pos); + } + + private function getToolbarHTML(Request $request, ?string $debugToken, array $nonces): string + { + return $this->twig->render( + '@WebProfiler/Profiler/toolbar_js.html.twig', + [ + 'full_stack' => class_exists(FullStack::class), + 'excluded_ajax_paths' => $this->excludedAjaxPaths, + 'token' => $debugToken, + 'request' => $request, + 'csp_script_nonce' => $nonces['csp_script_nonce'] ?? null, + 'csp_style_nonce' => $nonces['csp_style_nonce'] ?? null, + ] + ); + } + public static function getSubscribedEvents(): array { return [ diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/MockedStreamedResponse.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/MockedStreamedResponse.php new file mode 100644 index 0000000000000..430a327c4ff6e --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/MockedStreamedResponse.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Tests\EventListener; + +use Symfony\Component\HttpFoundation\StreamedResponse; + +class MockedStreamedResponse extends StreamedResponse +{ + public function getContent(): string|false + { + ob_start(); + ($this->callback)(); + $content = ob_get_contents(); + ob_end_clean(); + + return $content; + } + + public static function createFromContent(string $content = ''): self + { + $response = new self(); + $response->setCallback(function () use ($content) { + echo $content; + }); + $response->headers->set('Content-Type', 'text/html'); + + return $response; + } +} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php index ff9bd096fb13f..83be0f91856b7 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php @@ -28,13 +28,11 @@ class WebDebugToolbarListenerTest extends TestCase /** * @dataProvider getInjectToolbarTests */ - public function testInjectToolbar($content, $expected) + public function testInjectToolbar(Response $response, string $expected) { $listener = new WebDebugToolbarListener($this->getTwigMock()); $m = new \ReflectionMethod($listener, 'injectToolbar'); - $response = new Response($content); - $m->invoke($listener, $response, Request::create('/'), ['csp_script_nonce' => 'scripto', 'csp_style_nonce' => 'stylo']); $this->assertEquals($expected, $response->getContent()); } @@ -42,25 +40,44 @@ public function testInjectToolbar($content, $expected) public static function getInjectToolbarTests() { return [ - ['', "\nWDT\n"], - [' + [new Response(''), "\nWDT\n"], + [new Response(' - ', " + '), " \nWDT\n "], + [ + MockedStreamedResponse::createFromContent(''), + 'WDT', + ], + [ + MockedStreamedResponse::createFromContent( + ' + + + + + '), + ' + + + + WDT + ', + ], ]; } /** * @dataProvider provideRedirects */ - public function testHtmlRedirectionIsIntercepted($statusCode) + public function testHtmlRedirectionIsIntercepted(int $statusCode) { $response = new Response('Some content', $statusCode); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); @@ -86,9 +103,11 @@ public function testNonHtmlRedirectionIsNotIntercepted() $this->assertEquals('Some content', $response->getContent()); } - public function testToolbarIsInjected() + /** + * @dataProvider provideInjectedResponse + */ + public function testToolbarIsInjected(Response $response, string $expected) { - $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); @@ -96,15 +115,40 @@ public function testToolbarIsInjected() $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals("\nWDT\n", $response->getContent()); + $this->assertEquals($expected, $response->getContent()); + } + + public static function provideInjectedResponse(): array + { + return [ + [new Response(''), "\nWDT\n"], + [MockedStreamedResponse::createFromContent(''), 'WDT'], + ]; + } + + public static function provideNotInjectedResponse(): array + { + return [ + [new Response(''), ''], + [MockedStreamedResponse::createFromContent(''), ''], + ]; + } + + public static function provideEmptyResponse(): array + { + return [ + [new Response()], + [MockedStreamedResponse::createFromContent()], + ]; } /** * @depends testToolbarIsInjected + * + * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedOnNonHtmlContentType() + public function testToolbarIsNotInjectedOnNonHtmlContentType(Response $response, string $expected) { - $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $response->headers->set('Content-Type', 'text/xml'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); @@ -112,15 +156,16 @@ public function testToolbarIsNotInjectedOnNonHtmlContentType() $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals('', $response->getContent()); + $this->assertEquals($expected, $response->getContent()); } /** * @depends testToolbarIsInjected + * + * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedOnContentDispositionAttachment() + public function testToolbarIsNotInjectedOnContentDispositionAttachment(Response $response, string $expected) { - $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $response->headers->set('Content-Disposition', 'attachment; filename=test.html'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); @@ -128,7 +173,7 @@ public function testToolbarIsNotInjectedOnContentDispositionAttachment() $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals('', $response->getContent()); + $this->assertEquals($expected, $response->getContent()); } /** @@ -136,7 +181,7 @@ public function testToolbarIsNotInjectedOnContentDispositionAttachment() * * @dataProvider provideRedirects */ - public function testToolbarIsNotInjectedOnRedirection($statusCode) + public function testToolbarIsNotInjectedOnRedirection(int $statusCode) { $response = new Response('', $statusCode); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); @@ -158,25 +203,26 @@ public static function provideRedirects(): array /** * @depends testToolbarIsInjected + * + * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedWhenThereIsNoNoXDebugTokenResponseHeader() + public function testToolbarIsNotInjectedWhenThereIsNoNoXDebugTokenResponseHeader(Response $response, string $expected) { - $response = new Response(''); - $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals('', $response->getContent()); + $this->assertEquals($expected, $response->getContent()); } /** * @depends testToolbarIsInjected + * + * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedWhenOnSubRequest() + public function testToolbarIsNotInjectedWhenOnSubRequest(Response $response, string $expected) { - $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::SUB_REQUEST, $response); @@ -184,7 +230,7 @@ public function testToolbarIsNotInjectedWhenOnSubRequest() $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals('', $response->getContent()); + $this->assertEquals($expected, $response->getContent()); } /** @@ -205,10 +251,11 @@ public function testToolbarIsNotInjectedOnIncompleteHtmlResponses() /** * @depends testToolbarIsInjected + * + * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedOnXmlHttpRequests() + public function testToolbarIsNotInjectedOnXmlHttpRequests(Response $response, string $expected) { - $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $request = new Request(); @@ -219,15 +266,16 @@ public function testToolbarIsNotInjectedOnXmlHttpRequests() $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals('', $response->getContent()); + $this->assertEquals($expected, $response->getContent()); } /** * @depends testToolbarIsInjected + * + * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedOnNonHtmlRequests() + public function testToolbarIsNotInjectedOnNonHtmlRequests(Response $response, string $expected) { - $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request([], [], ['_format' => 'json']), HttpKernelInterface::MAIN_REQUEST, $response); @@ -235,12 +283,14 @@ public function testToolbarIsNotInjectedOnNonHtmlRequests() $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals('', $response->getContent()); + $this->assertEquals($expected, $response->getContent()); } - public function testXDebugUrlHeader() + /** + * @dataProvider provideEmptyResponse + */ + public function testXDebugUrlHeader(Response $response) { - $response = new Response(); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $urlGenerator = $this->createMock(UrlGeneratorInterface::class); @@ -259,9 +309,11 @@ public function testXDebugUrlHeader() $this->assertEquals('http://mydomain.com/_profiler/xxxxxxxx', $response->headers->get('X-Debug-Token-Link')); } - public function testThrowingUrlGenerator() + /** + * @dataProvider provideEmptyResponse + */ + public function testThrowingUrlGenerator(Response $response) { - $response = new Response(); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $urlGenerator = $this->createMock(UrlGeneratorInterface::class); @@ -280,9 +332,11 @@ public function testThrowingUrlGenerator() $this->assertEquals('Exception: foo', $response->headers->get('X-Debug-Error')); } - public function testThrowingErrorCleanup() + /** + * @dataProvider provideEmptyResponse + */ + public function testThrowingErrorCleanup(Response $response) { - $response = new Response(); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $urlGenerator = $this->createMock(UrlGeneratorInterface::class); @@ -301,9 +355,11 @@ public function testThrowingErrorCleanup() $this->assertEquals('Exception: This multiline tabbed text should come out on a single plain line', $response->headers->get('X-Debug-Error')); } - public function testCspIsDisabledIfDumperWasUsed() + /** + * @dataProvider provideInjectedResponse + */ + public function testCspIsDisabledIfDumperWasUsed(Response $response, string $expected) { - $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); @@ -319,12 +375,14 @@ public function testCspIsDisabledIfDumperWasUsed() $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, null, '', $cspHandler, $dumpDataCollector); $listener->onKernelResponse($event); - $this->assertEquals("\nWDT\n", $response->getContent()); + $this->assertEquals($expected, $response->getContent()); } - public function testCspIsKeptEnabledIfDumperWasNotUsed() + /** + * @dataProvider provideInjectedResponse + */ + public function testCspIsKeptEnabledIfDumperWasNotUsed(Response $response, string $expected) { - $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); @@ -340,7 +398,7 @@ public function testCspIsKeptEnabledIfDumperWasNotUsed() $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, null, '', $cspHandler, $dumpDataCollector); $listener->onKernelResponse($event); - $this->assertEquals("\nWDT\n", $response->getContent()); + $this->assertEquals($expected, $response->getContent()); } public function testNullContentTypeWithNoDebugEnv() @@ -356,7 +414,6 @@ public function testNullContentTypeWithNoDebugEnv() $this->expectNotToPerformAssertions(); } - public function testAjaxReplaceHeaderOnDisabledToolbar() { $response = new Response(); @@ -417,7 +474,7 @@ public function testAjaxReplaceHeaderOnEnabledAndXHRButPreviouslySet() $this->assertSame('0', $response->headers->get('Symfony-Debug-Toolbar-Replace')); } - protected function getTwigMock($render = 'WDT') + protected function getTwigMock(string $render = 'WDT') { $templating = $this->createMock(Environment::class); $templating->expects($this->any()) From 70b2ba605686151b5017655014bc594fd4afc49a Mon Sep 17 00:00:00 2001 From: Damien Fernandes Date: Thu, 7 Nov 2024 17:55:06 +0100 Subject: [PATCH 2/4] review correction from stof --- .../EventListener/WebDebugToolbarListener.php | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index 510e4c7a51c3c..e15a0b47edb55 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -110,6 +110,7 @@ public function onKernelResponse(ResponseEvent $event): void // keep current flashes for one more request if using AutoExpireFlashBag $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } + $response->setContent($this->twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location'), 'host' => $request->getSchemeAndHttpHost()])); $response->setStatusCode(200); $response->headers->remove('Location'); @@ -135,42 +136,34 @@ protected function injectToolbar(Response $response, Request $request, array $no { if ($response instanceof StreamedResponse) { $callback = $response->getCallback(); - if (false !== strripos($response->headers->get('Content-Type'), 'text/html')) { - $toolbarHTMLContent = $this->getToolbarHTML($request, $response->headers->get('X-Debug-Token'), $nonces); - $injectedCallback = static function () use ($toolbarHTMLContent, $callback): void { - ob_start(function (string $buffer, int $phase) use ($toolbarHTMLContent): string { - $pos = strripos($buffer, ''); - if (false !== $pos) { - $buffer = substr($buffer, 0, $pos).$toolbarHTMLContent.substr($buffer, $pos); - } - - return $buffer; - }, 8); // length of '' - - ($callback)(); - ob_end_flush(); - }; - $response->setCallback($injectedCallback); - } + $toolbarHTMLContent = $this->getToolbarHTML($request, $response->headers->get('X-Debug-Token'), $nonces); + $injectedCallback = static function () use ($toolbarHTMLContent, $callback): void { + ob_start(function (string $buffer, int $phase) use ($toolbarHTMLContent): string { + $pos = strripos($buffer, ''); + if (false !== $pos) { + $buffer = substr($buffer, 0, $pos).$toolbarHTMLContent.substr($buffer, $pos); + } + + return $buffer; + }, 8); // length of '' + + ($callback)(); + ob_end_flush(); + }; + $response->setCallback($injectedCallback); } else { $content = $response->getContent(); $pos = strripos($content, ''); if (false !== $pos) { - $response->setContent( - $this->renderToolbarInContent($content, $pos, $response->headers->get('X-Debug-Token'), $request, $nonces) - ); + $toolbar = "\n".str_replace("\n", '', $this->getToolbarHTML($request, $response->headers->get('X-Debug-Token'), $nonces))."\n"; + + $content = substr($content, 0, $pos).$toolbar.substr($content, $pos); + $response->setContent($content); } } } - protected function renderToolbarInContent(string $content, int $pos, ?string $debugToken, Request $request, array $nonces): string - { - $toolbar = "\n".str_replace("\n", '', $this->getToolbarHTML($request, $debugToken, $nonces))."\n"; - - return substr($content, 0, $pos).$toolbar.substr($content, $pos); - } - private function getToolbarHTML(Request $request, ?string $debugToken, array $nonces): string { return $this->twig->render( From 6f0f7645516aa9da3580a5e75e9df5a7aef0ca52 Mon Sep 17 00:00:00 2001 From: Damien Fernandes Date: Fri, 8 Nov 2024 15:03:38 +0100 Subject: [PATCH 3/4] write a separate test for streamed response and remove MockedStreamedResponse class --- .../EventListener/WebDebugToolbarListener.php | 11 +- .../EventListener/MockedStreamedResponse.php | 38 -- .../WebDebugToolbarListenerTest.php | 141 ++----- ...bugToolbarStreamedResponseListenerTest.php | 386 ++++++++++++++++++ 4 files changed, 437 insertions(+), 139 deletions(-) delete mode 100644 src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/MockedStreamedResponse.php create mode 100644 src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarStreamedResponseListenerTest.php diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index e15a0b47edb55..d1eeeee1aa616 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -111,7 +111,14 @@ public function onKernelResponse(ResponseEvent $event): void $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } - $response->setContent($this->twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location'), 'host' => $request->getSchemeAndHttpHost()])); + if ($response instanceof StreamedResponse) { + $twig = $this->twig; + $response->setCallback(function () use ($twig, $request, $response): void { + echo $twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location'), 'host' => $request->getSchemeAndHttpHost()]); + }); + } else { + $response->setContent($this->twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location'), 'host' => $request->getSchemeAndHttpHost()])); + } $response->setStatusCode(200); $response->headers->remove('Location'); } @@ -136,7 +143,7 @@ protected function injectToolbar(Response $response, Request $request, array $no { if ($response instanceof StreamedResponse) { $callback = $response->getCallback(); - $toolbarHTMLContent = $this->getToolbarHTML($request, $response->headers->get('X-Debug-Token'), $nonces); + $toolbarHTMLContent = "\n".str_replace("\n", '', $this->getToolbarHTML($request, $response->headers->get('X-Debug-Token'), $nonces))."\n"; $injectedCallback = static function () use ($toolbarHTMLContent, $callback): void { ob_start(function (string $buffer, int $phase) use ($toolbarHTMLContent): string { $pos = strripos($buffer, ''); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/MockedStreamedResponse.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/MockedStreamedResponse.php deleted file mode 100644 index 430a327c4ff6e..0000000000000 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/MockedStreamedResponse.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\WebProfilerBundle\Tests\EventListener; - -use Symfony\Component\HttpFoundation\StreamedResponse; - -class MockedStreamedResponse extends StreamedResponse -{ - public function getContent(): string|false - { - ob_start(); - ($this->callback)(); - $content = ob_get_contents(); - ob_end_clean(); - - return $content; - } - - public static function createFromContent(string $content = ''): self - { - $response = new self(); - $response->setCallback(function () use ($content) { - echo $content; - }); - $response->headers->set('Content-Type', 'text/html'); - - return $response; - } -} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php index 83be0f91856b7..bd4ca434596fd 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php @@ -28,11 +28,13 @@ class WebDebugToolbarListenerTest extends TestCase /** * @dataProvider getInjectToolbarTests */ - public function testInjectToolbar(Response $response, string $expected) + public function testInjectToolbar(string $content, string $expected) { $listener = new WebDebugToolbarListener($this->getTwigMock()); $m = new \ReflectionMethod($listener, 'injectToolbar'); + $response = new Response($content); + $m->invoke($listener, $response, Request::create('/'), ['csp_script_nonce' => 'scripto', 'csp_style_nonce' => 'stylo']); $this->assertEquals($expected, $response->getContent()); } @@ -40,37 +42,18 @@ public function testInjectToolbar(Response $response, string $expected) public static function getInjectToolbarTests() { return [ - [new Response(''), "\nWDT\n"], - [new Response(' + ['', "\nWDT\n"], + [' - '), " + ', " \nWDT\n "], - [ - MockedStreamedResponse::createFromContent(''), - 'WDT', - ], - [ - MockedStreamedResponse::createFromContent( - ' - - - - - '), - ' - - - - WDT - ', - ], ]; } @@ -92,7 +75,7 @@ public function testHtmlRedirectionIsIntercepted(int $statusCode) public function testNonHtmlRedirectionIsNotIntercepted() { - $response = new Response('Some content', '301'); + $response = new Response('Some content', 301); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request([], [], ['_format' => 'json']), HttpKernelInterface::MAIN_REQUEST, $response); @@ -103,11 +86,9 @@ public function testNonHtmlRedirectionIsNotIntercepted() $this->assertEquals('Some content', $response->getContent()); } - /** - * @dataProvider provideInjectedResponse - */ - public function testToolbarIsInjected(Response $response, string $expected) + public function testToolbarIsInjected() { + $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); @@ -115,40 +96,15 @@ public function testToolbarIsInjected(Response $response, string $expected) $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals($expected, $response->getContent()); - } - - public static function provideInjectedResponse(): array - { - return [ - [new Response(''), "\nWDT\n"], - [MockedStreamedResponse::createFromContent(''), 'WDT'], - ]; - } - - public static function provideNotInjectedResponse(): array - { - return [ - [new Response(''), ''], - [MockedStreamedResponse::createFromContent(''), ''], - ]; - } - - public static function provideEmptyResponse(): array - { - return [ - [new Response()], - [MockedStreamedResponse::createFromContent()], - ]; + $this->assertEquals("\nWDT\n", $response->getContent()); } /** * @depends testToolbarIsInjected - * - * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedOnNonHtmlContentType(Response $response, string $expected) + public function testToolbarIsNotInjectedOnNonHtmlContentType() { + $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $response->headers->set('Content-Type', 'text/xml'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); @@ -156,16 +112,15 @@ public function testToolbarIsNotInjectedOnNonHtmlContentType(Response $response, $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals($expected, $response->getContent()); + $this->assertEquals('', $response->getContent()); } /** * @depends testToolbarIsInjected - * - * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedOnContentDispositionAttachment(Response $response, string $expected) + public function testToolbarIsNotInjectedOnContentDispositionAttachment() { + $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $response->headers->set('Content-Disposition', 'attachment; filename=test.html'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); @@ -173,7 +128,7 @@ public function testToolbarIsNotInjectedOnContentDispositionAttachment(Response $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals($expected, $response->getContent()); + $this->assertEquals('', $response->getContent()); } /** @@ -203,26 +158,25 @@ public static function provideRedirects(): array /** * @depends testToolbarIsInjected - * - * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedWhenThereIsNoNoXDebugTokenResponseHeader(Response $response, string $expected) + public function testToolbarIsNotInjectedWhenThereIsNoNoXDebugTokenResponseHeader() { + $response = new Response(''); + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals($expected, $response->getContent()); + $this->assertEquals('', $response->getContent()); } /** * @depends testToolbarIsInjected - * - * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedWhenOnSubRequest(Response $response, string $expected) + public function testToolbarIsNotInjectedWhenOnSubRequest() { + $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::SUB_REQUEST, $response); @@ -230,7 +184,7 @@ public function testToolbarIsNotInjectedWhenOnSubRequest(Response $response, str $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals($expected, $response->getContent()); + $this->assertEquals('', $response->getContent()); } /** @@ -251,11 +205,10 @@ public function testToolbarIsNotInjectedOnIncompleteHtmlResponses() /** * @depends testToolbarIsInjected - * - * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedOnXmlHttpRequests(Response $response, string $expected) + public function testToolbarIsNotInjectedOnXmlHttpRequests() { + $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $request = new Request(); @@ -266,16 +219,15 @@ public function testToolbarIsNotInjectedOnXmlHttpRequests(Response $response, st $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals($expected, $response->getContent()); + $this->assertEquals('', $response->getContent()); } /** * @depends testToolbarIsInjected - * - * @dataProvider provideNotInjectedResponse */ - public function testToolbarIsNotInjectedOnNonHtmlRequests(Response $response, string $expected) + public function testToolbarIsNotInjectedOnNonHtmlRequests() { + $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request([], [], ['_format' => 'json']), HttpKernelInterface::MAIN_REQUEST, $response); @@ -283,14 +235,12 @@ public function testToolbarIsNotInjectedOnNonHtmlRequests(Response $response, st $listener = new WebDebugToolbarListener($this->getTwigMock()); $listener->onKernelResponse($event); - $this->assertEquals($expected, $response->getContent()); + $this->assertEquals('', $response->getContent()); } - /** - * @dataProvider provideEmptyResponse - */ - public function testXDebugUrlHeader(Response $response) + public function testXDebugUrlHeader() { + $response = new Response(); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $urlGenerator = $this->createMock(UrlGeneratorInterface::class); @@ -309,11 +259,9 @@ public function testXDebugUrlHeader(Response $response) $this->assertEquals('http://mydomain.com/_profiler/xxxxxxxx', $response->headers->get('X-Debug-Token-Link')); } - /** - * @dataProvider provideEmptyResponse - */ - public function testThrowingUrlGenerator(Response $response) + public function testThrowingUrlGenerator() { + $response = new Response(); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $urlGenerator = $this->createMock(UrlGeneratorInterface::class); @@ -332,11 +280,9 @@ public function testThrowingUrlGenerator(Response $response) $this->assertEquals('Exception: foo', $response->headers->get('X-Debug-Error')); } - /** - * @dataProvider provideEmptyResponse - */ - public function testThrowingErrorCleanup(Response $response) + public function testThrowingErrorCleanup() { + $response = new Response(); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $urlGenerator = $this->createMock(UrlGeneratorInterface::class); @@ -355,11 +301,9 @@ public function testThrowingErrorCleanup(Response $response) $this->assertEquals('Exception: This multiline tabbed text should come out on a single plain line', $response->headers->get('X-Debug-Error')); } - /** - * @dataProvider provideInjectedResponse - */ - public function testCspIsDisabledIfDumperWasUsed(Response $response, string $expected) + public function testCspIsDisabledIfDumperWasUsed() { + $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); @@ -375,14 +319,12 @@ public function testCspIsDisabledIfDumperWasUsed(Response $response, string $exp $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, null, '', $cspHandler, $dumpDataCollector); $listener->onKernelResponse($event); - $this->assertEquals($expected, $response->getContent()); + $this->assertEquals("\nWDT\n", $response->getContent()); } - /** - * @dataProvider provideInjectedResponse - */ - public function testCspIsKeptEnabledIfDumperWasNotUsed(Response $response, string $expected) + public function testCspIsKeptEnabledIfDumperWasNotUsed() { + $response = new Response(''); $response->headers->set('X-Debug-Token', 'xxxxxxxx'); $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); @@ -398,7 +340,7 @@ public function testCspIsKeptEnabledIfDumperWasNotUsed(Response $response, strin $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, null, '', $cspHandler, $dumpDataCollector); $listener->onKernelResponse($event); - $this->assertEquals($expected, $response->getContent()); + $this->assertEquals("\nWDT\n", $response->getContent()); } public function testNullContentTypeWithNoDebugEnv() @@ -414,6 +356,7 @@ public function testNullContentTypeWithNoDebugEnv() $this->expectNotToPerformAssertions(); } + public function testAjaxReplaceHeaderOnDisabledToolbar() { $response = new Response(); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarStreamedResponseListenerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarStreamedResponseListenerTest.php new file mode 100644 index 0000000000000..86f15c52d99ad --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarStreamedResponseListenerTest.php @@ -0,0 +1,386 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler; +use Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Twig\Environment; + +class WebDebugToolbarStreamedResponseListenerTest extends TestCase +{ + /** + * @dataProvider getInjectToolbarTests + */ + public function testInjectToolbar(string $content, string $expected) + { + $listener = new WebDebugToolbarListener($this->getTwigMock()); + $m = new \ReflectionMethod($listener, 'injectToolbar'); + + $response = new StreamedResponse($this->createCallbackFromContent($content)); + + $m->invoke($listener, $response, Request::create('/'), ['csp_script_nonce' => 'scripto', 'csp_style_nonce' => 'stylo']); + $this->assertEquals($expected, $this->getContentFromStreamedResponse($response)); + } + + public static function getInjectToolbarTests() + { + return [ + ['', "\nWDT\n"], + [' + + + + + ', " + + + + \nWDT\n + "], + ]; + } + + /** + * @dataProvider provideRedirects + */ + public function testHtmlRedirectionIsIntercepted(int $statusCode) + { + $response = new StreamedResponse($this->createCallbackFromContent('Some content'), $statusCode); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock('Redirection'), true); + $listener->onKernelResponse($event); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('Redirection', $this->getContentFromStreamedResponse($response)); + } + + public function testNonHtmlRedirectionIsNotIntercepted() + { + $response = new StreamedResponse($this->createCallbackFromContent('Some content'), 301); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + $event = new ResponseEvent($this->createMock(Kernel::class), new Request([], [], ['_format' => 'json']), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock('Redirection'), true); + $listener->onKernelResponse($event); + + $this->assertEquals(301, $response->getStatusCode()); + $this->assertEquals('Some content', $this->getContentFromStreamedResponse($response)); + } + + public function testToolbarIsInjected() + { + $response = new StreamedResponse($this->createCallbackFromContent('')); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock()); + $listener->onKernelResponse($event); + + $this->assertEquals("\nWDT\n", $this->getContentFromStreamedResponse($response)); + } + + /** + * @depends testToolbarIsInjected + */ + public function testToolbarIsNotInjectedOnNonHtmlContentType() + { + $response = new StreamedResponse($this->createCallbackFromContent('')); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + $response->headers->set('Content-Type', 'text/xml'); + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock()); + $listener->onKernelResponse($event); + + $this->assertEquals('', $this->getContentFromStreamedResponse($response)); + } + + /** + * @depends testToolbarIsInjected + */ + public function testToolbarIsNotInjectedOnContentDispositionAttachment() + { + $response = new StreamedResponse($this->createCallbackFromContent('')); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + $response->headers->set('Content-Disposition', 'attachment; filename=test.html'); + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock()); + $listener->onKernelResponse($event); + + $this->assertEquals('', $this->getContentFromStreamedResponse($response)); + } + + /** + * @depends testToolbarIsInjected + * + * @dataProvider provideRedirects + */ + public function testToolbarIsNotInjectedOnRedirection(int $statusCode) + { + $response = new StreamedResponse($this->createCallbackFromContent(''), $statusCode); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock()); + $listener->onKernelResponse($event); + + $this->assertEquals('', $this->getContentFromStreamedResponse($response)); + } + + public static function provideRedirects(): array + { + return [ + [301], + [302], + ]; + } + + /** + * @depends testToolbarIsInjected + */ + public function testToolbarIsNotInjectedWhenThereIsNoNoXDebugTokenResponseHeader() + { + $response = new StreamedResponse($this->createCallbackFromContent('')); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock()); + $listener->onKernelResponse($event); + + $this->assertEquals('', $this->getContentFromStreamedResponse($response)); + } + + /** + * @depends testToolbarIsInjected + */ + public function testToolbarIsNotInjectedWhenOnSubRequest() + { + $response = new StreamedResponse($this->createCallbackFromContent('')); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::SUB_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock()); + $listener->onKernelResponse($event); + + $this->assertEquals('', $this->getContentFromStreamedResponse($response)); + } + + /** + * @depends testToolbarIsInjected + */ + public function testToolbarIsNotInjectedOnIncompleteHtmlResponses() + { + $response = new StreamedResponse($this->createCallbackFromContent('
Some content
')); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock()); + $listener->onKernelResponse($event); + + $this->assertEquals('
Some content
', $this->getContentFromStreamedResponse($response)); + } + + /** + * @depends testToolbarIsInjected + */ + public function testToolbarIsNotInjectedOnXmlHttpRequests() + { + $response = new StreamedResponse($this->createCallbackFromContent('')); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $request = new Request(); + $request->headers->set('X-Requested-With', 'XMLHttpRequest'); + + $event = new ResponseEvent($this->createMock(Kernel::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock()); + $listener->onKernelResponse($event); + + $this->assertEquals('', $this->getContentFromStreamedResponse($response)); + } + + /** + * @depends testToolbarIsInjected + */ + public function testToolbarIsNotInjectedOnNonHtmlRequests() + { + $response = new StreamedResponse($this->createCallbackFromContent('')); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request([], [], ['_format' => 'json']), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock()); + $listener->onKernelResponse($event); + + $this->assertEquals('', $this->getContentFromStreamedResponse($response)); + } + + public function testXDebugUrlHeader() + { + $response = new StreamedResponse(); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $urlGenerator = $this->createMock(UrlGeneratorInterface::class); + $urlGenerator + ->expects($this->once()) + ->method('generate') + ->with('_profiler', ['token' => 'xxxxxxxx'], UrlGeneratorInterface::ABSOLUTE_URL) + ->willReturn('http://mydomain.com/_profiler/xxxxxxxx') + ; + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, $urlGenerator); + $listener->onKernelResponse($event); + + $this->assertEquals('http://mydomain.com/_profiler/xxxxxxxx', $response->headers->get('X-Debug-Token-Link')); + } + + public function testThrowingUrlGenerator() + { + $response = new StreamedResponse(); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $urlGenerator = $this->createMock(UrlGeneratorInterface::class); + $urlGenerator + ->expects($this->once()) + ->method('generate') + ->with('_profiler', ['token' => 'xxxxxxxx']) + ->willThrowException(new \Exception('foo')) + ; + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, $urlGenerator); + $listener->onKernelResponse($event); + + $this->assertEquals('Exception: foo', $response->headers->get('X-Debug-Error')); + } + + public function testThrowingErrorCleanup() + { + $response = new StreamedResponse(); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $urlGenerator = $this->createMock(UrlGeneratorInterface::class); + $urlGenerator + ->expects($this->once()) + ->method('generate') + ->with('_profiler', ['token' => 'xxxxxxxx']) + ->willThrowException(new \Exception("This\nmultiline\r\ntabbed text should\tcome out\r on\n \ta single plain\r\nline")) + ; + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, $urlGenerator); + $listener->onKernelResponse($event); + + $this->assertEquals('Exception: This multiline tabbed text should come out on a single plain line', $response->headers->get('X-Debug-Error')); + } + + public function testCspIsDisabledIfDumperWasUsed() + { + $response = new StreamedResponse($this->createCallbackFromContent('')); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $cspHandler = $this->createMock(ContentSecurityPolicyHandler::class); + $cspHandler->expects($this->once()) + ->method('disableCsp'); + $dumpDataCollector = $this->createMock(DumpDataCollector::class); + $dumpDataCollector->expects($this->once()) + ->method('getDumpsCount') + ->willReturn(1); + + $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, null, '', $cspHandler, $dumpDataCollector); + $listener->onKernelResponse($event); + + $this->assertEquals("\nWDT\n", $this->getContentFromStreamedResponse($response)); + } + + public function testCspIsKeptEnabledIfDumperWasNotUsed() + { + $response = new StreamedResponse($this->createCallbackFromContent('')); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $cspHandler = $this->createMock(ContentSecurityPolicyHandler::class); + $cspHandler->expects($this->never()) + ->method('disableCsp'); + $dumpDataCollector = $this->createMock(DumpDataCollector::class); + $dumpDataCollector->expects($this->once()) + ->method('getDumpsCount') + ->willReturn(0); + + $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, null, '', $cspHandler, $dumpDataCollector); + $listener->onKernelResponse($event); + + $this->assertEquals("\nWDT\n", $this->getContentFromStreamedResponse($response)); + } + + public function testNullContentTypeWithNoDebugEnv() + { + $response = new StreamedResponse($this->createCallbackFromContent('')); + $response->headers->set('Content-Type', null); + $response->headers->set('X-Debug-Token', 'xxxxxxxx'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, null); + $listener->onKernelResponse($event); + + $this->expectNotToPerformAssertions(); + } + + protected function getTwigMock(string $render = 'WDT') + { + $templating = $this->createMock(Environment::class); + $templating->expects($this->any()) + ->method('render') + ->willReturn($render); + + return $templating; + } + + private function createCallbackFromContent(string $content): callable + { + return function () use ($content) { + echo $content; + }; + } + + private function getContentFromStreamedResponse(StreamedResponse $response): string + { + ob_start(); + $response->sendContent(); + $content = ob_get_contents(); + ob_end_clean(); + + return $content; + } +} From 677f95caa441bafe507cf9a5c9ba3e26e8d38cc7 Mon Sep 17 00:00:00 2001 From: Damien Fernandes Date: Fri, 24 Jan 2025 11:32:41 +0100 Subject: [PATCH 4/4] add changelog in 7.3 section --- src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index 109f947e3e28d..50787452ea277 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -5,12 +5,12 @@ CHANGELOG --- * Add `ajax_replace` option for replacing toolbar on AJAX requests + * Show debug bar when using a streamed response 7.2 --- * Add support for displaying profiles of multiple serializer instances - * Show debug bar when using a streamed response 7.1 --- pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy