Remove ResponseRenderer::disable from UrlRedirector class

Creates a new Response object instead of using the response from
ResponseRenderer class.

Signed-off-by: Maurício Meneghini Fauth <mauricio@fauth.dev>
This commit is contained in:
Maurício Meneghini Fauth 2024-05-03 18:04:02 -03:00
parent 27f5a8aaf5
commit 98b3eda545
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
6 changed files with 64 additions and 56 deletions

View File

@ -3,3 +3,4 @@
window.location = '{{ url|escape('js') }}';
};
</script>
{{ t('Taking you to the target site.') }}

View File

@ -103,7 +103,7 @@ class Application
$requestHandler->add(new RequestProblemChecking($this->template, $this->responseFactory));
$requestHandler->add(new CurrentServerGlobalSetting($this->config));
$requestHandler->add(new ThemeInitialization());
$requestHandler->add(new UrlRedirection($this->config));
$requestHandler->add(new UrlRedirection($this->config, $this->template, $this->responseFactory));
$requestHandler->add(new SetupPageRedirection($this->config, $this->responseFactory));
$requestHandler->add(new MinimumCommonRedirection($this->config, $this->responseFactory));
$requestHandler->add(new LanguageAndThemeCookieSaving($this->config));

View File

@ -6,6 +6,9 @@ namespace PhpMyAdmin\Http\Middleware;
use PhpMyAdmin\Config;
use PhpMyAdmin\Container\ContainerBuilder;
use PhpMyAdmin\Http\Factory\ResponseFactory;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\Template;
use PhpMyAdmin\Theme\ThemeManager;
use PhpMyAdmin\UrlRedirector;
use Psr\Http\Message\ResponseInterface;
@ -13,12 +16,13 @@ use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use function is_string;
final class UrlRedirection implements MiddlewareInterface
{
public function __construct(private readonly Config $config)
{
public function __construct(
private readonly Config $config,
private readonly Template $template,
private readonly ResponseFactory $responseFactory,
) {
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
@ -32,11 +36,8 @@ final class UrlRedirection implements MiddlewareInterface
$themeManager = $container->get(ThemeManager::class);
$this->config->loadUserPreferences($themeManager, true);
return UrlRedirector::redirect($this->getUrlParam($request->getQueryParams()['url'] ?? null));
}
$urlRedirector = new UrlRedirector(ResponseRenderer::getInstance(), $this->template, $this->responseFactory);
private function getUrlParam(mixed $url): string
{
return is_string($url) ? $url : '';
return $urlRedirector->redirect($request->getQueryParams()['url'] ?? null);
}
}

View File

@ -428,21 +428,26 @@ class ResponseRenderer
throw new ExitException($message);
}
/**
* Avoid relative path redirect problems in case user entered URL
* like /phpmyadmin/index.php/ which some web servers happily accept.
*/
public function fixRelativeUrlForRedirect(string $url): string
{
if (! str_starts_with($url, '.')) {
return $url;
}
return $this->config->getRootPath() . substr($url, 2);
}
/**
* @psalm-param non-empty-string $url
* @psalm-param StatusCodeInterface::STATUS_* $statusCode
*/
public function redirect(string $url, int $statusCode = StatusCodeInterface::STATUS_FOUND): void
{
/**
* Avoid relative path redirect problems in case user entered URL
* like /phpmyadmin/index.php/ which some web servers happily accept.
*/
if (str_starts_with($url, '.')) {
$url = $this->config->getRootPath() . substr($url, 2);
}
$this->addHeader('Location', $url);
$this->addHeader('Location', $this->fixRelativeUrlForRedirect($url));
$this->setStatusCode($statusCode);
}

View File

