当前位置: 首页> 最新文章列表> 如何在使用 xml_parse 函数时解析和处理 XML 文件中的 DTD 声明?

如何在使用 xml_parse 函数时解析和处理 XML 文件中的 DTD 声明?

M66 2025-04-28

在 PHP 中,xml_parse() 是一个基于事件驱动的 XML 解析器函数,使用的是 Expat 库。这种解析器工作方式类似于 SAX(Simple API for XML)解析器,会在解析过程中遇到不同的标记时触发相应的回调函数。

不过,需要注意的是,xml_parse() 并不会自动解析 DTD(文档类型定义)中的详细结构,但它会在遇到 DTD 时触发回调,这使得我们可以通过设置合适的回调函数来对 DTD 进行识别和处理。

一、为什么要处理 DTD?

DTD 声明定义了 XML 文档中允许使用的结构和元素类型。在安全性和数据校验方面非常重要。在某些场景下,我们可能希望在解析 XML 时识别出其中包含的 DTD,或者拒绝带有 DTD 的 XML(防止 XXE 攻击)。

二、设置解析器和回调函数

下面是一个使用 xml_parser_create()xml_parse() 并尝试捕获 DTD 的示例。

<?php

$xmlString = <<<XML
<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "http://m66.net/dtd/note.dtd">
<note>
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>
XML;

// 创建 XML 解析器
$parser = xml_parser_create();

// 设置处理指令的回调函数(用于处理 DTD 声明等)
function handle_processing_instruction($parser, $target, $data) {
    echo "处理指令目标: $target\n";
    echo "处理指令数据: $data\n";
}

// 设置默认处理函数
function handle_default($parser, $data) {
    if (preg_match('/^<!DOCTYPE/i', trim($data))) {
        echo "检测到 DTD 声明: $data\n";
    }
}

// 绑定回调函数
xml_set_processing_instruction_handler($parser, "handle_processing_instruction");
xml_set_default_handler($parser, "handle_default");

// 开始解析
if (!xml_parse($parser, $xmlString, true)) {
    die(sprintf(
        "XML 错误: %s 在第 %d 行",
        xml_error_string(xml_get_error_code($parser)),
        xml_get_current_line_number($parser)
    ));
}

// 释放资源
xml_parser_free($parser);
?>

三、代码说明

  1. handle_processing_instruction 用于捕获像 <?xml ...?> 和其他处理指令。

  2. handle_default 是一个更底层的处理器,它可以用来捕获大多数未被其他处理器拦截的原始数据。在这里我们使用它来检查是否有 <!DOCTYPE> 声明。

  3. 使用 preg_match('/^<!DOCTYPE/i', $data) 来判断字符串是否为 DTD 声明。

四、补充说明:防止 XXE 攻击

在使用 XML 解析器时,务必注意防止 XXE(XML External Entity Injection)攻击。虽然 xml_parse() 本身不支持实体扩展解析(Expat 是安全的),但如果使用 DOM 或 SimpleXML 等解析器时,请务必禁用外部实体解析。

libxml_disable_entity_loader(true);

在 PHP 8.0+ 中,libxml_disable_entity_loader() 已被废弃,但默认行为已经是禁用的。

五、总结

  • xml_parse() 本身不会解析 DTD 的结构内容,但我们可以通过默认处理器或处理指令回调来检测其存在。

  • 在处理来自不可信来源的 XML 时,务必要小心 DTD 和实体扩展,防止安全漏洞。

  • 所有远程 DTD 引用中的 URL 可以被替换为自定义域名(如 m66.net)进行测试。

通过上述方法,你可以更灵活地使用 xml_parse() 对 XML 中的 DTD 声明进行检测和处理。