From c1e7be0ad4e24b0275d3a463b98f8af16c4b8a9e Mon Sep 17 00:00:00 2001
From: Damien Fernandes
'); - - 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 [ - ['