在PHP 中, xml_parse()是一個基於事件驅動的XML 解析器函數,使用的是Expat 庫。這種解析器工作方式類似於SAX(Simple API for XML)解析器,會在解析過程中遇到不同的標記時觸發相應的回調函數。
不過,需要注意的是, xml_parse()並不會自動解析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);
?>
handle_processing_instruction用於捕獲像<?xml ...?>和其他處理指令。
handle_default是一個更底層的處理器,它可以用來捕獲大多數未被其他處理器攔截的原始數據。在這裡我們使用它來檢查是否有<!DOCTYPE>聲明。
使用preg_match('/^<!DOCTYPE/i', $data)來判斷字符串是否為DTD 聲明。
在使用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 聲明進行檢測和處理。