【问题标题】:Apache mod_dav XML trailing content SAX parser bug in JavaJava中的Apache mod_dav XML尾随内容SAX解析器错误
【发布时间】:2012-01-10 05:16:05
【问题描述】:

我正在使用在我自己的服务器上编译的 Apache mod_dav。我的客户端是用 Java 从头开始​​构建的自定义 HTTP 解析代码。我多年来一直在使用这个服务器和代码库,在服务器上同步千兆字节的数据。

今天我遇到了一个以前从未出现过的问题:可怕的 SAX“尾部不允许内容”错误。在整个服务器资源树中执行 WebDAV PROPFIND 时,我总是在同一位置收到此错误。

我已经测试并重新测试了我的 HTTP 解析代码,但它非常简单:Apache 正在发回分块内容,并且块指示要消耗的字节数。

失败的地方是恰好使用 110 个块的 XML 响应——比大多数其他响应大得多(这是一个非常大的目录)。但是,在我的日志中,我可以看到没有“尾随内容”——每个 XML 响应(产生错误和不产生错误的响应)都以一个简单的换行符结尾。

但更令人苦恼的是:我有一个输入流,它解析 HTTP 分块内容并返回一个简单的字节串。当我将此输入流直接传递给 XML 解析器时,我收到以下错误。但是:如果我采用 相同的输入流 并从中提取所有字节,将它们放入 ByteArrayInputStream,然后将 ByteArrayInputStream(应该包含完全相同的数据!)提供给解析器,不发生错误!直接从导致错误的传入数据中解析是什么?

我的 XML 解析器非常简单:

final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setValidating(false);

有人见过这个吗? (我搜索了“mod_dav XML bug”——刚刚得到了五年前提交的不相关的bug。)

这是堆栈跟踪的相关部分:

Cause:org.xml.sax.SAXParseException: Content is not allowed in trailing section.
    com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(Unknown Source)
    com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(Unknown Source)
    javax.xml.parsers.DocumentBuilder.parse(Unknown Source)
    com.globalmentor.net.http.HTTPClientTCPConnection.readResponseBodyXML(HTTPClientTCPConnection.java:666)
    com.globalmentor.net.http.webdav.WebDAVResource.propFind(WebDAVResource.java:453)

更新:我一遍又一遍地完成了这个测试。最后我添加了代码来遍历堆栈跟踪并打印出我得到的 SAX 解析信息:

Public Id: null System Id: null Line# 21937 Column# 1

我从日志文件中复制了XML,果然,第21937行是文件的结尾---但是那里什么都没有!!

【问题讨论】:

    标签: java xml apache webdav


    【解决方案1】:

    哦,伙计——这是我处理过的最严重和最微妙的错误之一!我很想将响应 XML 读入字节并返回 ByteArrayInputStream 并返回,尽管我不知道为什么会解决问题。事实证明这是我的错,从技术上讲,但仍然......

    所以事实证明,如果您阅读InputStream.read(byte b[], int off, int len) 的 API 合约,该方法绝不应该返回零字节!如果它到达数据的末尾,它应该返回-1,或者阻塞直到数据可用。 (如果调用者请求一个为零的len 应该怎么做尚不清楚,因为这似乎没有被 API 禁止。更现代的 API 会指定如果 len<1 应该抛出一个 IllegalArgumentException,但我离题了。)

    我的HTTPChunkedInputStream 自动解析出 HTTP 分块响应的块。它的编写方式,如果HTTPChunkedInputStream.read(byte b[], int off, int len) 的调用者准确请求了最后一个块中可用的字节数,那么输入流将不会主动尝试加载更多块并识别溪流。这本身不是问题,但是下一次调用者想要更多字节时,算法的编写方式,我的输入流会尝试读取另一个块,认识到没有更多的块了,然后表示读取了零个字节! (请注意,这只发生在被调用者首先请求最后一个块中的字节数,然后再请求更多字节时。)在此之后的任何时候,它都会返回 -1,因为数据的末尾已经被击中.

    因此,在这种特殊情况下,无论出于何种原因,XML 解析器都准确地要求来自 WebDAV PROPFIND 的 XML 响应中的剩余字节。然后解析器想检查是否还有其他字符。实际读数发生在UTF8Reader;当我的输入流返回读取零字节时,这被传递了XMLEntityScanner。这些类都不知道如何处理“没有读取字节”——它只是假设 something 被读取。最后,XMLDocumentScannerImpl 检查第 1453 行的“某物”是什么:

    int ch = fEntityScanner.peekChar();
    if (ch == -1) {
        setScannerState(SCANNER_STATE_TERMINATED);
        return XMLEvent.END_DOCUMENT ;
    } else{
        reportFatalError("ContentIllegalInTrailingMisc",
                null);
        fEntityScanner.scanChar();
        setScannerState(SCANNER_STATE_TRAILING_MISC);
        return XMLEvent.CHARACTERS;
    }
    

    因为没有指明流的结尾(它不知道如何处理“无”),所以它假定那里有“东西”,而这个东西一定是非法的尾随内容。

    哇!我已经修复了我的HTTPChunkedInputStream 类,使其永远不会从read() 返回零字节。我筋疲力尽——这是除了在某些情况下不经常出现的情况之外甚至从未出现过的事情之一。当我读取字节并在ByteArrayInputStream 中返回它们时,这并没有出现,因为我从HTTPChunkedInputStream 中吸取字节的代码从未请求过最后一个块中的字节数——如果它确实做到了,它仍然知道如何吸出那些零字节并将它们与其他字节一起放入缓冲区。

    【讨论】:

      猜你喜欢
      • 2016-03-24
      • 1970-01-01
      • 2015-02-15
      • 2017-08-19
      • 2013-12-05
      • 2011-09-23
      • 1970-01-01
      • 1970-01-01
      • 2011-03-19
      相关资源
      最近更新 更多