Commit a848ba4f authored by Christian Eßl's avatar Christian Eßl Committed by Georg Ringer
Browse files

[FEATURE] Allow PageContentErrorHandler to resolve pages with sub requests

The PageContentErrorHandler provided by the core can take in either a
URL or a page uid for resolving an error page in the frontend. In both
cases, the class would then start a Guzzle/cURL request to fetch the
error page content.
This has now been changed for internal pages, where a page uid has been
given. In this case, the PageContentErrorHandler will now dispatch an
internal SubRequest instead, to avoid an unnecessary cURL call.

In staging environments, the website would often be access protected
with basic auth options (for example a .htpasswd auth file on Apache
Webservers). In such a case, error pages with the default
PageContentErrorHandler would have failed before, as the internal cURL
call for fetching the error page was lacking these required basic auth
options.
For internal pages, a sub request is now used, bypassing the need for
an external cURL call.

This solution is mostly based on Benni Mack's
LocalPageContentErrorHandler with his approval.

Resolves: #90505
Releases: master
Change-Id: I9da835cd42503d7a52f9050ae5658eae53336a56
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63389


Reviewed-by: Björn Jacob's avatarBjörn Jacob <bjoern.jacob@tritum.de>
Reviewed-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Reviewed-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Tested-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
parent 6c4d73f5
......@@ -18,14 +18,19 @@ namespace TYPO3\CMS\Core\Error\PageErrorHandler;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\DependencyInjection\FailsafeContainer;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
use TYPO3\CMS\Core\Http\MiddlewareStackResolver;
use TYPO3\CMS\Core\LinkHandling\LinkService;
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
use TYPO3\CMS\Core\Service\DependencyOrderingService;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Http\RequestHandler;
/**
* Renders the content of a page to be displayed (also in relation to language etc)
......@@ -68,8 +73,20 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
*/
public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
{
$linkService = GeneralUtility::makeInstance(LinkService::class);
$urlParams = $linkService->resolve((string)$this->errorHandlerConfiguration['errorContentSource']);
if ($urlParams['type'] !== 'page' && $urlParams['type'] !== 'url') {
throw new \InvalidArgumentException('PageContentErrorHandler can only handle TYPO3 urls of types "page" or "url"', 1522826609);
}
if ($urlParams['type'] === 'page') {
$response = $this->buildSubRequest($request, (int)$urlParams['pageuid']);
return $response->withStatus($this->statusCode);
}
$resolvedUrl = $urlParams['url'];
try {
$resolvedUrl = $this->resolveUrl($request, $this->errorHandlerConfiguration['errorContentSource']);
$content = null;
$report = [];
......@@ -82,59 +99,67 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
} catch (InvalidRouteArgumentsException | SiteNotFoundException $e) {
$content = 'Invalid error handler configuration: ' . $this->errorHandlerConfiguration['errorContentSource'];
}
return new HtmlResponse($content, $this->statusCode);
}
/**
* Resolve the URL (currently only page and external URL are supported)
*
* @param ServerRequestInterface $request
* @param string $typoLinkUrl
* @return string
* @param int $pageId
* @return ResponseInterface
* @throws SiteNotFoundException
* @throws InvalidRouteArgumentsException
* @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException
* @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
* @throws \TYPO3\CMS\Core\Exception
* @throws \RuntimeException
*/
protected function resolveUrl(ServerRequestInterface $request, string $typoLinkUrl): string
protected function buildSubRequest(ServerRequestInterface $request, int $pageId): ResponseInterface
{
$linkService = GeneralUtility::makeInstance(LinkService::class);
$urlParams = $linkService->resolve($typoLinkUrl);
if ($urlParams['type'] !== 'page' && $urlParams['type'] !== 'url') {
throw new \InvalidArgumentException('PageContentErrorHandler can only handle TYPO3 urls of types "page" or "url"', 1522826609);
}
if ($urlParams['type'] === 'url') {
return $urlParams['url'];
}
$site = $request->getAttribute('site', null);
if (!$site instanceof Site) {
$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId((int)$urlParams['pageuid']);
$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
$request = $request->withAttribute('site', $site);
}
$language = $request->getAttribute('language', null);
if (!$language instanceof SiteLanguage || !$language->isEnabled()) {
$language = $site->getDefaultLanguage();
if (!$this->pageExistsAndInRootline($pageId, $site->getRootPageId())) {
throw new \RuntimeException('Page does not exist or is not in rootline.', 1582448967);
}
// Build Url
$uri = $site->getRouter()->generateUri(
(int)$urlParams['pageuid'],
['_language' => $language]
$request = $request->withQueryParams(['id' => $pageId]);
$dispatcher = $this->buildDispatcher();
return $dispatcher->handle($request);
}
/**
* @return MiddlewareDispatcher
* @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException
* @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
* @throws \TYPO3\CMS\Core\Exception
*/
protected function buildDispatcher()
{
$requestHandler = GeneralUtility::makeInstance(RequestHandler::class);
$resolver = new MiddlewareStackResolver(
GeneralUtility::makeInstance(FailsafeContainer::class),
GeneralUtility::makeInstance(DependencyOrderingService::class),
GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core')
);
// Fallback to the current URL if the site is not having a proper scheme and host
$currentUri = $request->getUri();
if (empty($uri->getScheme())) {
$uri = $uri->withScheme($currentUri->getScheme());
}
if (empty($uri->getUserInfo())) {
$uri = $uri->withUserInfo($currentUri->getUserInfo());
}
if (empty($uri->getHost())) {
$uri = $uri->withHost($currentUri->getHost());
}
if ($uri->getPort() === null) {
$uri = $uri->withPort($currentUri->getPort());
}
$middlewares = $resolver->resolve('frontend');
return new MiddlewareDispatcher($requestHandler, $middlewares);
}
return (string)$uri;
/**
* @param int $pageId
* @param int $rootPageId
* @return bool
*/
protected function pageExistsAndInRootline(int $pageId, int $rootPageId): bool
{
try {
return GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId)->getRootPageId() === $rootPageId;
} catch (SiteNotFoundException $e) {
return false;
}
}
}
.. include:: ../../Includes.txt
===========================================================================================
Feature: #90505 - Allow PageContentErrorHandler to resolve internal pages with sub requests
===========================================================================================
See :issue:`90505`
Description
===========
The PageContentErrorHandler provided by the core can take in either a URL or a page uid for resolving an error page in the frontend. In both cases, the class would then start a Guzzle/cURL request to fetch the error page content.
This has now been changed for internal pages, where a page uid has been given. In this case, the PageContentErrorHandler will now dispatch an internal SubRequest instead, to avoid an unnecessary cURL call.
Impact
======
In staging environments, the website would often be access protected with basic auth options (for example a .htpasswd auth file on Apache Webservers).
In such a case, error pages with the default PageContentErrorHandler would have failed before, as the internal cURL call for fetching the error page was lacking these required basic auth options.
For internal pages, a sub request is now used, bypassing the need for an external cURL call.
.. index:: Frontend, ext:core
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment