Bug #93195

cHash comparison failure on a workspace page preview

Added by DANIEL Rémy 7 months ago. Updated 7 months ago.

Should have
Target version:
Start date:
Due date:
% Done:


Estimated time:
TYPO3 Version:
PHP Version:
Is Regression:
Sprint Focus:


Description of the bug

I use f:uri.action to generate a link to an Extbase plugin, with some plugin arguments.
There is not RouteEnhancer on the plugin, so a cHash is added to the generated uri.

When the target page of the link is a draft version of the page, accessing the generated uri leads to a 404 (cHash comparison failed).

How to reproduce

It is not easy to reproduce, but here is some steps.

The issue exists since TYPO3 10. I cannot reproduce it on TYPO3 9.

  1. setup ext:workspace with one draft workspace
  2. create a site with a rootpage and a working TS setup
  3. switch to the draft workspace
  4. on a page, create a TS template with this setup override:
      page.10 > 
      page.10 = FLUIDTEMPLATE
      page.10 {
        template = TEXT
        template.value = {f:uri.action(action: 'show', controller: 'Test', extensionName: 'Test', arguments: {foo: 'bar'})}
  5. the page has now a live version (uid=1, t3ver_wsid=0) and a draft version (uid=2, t3ver_oid=1, t3ver_wsid=1)
  6. display the preview of the page: you see the uri generated with a cHash
  7. go to the generated url: a 404 is triggered because of a cHash comparison failure


When building the cHash, the ID of the page is used along with other parameters in the cHash calculation .
When validating the cHash, the ID of the page found by the router is used (see \TYPO3\CMS\Frontend\Middleware\PageArgumentValidator::getRelevantParametersForCacheHashCalculation).
In most of the case, there is no reason that this page's id change, so this part of the cHash calculation/checking works well.

Now, while debugging, I found that when generating the uri, the uid of the live page is used.

But when the uri is requested and the cHash is validated, the page id is taken from $request->getAttribute('routing') (which is a PageArguments instance).
Here, the page id is the draft uid.

Because the page id are not the same, the cHash comparison could not succeed, and this will trigger a 404.


Updated by DANIEL Rémy 7 months ago

  • Description updated (diff)

Updated by DANIEL Rémy 7 months ago

The PageRouter already overrides the decoded page's uid with the l10n_parent if it exists.
See \TYPO3\CMS\Core\Routing\PageRouter::buildPageArguments()

The same logic exists also in \TYPO3\CMS\Core\Routing\PageSlugCandidateProvider::getRealPageIdForPageIdAsPossibleCandidate() and \TYPO3\CMS\Core\Routing\PageSlugCandidateProvider::getPagesFromDatabaseForCandidates().

Maybe the PageSlugCandidateProvider should be fixed in order to always return the live uid of the page.


Updated by DANIEL Rémy 7 months ago

Fix proposal

The PageSlugCandidateProvider should be fixed to return the live uid of the record (if exists), the versionOL will be done later in the request handling (TSFE->determineId).

The page record is actually loaded with the requested slug, and with language and workspace restrictions.
Then, mountpage logic is applied, in which versionOL is done.

I propose to fix the uid of the loaded record if it is a draft record.
A very rough fix could be:

--- a/Classes/Routing/PageSlugCandidateProvider.php    2020-12-22 07:59:57.000000000 +0100
+++ b/Classes/Routing/PageSlugCandidateProvider.php    2020-12-31 16:38:24.123223188 +0100
@@ -136,7 +136,13 @@
         if (empty($page)) {
             return null;
-        return (int)($page['l10n_parent'] ?: $page['uid']);
+        $pageIdInDefaultLanguage = (int)($page['l10n_parent'] ?: $page['uid']);
+        if ($liveRowId = \TYPO3\CMS\Backend\Utility\BackendUtility::getLiveVersionIdOfRecord('pages', $pageIdInDefaultLanguage)) {
+            return $liveRowId;
+        }
+        return $pageIdInDefaultLanguage;

@@ -230,6 +236,11 @@
         $isRecursiveCall = !empty($excludeUids);

         while ($row = $statement->fetch()) {
+            if ($liveRowId = \TYPO3\CMS\Backend\Utility\BackendUtility::getLiveVersionIdOfRecord('pages', $row['uid'])) {
+                $row['_ORIG_uid'] = $row['uid'];
+                $row['uid'] = $liveRowId;
+            }
             $mountPageInformation = null;
             // This changes the PID value and adds a _ORIG_PID value (only different in move actions)
             // In live: This fetches everything in a bad way ! as there is no workspace limitation given, fetching all new and moved placeholders here!

To be discussed, especially the way l10n_parent should be handled in case l10n_parent point to a draft version (whenever this is possible?)


Updated by DANIEL Rémy 7 months ago

  • Description updated (diff)

Also available in: Atom PDF