Skip to content

Commit 01ce663

Browse files
committed
[BUGFIX] Make importmaps cacheable
Render importmaps without a nonce in frontend mode and supply a hash to the policy registry to allow the page to be cached, as prepared in #104281. Resolves: #106564 Related: #104281 Releases: main, 13.4 Change-Id: Ie9a619e07b7eceb6858a84be597d86f29a1ca70b Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/80200 Tested-by: core-ci <typo3@b13.com> Tested-by: Oliver Hader <oliver.hader@typo3.org> Tested-by: Georg Ringer <georg.ringer@gmail.com> Reviewed-by: Benjamin Franzke <ben@bnf.dev> Reviewed-by: Andreas Kienast <akienast@scripting-base.de> Reviewed-by: Georg Ringer <georg.ringer@gmail.com> Tested-by: Benjamin Franzke <ben@bnf.dev> Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
1 parent e503ba7 commit 01ce663

File tree

4 files changed

+31
-4
lines changed

4 files changed

+31
-4
lines changed

typo3/sysext/core/Classes/Page/ImportMap.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
use TYPO3\CMS\Core\Package\PackageInterface;
2525
use TYPO3\CMS\Core\Page\Event\ResolveJavaScriptImportEvent;
2626
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\ConsumableNonce;
27+
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
28+
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\HashValue;
29+
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation;
30+
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection;
31+
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode;
32+
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\PolicyRegistry;
2733
use TYPO3\CMS\Core\Utility\ArrayUtility;
2834
use TYPO3\CMS\Core\Utility\GeneralUtility;
2935
use TYPO3\CMS\Core\Utility\PathUtility;
@@ -43,6 +49,7 @@ class ImportMap
4349
public function __construct(
4450
protected readonly HashService $hashService,
4551
protected readonly array $packages,
52+
protected readonly ?PolicyRegistry $policyRegistry = null,
4653
protected readonly ?FrontendInterface $cache = null,
4754
protected readonly string $cacheIdentifier = '',
4855
protected readonly ?EventDispatcherInterface $eventDispatcher = null,
@@ -129,8 +136,23 @@ public function render(
129136
$importMap,
130137
JSON_FORCE_OBJECT | JSON_UNESCAPED_SLASHES | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_THROW_ON_ERROR
131138
);
132-
$nonceAttr = $nonce !== null ? ' nonce="' . htmlspecialchars((string)$nonce) . '"' : '';
133-
$html[] = sprintf('<script type="importmap"%s>%s</script>', $nonceAttr, $json);
139+
$attributes = [
140+
'type' => 'importmap',
141+
];
142+
if ($nonce !== null) {
143+
$attributes['nonce'] = (string)$nonce;
144+
} else {
145+
$this->policyRegistry?->appendMutationCollection(
146+
new MutationCollection(
147+
new Mutation(MutationMode::Extend, Directive::ScriptSrc, HashValue::hash($json))
148+
)
149+
);
150+
}
151+
$html[] = sprintf(
152+
'<script %s>%s</script>',
153+
GeneralUtility::implodeAttributes($attributes, true),
154+
$json
155+
);
134156

135157
return implode(PHP_EOL, $html) . PHP_EOL;
136158
}

typo3/sysext/core/Classes/Page/ImportMapFactory.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
2424
use TYPO3\CMS\Core\Crypto\HashService;
2525
use TYPO3\CMS\Core\Package\PackageManager;
26+
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\PolicyRegistry;
2627
use TYPO3\CMS\Core\SingletonInterface;
2728

2829
#[Autoconfigure(public: true)]
@@ -31,6 +32,7 @@
3132
public function __construct(
3233
private HashService $hashService,
3334
private PackageManager $packageManager,
35+
private PolicyRegistry $policyRegistry,
3436
#[Autowire(service: 'cache.assets')]
3537
private FrontendInterface $assetsCache,
3638
private EventDispatcherInterface $eventDispatcher,
@@ -46,6 +48,7 @@ public function create(bool $bustSuffix = true): ImportMap
4648
return new ImportMap(
4749
$this->hashService,
4850
$activePackages,
51+
$this->policyRegistry,
4952
$this->assetsCache,
5053
$this->cacheIdentifier,
5154
$this->eventDispatcher,

typo3/sysext/core/Classes/Page/PageRenderer.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1442,9 +1442,11 @@ protected function renderMainJavaScriptLibraries()
14421442

14431443
// @todo hookup with PSR-7 request/response
14441444
$sitePath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1445+
1446+
$useNonce = $this->getApplicationType() === 'BE';
14451447
$out .= $this->javaScriptRenderer->renderImportMap(
14461448
$sitePath,
1447-
$this->nonce
1449+
$useNonce ? $this->nonce : null,
14481450
);
14491451

14501452
$this->loadJavaScriptLanguageStrings();

typo3/sysext/core/Tests/Unit/Page/ImportMapTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public function resolveNestedUrlImportWithoutBustSuffix(): void
116116
{
117117
$this->packages = ['core'];
118118

119-
$importMap = new ImportMap($this->hashService, $this->getPackages(), null, '', null, false);
119+
$importMap = new ImportMap($this->hashService, $this->getPackages(), null, null, '', null, false);
120120
$nestedUrl = $importMap->resolveImport('@typo3/core/nested/module.js');
121121

122122
self::assertEquals('Fixtures/ImportMap/core/Resources/Public/JavaScript/nested/module.js', $nestedUrl);

0 commit comments

Comments
 (0)