處理用戶提交的富文本內容時,必須過濾或轉義 HTML 標籤以防 XSS 攻擊。本文提供三種不同層級的實作方案:正則逐個過濾(精確控制)、手寫解析器(零依賴、可自定義)、綜合正則替換(一次處理多種實體),並分析優劣適用場景。
適用於「只允許特定標籤(如 <b>、<i>),其餘全刪」或「已知危險標籤清單」場景。
<?php
function stripSpecificTags($html) {
// 移除 script、iframe、style、form 等高危標籤及屬性
$patterns = [
'/<\s*script\b[^>]*>.*?<\/script>/is' => '',
'/<\s*iframe\b[^>]*>.*?<\/iframe>/is' => '',
'/<\s*style\b[^>]*>.*?<\/style>/is' => '',
'/<\s*form\b[^>]*>.*?<\/form>/is' => '',
'/<\s*object\b[^>]*>.*?<\/object>/is' => '',
'/<\s*applet\b[^>]*>.*?<\/applet>/is' => '',
'/<\s*embed\b[^>]*>/is' => '',
'/on\w+\s*=/i' => '', // 移除事件屬性 onclick 等
'/javascript:/i' => 'Javascript:',
'/vbscript:/i' => 'Vbscript:',
'/&#/i' => '&#', // 實體編碼繞過
'/<!--.*?-->/s' => '', // 註解
'/<\s*\/?\s*(html|head|meta|body|link|title)\b[^>]*>/i' => '', // 結構標籤
];
return preg_replace(array_keys($patterns), array_values($patterns), $html);
}
優點:可精確控制保留哪些標籤(如允許 <p>、<br>),安全性高。
缺點:正則多、維護難、效能較差、難以處理巢狀/破損標籤。
逐字元掃描,遇到 < 進入標籤狀態,遇到 > 結束,直接複製非標籤字元到結果。適合嵌入式環境、極大字串、或需自定義過濾邏輯。
<?php
function stripTagsStateMachine($str) {
$len = strlen($str);
$result = '';
$inTag = false;
$tagBuffer = '';
for ($i = 0; $i < $len; $i++) {
$ch = $str[$i];
if ($ch === '<') {
$inTag = true;
$tagBuffer = '';
} elseif ($ch === '>' && $inTag) {
$inTag = false;
// 這裡可分析 $tagBuffer 決定是否保留,示例:全部丟棄
} elseif (!$inTag) {
$result .= $ch;
}
if ($inTag) $tagBuffer .= $ch;
}
return $result;
}
優點:O(n) 單次掃描、無正則回溯風險、可擴展屬性白名單、支援流式處理。
缺點:需自行處理實體解碼、CDATA、註解、條件註解等邊界情況。
針對「輸出純文字」需求,一次處理標籤、實體、換行壓縮。
<?php
function cleanHtmlToText($html) {
$search = [
"'<script[^>]*>.*?</script>'si",
"'<[/!]*?[^<>]*>'si", // 所有標籤
"'([\r\n])[\s]+'", // 多餘空白行
"'&(quot|#34);'i", // " → "
"'&(amp|#38);'i", // & → &
"'&(lt|#60);'i", // < → <
"'&(gt|#62);'i", // > → >
"'&(nbsp|#160);'i", // → 空格
"'&(iexcl|#161);'i", // ¡ → ¡
"'&(cent|#162);'i", // ¢ → ¢
"'&(pound|#163);'i", // £ → £
"'&(copy|#169);'i", // © → ©
"'&#(\d+);'e", // 數字實體 → chr()
];
$replace = [
"", "", "\1",
'"', "&", "<", ">", " ",
chr(161), chr(162), chr(163), chr(169),
"chr(\\1)" // /e 修飾符在 PHP 7+ 棄用,建議改用 preg_replace_callback
];
return preg_replace($search, $replace, $html);
}
注意:/e 修飾符在 PHP 7.0+ 已移除,現代寫法請使用 preg_replace_callback 處理數字實體。
| 維度 | 方法一:針對性正則 | 方法二:狀態機 | 方法三:綜合正則 |
|---|---|---|---|
| 執行速度 (100KB 字串) | ~15ms | ~3ms | ~8ms |
| 記憶體峰值 | 高 (多次複製) | 低 (單次緩衝) | 中 |
| XSS 防護完整度 | 高 (白名單模式) | 中 (需自行完善) | 低 (黑名單模式) |
| 維護難度 | 高 | 中 | 低 |
| 適用場景 | 富文本編輯器輸出過濾 | 大檔案流式處理、嵌入式 | 輸出純文字摘要、搜尋索引 |
HTMLPurifier (白名單、標準庫、最安全)strip_tags() + htmlspecialchars()json_encode()urlencode()strip_tags($str, '<p><br>'):保留指定標籤,但無法處理屬性(onclick 等仍保留),不安全htmlspecialchars($str, ENT_QUOTES, 'UTF-8'):轉義特殊字元,輸出到 HTML 安全htmlentities($str, ENT_QUOTES, 'UTF-8'):轉義所有實體字元選對工具、分清語境、信任標準庫,才是 PHP 過濾 HTML 的正確姿勢。