Skip to content

Commit 2ca5113

Browse files
Create WebDebugToolbarTurboDriveFixSubscriber.php
1 parent 28e8b28 commit 2ca5113

File tree

1 file changed

+80
-0
lines changed

1 file changed

+80
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
namespace Symfony\UX\Turbo\EventSubscriber;
4+
5+
use Symfony\Component\DependencyInjection\Attribute\When;
6+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
7+
use Symfony\Component\HttpKernel\Event\ResponseEvent;
8+
use Symfony\Component\HttpKernel\KernelEvents;
9+
10+
11+
/**
12+
* Allows the Web Debug Toolbar (WDT) when navigating via Turbo Drive when a strict Content-Security-Policy is set.
13+
* This is done by reusing WDT nonces.
14+
*/
15+
16+
#[When(env: 'dev')]
17+
class WebDebugToolbarTurboDriveFixSubscriber implements EventSubscriberInterface
18+
{
19+
public function onKernelResponse(ResponseEvent $event): void
20+
{
21+
$request = $event->getRequest();
22+
$routeName = $request->get('_route');
23+
24+
if ($routeName === '_wdt') {return;}
25+
if ($request->headers->has('X-Turbo-Request-Id')) {return;}
26+
if ('html' !== $request->getRequestFormat()) {return;}
27+
28+
$response = $event->getResponse();
29+
$content = $response->getContent();
30+
31+
$scriptContent = <<<'EOD'
32+
document.addEventListener('turbo:before-fetch-request', (event) =>
33+
{
34+
var wdt = document.querySelector('.sf-toolbar');
35+
if (wdt)
36+
{
37+
let wdtStyle = wdt.nextElementSibling;
38+
let wdtScript = wdtStyle.nextElementSibling;
39+
40+
if (wdtStyle.nonce) {event.detail.fetchOptions.headers['X-SymfonyProfiler-Style-Nonce'] = wdtStyle.nonce;}
41+
if (wdtScript.nonce) {event.detail.fetchOptions.headers['X-SymfonyProfiler-Script-Nonce'] = wdtScript.nonce;}
42+
}
43+
});
44+
EOD;
45+
$scriptTag = '<script>' . $scriptContent . '</script>';
46+
47+
$hash = base64_encode(hash('sha256', $scriptContent, true));
48+
$hashString = "'sha256-" . $hash . "'";
49+
50+
$csp = $response->headers->get('Content-Security-Policy');
51+
if ($csp)
52+
{
53+
if (preg_match('/script-src\s+([^;]+)/', $csp, $matches))
54+
{
55+
$scriptSrc = $matches[1];
56+
57+
if (false === strpos($scriptSrc, $hashString))
58+
{
59+
$newScriptSrc = $scriptSrc . ' ' . $hashString;
60+
$csp = str_replace($matches[0], 'script-src ' . $newScriptSrc, $csp);
61+
}
62+
}
63+
else
64+
{
65+
$csp .= "; script-src 'self' " . $hashString;
66+
}
67+
$response->headers->set('Content-Security-Policy', $csp);
68+
}
69+
70+
$modifiedContent = str_replace('</head>', $scriptTag . '</head>', $content);
71+
$response->setContent($modifiedContent);
72+
}
73+
74+
public static function getSubscribedEvents() : array
75+
{
76+
return [
77+
KernelEvents::RESPONSE => ['onKernelResponse', -999999],
78+
];
79+
}
80+
}

0 commit comments

Comments
 (0)