1. 引言

本文将深入探讨XML实体的概念、用途及其实现方式。我们将重点了解XML内置的标准实体,以及如何根据需要定义自定义实体。

2. XML的基本结构

XML是一种用于表示任意数据的标记语言,通过分层的XML元素结构实现,每个元素可包含属性。例如:

<part number="1976">
    <name>Windscreen Wiper</name>
</part>

这个例子展示了一个名为"part"的元素,它有一个属性"number"和一个嵌套元素"name"。

⚠️ 关键点:XML使用特殊字符管理语法结构。例如元素始终以小于号"<"开始,以大于号">"结束。

但问题来了:这些特殊字符不能直接出现在内容中,否则会导致解析歧义甚至错误。比如尝试用XML表示数学公式:

<math> 1 < x > 5 </math>

这段代码本意表示x的值在1到5之间,但XML解析器无法区分"< x >"是内容还是嵌套元素。

3. 标准XML实体

XML通过实体(Entity) 解决特殊字符问题。实体是表示其他字符的特殊序列,格式为:

  • &开头
  • ;结尾
  • 中间是实体名称

例如&lt;表示小于号"<"。XML定义了五个标准实体:

实体 表示的字符
&amp; & (和号)
&apos; ' (单引号)
&gt; > (大于号)
&lt; < (小于号)
&quot; " (双引号)

使用实体后,之前的数学公式可正确表示为:

math equation

<math> 1 &lt; x &gt; 5 </math>

✅ 现在解析器能明确区分语法结构和内容。

4. 字符实体

除标准实体外,XML还支持通过Unicode码点表示任意字符:

  • 十进制格式:&#数字;
  • 十六进制格式:&#x十六进制;

例如除号"÷"(Unicode U+00F7)可表示为: code point U+00F7

&#247;  <!-- 十进制 -->

code point U+00F7

&#xF7;  <!-- 十六进制 -->

这种表示方式特别适用于:

  • 非Unicode编码文档(如ISO-8859-1)中插入Unicode字符
  • 表示不可见字符(如控制字符U+0000)
  • 明确标识组合字符,方便开发者识别

5. 自定义实体

XML允许定义自定义实体,通过指定名称和替换值简化重复内容。但需注意:不当使用可能引发安全风险。

定义自定义实体需要使用文档类型定义(DTD),在XML文档前声明:

<!DOCTYPE example [
    ....
]>
<part number="1976">
    <name>Windscreen Wiper</name>
</part>

DTD中可定义两种实体:内部实体和外部实体。

5.1. 内部实体

直接在DTD中定义名称和替换值:

<!DOCTYPE example [
    <!ENTITY windscreen "Windscreen Wiper">
]>
<part number="1976">
    <name>&windscreen;</name>
</part>

解析时&windscreen;会被替换为"Windscreen Wiper"。

5.2. 外部实体

通过外部资源定义替换值:

<!DOCTYPE example [
    <!ENTITY windscreen SYSTEM "http://example.com/parts/windscreen.txt">
]>
<part number="1976">
    <name>&windscreen;</name>
</part>

解析器会自动从指定URL获取内容替换实体。

5.3. 潜在安全风险

自定义实体虽强大,但处理不可信XML文档时需格外谨慎:

⚠️ XXE注入攻击

攻击者可构造恶意实体读取敏感文件:

<!DOCTYPE example [
    <!ENTITY windscreen SYSTEM "file:///etc/passwd">
]>
<part number="1976">
    <name>&windscreen;</name>
</part>

此例尝试读取系统密码文件。

⚠️ XML炸弹(DoS攻击)

通过实体嵌套指数级膨胀文档大小:

<!DOCTYPE test [
    <!ENTITY a0 "someLargeString">
    <!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;">
    <!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;">
    <!ENTITY a3 "&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;">
    <!ENTITY a4 "&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;">
]>
<document>&a4;</document>

&a4;展开后包含10,000个"someLargeString"(10层嵌套将达100亿个实例,约140GB)。

安全建议

  • 处理不可信XML时禁用自定义实体
  • 内部文档可谨慎使用,但需评估风险
  • 优先使用白名单机制限制可访问资源

6. 总结

本文介绍了XML实体的核心概念:

  • 标准实体解决特殊字符编码问题
  • 字符实体支持任意Unicode字符表示
  • 自定义实体提供内容复用能力,但伴随安全风险

对于有经验的开发者,掌握这些知识能有效避免XML解析中的常见坑点,特别是在处理用户提交的XML时务必做好安全防护。


原始标题:Encoding Special Characters in XML | Baeldung