Commit af512dbd authored by Stefan Bürk's avatar Stefan Bürk Committed by Benni Mack
Browse files

[BUGFIX] Resolving page slug ending with index and type suffix '.php'

If a page slug ends with 'index' and site has a routing
configuration with a PageTypeSuffix with index and suffix
'.php' as default suffix, route resolving cuts 'index.php'
from the uri, ending with a slug without index in it and
thus not resolving the page and displaying 404 page.

The reason for this is the code fragment which strips the
document root from the uri as a cleanup for some systems,
and fixes an issue for calling id/type parameters with
index.php (main entry point), which has been added as a
bugfix in v9 with #88028.

There was a first attempt to fix this page resolving issue
with #94537, which has been a regression and reverted with
issue #94968.

To ensure everything works and regression is covered before
a new patch, there has been two patches with #95096 and #95362.

After the regression there has been a really deep analysis,
to get all puzzle pieces together over the time, which ended
in the pre-patches and finally in this patch.

The stripping of the document root has been implemented to
late in the resolving chain to solve #88028, thus the followup
fixes interferred with the resolving concept. This patch moves
this stripping from the page-resolving point some steps early
to the site-resolving step, and doing the stripping only on
the left site of the uri and avoiding a generall str_replace(),
thus fixing past issues and the current issue in the correct
place.

Furthermore tests for this issue are (re)added with this patch,
as they were removed with the revert.

