两种引用的取舍
命名实体用名字引用字符,可读性最好,但可用名字随规范版本变化:HTML 定义的实体集合远多于 XML 的五个内置实体(< > & " '),因此在 XML、SVG、RSS 等场景直接写 HTML 命名实体会被判为未定义实体而解析失败。数字实体用码点引用,形式为十进制 © 或十六进制 ©,能表示任意 Unicode 码点,覆盖面完整、跨版本稳定,代价是可读性差。实践中常见组合是:常用符号用命名实体,其余用数字实体。
基本信息
| 命名实体 | 以名字引用,可读性好,集合依规范版本而定 |
|---|---|
| 数字实体 | 十进制 © 或十六进制 ©,可表示任意码点 |
| XML 内置 | 只有 < > & " ' 五个 |
| 大小写 | 命名实体区分大小写,写错不会解析 |
| 结束分号 | 现代写法必须带分号 |
| 安全建议 | 不可信输入场景优先数字形式,减少解析差异 |
典型使用场景
- XML 与 SVG 内容:只用内置命名实体或数字实体,避免未定义实体报错
- 邮件模板:邮件客户端对命名实体支持较全,但仍建议保守使用
- 富文本净化:把内容统一转换为数字实体,便于后续处理与比较
- 跨系统交换:数字形式不受命名集合差异影响,兼容性更好
- 可读性维护:面向人工编辑的模板可保留常用命名实体
转换与对照示例
转换与对照示例
# 命名与数字形式等价
python -c "import html; print(html.unescape('©'), html.unescape('©'), html.unescape('©'))"
# 把字符转成数字实体(避免依赖命名集合)
python -c "print(''.join('&#%d;' % ord(c) for c in '©®中'))"
# XML 场景:内置命名实体之外的写法会解析失败
python -c "import xml.etree.ElementTree as ET; ET.fromstring('<a>©</a>')" 2>&1 | tail -1
python -c "import xml.etree.ElementTree as ET; print(ET.fromstring('<a>©</a>').text)" # 数字实体正常
# 名字区分大小写,未定义的名字按字面返回
python -c "import html; print(repr(html.unescape('¬anentity;')))"形式对照
| 需求 | 推荐形式 | 原因 |
|---|---|---|
| 常用符号、可读性优先 | 命名实体 | 模板易读易维护 |
| 任意 Unicode 码点 | 数字实体 | 覆盖完整,不受命名集合限制 |
| XML / SVG 内容 | 内置命名实体或数字实体 | 其他命名实体会被判定未定义 |
| 不可信输入的回显 | 数字实体 | 解析行为确定,减少歧义 |
| 批量转换与比较 | 数字实体 | 形式统一,字符串比较可靠 |
命名实体的集合随规范版本变化:某些名字在旧版规范中未定义,在较新版本中被加入。因此对解析器与库的版本要有意识,跨系统传递内容时优先选择数字形式以获得稳定行为。
实体解析差异可能被用于绕过过滤:不同解析器对未知实体、缺失分号、重载字符的处理不同,攻击者可构造在「校验器」与「浏览器」中解析结果不同的内容。安全场景应统一使用数字实体并规范解析流程。
转换需要保持语义:把内容统一转换为数字实体属于表示层面的改写,不会改变字符本身;但若中途经过一次转义再转义,就会出现「实体被显示成实体文本」的问题,转换前后都应做往返验证。