86 lines
3.3 KiB
PHP
86 lines
3.3 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Middleware;
|
||
|
||
use Closure;
|
||
use Illuminate\Http\Request;
|
||
use Symfony\Component\HttpFoundation\Response;
|
||
|
||
class AddCspNonceToScripts
|
||
{
|
||
/**
|
||
* Handle an incoming request.
|
||
*
|
||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||
*/
|
||
public function handle(Request $request, Closure $next): Response
|
||
{
|
||
$response = $next($request);
|
||
|
||
// 只處理 HTML 響應
|
||
if ($response->headers->get('Content-Type') === 'text/html; charset=UTF-8') {
|
||
$content = $response->getContent();
|
||
|
||
// 儲存提取出的樣式內容
|
||
$styles = [];
|
||
$styleCounter = 0;
|
||
|
||
// 第一步:處理內聯樣式,將 style 轉換為 class
|
||
$content = preg_replace_callback(
|
||
'/<([a-z][a-z0-9]*)\b([^>]*)style=["\']([^"\']+)["\']([^>]*)>/i',
|
||
function ($matches) use (&$styles, &$styleCounter) {
|
||
$tag = $matches[1]; // 標籤名稱,例如 div
|
||
$beforeAttrs = trim($matches[2]); // style 前的屬性
|
||
$styleContent = $matches[3]; // 內聯樣式內容
|
||
$afterAttrs = trim($matches[4]); // style 後的屬性
|
||
|
||
// 生成唯一的 class 名稱
|
||
$className = 'inline-style-' . $styleCounter++;
|
||
$styles[$className] = $styleContent;
|
||
|
||
// 組合新的標籤,保留其他屬性並添加 class
|
||
$newAttrs = $beforeAttrs . ($beforeAttrs ? ' ' : '') . 'class="' . $className . '"' . ($afterAttrs ? ' ' : '') . $afterAttrs;
|
||
return '<' . $tag . ' ' . trim($newAttrs) . '>';
|
||
},
|
||
$content
|
||
);
|
||
|
||
// 第二步:為 <script> 和 <style> 標籤添加 nonce
|
||
$content = preg_replace_callback(
|
||
'/<(script|style)\b([^>]*)>(.*?)<\/\1>/is',
|
||
function ($matches) {
|
||
$tag = $matches[1];
|
||
$attributes = $matches[2];
|
||
$contentInside = $matches[3];
|
||
|
||
if (strpos($attributes, 'nonce=') !== false) {
|
||
return $matches[0];
|
||
}
|
||
|
||
$nonce = csp_nonce();
|
||
return '<' . $tag . ' ' . $attributes . ' nonce="' . $nonce . '">' . $contentInside . '</' . $tag . '>';
|
||
},
|
||
$content
|
||
);
|
||
|
||
// 第三步:將提取的樣式追加為 <style> 標籤
|
||
if (!empty($styles)) {
|
||
$styleBlock = '<style nonce="' . csp_nonce() . '">' . PHP_EOL;
|
||
foreach ($styles as $className => $styleContent) {
|
||
$styleBlock .= '.' . $className . ' {' . $styleContent . '}' . PHP_EOL;
|
||
}
|
||
$styleBlock .= '</style>';
|
||
|
||
// 將樣式塊插入到 </head> 之前(如果有 head),否則插入到文件末尾
|
||
$content = str_contains($content, '</head>')
|
||
? str_replace('</head>', $styleBlock . '</head>', $content)
|
||
: $content . $styleBlock;
|
||
}
|
||
|
||
$response->setContent($content);
|
||
}
|
||
|
||
return $response;
|
||
}
|
||
}
|