## 12.12 XML 函数

[](<>)

**表 12.16 XML 函数**

| 姓名 | 描述 |
| --- | --- |
| [`提取值（）`](xml-functions.html#function_extractvalue) | 使用 XPath 表示法从 XML 字符串中提取值 |
| [`更新XML()`](xml-functions.html#function_updatexml) | 返回替换的 XML 片段 |

本节讨论 MySQL 中的 XML 和相关功能。

笔记

可以从 MySQL 中获取 XML 格式的输出[**mysql**](mysql.html)和[**mysql转储**](mysqldump.html)客户通过调用他们[`--xml`](mysql-command-options.html#option_mysql_xml)选项。看[第 4.5.1 节，“mysql - MySQL 命令行客户端”](mysql.html)， 和[第 4.5.4 节，“mysqldump - 数据库备份程序”](mysqldump.html).

提供了两个提供基本 XPath 1.0（XML 路径语言，1.0 版）功能的函数。本节后面提供了一些关于 XPath 语法和用法的基本信息；但是，对这些主题的深入讨论超出了本手册的范围，您应该参考[XML 路径语言 (XPath) 1.0 标准](http://www.w3.org/TR/xpath)以获得确切的信息。对于那些刚接触 XPath 或希望复习基础知识的人来说，一个有用的资源是[Zvon.org XPath 教程](http://www.zvon.org/xxl/XPathTutorial/)，有多种语言版本。

笔记

这些功能仍在开发中。我们在 MySQL 8.0 及更高版本中继续改进 XML 和 XPath 功能的这些和其他方面。您可以在[MySQL XML 用户论坛](https://forums.mysql.com/list.php?44).

与这些函数一起使用的 XPath 表达式支持用户变量和本地存储的程序变量。用户变量被弱检查；对存储程序的本地变量进行严格检查（另请参阅错误 #26518）：

-   **用户变量（弱检查）。**使用语法的变量`$@*`变量名`*`（即用户变量）不被检查。如果变量的类型错误或之前没有被赋值，服务器不会发出警告或错误。这也意味着用户对任何印刷错误负全部责任，因为如果（例如）不会给出警告`$@myvairable`用在哪里`$@my变量`是有意的。

    例子：

    ```
    mysql> SET @xml = '<a><b>X</b><b>Y</b></a>';
    Query OK, 0 rows affected (0.00 sec)

    mysql> SET @i =1, @j = 2;
    Query OK, 0 rows affected (0.00 sec)

    mysql> SELECT @i, ExtractValue(@xml, '//b[$@i]');
    +------+--------------------------------+
    | @i   | ExtractValue(@xml, '//b[$@i]') |
    +------+--------------------------------+
    |    1 | X                              |
    +------+--------------------------------+
    1 row in set (0.00 sec)

    mysql> SELECT @j, ExtractValue(@xml, '//b[$@j]');
    +------+--------------------------------+
    | @j   | ExtractValue(@xml, '//b[$@j]') |
    +------+--------------------------------+
    |    2 | Y                              |
    +------+--------------------------------+
    1 row in set (0.00 sec)

    mysql> SELECT @k, ExtractValue(@xml, '//b[$@k]');
    +------+--------------------------------+
    | @k   | ExtractValue(@xml, '//b[$@k]') |
    +------+--------------------------------+
    | NULL |                                |
    +------+--------------------------------+
    1 row in set (0.00 sec)
    ```

-   **存储程序中的变量（强检查）。**使用语法的变量`$*`变量名`*`可以在存储程序中调用这些函数时声明和使用它们。这些变量对于定义它们的存储程序是本地的，并且会严格检查类型和值。

    例子：

    ```
    mysql> DELIMITER |

    mysql> CREATE PROCEDURE myproc ()
        -> BEGIN
        ->   DECLARE i INT DEFAULT 1;
        ->   DECLARE xml VARCHAR(25) DEFAULT '<a>X</a><a>Y</a><a>Z</a>';
        ->
        ->   WHILE i < 4 DO
        ->     SELECT xml, i, ExtractValue(xml, '//a[$i]');
        ->     SET i = i+1;
        ->   END WHILE;
        -> END |
    Query OK, 0 rows affected (0.01 sec)

    mysql> DELIMITER ;

    mysql> CALL myproc();
    +--------------------------+---+------------------------------+
    | xml                      | i | ExtractValue(xml, '//a[$i]') |
    +--------------------------+---+------------------------------+
    | <a>X</a><a>Y</a><a>Z</a> | 1 | X                            |
    +--------------------------+---+------------------------------+
    1 row in set (0.00 sec)

    +--------------------------+---+------------------------------+
    | xml                      | i | ExtractValue(xml, '//a[$i]') |
    +--------------------------+---+------------------------------+
    | <a>X</a><a>Y</a><a>Z</a> | 2 | Y                            |
    +--------------------------+---+------------------------------+
    1 row in set (0.01 sec)

    +--------------------------+---+------------------------------+
    | xml                      | i | ExtractValue(xml, '//a[$i]') |
    +--------------------------+---+------------------------------+
    | <a>X</a><a>Y</a><a>Z</a> | 3 | Z                            |
    +--------------------------+---+------------------------------+
    1 row in set (0.01 sec)
    ```

     **参数。**作为参数传入的存储例程内的 XPath 表达式中使用的变量也需要经过严格检查。

    包含用户变量或存储程序本地变量的表达式必须另外（符号除外）符合 XPath 1.0 规范中给定的包含变量的 XPath 表达式规则。

笔记

用于存储 XPath 表达式的用户变量被视为空字符串。因此，不可能将 XPath 表达式存储为用户变量。（错误 #32911）

-   [](<>)

    [`提取值（*`xml_frag`*, *`xpath_expr`*)`](xml-functions.html#function_extractvalue)

    [](<>)

    [`提取值（）`](xml-functions.html#function_extractvalue)接受两个字符串参数，一个 XML 标记片段*`xml_frag`*和一个 XPath 表达式*`xpath_expr`*（也称为定位器）；它返回文本（`数据中心`) 的第一个文本节点，它是 XPath 表达式匹配的一个或多个元素的子元素。

    使用此函数相当于使用*`xpath_expr`*附加后`/文本（）`.换一种说法，[`ExtractValue('<a><b>Sakila</b></a>', '/a/b')`](xml-functions.html#function_extractvalue)和[`ExtractValue('<a><b>Sakila</b></a>', '/a/b/text()')`](xml-functions.html#function_extractvalue)产生相同的结果。

    如果找到多个匹配项，则每个匹配元素的第一个子文本节点的内容将作为单个空格分隔的字符串返回（按照匹配的顺序）。

    如果没有为表达式找到匹配的文本节点（包括隐式`/文本（）`)——无论出于何种原因，只要*`xpath_expr`*是有效的，并且*`xml_frag`*由正确嵌套和关闭的元素组成——返回一个空字符串。空元素上的匹配和根本不匹配之间没有区别。这是设计使然。

    如果需要判断是否没有找到匹配的元素*`xml_frag`*或者找到这样的元素但不包含子文本节点，您应该测试使用 XPath 的表达式的结果`数数（）`功能。例如，这两个语句都返回一个空字符串，如下所示：

    ```
    mysql> SELECT ExtractValue('<a><b/></a>', '/a/b');
    +-------------------------------------+
    | ExtractValue('<a><b/></a>', '/a/b') |
    +-------------------------------------+
    |                                     |
    +-------------------------------------+
    1 row in set (0.00 sec)

    mysql> SELECT ExtractValue('<a><c/></a>', '/a/b');
    +-------------------------------------+
    | ExtractValue('<a><c/></a>', '/a/b') |
    +-------------------------------------+
    |                                     |
    +-------------------------------------+
    1 row in set (0.00 sec)
    ```

    但是，您可以使用以下方法确定是否确实存在匹配元素：

    ```
    mysql> SELECT ExtractValue('<a><b/></a>', 'count(/a/b)');
    +-------------------------------------+
    | ExtractValue('<a><b/></a>', 'count(/a/b)') |
    +-------------------------------------+
    | 1                                   |
    +-------------------------------------+
    1 row in set (0.00 sec)

    mysql> SELECT ExtractValue('<a><c/></a>', 'count(/a/b)');
    +-------------------------------------+
    | ExtractValue('<a><c/></a>', 'count(/a/b)') |
    +-------------------------------------+
    | 0                                   |
    +-------------------------------------+
    1 row in set (0.01 sec)
    ```

    重要的

    [`提取值（）`](xml-functions.html#function_extractvalue)只返回`数据中心`，并且不返回任何可能包含在匹配标签中的标签，也不返回它们的任何内容（请参阅返回的结果为`val1`在以下示例中）。

    ```
    mysql> SELECT
        ->   ExtractValue('<a>ccc<b>ddd</b></a>', '/a') AS val1,
        ->   ExtractValue('<a>ccc<b>ddd</b></a>', '/a/b') AS val2,
        ->   ExtractValue('<a>ccc<b>ddd</b></a>', '//b') AS val3,
        ->   ExtractValue('<a>ccc<b>ddd</b></a>', '/b') AS val4,
        ->   ExtractValue('<a>ccc<b>ddd</b><b>eee</b></a>', '//b') AS val5;

    +------+------+------+------+---------+
    | val1 | val2 | val3 | val4 | val5    |
    +------+------+------+------+---------+
    | ccc  | ddd  | ddd  |      | ddd eee |
    +------+------+------+------+---------+
    ```

    此函数使用当前 SQL 排序规则进行比较`包含（）`，执行与其他字符串函数相同的排序规则聚合（例如[`连接（）`](string-functions.html#function_concat))，考虑到他们论点的整理强制性；看[第 10.8.4 节，“表达式中的排序规则强制性”](charset-collation-coercibility.html)，用于解释管理此行为的规则。

    （以前，总是使用二进制（即区分大小写）比较。）

    `空值`如果返回*`xml_frag`*包含未正确嵌套或关闭的元素，并生成警告，如下例所示：

    ```
    mysql> SELECT ExtractValue('<a>c</a><b', '//a');
    +-----------------------------------+
    | ExtractValue('<a>c</a><b', '//a') |
    +-----------------------------------+
    | NULL                              |
    +-----------------------------------+
    1 row in set, 1 warning (0.00 sec)

    mysql> SHOW WARNINGS\G
    *************************** 1. row ***************************
      Level: Warning
       Code: 1525
    Message: Incorrect XML value: 'parse error at line 1 pos 11:
             END-OF-INPUT unexpected ('>' wanted)'
    1 row in set (0.00 sec)

    mysql> SELECT ExtractValue('<a>c</a><b/>', '//a');
    +-------------------------------------+
    | ExtractValue('<a>c</a><b/>', '//a') |
    +-------------------------------------+
    | c                                   |
    +-------------------------------------+
    1 row in set (0.00 sec)
    ```

-   [](<>)

    [`更新XML(*`xml_target`*, *`xpath_expr`*, *`新的\_xml`*)`](xml-functions.html#function_updatexml)

    [](<>)

    此函数替换给定 XML 标记片段的单个部分*`xml_target`*使用新的 XML 片段*`新的_xml`*，然后返回更改后的 XML。的部分*`xml_target`*被替换的匹配 XPath 表达式*`xpath_expr`*由用户提供。

    如果没有表达式匹配*`xpath_expr`*找到，或者如果找到多个匹配项，则该函数返回原始*`xml_target`*XML 片段。所有三个参数都应该是字符串。

    ```
    mysql> SELECT
        ->   UpdateXML('<a><b>ccc</b><d></d></a>', '/a', '<e>fff</e>') AS val1,
        ->   UpdateXML('<a><b>ccc</b><d></d></a>', '/b', '<e>fff</e>') AS val2,
        ->   UpdateXML('<a><b>ccc</b><d></d></a>', '//b', '<e>fff</e>') AS val3,
        ->   UpdateXML('<a><b>ccc</b><d></d></a>', '/a/d', '<e>fff</e>') AS val4,
        ->   UpdateXML('<a><d></d><b>ccc</b><d></d></a>', '/a/d', '<e>fff</e>') AS val5
        -> \G

    *************************** 1. row ***************************
    val1: <e>fff</e>
    val2: <a><b>ccc</b><d></d></a>
    val3: <a><e>fff</e><d></d></a>
    val4: <a><b>ccc</b><e>fff</e></a>
    val5: <a><d></d><b>ccc</b><d></d></a>
    ```

笔记

XPath 语法和用法的深入讨论超出了本手册的范围。请参阅[XML 路径语言 (XPath) 1.0 规范](http://www.w3.org/TR/xpath)以获得确切的信息。对于那些刚接触 XPath 或希望复习基础知识的人来说，一个有用的资源是[Zvon.org XPath 教程](http://www.zvon.org/xxl/XPathTutorial/)，有多种语言版本。

一些基本 XPath 表达式的描述和示例如下：

-   `/*`标签`*`

    火柴`<*`标签`*/>`当且仅当`<*`标签`*/>`是根元素。

    例子：`/一种`有一场比赛`<a><b/></a>`因为它匹配最外层（根）标签。它与内在不匹配*`一种`*元素在`<b><a/></b>`因为在这种情况下，它是另一个元素的子元素。

-   `/*`标签1`*/*`标签2`*`

    火柴`<*`标签2`*/>`当且仅当它是`<*`标签1`*/>`， 和`<*`标签1`*/>`是根元素。

    例子：`/a/b`匹配*`b`*XML 片段中的元素`<a><b/></a>`因为它是根元素的子元素*`一种`*.它没有匹配项`<b><a/></b>`因为在这种情况下，*`b`*是根元素（因此不是其他元素的子元素）。XPath 表达式在`<a><c><b/></c></a>`;这里，*`b`*是*`一种`*，但实际上不是*`一种`*.

    此构造可扩展到三个或更多元素。例如，XPath 表达式`/a/b/c`匹配*`c`*片段中的元素`<a><b><c/></b></a>`.

-   `//*`标签`*`

    匹配任何实例`<*`标签`*>`.

    例子：`//一种`匹配*`一种`*以下任何一项中的元素：`<a><b><c/></b></a>`;`<c><a><b/></a></b>`;`<c><b><a/></b></c>`.

    `//`可以结合`/`.例如，`//a/b`匹配*`b`*任一片段中的元素`<a><b/></a>`要么`<c><a><b/></a></c>`.

    笔记

    `//*`标签`*`相当于`/后代或自我::*/*`标签`*`.一个常见的错误是将其与`/后代或自我::*`标签`*`，尽管后一种表达式实际上会导致非常不同的结果，如下所示：

    ```
    mysql> SET @xml = '<a><b><c>w</c><b>x</b><d>y</d>z</b></a>';
    Query OK, 0 rows affected (0.00 sec)

    mysql> SELECT @xml;
    +-----------------------------------------+
    | @xml                                    |
    +-----------------------------------------+
    | <a><b><c>w</c><b>x</b><d>y</d>z</b></a> |
    +-----------------------------------------+
    1 row in set (0.00 sec)

    mysql> SELECT ExtractValue(@xml, '//b[1]');
    +------------------------------+
    | ExtractValue(@xml, '//b[1]') |
    +------------------------------+
    | x z                          |
    +------------------------------+
    1 row in set (0.00 sec)

    mysql> SELECT ExtractValue(@xml, '//b[2]');
    +------------------------------+
    | ExtractValue(@xml, '//b[2]') |
    +------------------------------+
    |                              |
    +------------------------------+
    1 row in set (0.01 sec)

    mysql> SELECT ExtractValue(@xml, '/descendant-or-self::*/b[1]');
    +---------------------------------------------------+
    | ExtractValue(@xml, '/descendant-or-self::*/b[1]') |
    +---------------------------------------------------+
    | x z                                               |
    +---------------------------------------------------+
    1 row in set (0.06 sec)

    mysql> SELECT ExtractValue(@xml, '/descendant-or-self::*/b[2]');
    +---------------------------------------------------+
    | ExtractValue(@xml, '/descendant-or-self::*/b[2]') |
    +---------------------------------------------------+
    |                                                   |
    +---------------------------------------------------+
    1 row in set (0.00 sec)

    mysql> SELECT ExtractValue(@xml, '/descendant-or-self::b[1]');
    +-------------------------------------------------+
    | ExtractValue(@xml, '/descendant-or-self::b[1]') |
    +-------------------------------------------------+
    | z                                               |
    +-------------------------------------------------+
    1 row in set (0.00 sec)

    mysql> SELECT ExtractValue(@xml, '/descendant-or-self::b[2]');
    +-------------------------------------------------+
    | ExtractValue(@xml, '/descendant-or-self::b[2]') |
    +-------------------------------------------------+
    | x                                               |
    +-------------------------------------------------+
    1 row in set (0.00 sec)
    ```

-   这`*`运算符充当匹配任何元素的“通配符”。例如，表达式`/*/b`匹配*`b`*任一 XML 片段中的元素`<a><b/></a>`要么`<c><b/></c>`.但是，表达式不会在片段中产生匹配`<b><a/></b>`因为*`b`*必须是其他元素的子元素。通配符可以用在任何位置：表达式`/*/b/*`匹配 a 的任何孩子*`b`*本身不是根元素的元素。

-   您可以使用`|`([`联盟`](union.html)） 操作员。例如，表达式`//b|//c`匹配所有*`b`*和*`c`*XML 目标中的元素。

-   也可以根据元素的一个或多个属性的值来匹配元素。这是使用语法完成的`*`标签`*[@*`属性`*="*`价值`*"]`.例如，表达式`//b[@id="idB"]`匹配第二个*`b`*片段中的元素`<a><b id="idA"/><c/><b id="idB"/></a>`.与对手相匹配*任何*元素有`*`属性`*="*`价值`*"`, 使用 XPath 表达式`//*[*`属性`*="*`价值`*"]`.

    要过滤多个属性值，只需连续使用多个属性比较子句。例如，表达式`//b[@c="x"][@d="y"]`匹配元素`<b c="x" d="y"/>`出现在给定 XML 片段中的任何位置。

    要查找相同属性与多个值中的任何一个匹配的元素，您可以使用由`|`操作员。例如，匹配所有*`b`*元素*`c`*属性具有值 23 或 17，使用表达式`//b[@c="23"]|//b[@c="17"]`.您还可以使用逻辑`要么`为此目的的操作员：`//b[@c="23" 或@c="17"]`.

    笔记

    和...之间的不同`要么`和`|`就是它`要么`加入条件，而`|`加入结果集。

**XPath 限制。**这些函数支持的 XPath 语法目前受到以下限制：

-   节点集到节点集的比较（例如`'/a/b[@c=@d]'`) 不受支持。

-   支持所有标准 XPath 比较运算符。（错误 #22823）

-   相对定位器表达式在根节点的上下文中解析。例如，考虑以下查询和结果：

    ```
    mysql> SELECT ExtractValue(
        ->   '<a><b c="1">X</b><b c="2">Y</b></a>',
        ->    'a/b'
        -> ) AS result;
    +--------+
    | result |
    +--------+
    | X Y    |
    +--------+
    1 row in set (0.03 sec)
    ```

    在这种情况下，定位器`a/b`解决为`/a/b`.

    谓词中也支持相对定位符。在以下示例中，`d[../@c="1"]`被解决为`/a/b[@c="1"]/d`：

    ```
    mysql> SELECT ExtractValue(
        ->      '<a>
        ->        <b c="1"><d>X</d></b>
        ->        <b c="2"><d>X</d></b>
        ->      </a>',
        ->      'a/b/d[../@c="1"]')
        -> AS result;
    +--------+
    | result |
    +--------+
    | X      |
    +--------+
    1 row in set (0.00 sec)
    ```

-   不允许使用以标量值计算的表达式为前缀的定位器（包括变量引用、文字、数字和标量函数调用），并且它们的使用会导致错误。

-   这`::`运算符不支持与以下节点类型结合使用：

    -   `*`轴`*：：评论（）`

    -   `*`轴`*：：文本（）`

    -   `*`轴`*::处理指令（）`

    -   `*`轴`*：：节点（）`

        但是，名称测试（例如`*`轴`*::*`姓名`*`和`*`轴`*::*`) 受支持，如以下示例所示：

    ```
    mysql> SELECT ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::b');
    +-------------------------------------------------------+
    | ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::b') |
    +-------------------------------------------------------+
    | x                                                     |
    +-------------------------------------------------------+
    1 row in set (0.02 sec)

    mysql> SELECT ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::*');
    +-------------------------------------------------------+
    | ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::*') |
    +-------------------------------------------------------+
    | x y                                                   |
    +-------------------------------------------------------+
    1 row in set (0.01 sec)
    ```

-   在路径将“高于”根元素的情况下，不支持“上下”导航。也就是说，您不能使用与给定元素的祖先的后代匹配的表达式，其中当前元素的一个或多个祖先也是根元素的祖先（参见错误 #16321）。

-   不支持以下 XPath 函数，或存在已知问题，如下所示：

    -   `ID（）`

    -   `语言（）`

    -   `本地名称（）`

    -   `姓名（）`

    -   `命名空间-uri()`

    -   `规范化空间（）`

    -   `以。。开始（）`

    -   `细绳（）`

    -   `子字符串后（）`

    -   `子串之前（）`

    -   `翻译（）`

-   不支持以下轴：

    -   `继兄弟`

    -   `下列的`

    -   `前兄弟姐妹`

    -   `前`

    XPath 表达式作为参数传递给[`提取值（）`](xml-functions.html#function_extractvalue)和[`更新XML()`](xml-functions.html#function_updatexml)可能包含冒号字符 (`：`) 在元素选择器中，这使得它们能够与采用 XML 命名空间表示法的标记一起使用。例如：


```
mysql> SET @xml = '<a>111<b:c>222<d>333</d><e:f>444</e:f></b:c></a>';
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT ExtractValue(@xml, '//e:f');
+-----------------------------+
| ExtractValue(@xml, '//e:f') |
+-----------------------------+
| 444                         |
+-----------------------------+
1 row in set (0.00 sec)

mysql> SELECT UpdateXML(@xml, '//b:c', '<g:h>555</g:h>');
+--------------------------------------------+
| UpdateXML(@xml, '//b:c', '<g:h>555</g:h>') |
+--------------------------------------------+
| <a>111<g:h>555</g:h></a>                   |
+--------------------------------------------+
1 row in set (0.00 sec)
```

这在某些方面类似于[阿帕奇夏兰](http://xalan.apache.org/)和其他一些解析器，并且比要求命名空间声明或使用`命名空间-uri()`和`本地名称（）`职能。

**错误处理。**对彼此而言[`提取值（）`](xml-functions.html#function_extractvalue)和[`更新XML()`](xml-functions.html#function_updatexml)，使用的 XPath 定位器必须有效，并且要搜索的 XML 必须由正确嵌套和关闭的元素组成。如果定位器无效，则会产生错误：

```
mysql> SELECT ExtractValue('<a>c</a><b/>', '/&a');
ERROR 1105 (HY000): XPATH syntax error: '&a'
```

如果*`xml_frag`*不包含正确嵌套和封闭的元素，`空值`返回并生成警告，如下例所示：

```
mysql> SELECT ExtractValue('<a>c</a><b', '//a');
+-----------------------------------+
| ExtractValue('<a>c</a><b', '//a') |
+-----------------------------------+
| NULL                              |
+-----------------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Warning
   Code: 1525
Message: Incorrect XML value: 'parse error at line 1 pos 11:
         END-OF-INPUT unexpected ('>' wanted)'
1 row in set (0.00 sec)

mysql> SELECT ExtractValue('<a>c</a><b/>', '//a');
+-------------------------------------+
| ExtractValue('<a>c</a><b/>', '//a') |
+-------------------------------------+
| c                                   |
+-------------------------------------+
1 row in set (0.00 sec)
```

重要的

用作第三个参数的替换 XML[`更新XML()`](xml-functions.html#function_updatexml)是*不是*检查以确定它是否仅由正确嵌套和关闭的元素组成。

**XPath 注入。**当恶意代码被引入系统以获得对特权和数据的未经授权的访问时，就会发生代码注入。它基于开发人员对用户输入的数据类型和内容所做的假设。XPath 在这方面也不例外。

[](<>)[](<>)[](<>)

可能发生这种情况的一个常见场景是应用程序通过将登录名和密码的组合与 XML 文件中的组合匹配来处理授权的情况，使用如下 XPath 表达式：

```
//user[login/text()='neapolitan' and password/text()='1c3cr34m']/attribute::id
```

这是一个类似这样的 SQL 语句的 XPath 等价物：

```
SELECT id FROM users WHERE login='neapolitan' AND password='1c3cr34m';
```

使用 XPath 的 PHP 应用程序可能会像这样处理登录过程：

```
<?php

  $file     =   "users.xml";

  $login    =   $POST["login"];
  $password =   $POST["password"];

  $xpath = "//user[login/text()=$login and password/text()=$password]/attribute::id";

  if( file_exists($file) )
  {
    $xml = simplexml_load_file($file);

    if($result = $xml->xpath($xpath))
      echo "You are now logged in as user $result[0].";
    else
      echo "Invalid login name or password.";
  }
  else
    exit("Failed to open $file.");

?>
```

不对输入执行任何检查。这意味着恶意用户可以通过输入“短路”测试`' 或 1=1`对于登录名和密码，导致`$xpath`正在评估，如下所示：

```
//user[login/text()='' or 1=1 and password/text()='' or 1=1]/attribute::id
```

由于方括号内的表达式总是计算为`真的`，它实际上与这个相同，它匹配`id`每个属性`用户`XML 文档中的元素：

```
//user/attribute::id
```

可以规避这种特定攻击的一种方法是简单地引用要在定义中插入的变量名称`$xpath`，强制从 Web 表单传递的值转换为字符串：

```
$xpath = "//user[login/text()='$login' and password/text()='$password']/attribute::id";
```

这与通常推荐用于防止 SQL 注入攻击的策略相同。一般来说，防止 XPath 注入攻击应遵循的做法与防止 SQL 注入相同：

[](<>)[](<>)

-   从不接受来自您应用程序中用户的未经测试的数据。

-   检查所有用户提交的数据类型；拒绝或转换类型错误的数据

-   测试超出范围值的数值数据；截断、舍入或拒绝超出范围的值。测试字符串中是否存在非法字符，然后将其删除或拒绝包含它们的输入。

-   不要输出可能为未经授权的用户提供可能用于破坏系统的线索的明确错误消息；而是将这些记录到文件或数据库表中。

    正如 SQL 注入攻击可用于获取有关数据库模式的信息一样，XPath 注入也可用于遍历 XML 文件以揭示其结构，正如 Amit Klein 的论文中所讨论的那样[盲 XPath 注入](http://www.packetstormsecurity.org/papers/bypass/Blind_XPath_Injection_20040518.pdf)（PDF 文件，46KB）。

    检查发送回客户端的输出也很重要。考虑当我们使用 MySQL 时会发生什么[`提取值（）`](xml-functions.html#function_extractvalue)功能：


```
mysql> SELECT ExtractValue(
    ->     LOAD_FILE('users.xml'),
    ->     '//user[login/text()="" or 1=1 and password/text()="" or 1=1]/attribute::id'
    -> ) AS id;
+-------------------------------+
| id                            |
+-------------------------------+
| 00327 13579 02403 42354 28570 |
+-------------------------------+
1 row in set (0.01 sec)
```

因为[`提取值（）`](xml-functions.html#function_extractvalue)返回多个匹配项作为单个空格分隔的字符串，这种注入攻击提供了其中包含的每个有效 ID`用户.xml`给用户作为单行输出。作为额外的保障，您还应该在将输出返回给用户之前对其进行测试。这是一个简单的例子：

```
mysql> SELECT @id = ExtractValue(
    ->     LOAD_FILE('users.xml'),
    ->     '//user[login/text()="" or 1=1 and password/text()="" or 1=1]/attribute::id'
    -> );
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT IF(
    ->     INSTR(@id, ' ') = 0,
    ->     @id,
    ->     'Unable to retrieve user ID')
    -> AS singleID;
+----------------------------+
| singleID                   |
+----------------------------+
| Unable to retrieve user ID |
+----------------------------+
1 row in set (0.00 sec)
```

通常，安全地向用户返回数据的准则与接受用户输入的准则相同。这些可以概括为：

-   始终测试输出数据的类型和允许值。

-   切勿允许未经授权的用户查看可能提供有关可用于利用它的应用程序信息的错误消息。
