
HTML Purifier 默认会对特殊字符(如 &)进行安全转义,并将 Unicode 字符(如 ø)以 UTF-8 原生形式输出,导致直接比对原始与净化后字符串无法准确识别真实修改。应使用 Core.CollectErrors 配置结合错误收集器获取语义级变更记录,而非依赖字符串差异比对。
html purifier 默认会对特殊字符(如 `&`)进行安全转义,并将 unicode 字符(如 `ø`)以 utf-8 原生形式输出,导致直接比对原始与净化后字符串无法准确识别真实修改。应使用 `core.collecterrors` 配置结合错误收集器获取语义级变更记录,而非依赖字符串差异比对。
HTML Purifier 的核心目标是保障 HTML 安全性,而非保持输入输出的字面一致性。因此,它会主动执行两类不可避免的转换:
- HTML 特殊字符强制转义:例如 & → &,即使上下文安全,Purifier 也绝不冒险跳过;
- Unicode 字符原生化处理:如 ø → ø,因其内部统一采用 UTF-8 编码表示,提升解析一致性与性能。
这意味着,若你通过简单的字符串 diff(如 str_replace() 或 diff() 工具)对比净化前后内容,将大量捕获这些“非恶意但非语义变更”的差异,干扰真正关键的过滤行为(如标签移除、属性剥离、危险脚本拦截等)的审计。
✅ 正确做法:启用并利用 Core.CollectErrors 功能
该配置项可激活 Purifier 内置的错误/变更收集器,精准记录所有由安全策略驱动的实际修改操作(如“已移除 script 标签”“已清理 onerror 属性”),而非底层编码转换。
以下为完整实现示例:
require_once 'htmlpurifier/library/HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('Core.CollectErrors', true); // ✅ 启用变更收集
$config->set('HTML.Allowed', 'p,b,i,a[href]'); // 示例白名单
$purifier = new HTMLPurifier($config);
$dirtyHtml = '<p>Hello & ø <script>alert(1)</script></p>';
$cleanHtml = $purifier->purify($dirtyHtml);
// 获取变更收集器并提取原始变更记录
$errorCollector = $purifier->context->get('ErrorCollector');
$changes = $errorCollector->getRaw(); // 返回数组:[0 => ['type' => 'RemovedElement', 'line' => 1, 'raw' => 'script']]
// 可选:格式化输出用于日志
foreach ($changes as $change) {
echo sprintf(
"[SECURITY] %s at line %d: %s\n",
ucfirst($change['type']),
$change['line'] ?? '?',
$change['raw'] ?? '(n/a)'
);
}⚠️ 注意事项:
立即学习“前端免费学习笔记(深入)”;
- Core.CollectErrors 是稳定功能,文档中“experimental”警告已过时,可放心用于生产环境;
- 不要启用 Core.EscapeNonASCIICharacters 试图“统一实体化”——它会强制将所有非 ASCII 字符转为 xx;,破坏可读性且不解决 & → & 等根本问题;
- 日志目标应聚焦于 RemovedElement、RemovedAttr、BadValue 等安全相关类型,忽略 AutoClose 或 Chopping 等分析过程日志;
- 若需审计全部变更(含编码转换),建议在 Purifier 处理前,先对原始 HTML 执行 html_entity_decode($input, ENT_QUOTES | ENT_HTML5, 'UTF-8') 统一归一化,再与净化结果做 UTF-8 字节级比对——但这属于高级调试场景,非推荐默认方案。
总结:HTML Purifier 的“不可逆转换”是其安全设计的必然体现。与其对抗编码逻辑,不如拥抱其内置的语义化变更反馈机制——Core.CollectErrors 提供了最轻量、最准确、最符合安全审计需求的日志能力。


















