release
This commit is contained in:
85
app/Http/Middleware/AddCspNonceToScripts.php
Normal file
85
app/Http/Middleware/AddCspNonceToScripts.php
Normal 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user