vendor/symfony/web-profiler-bundle/Controller/ProfilerController.php line 70

  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\Controller;
  11. use Symfony\Bundle\FullStack;
  12. use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler;
  13. use Symfony\Bundle\WebProfilerBundle\Profiler\TemplateManager;
  14. use Symfony\Component\HttpFoundation\RedirectResponse;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use Symfony\Component\HttpFoundation\Response;
  17. use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag;
  18. use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector;
  19. use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector;
  20. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  21. use Symfony\Component\HttpKernel\Profiler\Profiler;
  22. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  23. use Twig\Environment;
  24. /**
  25.  * @author Fabien Potencier <fabien@symfony.com>
  26.  *
  27.  * @internal
  28.  */
  29. class ProfilerController
  30. {
  31.     private $templateManager;
  32.     private $generator;
  33.     private $profiler;
  34.     private $twig;
  35.     private $templates;
  36.     private $cspHandler;
  37.     private $baseDir;
  38.     public function __construct(UrlGeneratorInterface $generatorProfiler $profiler nullEnvironment $twig, array $templatesContentSecurityPolicyHandler $cspHandler nullstring $baseDir null)
  39.     {
  40.         $this->generator $generator;
  41.         $this->profiler $profiler;
  42.         $this->twig $twig;
  43.         $this->templates $templates;
  44.         $this->cspHandler $cspHandler;
  45.         $this->baseDir $baseDir;
  46.     }
  47.     /**
  48.      * Redirects to the last profiles.
  49.      *
  50.      * @throws NotFoundHttpException
  51.      */
  52.     public function homeAction(): RedirectResponse
  53.     {
  54.         $this->denyAccessIfProfilerDisabled();
  55.         return new RedirectResponse($this->generator->generate('_profiler_search_results', ['token' => 'empty''limit' => 10]), 302, ['Content-Type' => 'text/html']);
  56.     }
  57.     /**
  58.      * Renders a profiler panel for the given token.
  59.      *
  60.      * @throws NotFoundHttpException
  61.      */
  62.     public function panelAction(Request $requeststring $token): Response
  63.     {
  64.         $this->denyAccessIfProfilerDisabled();
  65.         $this->cspHandler?->disableCsp();
  66.         $panel $request->query->get('panel');
  67.         $page $request->query->get('page''home');
  68.         if ('latest' === $token && $latest current($this->profiler->find(nullnull1nullnullnull))) {
  69.             $token $latest['token'];
  70.         }
  71.         if (!$profile $this->profiler->loadProfile($token)) {
  72.             return $this->renderWithCspNonces($request'@WebProfiler/Profiler/info.html.twig', ['about' => 'no_token''token' => $token'request' => $request]);
  73.         }
  74.         if (null === $panel) {
  75.             $panel 'request';
  76.             foreach ($profile->getCollectors() as $collector) {
  77.                 if ($collector instanceof ExceptionDataCollector && $collector->hasException()) {
  78.                     $panel $collector->getName();
  79.                     break;
  80.                 }
  81.                 if ($collector instanceof DumpDataCollector && $collector->getDumpsCount() > 0) {
  82.                     $panel $collector->getName();
  83.                 }
  84.             }
  85.         }
  86.         if (!$profile->hasCollector($panel)) {
  87.             throw new NotFoundHttpException(sprintf('Panel "%s" is not available for token "%s".'$panel$token));
  88.         }
  89.         return $this->renderWithCspNonces($request$this->getTemplateManager()->getName($profile$panel), [
  90.             'token' => $token,
  91.             'profile' => $profile,
  92.             'collector' => $profile->getCollector($panel),
  93.             'panel' => $panel,
  94.             'page' => $page,
  95.             'request' => $request,
  96.             'templates' => $this->getTemplateManager()->getNames($profile),
  97.             'is_ajax' => $request->isXmlHttpRequest(),
  98.             'profiler_markup_version' => 3// 1 = original profiler, 2 = Symfony 2.8+ profiler, 3 = Symfony 6.2+ profiler
  99.         ]);
  100.     }
  101.     /**
  102.      * Renders the Web Debug Toolbar.
  103.      *
  104.      * @throws NotFoundHttpException
  105.      */
  106.     public function toolbarAction(Request $requeststring $token null): Response
  107.     {
  108.         if (null === $this->profiler) {
  109.             throw new NotFoundHttpException('The profiler must be enabled.');
  110.         }
  111.         if ($request->hasSession() && ($session $request->getSession())->isStarted() && $session->getFlashBag() instanceof AutoExpireFlashBag) {
  112.             // keep current flashes for one more request if using AutoExpireFlashBag
  113.             $session->getFlashBag()->setAll($session->getFlashBag()->peekAll());
  114.         }
  115.         if ('empty' === $token || null === $token) {
  116.             return new Response(''200, ['Content-Type' => 'text/html']);
  117.         }
  118.         $this->profiler->disable();
  119.         if (!$profile $this->profiler->loadProfile($token)) {
  120.             return new Response(''404, ['Content-Type' => 'text/html']);
  121.         }
  122.         $url null;
  123.         try {
  124.             $url $this->generator->generate('_profiler', ['token' => $token], UrlGeneratorInterface::ABSOLUTE_URL);
  125.         } catch (\Exception) {
  126.             // the profiler is not enabled
  127.         }
  128.         return $this->renderWithCspNonces($request'@WebProfiler/Profiler/toolbar.html.twig', [
  129.             'full_stack' => class_exists(FullStack::class),
  130.             'request' => $request,
  131.             'profile' => $profile,
  132.             'templates' => $this->getTemplateManager()->getNames($profile),
  133.             'profiler_url' => $url,
  134.             'token' => $token,
  135.             'profiler_markup_version' => 3// 1 = original toolbar, 2 = Symfony 2.8+ profiler, 3 = Symfony 6.2+ profiler
  136.         ]);
  137.     }
  138.     /**
  139.      * Renders the profiler search bar.
  140.      *
  141.      * @throws NotFoundHttpException
  142.      */
  143.     public function searchBarAction(Request $request): Response
  144.     {
  145.         $this->denyAccessIfProfilerDisabled();
  146.         $this->cspHandler?->disableCsp();
  147.         if (!$request->hasSession()) {
  148.             $ip =
  149.             $method =
  150.             $statusCode =
  151.             $url =
  152.             $start =
  153.             $end =
  154.             $limit =
  155.             $token null;
  156.         } else {
  157.             $session $request->getSession();
  158.             $ip $request->query->get('ip'$session->get('_profiler_search_ip'));
  159.             $method $request->query->get('method'$session->get('_profiler_search_method'));
  160.             $statusCode $request->query->get('status_code'$session->get('_profiler_search_status_code'));
  161.             $url $request->query->get('url'$session->get('_profiler_search_url'));
  162.             $start $request->query->get('start'$session->get('_profiler_search_start'));
  163.             $end $request->query->get('end'$session->get('_profiler_search_end'));
  164.             $limit $request->query->get('limit'$session->get('_profiler_search_limit'));
  165.             $token $request->query->get('token'$session->get('_profiler_search_token'));
  166.         }
  167.         return new Response(
  168.             $this->twig->render('@WebProfiler/Profiler/search.html.twig', [
  169.                 'token' => $token,
  170.                 'ip' => $ip,
  171.                 'method' => $method,
  172.                 'status_code' => $statusCode,
  173.                 'url' => $url,
  174.                 'start' => $start,
  175.                 'end' => $end,
  176.                 'limit' => $limit,
  177.                 'request' => $request,
  178.                 'render_hidden_by_default' => false,
  179.             ]),
  180.             200,
  181.             ['Content-Type' => 'text/html']
  182.         );
  183.     }
  184.     /**
  185.      * Renders the search results.
  186.      *
  187.      * @throws NotFoundHttpException
  188.      */
  189.     public function searchResultsAction(Request $requeststring $token): Response
  190.     {
  191.         $this->denyAccessIfProfilerDisabled();
  192.         $this->cspHandler?->disableCsp();
  193.         $profile $this->profiler->loadProfile($token);
  194.         $ip $request->query->get('ip');
  195.         $method $request->query->get('method');
  196.         $statusCode $request->query->get('status_code');
  197.         $url $request->query->get('url');
  198.         $start $request->query->get('start'null);
  199.         $end $request->query->get('end'null);
  200.         $limit $request->query->get('limit');
  201.         return $this->renderWithCspNonces($request'@WebProfiler/Profiler/results.html.twig', [
  202.             'request' => $request,
  203.             'token' => $token,
  204.             'profile' => $profile,
  205.             'tokens' => $this->profiler->find($ip$url$limit$method$start$end$statusCode),
  206.             'ip' => $ip,
  207.             'method' => $method,
  208.             'status_code' => $statusCode,
  209.             'url' => $url,
  210.             'start' => $start,
  211.             'end' => $end,
  212.             'limit' => $limit,
  213.             'panel' => null,
  214.         ]);
  215.     }
  216.     /**
  217.      * Narrows the search bar.
  218.      *
  219.      * @throws NotFoundHttpException
  220.      */
  221.     public function searchAction(Request $request): Response
  222.     {
  223.         $this->denyAccessIfProfilerDisabled();
  224.         $ip $request->query->get('ip');
  225.         $method $request->query->get('method');
  226.         $statusCode $request->query->get('status_code');
  227.         $url $request->query->get('url');
  228.         $start $request->query->get('start'null);
  229.         $end $request->query->get('end'null);
  230.         $limit $request->query->get('limit');
  231.         $token $request->query->get('token');
  232.         if ($request->hasSession()) {
  233.             $session $request->getSession();
  234.             $session->set('_profiler_search_ip'$ip);
  235.             $session->set('_profiler_search_method'$method);
  236.             $session->set('_profiler_search_status_code'$statusCode);
  237.             $session->set('_profiler_search_url'$url);
  238.             $session->set('_profiler_search_start'$start);
  239.             $session->set('_profiler_search_end'$end);
  240.             $session->set('_profiler_search_limit'$limit);
  241.             $session->set('_profiler_search_token'$token);
  242.         }
  243.         if (!empty($token)) {
  244.             return new RedirectResponse($this->generator->generate('_profiler', ['token' => $token]), 302, ['Content-Type' => 'text/html']);
  245.         }
  246.         $tokens $this->profiler->find($ip$url$limit$method$start$end$statusCode);
  247.         return new RedirectResponse($this->generator->generate('_profiler_search_results', [
  248.             'token' => $tokens $tokens[0]['token'] : 'empty',
  249.             'ip' => $ip,
  250.             'method' => $method,
  251.             'status_code' => $statusCode,
  252.             'url' => $url,
  253.             'start' => $start,
  254.             'end' => $end,
  255.             'limit' => $limit,
  256.         ]), 302, ['Content-Type' => 'text/html']);
  257.     }
  258.     /**
  259.      * Displays the PHP info.
  260.      *
  261.      * @throws NotFoundHttpException
  262.      */
  263.     public function phpinfoAction(): Response
  264.     {
  265.         $this->denyAccessIfProfilerDisabled();
  266.         $this->cspHandler?->disableCsp();
  267.         ob_start();
  268.         phpinfo();
  269.         $phpinfo ob_get_clean();
  270.         return new Response($phpinfo200, ['Content-Type' => 'text/html']);
  271.     }
  272.     /**
  273.      * Displays the Xdebug info.
  274.      *
  275.      * @throws NotFoundHttpException
  276.      */
  277.     public function xdebugAction(): Response
  278.     {
  279.         $this->denyAccessIfProfilerDisabled();
  280.         if (!\function_exists('xdebug_info')) {
  281.             throw new NotFoundHttpException('Xdebug must be installed in version 3.');
  282.         }
  283.         $this->cspHandler?->disableCsp();
  284.         ob_start();
  285.         xdebug_info();
  286.         $xdebugInfo ob_get_clean();
  287.         return new Response($xdebugInfo200, ['Content-Type' => 'text/html']);
  288.     }
  289.     /**
  290.      * Displays the source of a file.
  291.      *
  292.      * @throws NotFoundHttpException
  293.      */
  294.     public function openAction(Request $request): Response
  295.     {
  296.         if (null === $this->baseDir) {
  297.             throw new NotFoundHttpException('The base dir should be set.');
  298.         }
  299.         $this->profiler?->disable();
  300.         $file $request->query->get('file');
  301.         $line $request->query->get('line');
  302.         $filename $this->baseDir.\DIRECTORY_SEPARATOR.$file;
  303.         if (preg_match("'(^|[/\\\\])\.'"$file) || !is_readable($filename)) {
  304.             throw new NotFoundHttpException(sprintf('The file "%s" cannot be opened.'$file));
  305.         }
  306.         return $this->renderWithCspNonces($request'@WebProfiler/Profiler/open.html.twig', [
  307.             'file_info' => new \SplFileInfo($filename),
  308.             'file' => $file,
  309.             'line' => $line,
  310.         ]);
  311.     }
  312.     protected function getTemplateManager(): TemplateManager
  313.     {
  314.         return $this->templateManager ??= new TemplateManager($this->profiler$this->twig$this->templates);
  315.     }
  316.     private function denyAccessIfProfilerDisabled()
  317.     {
  318.         if (null === $this->profiler) {
  319.             throw new NotFoundHttpException('The profiler must be enabled.');
  320.         }
  321.         $this->profiler->disable();
  322.     }
  323.     private function renderWithCspNonces(Request $requeststring $template, array $variablesint $code 200, array $headers = ['Content-Type' => 'text/html']): Response
  324.     {
  325.         $response = new Response(''$code$headers);
  326.         $nonces $this->cspHandler $this->cspHandler->getNonces($request$response) : [];
  327.         $variables['csp_script_nonce'] = $nonces['csp_script_nonce'] ?? null;
  328.         $variables['csp_style_nonce'] = $nonces['csp_style_nonce'] ?? null;
  329.         $response->setContent($this->twig->render($template$variables));
  330.         return $response;
  331.     }
  332. }