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

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