KDSoapMessageReader.cpp has a recovery path that strips invalid numeric character references and re-parses. It doesn't work if the response contains any non-ASCII characters before the bad reference.
xmlToMessage() passes reader.characterOffset() to handleNotWellFormedError(), which uses it to index a QByteArray:
qint64 i = offset - 1;
while (i >= 0 && data.at(i) != '&') {
if (data.at(i) == '<') {
return dataCleanedUp; // empty -> no retry
}
characterOffset() counts decoded characters; data.at() indexes bytes. Multi-byte UTF-8 earlier in the document makes the byte position larger than the reported character position, so the backward scan starts too early, hits an earlier <, and returns empty. xmlToMessage() then skips the retry and produces a fault instead of recovering.
I'm happy to look at implementing a solution with some guidance as to what would be preferred.
- Ignore the offset. Scan the buffer forward for
&#…; and test each candidate with isInvalidCharRef(). (I did this in a fork and am currently using it now.)
- Convert the offset. Walk the buffer to translate character offset to byte offset before scanning. Has to account for what
characterOffset() counts (UTF-16 code units), CRLF normalization, and non-UTF-8 encodings.
- Parse from a
QString. QXmlStreamReader reader(QString::fromUtf8(data)) and do the repair in character space, so the offset lines up natively.
- Sanitize before parsing. Filter illegal characters and references in a single pass up front, so the recovery path is never needed.
Separately, in isInvalidCharRef(): the test is val <= 0x20, which classifies 0x20 (space) as invalid, so   is replaced with ?. Should be val < 0x20. Doesn't really come up when using the offset, but I did need to correct it for the full scan in my fork.
Here is a unit test that demonstrates the issue: unittests/msexchange_wsdl/test_msexchange_wsdl.cpp (line ~500)
QTest::newRow("invalid 13 after non-ascii") << serverResponseXML.arg(QString::fromUtf8("\xc3\xa9\xc3\xa9\xc3\xa9")) << request << QString::fromUtf8("subject \xc3\xa9\xc3\xa9\xc3\xa9?");
KDSoapMessageReader.cpphas a recovery path that strips invalid numeric character references and re-parses. It doesn't work if the response contains any non-ASCII characters before the bad reference.xmlToMessage()passesreader.characterOffset()tohandleNotWellFormedError(), which uses it to index aQByteArray:characterOffset()counts decoded characters;data.at()indexes bytes. Multi-byte UTF-8 earlier in the document makes the byte position larger than the reported character position, so the backward scan starts too early, hits an earlier<, and returns empty.xmlToMessage()then skips the retry and produces a fault instead of recovering.I'm happy to look at implementing a solution with some guidance as to what would be preferred.
&#…;and test each candidate withisInvalidCharRef(). (I did this in a fork and am currently using it now.)characterOffset()counts (UTF-16 code units), CRLF normalization, and non-UTF-8 encodings.QString.QXmlStreamReader reader(QString::fromUtf8(data))and do the repair in character space, so the offset lines up natively.Separately, in
isInvalidCharRef(): the test isval <= 0x20, which classifies 0x20 (space) as invalid, so is replaced with?. Should beval < 0x20. Doesn't really come up when using the offset, but I did need to correct it for the full scan in my fork.Here is a unit test that demonstrates the issue:
unittests/msexchange_wsdl/test_msexchange_wsdl.cpp(line ~500)