Resolves: #94905
Related: #95096
Related: #95362
Related: #94537
Related: #94968
Related: #88028
Releases: main, 11.5
Change-Id: I2e10689893047e2d8f19057dfac2fb3b5f0bdcde
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70640


Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 95b3352c
......@@ -26,7 +26,6 @@ use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\LanguageAspectFactory;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Core\Routing\Aspect\AspectFactory;
use TYPO3\CMS\Core\Routing\Aspect\MappableProcessor;
......@@ -141,17 +140,6 @@ class PageRouter implements RouterInterface
}
$urlPath = $previousResult->getTail();
// Remove the script name (e.g. index.php), if given
if (!empty($urlPath)) {
$normalizedParams = $request->getAttribute('normalizedParams');
if ($normalizedParams instanceof NormalizedParams) {
$scriptName = ltrim($normalizedParams->getScriptName(), '/');
if ($scriptName !== '' && str_contains($urlPath, $scriptName)) {
$urlPath = str_replace($scriptName, '', $urlPath);
}
}
}
$language = $previousResult->getLanguage();
// Keep possible existing "/" at the end (no trim, just ltrim), even though the page slug might not
// contain a "/" at the end. This way we find page candidates where pages MIGHT have a trailing slash
......
......@@ -24,6 +24,7 @@ use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Site\Entity\NullSite;
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
......@@ -116,6 +117,20 @@ class SiteMatcher implements SingletonInterface
}
}
$uri = $request->getUri();
if (!empty($uri->getPath())) {
$normalizedParams = $request->getAttribute('normalizedParams');
if ($normalizedParams instanceof NormalizedParams) {
$urlPath = ltrim($uri->getPath(), '/');
$scriptName = ltrim($normalizedParams->getScriptName(), '/');
$scriptPath = ltrim($normalizedParams->getSitePath(), '/');
if ($scriptName !== '' && str_starts_with($urlPath, $scriptName)) {
$urlPath = '/' . $scriptPath . substr($urlPath, mb_strlen($scriptName));
$uri = $uri->withPath($urlPath);
}
}
}
// No language found at this point means that the URL was not used with a valid "?id=1&L=2" parameter
// which resulted in a site / language combination that was found. Now, the matching is done
// on the incoming URL.
......@@ -124,18 +139,18 @@ class SiteMatcher implements SingletonInterface
$context = new RequestContext(
'',
$request->getMethod(),
(string)idn_to_ascii($request->getUri()->getHost()),
$request->getUri()->getScheme(),
(string)idn_to_ascii($uri->getHost()),
$uri->getScheme(),
// Ports are only necessary for URL generation in Symfony which is not used by TYPO3
80,
443,
$request->getUri()->getPath()
$uri->getPath()
);
$matcher = new UrlMatcher($collection, $context);
try {
$result = $matcher->match($request->getUri()->getPath());
$result = $matcher->match($uri->getPath());
return new SiteRouteResult(
$request->getUri(),
$uri,
$result['site'],
// if no language is found, this usually results due to "/" called instead of "/fr/"
// but it could also be the reason that "/index.php?id=23" was called, so the default
......@@ -152,7 +167,7 @@ class SiteMatcher implements SingletonInterface
}
}
return new SiteRouteResult($request->getUri(), $site, $language);
return new SiteRouteResult($uri, $site, $language);
}
/**
......
......@@ -54,21 +54,6 @@ class EidRequestTest extends AbstractTestCase
static::failIfArrayIsNotEmpty(
$writer->getErrors()
);
$this->setUpFrontendRootPage(
1000,
[
'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript',
'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/JsonRenderer.typoscript',
],
[
'title' => 'ACME Root',
]
);
$this->writeSiteConfiguration(
'website-local',
$this->buildSiteConfiguration(1000, 'https://website.local/')
);
}
/**
......@@ -218,15 +203,119 @@ class EidRequestTest extends AbstractTestCase
[],
null,
],
// without id/type parameters
'eid with index.php without id parameter' => [
'https://website.local/index.php?eID=test_eid&some_parameter=1',
200,
[
'content-type' => [
'application/json',
],
'eid_responder' => [
'responded',
],
],
[
'eid_responder' => true,
'uri' => 'https://website.local/index.php?eID=test_eid&some_parameter=1',
'method' => 'GET',
'queryParams' => [
'eID' => 'test_eid',
'some_parameter' => '1',
],
],
],
'eid on slug page without id parameter' => [
'https://website.local/en-welcome/?eID=test_eid&some_parameter=1',
200,
[
'content-type' => [
'application/json',
],
'eid_responder' => [
'responded',
],
],
[
'eid_responder' => true,
'uri' => 'https://website.local/en-welcome/?eID=test_eid&some_parameter=1',
'method' => 'GET',
'queryParams' => [
'eID' => 'test_eid',
'some_parameter' => '1',
],
],
],
'eid without index.php with type without id parameter' => [
'https://website.local/?eID=test_eid&some_parameter=1&type=0',
200,
[
'content-type' => [
'application/json',
],
'eid_responder' => [
'responded',
],
],
[
'eid_responder' => true,
'uri' => 'https://website.local/?eID=test_eid&some_parameter=1&type=0',
'method' => 'GET',
'queryParams' => [
'eID' => 'test_eid',
'some_parameter' => '1',
'type' => '0',
],
],
],
'eid with index.php with type without id parameter' => [
'https://website.local/index.php?eID=test_eid&some_parameter=1&type=0',
200,
[
'content-type' => [
'application/json',
],
'eid_responder' => [
'responded',
],
],
[
'eid_responder' => true,
'uri' => 'https://website.local/index.php?eID=test_eid&some_parameter=1&type=0',
'method' => 'GET',
'queryParams' => [
'eID' => 'test_eid',
'some_parameter' => '1',
'type' => '0',
],
],
],
'eid on slug page with type without id parameter' => [
'https://website.local/en-welcome/?eID=test_eid&some_parameter=1&type=0',
200,
[
'content-type' => [
'application/json',
],
'eid_responder' => [
'responded',
],
],
[
'eid_responder' => true,
'uri' => 'https://website.local/en-welcome/?eID=test_eid&some_parameter=1&type=0',
'method' => 'GET',
'queryParams' => [
'eID' => 'test_eid',
'some_parameter' => '1',
'type' => '0',
],
],
],
];
}
/**
* @param string $uri
* @param int $expectedStatusCode
* @param array $expectedHeaders
* @param array $expectedResponseData
*
* @test
* @dataProvider ensureEidRequestsWorkDataProvider
*/
......@@ -236,6 +325,66 @@ class EidRequestTest extends AbstractTestCase
array $expectedHeaders,
?array $expectedResponseData
): void {
$this->setUpFrontendRootPage(
1000,
[
'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript',
'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/JsonRenderer.typoscript',
],
[
'title' => 'ACME Root',
]
);
$this->writeSiteConfiguration(
'website-local',
$this->buildSiteConfiguration(1000, 'https://website.local/')
);
$response = $this->executeFrontendSubRequest(new InternalRequest($uri));
self::assertSame($expectedStatusCode, $response->getStatusCode());
self::assertSame($expectedHeaders, $response->getHeaders());
if ($expectedResponseData !== null) {
self::assertSame($expectedResponseData, json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR));
}
}
/**
* @test
* @dataProvider ensureEidRequestsWorkDataProvider
*/
public function ensureEidRequestsWorkWithDotPhpPageTypeSuffixRoutingConfiguration(
string $uri,
int $expectedStatusCode,
array $expectedHeaders,
?array $expectedResponseData
): void {
$this->setUpFrontendRootPage(
1000,
[
'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript',
'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/JsonRenderer.typoscript',
],
[
'title' => 'ACME Root',
]
);
$this->writeSiteConfiguration(
'website-local',
array_replace(
$this->buildSiteConfiguration(1000, 'https://website.local/'),
[
'routeEnhancers' => [
'PageTypeSuffix' => [
'type' => 'PageType',
'default' => '.php',
'index' => 'index',
'map' => [],
],
],
]
)
);
$response = $this->executeFrontendSubRequest(new InternalRequest($uri));
self::assertSame($expectedStatusCode, $response->getStatusCode());
self::assertSame($expectedHeaders, $response->getHeaders());
......
......@@ -126,6 +126,16 @@ class Builder
'menu.json' => 10,
],
];
$multipleTypesConfigurationDotPhp = [
'type' => 'PageType',
'default' => '.php',
'index' => 'index',
'map' => [
'.php' => 0,
'menu.json' => 10,
'.xml' => 20,
],
];
return [
PageTypeDeclaration::create('null ".html"')
......@@ -156,6 +166,15 @@ class Builder
->withGenerateParameters(['&type=0'])
->withResolveArguments(['pageType' => 0])
->withVariables(Variables::create(['pathSuffix' => '/', 'index' => ''])),
PageTypeDeclaration::create('null ".php"')
->withConfiguration($multipleTypesConfigurationDotPhp)
->withResolveArguments(['pageType' => 0])
->withVariables(Variables::create(['pathSuffix' => '.php', 'index' => 'index'])),
PageTypeDeclaration::create('0 ".php"')
->withConfiguration($multipleTypesConfigurationDotPhp)
->withGenerateParameters(['&type=0'])
->withResolveArguments(['pageType' => 0])
->withVariables(Variables::create(['pathSuffix' => '.php', 'index' => 'index'])),
];
}
......
......@@ -426,6 +426,23 @@ class SiteRequestTest extends AbstractTestCase
['https://website.local/?id=1515', 3, 'Research'],
['https://website.local/?id=1520', 3, 'Forecasts'],
['https://website.local/?id=1521', 3, 'Current Year'],
// frontend user 1 with index
['https://website.local/index.php?id=1510', 1, 'Whitepapers'],
['https://website.local/index.php?id=1511', 1, 'Products'],
['https://website.local/index.php?id=1512', 1, 'Solutions'],
// frontend user 2
['https://website.local/index.php?id=1510', 2, 'Whitepapers'],
['https://website.local/index.php?id=1511', 2, 'Products'],
['https://website.local/index.php?id=1515', 2, 'Research'],
['https://website.local/index.php?id=1520', 2, 'Forecasts'],
['https://website.local/index.php?id=1521', 2, 'Current Year'],
// frontend user 3
['https://website.local/index.php?id=1510', 3, 'Whitepapers'],
['https://website.local/index.php?id=1511', 3, 'Products'],
['https://website.local/index.php?id=1512', 3, 'Solutions'],
['https://website.local/index.php?id=1515', 3, 'Research'],
['https://website.local/index.php?id=1520', 3, 'Forecasts'],
['https://website.local/index.php?id=1521', 3, 'Current Year'],
];
return $this->keysFromTemplate($instructions, '%1$s (user:%2$s)');
......
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