java - Java 中的 Apache mod_dav XML 尾随内容 SAX 解析器错误

标签 java xml apache webdav

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

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

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

它失败的地方是 XML 响应恰好使用了 110 个 block ——比大多数其他响应大得多(这是一个非常大的目录)。然而,在我的日志中,我可以看到没有“尾随内容”——每个 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 行是文件的末尾——但是那里什么也没有!!

最佳答案

哦,天哪——这是我处理过的最令人烦恼和最微妙的错误之一!我很想将响应 XML 读取为字节并返回 ByteArrayInputStream并返回,尽管我不知道为什么可以解决问题。事实证明,从技术上来说,这是我的错,但仍然......

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

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

因此,在这种特殊情况下,无论出于何种原因,XML 解析器都会要求从 WebDAV PROPFIND 获取 XML 响应中的剩余字节。然后解析器想要检查是否还有更多字符。实际读取发生在 UTF8Reader ;当我的输入流返回读取了零字节时,这被传递了 XMLEntityScanner 。这些类都不知道如何处理“未读取任何字节”——它只是假设读取了某些内容。最后,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 中吸取了字节。从来没有请求过最后一个 block 中的确切字节数——即使它这样做了,它仍然知道如何取出那些零字节并将它们与其他字节一起放入缓冲区中。

关于java - Java 中的 Apache mod_dav XML 尾随内容 SAX 解析器错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8798864/

相关文章:

java - PreparedStatement ResultSet - 列包含所选列名称作为字符串而不是所选列的实际值

java - 销毁日期对象

java - OpenNTF Domino API - 最佳入门方式

java - 在android中的textview上创建一条水平线

laravel - Apache 2.4 具有不同别名和路径的多个站点

java - android java中关于Thread life和AudioTrack的困惑

xml - 如何返回同一级别的子类别?

xml - 这个 "PCDATA invalid Char value 11"错误是什么?

mysql - Apache/MySQL 上的 Django 内存泄漏

apache - 如何在本地主机上为 Apache 启用 HTTPS?