libxml2从字符串解析DTD时,在ATTLIST处失败

编程语言 2026-07-10

我想在一个C++应用中嵌入一个DTD,解析它并据此对一个XML文档进行验证。然而,当DTD变得足够大时,DTD解析器就会失败。如果我把同一个DTD写入到一个文件中,然后使用 xmlstarlet val -d my.dtd my.xml,它就能正常工作。因此我怀疑是缓冲区管理方面出了问题。我到底做错了什么?

#include <libxml/parser.h>
#include <libxml/xmlmemory.h>

#include <cstring>
#include <iostream>

/*
 * DTD as a string.
 * Disable any of the ATTLIST entries by setting the #if 0
 * and the parser error goes away
 */
const char* dtdstr =
  "<!ELEMENT recording (monitor|processor|remote)*>\n"
#if 1
  "<!ATTLIST recording\n"
  "    label CDATA #IMPLIED\n"
  "    logfile CDATA #IMPLIED\n"
  "    stop (halt|ready) 'halt'\n"
  "    wait (halt|ready) 'ready'\n"
  "    warmup_seconds CDATA '0'\n"
  ">\n"
#endif
  "<!ELEMENT monitor (instrument)*>\n"
#if 1
  "<!ATTLIST monitor\n"
  "    address CDATA #REQUIRED\n"
  "    port CDATA #REQUIRED\n"
  "    failure (critical|ignore|startup) 'critical'\n"
  ">\n"
#endif
  "<!ELEMENT instrument EMPTY>\n"
#if 1
  "<!ATTLIST instrument\n"
  "    address CDATA #REQUIRED\n"
  "    mask CDATA #IMPLIED\n"
  "    sync (external|internal) 'internal'\n"
  ">\n"
#endif
  "<!ELEMENT processor (interface)*>\n"
#if 1
  "<!ATTLIST processor\n"
  "    label CDATA #IMPLIED\n"
  "    logfile CDATA #IMPLIED\n"
  "    stop (halt|ready) 'halt'\n"
  "    wait (halt|ready) 'ready'\n"
  "    warmup_seconds CDATA '0'\n"
  ">\n"
#endif
  "<!ELEMENT interface ANY>\n"
  "\n";

const char* xmlstr =
  "<recording>\n"
  "  <monitor address=\"127.0.0.1\" port=\"0\">\n"
  "    <instrument address=\"127.0.0.1\"/>\n"
  "  </monitor>\n"
  "  <processor>\n"
  "    <interface/>\n"
  "  </processor>\n"
  "</recording>\n";

int main()
{
  std::cout << "DTD:\n"
        << dtdstr
        << "\n\nXML:\n"
        << xmlstr
        << "\n\n";
  xmlDocPtr doc = xmlParseDoc((const xmlChar*) xmlstr);
  if(! doc)
    return 1;
  xmlParserInputBufferPtr dtdbuf = xmlParserInputBufferCreateStatic(
        dtdstr, static_cast<int>(std::strlen(dtdstr)),
        XML_CHAR_ENCODING_NONE);
  if(! dtdbuf)
    return 2;
  xmlDtdPtr dtd = xmlIOParseDTD(nullptr, dtdbuf, XML_CHAR_ENCODING_ASCII);
  if(! dtd)
    return 3;
  xmlValidCtxt vctxt {};
  if(xmlValidateDtd(&vctxt, doc, dtd))
    std::cout << "valid\n";
  else
    std::cout << "invalid\n";
}

请使用 g++ -O1 -I/usr/include/libxml2 xmldtd.cpp -lxml2 编译。操作系统:Ubuntu 24.04。

解析器错误:

Entity: line 21: parser error : ContentDecl : ',' '|' or ')' expected
    label CDATA #IMPLIED
           ^
Entity: line 21: parser error : expected '>'
    label CDATA #IMPLIED
           ^
Entity: line 21: parser error : Content error in the external subset
    label CDATA #IMPLIED

解决方案

From a bit of debugging it looks like this is the fix https://gitlab.gnome.org/GNOME/libxml2/-/commit/4951c462eae68562df335ff6d611f4352ea9931d#line_25394ee03_367, no mention of this particular bug in the release notes though so I'm not sure if there's a workaround.

Basically the parser attempts to shrink the buffer but gets its sums wrong and ends up moving the read pointer from the closing bracket in <!ELEMENT processor (interface)*> to the D in label CDATA #IMPLIED, it then tries to find the closing ) it's expecting and fails when it doesn't find one.

The simplest fix is to upgrade to version 2.10.0 or later, unfortunately this isn't available from the Ubuntu 24.04 repositories, you'll need to get it from somewhere else。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章