This commit is contained in:
ericli1018
2025-06-13 12:24:35 +08:00
parent 1caae33c43
commit 6dfe3e0677
74 changed files with 1497 additions and 253 deletions

View File

@ -0,0 +1,85 @@
<?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;
}
}