vendor/symfony/web-profiler-bundle/EventListener/WebDebugToolbarListener.php line 76

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Bundle\WebProfilerBundle\EventListener;
  11. use Symfony\Bundle\FullStack;
  12. use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler;
  13. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\HttpFoundation\Response;
  16. use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag;
  17. use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector;
  18. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  19. use Symfony\Component\HttpKernel\KernelEvents;
  20. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  21. use Twig\Environment;
  22. /**
  23.  * WebDebugToolbarListener injects the Web Debug Toolbar.
  24.  *
  25.  * The onKernelResponse method must be connected to the kernel.response event.
  26.  *
  27.  * The WDT is only injected on well-formed HTML (with a proper </body> tag).
  28.  * This means that the WDT is never included in sub-requests or ESI requests.
  29.  *
  30.  * @author Fabien Potencier <fabien@symfony.com>
  31.  *
  32.  * @final
  33.  */
  34. class WebDebugToolbarListener implements EventSubscriberInterface
  35. {
  36.     public const DISABLED 1;
  37.     public const ENABLED 2;
  38.     protected $twig;
  39.     protected $urlGenerator;
  40.     protected $interceptRedirects;
  41.     protected $mode;
  42.     protected $excludedAjaxPaths;
  43.     private $cspHandler;
  44.     private $dumpDataCollector;
  45.     public function __construct(Environment $twigbool $interceptRedirects falseint $mode self::ENABLEDUrlGeneratorInterface $urlGenerator nullstring $excludedAjaxPaths '^/bundles|^/_wdt'ContentSecurityPolicyHandler $cspHandler nullDumpDataCollector $dumpDataCollector null)
  46.     {
  47.         $this->twig $twig;
  48.         $this->urlGenerator $urlGenerator;
  49.         $this->interceptRedirects $interceptRedirects;
  50.         $this->mode $mode;
  51.         $this->excludedAjaxPaths $excludedAjaxPaths;
  52.         $this->cspHandler $cspHandler;
  53.         $this->dumpDataCollector $dumpDataCollector;
  54.     }
  55.     public function isEnabled(): bool
  56.     {
  57.         return self::DISABLED !== $this->mode;
  58.     }
  59.     public function setMode(int $mode): void
  60.     {
  61.         if (self::DISABLED !== $mode && self::ENABLED !== $mode) {
  62.             throw new \InvalidArgumentException(sprintf('Invalid value provided for mode, use one of "%s::DISABLED" or "%s::ENABLED".'self::class, self::class));
  63.         }
  64.         $this->mode $mode;
  65.     }
  66.     public function onKernelResponse(ResponseEvent $event)
  67.     {
  68.         $response $event->getResponse();
  69.         $request $event->getRequest();
  70.         if ($response->headers->has('X-Debug-Token') && null !== $this->urlGenerator) {
  71.             try {
  72.                 $response->headers->set(
  73.                     'X-Debug-Token-Link',
  74.                     $this->urlGenerator->generate('_profiler', ['token' => $response->headers->get('X-Debug-Token')], UrlGeneratorInterface::ABSOLUTE_URL)
  75.                 );
  76.             } catch (\Exception $e) {
  77.                 $response->headers->set('X-Debug-Error'\get_class($e).': '.preg_replace('/\s+/'' '$e->getMessage()));
  78.             }
  79.         }
  80.         if (!$event->isMainRequest()) {
  81.             return;
  82.         }
  83.         $nonces = [];
  84.         if ($this->cspHandler) {
  85.             if ($this->dumpDataCollector && $this->dumpDataCollector->getDumpsCount() > 0) {
  86.                 $this->cspHandler->disableCsp();
  87.             }
  88.             $nonces $this->cspHandler->updateResponseHeaders($request$response);
  89.         }
  90.         // do not capture redirects or modify XML HTTP Requests
  91.         if ($request->isXmlHttpRequest()) {
  92.             return;
  93.         }
  94.         if ($response->headers->has('X-Debug-Token') && $response->isRedirect() && $this->interceptRedirects && 'html' === $request->getRequestFormat()) {
  95.             if ($request->hasSession() && ($session $request->getSession())->isStarted() && $session->getFlashBag() instanceof AutoExpireFlashBag) {
  96.                 // keep current flashes for one more request if using AutoExpireFlashBag
  97.                 $session->getFlashBag()->setAll($session->getFlashBag()->peekAll());
  98.             }
  99.             $response->setContent($this->twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location')]));
  100.             $response->setStatusCode(200);
  101.             $response->headers->remove('Location');
  102.         }
  103.         if (self::DISABLED === $this->mode
  104.             || !$response->headers->has('X-Debug-Token')
  105.             || $response->isRedirection()
  106.             || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type'), 'html'))
  107.             || 'html' !== $request->getRequestFormat()
  108.             || false !== stripos($response->headers->get('Content-Disposition'''), 'attachment;')
  109.         ) {
  110.             return;
  111.         }
  112.         $this->injectToolbar($response$request$nonces);
  113.     }
  114.     /**
  115.      * Injects the web debug toolbar into the given Response.
  116.      */
  117.     protected function injectToolbar(Response $responseRequest $request, array $nonces)
  118.     {
  119.         $content $response->getContent();
  120.         $pos strripos($content'</body>');
  121.         if (false !== $pos) {
  122.             $toolbar "\n".str_replace("\n"''$this->twig->render(
  123.                 '@WebProfiler/Profiler/toolbar_js.html.twig',
  124.                 [
  125.                     'full_stack' => class_exists(FullStack::class),
  126.                     'excluded_ajax_paths' => $this->excludedAjaxPaths,
  127.                     'token' => $response->headers->get('X-Debug-Token'),
  128.                     'request' => $request,
  129.                     'csp_script_nonce' => $nonces['csp_script_nonce'] ?? null,
  130.                     'csp_style_nonce' => $nonces['csp_style_nonce'] ?? null,
  131.                 ]
  132.             ))."\n";
  133.             $content substr($content0$pos).$toolbar.substr($content$pos);
  134.             $response->setContent($content);
  135.         }
  136.     }
  137.     public static function getSubscribedEvents(): array
  138.     {
  139.         return [
  140.             KernelEvents::RESPONSE => ['onKernelResponse', -128],
  141.         ];
  142.     }
  143. }