[TASK] Ensure HTTP RequestHandlers always return a PSR-7 Repsonse
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Http / AjaxRequestHandler.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Backend\Http;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use TYPO3\CMS\Backend\Routing\Exception\InvalidRequestTokenException;
21 use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException;
22 use TYPO3\CMS\Core\Core\Bootstrap;
23 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
24 use TYPO3\CMS\Core\Http\Response;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26
27 /**
28 * AJAX dispatcher
29 *
30 * Main entry point for AJAX calls in the TYPO3 Backend. Based on ?route=/ajax/* of the outside application.
31 * Before doing the basic BE-related set up of this request (see the additional calls on $this->bootstrap inside
32 * handleRequest()), some AJAX-calls can be made without a valid user, which is determined here.
33 *
34 * AJAX Requests are typically registered within EXT:myext/Configuration/Backend/AjaxRoutes.php
35 */
36 class AjaxRequestHandler implements RequestHandlerInterface
37 {
38 /**
39 * Instance of the current TYPO3 bootstrap
40 * @var Bootstrap
41 */
42 protected $bootstrap;
43
44 /**
45 * List of requests that don't need a valid BE user
46 * @var array
47 */
48 protected $publicAjaxRoutes = [
49 '/ajax/login',
50 '/ajax/logout',
51 '/ajax/login/refresh',
52 '/ajax/login/timedout',
53 '/ajax/rsa/publickey'
54 ];
55
56 /**
57 * Constructor handing over the bootstrap and the original request
58 *
59 * @param Bootstrap $bootstrap
60 */
61 public function __construct(Bootstrap $bootstrap)
62 {
63 $this->bootstrap = $bootstrap;
64 }
65
66 /**
67 * Handles any AJAX request in the TYPO3 Backend
68 *
69 * @param ServerRequestInterface $request
70 * @return ResponseInterface
71 */
72 public function handleRequest(ServerRequestInterface $request): ResponseInterface
73 {
74 // First get the name of the route
75 $routePath = $request->getParsedBody()['route'] ?? $request->getQueryParams()['route'] ?? '';
76 $request = $request->withAttribute('routePath', $routePath);
77
78 $proceedIfNoUserIsLoggedIn = $this->isLoggedInBackendUserRequired($routePath);
79 $this->boot($proceedIfNoUserIsLoggedIn);
80
81 // Backend Routing - check if a valid route is there, and dispatch
82 return $this->dispatch($request);
83 }
84
85 /**
86 * This request handler can handle any backend request having
87 * an /ajax/ request
88 *
89 * @param ServerRequestInterface $request
90 * @return bool If the request is an AJAX backend request, TRUE otherwise FALSE
91 */
92 public function canHandleRequest(ServerRequestInterface $request): bool
93 {
94 $routePath = $request->getParsedBody()['route'] ?? $request->getQueryParams()['route'] ?? '';
95 return strpos($routePath, '/ajax/') === 0;
96 }
97
98 /**
99 * Returns the priority - how eager the handler is to actually handle the request.
100 *
101 * @return int The priority of the request handler.
102 */
103 public function getPriority(): int
104 {
105 return 80;
106 }
107
108 /**
109 * Check if the user is required for the request
110 * If we're trying to do an ajax login, don't require a user
111 *
112 * @param string $routePath the Route path to check against, something like '
113 * @return bool whether the request can proceed without a login required
114 */
115 protected function isLoggedInBackendUserRequired(string $routePath): bool
116 {
117 return in_array($routePath, $this->publicAjaxRoutes, true);
118 }
119
120 /**
121 * Start the Backend bootstrap part
122 *
123 * @param bool $proceedIfNoUserIsLoggedIn a flag if a backend user is required
124 */
125 protected function boot(bool $proceedIfNoUserIsLoggedIn)
126 {
127 $this->bootstrap
128 ->checkLockedBackendAndRedirectOrDie($proceedIfNoUserIsLoggedIn)
129 ->checkBackendIpOrDie()
130 ->checkSslBackendAndRedirectIfNeeded()
131 ->initializeBackendRouter()
132 ->loadExtTables()
133 ->initializeBackendUser()
134 ->initializeBackendAuthentication($proceedIfNoUserIsLoggedIn)
135 ->initializeLanguageObject()
136 ->initializeBackendTemplate()
137 ->endOutputBufferingAndCleanPreviousOutput()
138 ->initializeOutputCompression()
139 ->sendHttpHeaders();
140 }
141
142 /**
143 * Creates a response object with JSON headers automatically, and then dispatches to the correct route
144 *
145 * @param ServerRequestInterface $request
146 * @return ResponseInterface $response
147 * @throws ResourceNotFoundException if no valid route was found
148 * @throws InvalidRequestTokenException if the request could not be verified
149 */
150 protected function dispatch(ServerRequestInterface $request): ResponseInterface
151 {
152 /** @var Response $response */
153 $response = GeneralUtility::makeInstance(Response::class, 'php://temp', 200, [
154 'Content-Type' => 'application/json; charset=utf-8',
155 'X-JSON' => 'true'
156 ]);
157
158 /** @var RouteDispatcher $dispatcher */
159 $dispatcher = GeneralUtility::makeInstance(RouteDispatcher::class);
160 return $dispatcher->dispatch($request, $response);
161 }
162 }