Strip invalid XML chars when writing databases.

These characters are unprintable or just plain invalid.
QXmlStreamReader throws and error when reading XML documents with such chars.

Closes #392
This commit is contained in:
Felix Geyer 2016-01-24 17:20:16 +01:00
parent 5e6b17aba4
commit 2d741afe3e
4 changed files with 52 additions and 2 deletions

View File

@ -374,7 +374,7 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
} }
if (!value.isEmpty()) { if (!value.isEmpty()) {
m_xml.writeCharacters(value); m_xml.writeCharacters(stripInvalidXml10Chars(value));
} }
m_xml.writeEndElement(); m_xml.writeEndElement();
@ -445,7 +445,7 @@ void KeePass2XmlWriter::writeString(const QString& qualifiedName, const QString&
m_xml.writeEmptyElement(qualifiedName); m_xml.writeEmptyElement(qualifiedName);
} }
else { else {
m_xml.writeTextElement(qualifiedName, string); m_xml.writeTextElement(qualifiedName, stripInvalidXml10Chars(string));
} }
} }
@ -549,6 +549,23 @@ QString KeePass2XmlWriter::colorPartToString(int value)
return str; return str;
} }
QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str)
{
for (int i = str.size() - 1; i >= 0; i--) {
const ushort uc = str.at(i).unicode();
if ((uc < 0x20 && uc != 0x09 && uc != 0x0A && uc != 0x0D)
|| (uc > 0xD7FF && uc < 0xE000)
|| (uc > 0xFFFD))
{
qWarning("Stripping invalid XML 1.0 codepoint %x", uc);
str.remove(i, 1);
}
}
return str;
}
void KeePass2XmlWriter::raiseError(const QString& errorMessage) void KeePass2XmlWriter::raiseError(const QString& errorMessage)
{ {
m_error = true; m_error = true;

View File

@ -73,6 +73,7 @@ private:
void writeColor(const QString& qualifiedName, const QColor& color); void writeColor(const QString& qualifiedName, const QColor& color);
void writeTriState(const QString& qualifiedName, Group::TriState triState); void writeTriState(const QString& qualifiedName, Group::TriState triState);
QString colorPartToString(int value); QString colorPartToString(int value);
QString stripInvalidXml10Chars(QString str);
void raiseError(const QString& errorMessage); void raiseError(const QString& errorMessage);

View File

@ -17,6 +17,7 @@
#include "TestKeePass2XmlReader.h" #include "TestKeePass2XmlReader.h"
#include <QBuffer>
#include <QFile> #include <QFile>
#include <QTest> #include <QTest>
@ -26,6 +27,7 @@
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/Crypto.h" #include "crypto/Crypto.h"
#include "format/KeePass2XmlReader.h" #include "format/KeePass2XmlReader.h"
#include "format/KeePass2XmlWriter.h"
#include "config-keepassx-tests.h" #include "config-keepassx-tests.h"
QTEST_GUILESS_MAIN(TestKeePass2XmlReader) QTEST_GUILESS_MAIN(TestKeePass2XmlReader)
@ -408,6 +410,35 @@ void TestKeePass2XmlReader::testEmptyUuids()
QVERIFY(!reader.hasError()); QVERIFY(!reader.hasError());
} }
void TestKeePass2XmlReader::testInvalidXmlChars()
{
QScopedPointer<Database> dbWrite(new Database());
Entry* entry = new Entry();
entry->setUuid(Uuid::random());
entry->setNotes(QString("a %1 b %2 c %3").arg(QChar(0x02)).arg(QChar(0xD800)).arg(QChar(0xFFFE)));
entry->setGroup(dbWrite->rootGroup());
QBuffer buffer;
buffer.open(QIODevice::ReadWrite);
KeePass2XmlWriter writer;
writer.writeDatabase(&buffer, dbWrite.data());
QVERIFY(!writer.hasError());
buffer.seek(0);
KeePass2XmlReader reader;
reader.setStrictMode(true);
QScopedPointer<Database> dbRead(reader.readDatabase(&buffer));
if (reader.hasError()) {
qWarning("Database read error: %s", qPrintable(reader.errorString()));
}
QVERIFY(!reader.hasError());
QVERIFY(!dbRead.isNull());
QCOMPARE(dbRead->rootGroup()->entries().size(), 1);
// check that the invalid codepoints have been stripped
QCOMPARE(dbRead->rootGroup()->entries().first()->notes(), QString("a b c "));
}
void TestKeePass2XmlReader::cleanupTestCase() void TestKeePass2XmlReader::cleanupTestCase()
{ {
delete m_db; delete m_db;

View File

@ -42,6 +42,7 @@ private Q_SLOTS:
void testBroken(); void testBroken();
void testBroken_data(); void testBroken_data();
void testEmptyUuids(); void testEmptyUuids();
void testInvalidXmlChars();
void cleanupTestCase(); void cleanupTestCase();
private: private: