XML中特殊字符的编码
XML中特殊字符的编码
在这篇文章中,我们将探索XML实体,它们是什么,以它们能为我们做些什么。特别是,我们将看到XML中的标准实体以及如何定义我们自己的实体(如果需要的话)。
2. XML是如何构建的?
XML是一种用于表示任意数据的标记格式。它使用XML元素的层次结构来实现这一点,每个元素都可以有属性。例如:
`````<part number="1976">`````
`````<name>`````Windscreen Wiper`````</name>`````
`````</part>`````
这显示了一个名为“part”的元素,它有一个属性——“number”——以及一个嵌套元素——“name”。
值得注意的是,XML语言使用一些特殊字符来管理这一点。 例如,一个元素总是以小于号——“<”开始,并以大于号——“>”结束。
然而,如果这些字符对XML有特殊含义,那就意味着我们不能在内容中使用它们。 这样做至少会导致歧义,在最坏的情况下会导致无法解析。
例如,如果我们试图使用XML来表示一个简单的数学方程,我们可能会这样写:
`<math>` 1 `< x >` 5 `</math>`
这试图表示_x_的值在1和5之间。然而,XML处理器无法知道意图不是在两个数字之间有一个“x”元素。
3. 标准XML实体
XML通过使用XML实体来解决这个问题。这些是特殊的序列,它们代表其他字符。
XML实体总是以和号字符——“&”开始,并以分号字符——“;”结束。然后实体的名称位于这两个字符之间。例如,实体“<”用于表示小于字符——“`<”。
有一组五个标准实体,对于表示XML中具有特殊含义的字符是必要的:
| 实体 | 表示的字符 |
|---|---|
| & | 和号——& |
| ' | 单引号——‘ |
| > | 大于号——>` |
| < | 小于号——`< |
| " | 引号——“ |
知道了这一点,我们上面尝试表示数学方程的尝试将变成:

突然之间,如何理解这一点就没有歧义了。
4. 字符实体
除了上述内容,XML还提供了表示任意Unicode字符的能力。 我们通过直接引用Unicode代码点的十进制或十六进制形式来实现这一点。
这些是标准XML实体——意味着它们以“&”字符为前缀,并以“;”字符为后缀。十进制代码点以“#”字符为前缀,十六进制的以“#x”为前缀。
例如,字符“÷”是除法符号。Unicode将其表示为代码点U+00F7。因此,我们可以将其表示为XML中的
或
。
如果我们不使用Unicode字符集来编码我们的XML文档——例如,如果我们使用ISO-8859-1——但仍然想要表示Unicode字符,这将特别有用。它也可以用于表示某些特殊字符,如非打印或组合字符,以便开发人员在阅读时可以看到它们的存在。
最后,我们可以使用它来表示否则无法出现在文档中的控制字符——例如,U+0000是空字符,但将这个裸字符直接放在文档中可能会破坏许多阅读器。
5. 自定义实体
我们也可以定义自己的XML实体。 这让我们指定我们选择的实体名称,并定义它将被替换的值。如果我们有某些重复的值需要轻松管理,这可能会有所帮助,但如果使用不当,它也会带来一些潜在的安全风险。
我们需要使用文档类型定义(DTD)来定义自定义实体。这是XML文档开始之前的一个部分,可以用来定义其结构——类似于XSD。我们使用“<!DOCTYPE name [...”构造,其中“name”是DTD的任意名称:
<!DOCTYPE example [
....
]>`
`````<part number="1976">`````
`````<name>`````Windscreen Wiper`````</name>`````
`````</part>`````
在这个构造内部,我们包括了DTD定义。这可以包括除其他事项外的自定义实体定义——无论是内部还是外部实体。
5.1. 内部实体
内部实体是直接定义的,给它一个名称和一个值。 一旦这样做了,就可以像使用任何其他实体一样使用这个名称的实体。例如:
`<!DOCTYPE example [
<!ENTITY windscreen "Windscreen Wiper">`
]>
`````<part number="1976">`````
`````<name>`````&windscreen;`````</name>`````
`````</part>`````
在这里,我们定义了一个名为“windscreen”的自定义实体和一个替换值为“Windscreen Wiper”。我们使用“&windscreen;”。我们的XML处理程序将用“Windscreen Wiper”值替换这个。
5.2. 外部实体
外部实体的工作方式相同,但不是直接在DTD中提供值,而是提供找到它的位置。 例如:
`<!DOCTYPE example [
<!ENTITY windscreen SYSTEM "http://example.com/parts/windscreen.txt">`
]>
`````<part number="1976">`````
`````<name>`````&windscreen;`````</name>`````
`````</part>`````
在这里,我们定义了一个名为“windscreen”的自定义实体,替换值为URL“http://example.com/parts/windscreen.txt”上找到的内容。我们可以像以前一样使用它,XML处理器将在需要时自动获取这个外部资源。
5.3. 潜在的安全风险
使用自定义实体可能很强大,但也可能使我们面临一些潜在的安全风险。 特别是,如果我们处理的XML文档是由不受信任的来源提供的,那么我们需要特别小心。
这里最明显的攻击是XML外部实体(XXE)注入。这是某人可以精心制作一个XML文档,恶意加载攻击者不应该访问的资源。例如:
`<!DOCTYPE example [
<!ENTITY windscreen SYSTEM "file:///etc/passwd">`
]>
`````<part number="1976">`````
`````<name>`````&windscreen;`````</name>`````
`````</part>`````
这个XML文档声明了一个自定义实体,系统密码文件的内容将替换它。显然,这是不应该发生的,但如果我们不小心,攻击者确实可以做到这一点。
另一种潜在的攻击有时被称为XML炸弹。 这是一种使用XML实体重复扩展的DoS攻击:
`<!DOCTYPE test [
<!ENTITY a0 "someLargeString">`
`<!ENTITY a1 "&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;”实体。这扩展为“&a3;”的10个实例,每个实例又扩展为“&a2;”的10个实例,依此类推。这导致我们的文档包括了“someLargeString”的10,000个实例。如果攻击者走得更远,我们可以得到更多——深入10个级别将给我们10,000,000,000个实例,这将是140 GB的大小。
总的来说,避免这些风险的唯一方法是在XML处理器中完全禁用自定义实体。 然而,这也消除了它们带来的好处。如果我们处理来自不受信任来源的XML文档,那么这个风险可能不值得好处,但对于内部文档,可能是有益的。
6. 结论
在这篇文章中,我们看到了如何在XML文档中使用XML实体来表示特殊字符。我们甚至学会了如何定义我们自己的实体(如果必要的话)。