處理用戶提交的富文本內容時,需根據業務場景選擇適當的清理策略。本文系統梳理 strip_tags、HTML 實體轉義、正則清理、HTMLPurifier 等多層級方案。
<?php
$html = '<p>段落</p><script>alert(1)</script><b>粗體</b>';
// 刪除所有標籤
echo strip_tags($html);
// 輸出:段落alert(1)粗體
// 保留 <p> <b>
echo strip_tags($html, '<p><b>');
// 輸出:<p>段落</p>alert(1)<b>粗體</b>
// 保留多個標籤(空格分隔)
echo strip_tags($html, '<p> <b> <i> <br>');
?>
缺陷:不處理屬性(如 onclick)、不修復破損標籤、不處理 CSS/JS 注入。
| 函數 | 轉義範圍 | 適用場景 | 備註 |
|---|---|---|---|
| htmlspecialchars() | & " < > | HTML 輸出、屬性值 | 性能最好,推薦默認使用 |
| htmlentities() | 所有可轉義字符 | 需完全實體化時 | 中文會變實體,源碼難讀 |
| addslashes() | ' " \ NULL | 入庫前(已棄用) | 應用參數化查詢替代 |
| stripslashes() | 移除反斜杠 | 配合 addslashes | 魔術引號時代遺物 |
| nl2br() | \n → <br> | 純文本換行轉 HTML | 不轉義其他字符 |
<?php
// 輸出到 HTML 內容區域
function h($str) {
return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// 輸出到 HTML 屬性值(單引號包裹)
function h_attr($str) {
return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// 輸出到 JavaScript 字符串
function h_js($str) {
return json_encode($str, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE);
}
// 使用範例
echo "<div class='".h_attr($userClass)."'>".h($userContent)."</div>";
echo "<script>var msg = ".h_js($userMsg).";</script>";
?>
警告:正則無法完整解析 HTML,僅適用於簡單、可控的內容清理。
<?php
function stripScriptsStyles($html) {
$patterns = [
'/<script\b[^>]*>.*?<\/script>/is', // 移除 script
'/<style\b[^>]*>.*?<\/style>/is', // 移除 style
'/<!--.*?-->/s', // 移除註釋
];
return preg_replace($patterns, '', $html);
}
?>
<?php
function stripAllTags($html) {
// 移除標籤
$text = preg_replace('/<[^>]+>/', '', $html);
// 解碼實體
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// 清理多餘空白
$text = preg_replace('/\s+/', ' ', $text);
return trim($text);
}
?>
唯一推薦用於生產環境的富文本過濾庫,符合 W3C 標準,防 XSS 攻擊。
composer require ezyang/htmlpurifier
<?php
require_once 'vendor/autoload.php';
$config = HTMLPurifier_Config::createDefault();
// 核心配置
$config->set('HTML.Doctype', 'HTML5');
$config->set('HTML.Allowed', 'p,b,i,em,strong,u,strike,a[href|title],ul,ol,li,br,img[src|alt|width|height],table,thead,tbody,tr,th,td,div[class],span[class]');
$config->set('CSS.AllowedProperties', 'font-family,font-size,color,background-color,text-align,width,height,margin,padding,border');
$config->set('AutoFormat.RemoveEmpty', true);
$config->set('AutoFormat.AutoParagraph', true);
// URI 安全
$config->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true]);
$config->set('URI.DisableExternalResources', false);
// 緩存配置(生產環境必須)
$config->set('Cache.SerializerPath', __DIR__ . '/storage/cache/htmlpurifier');
$purifier = new HTMLPurifier($config);
$dirtyHtml = '<script>alert(1)</script><p onclick="evil()">安全內容</p><img src=x onerror=alert(2)>';
$cleanHtml = $purifier->purify($dirtyHtml);
// 結果:<p>安全內容</p><img src="x" alt="">
?>
<?php
// 完整允許規則語法
$config->set('HTML.Allowed', '
a[href|title|target|rel],
img[src|alt|width|height|class],
div[class|style],
span[class|style],
p[class|style],
br, hr,
b, i, em, strong, u, strike, s,
ul, ol, li,
table, thead, tbody, tr, th, td,
h1, h2, h3, h4, h5, h6,
blockquote, pre, code
');
// 格式:標籤名[屬性1|屬性2|...],多標籤用逗號分隔
?>
| 業務場景 | 推薦方案 | 說明 |
|---|---|---|
| 純文本輸入(用戶名、評論) | htmlspecialchars() | 完全轉義,最安全 |
| 簡單富文本(允許粗體、換行) | strip_tags($html, '<b><i><br>') + htmlspecialchars() 屬性值 | 輕量級,但無法防屬性 XSS |
| 編輯器內容(CKEditor、TinyMCE) | HTMLPurifier | 標準合規、可配置、防 XSS |
| API 輸出 JSON | json_encode() 含安全標誌 | 前端負責渲染安全 |
| 郵件模板 | HTMLPurifier(郵件模式配置) | 郵件客戶端兼容性要求高 |
Cache.SerializerPath),首次運行會編譯定義庫