PHP xmllib XSD validation namespace error: Did not expect X, expected X
PHP xmllib XSD validation namespace error: Did not expect X, expected X
我一直在尝试让 xmllib 合作并通过 DOMDocument
验证 xmldsig <Signature>
元素,但无论如何它都会不断产生虚假错误我如何将元素扔向它。显然,由于 DOMDocument
,将 <Signature>
设为 root 是不行的。我无法在根上正确设置 xmlns
属性:Trying to validate results in:
Element 'Signature': No matching global declaration available for the validation root.
我认为这可能是因为架构旨在 'included',毕竟您想要签名 'something',而不仅仅是签名本身。所以,通过创建这个:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
version="1.0"
elementFormDefault="qualified">
<xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="xmldsig-core-schema.xsd" />
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element ref="ds:Signature" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
我现在可以包含虚拟元素的签名。
我意识到 PHP XML 处理很糟糕,none 的工具真的很好用。但是,一旦它真正正确地将事情交给 xmllint,这样做应该没有问题。其他答案也建议添加 XPath,但我想保留 KISS;尽可能少地使用 PHP 库。
通过几百行代码的欺骗,我让它合作并将 xml 文件的 <Signature>
部分包装到一个普通的测试文件中,在 <root>
元素中.
我认为它正在破坏文档中的命名空间。失败后调用$myDocument->saveXML();
$myDocument->schemaValidate();
returns 奇怪的结果:
Element 'Signature': This element is not expected. Expected is ( {http://www.w3.org/2000/09/xmldsig#}Signature ).
当 xml 看起来像这样(我添加了格式;真实的东西是未格式化的;虽然空格不应该很重要):
<?xml version="1.0"?>
<root>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
(... omitted crypto ...)
</Signature>
</root>
根据我对它所做的阅读,该错误应该意味着 <Signature>
元素上的命名空间错误。但是这里用 xmlns
清楚地明确指定了错误消息所期望的内容。那么,这里出了什么问题?
附录:准备验证签名的代码(在任意文档中)看起来像这样。这是一个复杂的版本,我在其中添加了一个 'root' 元素而不是直接使用模式,这在下面我的回答中被证明是不必要的,尽管这是一个很好的测试,因为在更复杂的场景中你d 想在一次调用 libxml 中验证一个签名的东西,这是一件有效的事情:
function verifySchema(\DOMDocument $doc) {
libxml_use_internal_errors(true);
$new = new \DOMDocument;
$newRoot = $new->createElement("root");
$root = $doc->documentElement;
foreach($root->childNodes as $node) {
if($node->nodeName == "Signature") {
$signature = $new->createElement("Signature");
$signature->setAttributeNS('http://www.w3.org/2000/xmlns/','xmlns','http://www.w3.org/2000/09/xmldsig#');
foreach($node->childNodes as $subnode) {
$signature->appendChild($new->importNode($subnode, true));
}
$newRoot->appendChild($signature);
break;
}
}
$new->appendChild($newRoot);
if(!$signature) {
throw new \Exception("Document is not signed: No signature node found.");
}
if(!$new->schemaValidate("xmldsig-test.xsd")) {
// Error handling code here.
} else {
return true;
}
}
PHP 是问题所在。它有细微的错误,并且不会t/doesn无法正确理解 XML 以编程方式创建的文档中的命名空间。
adding/editing DOMDocument 元素有多种方式,其中 some/most 可以导致某个名称空间 [=38= 中的元素 E ]X 当您只查看 XML 输出时,但 DOMDocument 对象认为它位于命名空间 Y 中。即使您只使用提供的函数而不访问对象的内部结构,也有可能将它们置于不一致的状态。在某些情况下,如果您想要特定 格式的名称空间(由于规范化问题)甚至是不可避免的。
参见:
https://bugs.php.net/bug.php?id=78352
替换代码行
$myDocument->schemaValidate();
和
$myDocument->loadXML($myDocument->saveXML());
$myDocument->schemaValidate();
并且验证错误应该消失了。无论哪种方式(使用带有 include 的自定义 xsd
或直接使用 xmldsig 验证文件)都应该有效。
虽然错误被视为 'not a bug',但它确实是一个问题,因为 DOMDOcument
实际上不会使用 xmlns
属性输出准确的语法,而是使用前缀符号相反,如果您使用 createElementNS()
。 (虽然语法上可能相同,但没有人理解 xml 名称空间,因此接受您的 XML 的人可能希望它与示例完全相同,即:使用 xmlns
、 而不是 使用 ds:Signature
个元素)。
而不是使用 DOMElement::setAttributeNS($currentNS, 'xmlns', $subElementNS)
输出默认的命名空间标签,但不 正确地标记元素下的编程实体使用新命名空间递归。
我一直在尝试让 xmllib 合作并通过 DOMDocument
验证 xmldsig <Signature>
元素,但无论如何它都会不断产生虚假错误我如何将元素扔向它。显然,由于 DOMDocument
,将 <Signature>
设为 root 是不行的。我无法在根上正确设置 xmlns
属性:Trying to validate results in:
Element 'Signature': No matching global declaration available for the validation root.
我认为这可能是因为架构旨在 'included',毕竟您想要签名 'something',而不仅仅是签名本身。所以,通过创建这个:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
version="1.0"
elementFormDefault="qualified">
<xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="xmldsig-core-schema.xsd" />
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element ref="ds:Signature" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
我现在可以包含虚拟元素的签名。
我意识到 PHP XML 处理很糟糕,none 的工具真的很好用。但是,一旦它真正正确地将事情交给 xmllint,这样做应该没有问题。其他答案也建议添加 XPath,但我想保留 KISS;尽可能少地使用 PHP 库。
通过几百行代码的欺骗,我让它合作并将 xml 文件的 <Signature>
部分包装到一个普通的测试文件中,在 <root>
元素中.
我认为它正在破坏文档中的命名空间。失败后调用$myDocument->saveXML();
$myDocument->schemaValidate();
returns 奇怪的结果:
Element 'Signature': This element is not expected. Expected is ( {http://www.w3.org/2000/09/xmldsig#}Signature ).
当 xml 看起来像这样(我添加了格式;真实的东西是未格式化的;虽然空格不应该很重要):
<?xml version="1.0"?>
<root>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
(... omitted crypto ...)
</Signature>
</root>
根据我对它所做的阅读,该错误应该意味着 <Signature>
元素上的命名空间错误。但是这里用 xmlns
清楚地明确指定了错误消息所期望的内容。那么,这里出了什么问题?
附录:准备验证签名的代码(在任意文档中)看起来像这样。这是一个复杂的版本,我在其中添加了一个 'root' 元素而不是直接使用模式,这在下面我的回答中被证明是不必要的,尽管这是一个很好的测试,因为在更复杂的场景中你d 想在一次调用 libxml 中验证一个签名的东西,这是一件有效的事情:
function verifySchema(\DOMDocument $doc) {
libxml_use_internal_errors(true);
$new = new \DOMDocument;
$newRoot = $new->createElement("root");
$root = $doc->documentElement;
foreach($root->childNodes as $node) {
if($node->nodeName == "Signature") {
$signature = $new->createElement("Signature");
$signature->setAttributeNS('http://www.w3.org/2000/xmlns/','xmlns','http://www.w3.org/2000/09/xmldsig#');
foreach($node->childNodes as $subnode) {
$signature->appendChild($new->importNode($subnode, true));
}
$newRoot->appendChild($signature);
break;
}
}
$new->appendChild($newRoot);
if(!$signature) {
throw new \Exception("Document is not signed: No signature node found.");
}
if(!$new->schemaValidate("xmldsig-test.xsd")) {
// Error handling code here.
} else {
return true;
}
}
PHP 是问题所在。它有细微的错误,并且不会t/doesn无法正确理解 XML 以编程方式创建的文档中的命名空间。
adding/editing DOMDocument 元素有多种方式,其中 some/most 可以导致某个名称空间 [=38= 中的元素 E ]X 当您只查看 XML 输出时,但 DOMDocument 对象认为它位于命名空间 Y 中。即使您只使用提供的函数而不访问对象的内部结构,也有可能将它们置于不一致的状态。在某些情况下,如果您想要特定 格式的名称空间(由于规范化问题)甚至是不可避免的。
参见:
https://bugs.php.net/bug.php?id=78352
替换代码行
$myDocument->schemaValidate();
和
$myDocument->loadXML($myDocument->saveXML());
$myDocument->schemaValidate();
并且验证错误应该消失了。无论哪种方式(使用带有 include 的自定义 xsd
或直接使用 xmldsig 验证文件)都应该有效。
虽然错误被视为 'not a bug',但它确实是一个问题,因为 DOMDOcument
实际上不会使用 xmlns
属性输出准确的语法,而是使用前缀符号相反,如果您使用 createElementNS()
。 (虽然语法上可能相同,但没有人理解 xml 名称空间,因此接受您的 XML 的人可能希望它与示例完全相同,即:使用 xmlns
、 而不是 使用 ds:Signature
个元素)。
而不是使用 DOMElement::setAttributeNS($currentNS, 'xmlns', $subElementNS)
输出默认的命名空间标签,但不 正确地标记元素下的编程实体使用新命名空间递归。