Translated using Weblate (Portuguese)
[phpmyadmin.git] / src / Routing / Routing.php
blobe2abca08b290934bef5165d5f310d27f5c4f8893
1 <?php
3 declare(strict_types=1);
5 namespace PhpMyAdmin\Routing;
7 use FastRoute\DataGenerator\GroupCountBased as DataGeneratorGroupCountBased;
8 use FastRoute\Dispatcher;
9 use FastRoute\Dispatcher\GroupCountBased as DispatcherGroupCountBased;
10 use FastRoute\RouteCollector;
11 use FastRoute\RouteParser\Std as RouteParserStd;
12 use Fig\Http\Message\StatusCodeInterface;
13 use PhpMyAdmin\Bookmarks\BookmarkRepository;
14 use PhpMyAdmin\Config;
15 use PhpMyAdmin\ConfigStorage\Relation;
16 use PhpMyAdmin\Console;
17 use PhpMyAdmin\Controllers\HomeController;
18 use PhpMyAdmin\Controllers\InvocableController;
19 use PhpMyAdmin\Controllers\Setup\MainController;
20 use PhpMyAdmin\Controllers\Setup\ShowConfigController;
21 use PhpMyAdmin\Controllers\Setup\ValidateController;
22 use PhpMyAdmin\Core;
23 use PhpMyAdmin\DatabaseInterface;
24 use PhpMyAdmin\Http\Factory\ResponseFactory;
25 use PhpMyAdmin\Http\Response;
26 use PhpMyAdmin\Http\ServerRequest;
27 use PhpMyAdmin\LanguageManager;
28 use PhpMyAdmin\Message;
29 use PhpMyAdmin\Sanitize;
30 use PhpMyAdmin\Template;
31 use Psr\Container\ContainerInterface;
33 use function __;
34 use function array_pop;
35 use function assert;
36 use function explode;
37 use function file_exists;
38 use function file_put_contents;
39 use function htmlspecialchars;
40 use function implode;
41 use function is_array;
42 use function is_readable;
43 use function is_writable;
44 use function mb_strlen;
45 use function mb_strpos;
46 use function mb_strrpos;
47 use function mb_substr;
48 use function rawurldecode;
49 use function sprintf;
50 use function trigger_error;
51 use function urldecode;
52 use function var_export;
54 use const CACHE_DIR;
55 use const E_USER_WARNING;
57 /**
58 * Class used to warm up the routing cache and manage routing.
60 class Routing
62 /**
63 * @deprecated Use {@see ServerRequest::getRoute()} instead.
65 * @psalm-var non-empty-string
67 public static string $route = '/';
69 public const ROUTES_CACHE_FILE = CACHE_DIR . 'routes.cache.php';
71 public static function skipCache(): bool
73 return (Config::getInstance()->settings['environment'] ?? '') === 'development';
76 public static function canWriteCache(): bool
78 $cacheFileExists = file_exists(self::ROUTES_CACHE_FILE);
79 $canWriteFile = is_writable(self::ROUTES_CACHE_FILE);
80 if ($cacheFileExists && $canWriteFile) {
81 return true;
84 // Write without read does not work, chmod 200 for example
85 if (! $cacheFileExists && is_writable(CACHE_DIR) && is_readable(CACHE_DIR)) {
86 return true;
89 return $canWriteFile;
92 public static function getDispatcher(): Dispatcher
94 $skipCache = self::skipCache();
96 // If skip cache is enabled, do not try to read the file
97 // If no cache skipping then read it and use it
98 if (! $skipCache && file_exists(self::ROUTES_CACHE_FILE)) {
99 /** @psalm-suppress MissingFile, UnresolvableInclude, MixedAssignment */
100 $dispatchData = require self::ROUTES_CACHE_FILE;
101 if (self::isRoutesCacheFileValid($dispatchData)) {
102 return new DispatcherGroupCountBased($dispatchData);
106 $routeCollector = new RouteCollector(new RouteParserStd(), new DataGeneratorGroupCountBased());
107 Routes::collect($routeCollector);
109 $dispatchData = $routeCollector->getData();
110 $canWriteCache = self::canWriteCache();
112 // If skip cache is enabled, do not try to write it
113 // If no skip cache then try to write if write is possible
114 if (! $skipCache && $canWriteCache) {
115 $writeWorks = self::writeCache(
116 '<?php return ' . var_export($dispatchData, true) . ';',
118 if (! $writeWorks) {
119 trigger_error(
120 sprintf(
122 'The routing cache could not be written, '
123 . 'you need to adjust permissions on the folder/file "%s"',
125 self::ROUTES_CACHE_FILE,
127 E_USER_WARNING,
132 return new DispatcherGroupCountBased($dispatchData);
135 public static function writeCache(string $cacheContents): bool
137 return @file_put_contents(self::ROUTES_CACHE_FILE, $cacheContents) !== false;
141 * Call associated controller for a route using the dispatcher
143 public static function callControllerForRoute(
144 ServerRequest $request,
145 Dispatcher $dispatcher,
146 ContainerInterface $container,
147 ResponseFactory $responseFactory,
148 ): Response|null {
149 $route = $request->getRoute();
150 $routeInfo = $dispatcher->dispatch($request->getMethod(), rawurldecode($route));
152 if ($routeInfo[0] === Dispatcher::NOT_FOUND) {
153 $response = $responseFactory->createResponse(StatusCodeInterface::STATUS_NOT_FOUND);
155 return $response->write(Message::error(sprintf(
156 __('Error 404! The page %s was not found.'),
157 '<code>' . htmlspecialchars($route) . '</code>',
158 ))->getDisplay());
161 if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
162 $response = $responseFactory->createResponse(StatusCodeInterface::STATUS_METHOD_NOT_ALLOWED);
164 return $response->write(Message::error(__('Error 405! Request method not allowed.'))->getDisplay());
167 if ($routeInfo[0] !== Dispatcher::FOUND) {
168 return $responseFactory->createResponse(StatusCodeInterface::STATUS_BAD_REQUEST);
171 /** @psalm-var class-string<InvocableController> $controllerName */
172 $controllerName = $routeInfo[1];
174 $controller = $container->get($controllerName);
175 assert($controller instanceof InvocableController);
177 return $controller($request->withAttribute('routeVars', $routeInfo[2]));
180 /** @psalm-assert-if-true array[] $dispatchData */
181 private static function isRoutesCacheFileValid(mixed $dispatchData): bool
183 return is_array($dispatchData)
184 && isset($dispatchData[1])
185 && is_array($dispatchData[1])
186 && isset($dispatchData[0]['GET']['/'])
187 && $dispatchData[0]['GET']['/'] === HomeController::class;
190 public static function callSetupController(ServerRequest $request, ResponseFactory $responseFactory): Response
192 $route = $request->getRoute();
193 $template = new Template();
194 if ($route === '/setup' || $route === '/') {
195 $dbi = DatabaseInterface::getInstance();
196 $relation = new Relation($dbi);
197 $console = new Console($relation, $template, new BookmarkRepository($dbi, $relation));
199 return (new MainController($responseFactory, $template, $console))($request);
202 if ($route === '/setup/show-config') {
203 return (new ShowConfigController())($request);
206 if ($route === '/setup/validate') {
207 return (new ValidateController($responseFactory))($request);
210 $response = $responseFactory->createResponse(StatusCodeInterface::STATUS_NOT_FOUND);
212 return $response->write($template->render('error/generic', [
213 'lang' => $GLOBALS['lang'] ?? 'en',
214 'dir' => LanguageManager::$textDir,
215 'error_message' => Sanitize::convertBBCode(sprintf(
216 __('Error 404! The page %s was not found.'),
217 '[code]' . htmlspecialchars($route) . '[/code]',
219 ]));
223 * PATH_INFO could be compromised if set, so remove it from PHP_SELF
224 * and provide a clean PHP_SELF here
226 public static function getCleanPathInfo(): string
228 $pmaPhpSelf = Core::getEnv('PHP_SELF');
229 if ($pmaPhpSelf === '') {
230 $pmaPhpSelf = urldecode(Core::getEnv('REQUEST_URI'));
233 $pathInfo = Core::getEnv('PATH_INFO');
234 if ($pathInfo !== '' && $pmaPhpSelf !== '') {
235 $questionPos = mb_strpos($pmaPhpSelf, '?');
236 if ($questionPos != false) {
237 $pmaPhpSelf = mb_substr($pmaPhpSelf, 0, $questionPos);
240 $pathInfoPos = mb_strrpos($pmaPhpSelf, $pathInfo);
241 if ($pathInfoPos !== false) {
242 $pathInfoPart = mb_substr($pmaPhpSelf, $pathInfoPos, mb_strlen($pathInfo));
243 if ($pathInfoPart === $pathInfo) {
244 $pmaPhpSelf = mb_substr($pmaPhpSelf, 0, $pathInfoPos);
249 $path = [];
250 foreach (explode('/', $pmaPhpSelf) as $part) {
251 // ignore parts that have no value
252 if ($part === '' || $part === '.') {
253 continue;
256 if ($part !== '..') {
257 // cool, we found a new part
258 $path[] = $part;
259 } elseif ($path !== []) {
260 // going back up? sure
261 array_pop($path);
264 // Here we intentionall ignore case where we go too up
265 // as there is nothing sane to do
268 /** TODO: Do we really need htmlspecialchars here? */
269 return htmlspecialchars('/' . implode('/', $path));