@ -4,10 +4,11 @@ declare(strict_types=1);
namespace PhpMyAdmin;
use PhpMyAdmin\Container\ContainerBuilder;
use Fig\Http\Message\StatusCodeInterface;
use PhpMyAdmin\Http\Factory\ResponseFactory;
use PhpMyAdmin\Http\Response;
use function __;
use function is_string;
use function preg_match;
/**
@ -15,37 +16,36 @@ use function preg_match;
*/
final class UrlRedirector
{
public static function redirect(string $url): Response
public function __construct(
private readonly ResponseRenderer $response,
private readonly Template $template,
private readonly ResponseFactory $responseFactory,
) {
}
public function redirect(mixed $urlParam): Response
{
$container = ContainerBuilder::getContainer();
// Only output the http headers
$response = ResponseRenderer::getInstance();
$response->getHeader()->sendHttpHeaders();
$response->disable();
$response = $this->responseFactory->createResponse();
foreach ($this->response->getHeader()->getHttpHeaders() as $name => $value) {
$response = $response->withHeader($name, $value);
}
$url = is_string($urlParam) ? $urlParam : '';
if (
$url === ''
|| ! preg_match('/^https:\/\/[^\n\r]*$/', $url)
|| ! Core::isAllowedDomain($url)
) {
$response->redirect('./');
$response = $response->withHeader('Location', $this->response->fixRelativeUrlForRedirect('./'));
return $response->response();
return $response->withStatus(StatusCodeInterface::STATUS_FOUND);
}
/**
* JavaScript redirection is necessary. Because if header() is used then web browser sometimes does not change
* the HTTP_REFERER field and so with old URL as Referer, token also goes to external site.
*
* @var Template $template
*/
$template = $container->get('template');
echo $template->render('javascript/redirect', ['url' => $url]);
// Display redirecting msg on screen.
// Do not display the value of $_GET['url'] to avoid showing injected content
echo __('Taking you to the target site.');
return $response->response();
return $response->write($this->template->render('javascript/redirect', ['url' => $url]));
}
}

View File

@ -6,43 +6,44 @@ namespace PhpMyAdmin\Tests;
use Fig\Http\Message\StatusCodeInterface;
use PhpMyAdmin\Config;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\Http\Factory\ResponseFactory;
use PhpMyAdmin\Template;
use PhpMyAdmin\Tests\Stubs\ResponseRenderer;
use PhpMyAdmin\UrlRedirector;
use PHPUnit\Framework\Attributes\CoversClass;
use ReflectionProperty;
#[CoversClass(UrlRedirector::class)]
final class UrlRedirectorTest extends AbstractTestCase
{
public function testRedirectWithDisallowedUrl(): void
{
(new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null);
$GLOBALS['lang'] = 'en';
$config = Config::getInstance();
$config->settings['PmaAbsoluteUri'] = 'http://localhost/phpmyadmin';
$response = UrlRedirector::redirect('https://user:pass@example.com/');
$urlRedirector = new UrlRedirector(new ResponseRenderer(), new Template(), ResponseFactory::create());
$response = $urlRedirector->redirect('https://user:pass@example.com/');
self::assertSame('/phpmyadmin/', $response->getHeaderLine('Location'));
self::assertSame(StatusCodeInterface::STATUS_FOUND, $response->getStatusCode());
}
public function testRedirectWithAllowedUrl(): void
{
(new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null);
$GLOBALS['lang'] = 'en';
$_SERVER['SERVER_NAME'] = 'localhost';
UrlRedirector::redirect('https://phpmyadmin.net/');
$output = self::getActualOutputForAssertion();
$expected = <<<'HTML'
<script>
window.onload = function () {
window.location = 'https\u003A\/\/phpmyadmin.net\/';
};
</script>
Taking you to the target site.
HTML;
$urlRedirector = new UrlRedirector(new ResponseRenderer(), new Template(), ResponseFactory::create());
self::assertSame($expected, $output);
$response = $urlRedirector->redirect('https://phpmyadmin.net/');
$expected = <<<'HTML'
<script>
window.onload = function () {
window.location = 'https\u003A\/\/phpmyadmin.net\/';
};
</script>
Taking you to the target site.
HTML;
self::assertSame($expected, (string) $response->getBody());
}
}