PHP htmlentities() 深度解析:防 XSS 輸出編碼、旗標組合、字元集處理
htmlentities() 將所有可轉換字元轉為 HTML 實體,是輸出端防 XSS 的核心函數。本文詳細說明四個參數、旗標組合策略、字元集選擇、與 htmlspecialchars() 差異、實戰最佳實踐。
函數簽名與參數
htmlentities(
string $string,
int $flags = ENT_COMPAT | ENT_HTML401,
?string $encoding = null,
bool $double_encode = true
): string
旗標常數完整對照表
| 分類 | 常數 | 值 | 說明 |
| 引號處理 | ENT_COMPAT | 2 | 預設。僅轉換雙引號 " |
| ENT_QUOTES | 3 | 轉換雙引號 " 和單引號 ' (推薦) |
| ENT_NOQUOTES | 0 | 不轉換任何引號 |
| 無效編碼 | ENT_IGNORE | 4 | 忽略無效編碼 (不安全,避免使用) |
| ENT_SUBSTITUTE | 8 | 無效編碼替換為 U+FFFD (推薦) |
| ENT_DISALLOWED | 128 | 文檔類型不允許的碼位替換為 U+FFFD |
| 文檔類型 | ENT_HTML401 | 0 | 預設。HTML 4.01 實體表 |
| ENT_HTML5 | 16 | HTML5 實體表 (更完整,推薦現代專案) |
| ENT_XML1 | 32 | XML 1 實體表 |
| ENT_XHTML | 64 | XHTML 實體表 |
字元集參數 (encoding)
- 預設:PHP 5.6+ 為
UTF-8,舊版為 ISO-8859-1
- 常用值:
UTF-8、ISO-8859-1、ISO-8859-15、GB2312、BIG5
- 強烈建議顯式指定
'UTF-8' 避免版本差異
推薦預設旗標組合
// 現代 Web 專案 (HTML5、UTF-8、雙引號單引號皆轉、錯誤替換)
$defaultFlags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5;
// 使用範例
$safe = htmlentities($userInput, $defaultFlags, 'UTF-8');
htmlentities() vs htmlspecialchars()
| 特性 | htmlentities() | htmlspecialchars() |
| 轉換範圍 | 所有有 HTML 實體的字元 (含 accented chars、符號等) | 僅 & < > " ' 五個字元 |
| 輸出大小 | 較大 (如 é → é) | 較小 (保留原字元) |
| 適用場景 | 舊系統、需純 ASCII 輸出、XML 文檔 | 現代 HTML5 專案首選 |
| 效能 | 稍慢 | 較快 |
實戰範例
<?php
$input = "Tom & Jerry's \"Adventure\" bold café naïve résumé";
// 1. 基本防 XSS (推薦)
echo htmlentities($input, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
// 輸出: Tom & Jerry's "Adventure" <b>bold</b> café naïve résumé
// 2. 僅轉換必要字元 (htmlspecialchars 足夠)
echo htmlspecialchars($input, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
// 輸出: Tom & Jerry's "Adventure" <b>bold</b> café naïve résumé
// 3. 雙重編碼控制
$alreadyEncoded = '<script>alert(1)</script>';
echo htmlentities($alreadyEncoded, ENT_QUOTES, 'UTF-8', true); // 預設雙重編碼
// <script>alert(1)</script>
echo htmlentities($alreadyEncoded, ENT_QUOTES, 'UTF-8', false); // 保留現有實體
// <script>alert(1)</script>
// 4. 不同文檔類型
echo htmlentities('©®™', ENT_QUOTES, 'UTF-8', true, ENT_HTML401);
// ©®™ (HTML4.01 有定義)
echo htmlentities('©®™', ENT_QUOTES, 'UTF-8', true, ENT_HTML5);
// ©®™ (HTML5 同)
echo htmlentities('→←', ENT_QUOTES, 'UTF-8', true, ENT_HTML401);
// →← (HTML4.01 無定義,保留原字)
echo htmlentities('→←', ENT_QUOTES, 'UTF-8', true, ENT_HTML5);
// →← (HTML5 有定義)
常見錯誤與陷阱
- 雙重編碼:已編碼字串再次編碼 →
&lt;。解決:double_encode = false 或統一在輸出層編碼
- 字元集不匹配:GBK 字串用 UTF-8 編碼 → 亂碼/空字串。解決:
iconv('GBK', 'UTF-8', $str) 先轉碼
- ENT_IGNORE 導致資料遺失:無效編碼直接刪除。解決:改用
ENT_SUBSTITUTE
- JavaScript 內容輸出:
htmlentities 不足。需 json_encode() 或專用 JS 跳脫
現代框架整合模式
// Laravel Blade: {{ $var }} 自動 htmlspecialchars (非 htmlentities)
// CI4: esc($var) → htmlspecialchars
// Symfony Twig: {{ var|escape('html') }}
// 原生 PHP 模板建議封裝:
function e(string $str, int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5): string {
return htmlspecialchars($str, $flags, 'UTF-8');
}
// 視圖中使用
<div class="user-input"><?= e($comment) ?></div>
效能微優化
- 大量輸出時,
htmlspecialchars 比 htmlentities 快 ~20%
- 靜態內容模板預編譯階段預先編碼
- 使用
opcache 加速函數呼叫
掌握旗標組合、字元集指定、雙重編碼控制,並根據專案類型選擇 htmlspecialchars (現代 HTML5) 或 htmlentities (舊系統/XML),能構建堅固的輸出端 XSS 防線。
- 上一篇文章: CodeIgniter 解決 URL 中文參數問題:自定義 URI 類別與編碼轉換
- 下一篇文章: CodeIgniter 框架與原生 PHP 批量驗證 POST/GET 必填欄位的實用技巧
Copyright ©
程式碼之家 All Rights Reserved