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

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