Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Geyer <debfx@fobos.de>2012-04-21 18:45:46 +0400
committerFelix Geyer <debfx@fobos.de>2012-04-21 18:45:46 +0400
commit8acd6f74d8c4ef396787c2e7b1f4ef91b5670a1e (patch)
tree410948b4ae7b1181b6a56633c6a15296bcfee075
parente8ac70120b113b28cd42ac7a5e72663c912fbe64 (diff)
Support KeePass format 3.00 (used by KeePass2 >= 2.15).
Closes #6 Attachments are now stored in a pool under Metadata instead of in entries. The protected flag of attachments isn't supported anymore. New metadata attributes: color, historyMaxItems and historyMaxSize. Dropped metadata attribute: autoEnableVisualHiding.
-rw-r--r--src/core/EntryAttachments.cpp26
-rw-r--r--src/core/EntryAttachments.h4
-rw-r--r--src/core/Group.cpp19
-rw-r--r--src/core/Group.h1
-rw-r--r--src/core/Metadata.cpp42
-rw-r--r--src/core/Metadata.h16
-rw-r--r--src/format/KeePass2.h3
-rw-r--r--src/format/KeePass2Reader.cpp5
-rw-r--r--src/format/KeePass2XmlReader.cpp127
-rw-r--r--src/format/KeePass2XmlReader.h6
-rw-r--r--src/format/KeePass2XmlWriter.cpp91
-rw-r--r--src/format/KeePass2XmlWriter.h4
-rw-r--r--tests/TestKeePass2Reader.cpp34
-rw-r--r--tests/TestKeePass2Reader.h1
-rw-r--r--tests/TestKeePass2Writer.cpp10
-rw-r--r--tests/TestKeePass2Writer.h1
-rw-r--r--tests/TestKeePass2XmlReader.cpp25
-rw-r--r--tests/data/Compressed.kdbxbin1870 -> 1918 bytes
-rw-r--r--tests/data/Format200.kdbxbin0 -> 2302 bytes
-rw-r--r--tests/data/NewDatabase.xml41
-rw-r--r--tests/data/NonAscii.kdbxbin2574 -> 2798 bytes
-rw-r--r--tests/data/NonAscii.kdbx.key1
-rw-r--r--tests/data/ProtectedStrings.kdbxbin1950 -> 1934 bytes
23 files changed, 376 insertions, 81 deletions
diff --git a/src/core/EntryAttachments.cpp b/src/core/EntryAttachments.cpp
index 2843f9318..820d24a67 100644
--- a/src/core/EntryAttachments.cpp
+++ b/src/core/EntryAttachments.cpp
@@ -32,12 +32,7 @@ QByteArray EntryAttachments::value(const QString& key) const
return m_attachments.value(key);
}
-bool EntryAttachments::isProtected(const QString& key) const
-{
- return m_protectedAttachments.contains(key);
-}
-
-void EntryAttachments::set(const QString& key, const QByteArray& value, bool protect)
+void EntryAttachments::set(const QString& key, const QByteArray& value)
{
bool emitModified = false;
bool addAttachment = !m_attachments.contains(key);
@@ -51,17 +46,6 @@ void EntryAttachments::set(const QString& key, const QByteArray& value, bool pro
emitModified = true;
}
- if (protect != m_protectedAttachments.contains(key)) {
- if (protect) {
- m_protectedAttachments.insert(key);
- }
- else {
- m_protectedAttachments.remove(key);
- }
-
- emitModified = true;
- }
-
if (addAttachment) {
Q_EMIT added(key);
}
@@ -84,7 +68,6 @@ void EntryAttachments::remove(const QString& key)
Q_EMIT aboutToBeRemoved(key);
m_attachments.remove(key);
- m_protectedAttachments.remove(key);
Q_EMIT removed(key);
Q_EMIT modified();
@@ -96,13 +79,9 @@ void EntryAttachments::copyFrom(const EntryAttachments* other)
Q_EMIT aboutToBeReset();
m_attachments.clear();
- m_protectedAttachments.clear();
Q_FOREACH (const QString& key, other->keys()) {
m_attachments.insert(key, other->value(key));
- if (other->isProtected(key)) {
- m_protectedAttachments.insert(key);
- }
}
Q_EMIT reset();
@@ -119,7 +98,6 @@ void EntryAttachments::clear()
Q_EMIT aboutToBeReset();
m_attachments.clear();
- m_protectedAttachments.clear();
Q_EMIT reset();
Q_EMIT modified();
@@ -127,5 +105,5 @@ void EntryAttachments::clear()
bool EntryAttachments::operator!=(const EntryAttachments& other) const
{
- return m_attachments != other.m_attachments || m_protectedAttachments != other.m_protectedAttachments;
+ return m_attachments != other.m_attachments;
}
diff --git a/src/core/EntryAttachments.h b/src/core/EntryAttachments.h
index 915c3b751..be1876f0c 100644
--- a/src/core/EntryAttachments.h
+++ b/src/core/EntryAttachments.h
@@ -29,8 +29,7 @@ public:
explicit EntryAttachments(QObject* parent = 0);
QList<QString> keys() const;
QByteArray value(const QString& key) const;
- bool isProtected(const QString& key) const;
- void set(const QString& key, const QByteArray& value, bool protect = false);
+ void set(const QString& key, const QByteArray& value);
void remove(const QString& key);
void copyFrom(const EntryAttachments* other);
void clear();
@@ -48,7 +47,6 @@ Q_SIGNALS:
private:
QMap<QString, QByteArray> m_attachments;
- QSet<QString> m_protectedAttachments;
};
#endif // KEEPASSX_ENTRYATTACHMENTS_H
diff --git a/src/core/Group.cpp b/src/core/Group.cpp
index b37821bfd..8e5fe113c 100644
--- a/src/core/Group.cpp
+++ b/src/core/Group.cpp
@@ -305,6 +305,25 @@ const QList<Entry*>& Group::entries() const
return m_entries;
}
+QList<Entry*> Group::entriesRecursive(bool includeHistoryItems)
+{
+ QList<Entry*> entryList;
+
+ entryList.append(m_entries);
+
+ if (includeHistoryItems) {
+ Q_FOREACH (Entry* entry, m_entries) {
+ entryList.append(entry->historyItems());
+ }
+ }
+
+ Q_FOREACH (Group* group, m_children) {
+ entryList.append(group->entriesRecursive(includeHistoryItems));
+ }
+
+ return entryList;
+}
+
void Group::addEntry(Entry *entry)
{
Q_ASSERT(entry);
diff --git a/src/core/Group.h b/src/core/Group.h
index 6fc657318..3cbd2e573 100644
--- a/src/core/Group.h
+++ b/src/core/Group.h
@@ -74,6 +74,7 @@ public:
const QList<Group*>& children() const;
QList<Entry*> entries();
const QList<Entry*>& entries() const;
+ QList<Entry*> entriesRecursive(bool includeHistoryItems = false);
Q_SIGNALS:
void dataChanged(Group* group);
diff --git a/src/core/Metadata.cpp b/src/core/Metadata.cpp
index e549e0d3f..e0f5dcfda 100644
--- a/src/core/Metadata.cpp
+++ b/src/core/Metadata.cpp
@@ -34,6 +34,8 @@ Metadata::Metadata(Database* parent)
m_recycleBinEnabled = true;
m_masterKeyChangeRec = -1;
m_masterKeyChangeForce = -1;
+ m_historyMaxItems = 10;
+ m_historyMaxSize = 6291456;
QDateTime now = QDateTime::currentDateTimeUtc();
m_nameChanged = now;
@@ -48,7 +50,7 @@ Metadata::Metadata(Database* parent)
m_protectPassword = true;
m_protectUrl = false;
m_protectNotes = false;
- m_autoEnableVisualHiding = false;
+ // m_autoEnableVisualHiding = false;
m_updateDatetime = true;
}
@@ -122,6 +124,11 @@ int Metadata::maintenanceHistoryDays() const
return m_maintenanceHistoryDays;
}
+QColor Metadata::color() const
+{
+ return m_color;
+}
+
bool Metadata::protectTitle() const
{
return m_protectTitle;
@@ -147,10 +154,10 @@ bool Metadata::protectNotes() const
return m_protectNotes;
}
-bool Metadata::autoEnableVisualHiding() const
+/*bool Metadata::autoEnableVisualHiding() const
{
return m_autoEnableVisualHiding;
-}
+}*/
QImage Metadata::customIcon(const Uuid& uuid) const
{
@@ -217,6 +224,16 @@ int Metadata::masterKeyChangeForce() const
return m_masterKeyChangeForce;
}
+int Metadata::historyMaxItems() const
+{
+ return m_historyMaxItems;
+}
+
+int Metadata::historyMaxSize() const
+{
+ return m_historyMaxSize;
+}
+
QHash<QString, QString> Metadata::customFields() const
{
return m_customFields;
@@ -264,6 +281,11 @@ void Metadata::setMaintenanceHistoryDays(int value)
set(m_maintenanceHistoryDays, value);
}
+void Metadata::setColor(const QColor& value)
+{
+ set(m_color, value);
+}
+
void Metadata::setProtectTitle(bool value)
{
set(m_protectTitle, value);
@@ -289,10 +311,10 @@ void Metadata::setProtectNotes(bool value)
set(m_protectNotes, value);
}
-void Metadata::setAutoEnableVisualHiding(bool value)
+/*void Metadata::setAutoEnableVisualHiding(bool value)
{
set(m_autoEnableVisualHiding, value);
-}
+}*/
void Metadata::addCustomIcon(const Uuid& uuid, const QImage& icon)
{
@@ -362,6 +384,16 @@ void Metadata::setMasterKeyChangeForce(int value)
set(m_masterKeyChangeForce, value);
}
+void Metadata::setHistoryMaxItems(int value)
+{
+ set(m_historyMaxItems, value);
+}
+
+void Metadata::setHistoryMaxSize(int value)
+{
+ set(m_historyMaxSize, value);
+}
+
void Metadata::addCustomField(const QString& key, const QString& value)
{
Q_ASSERT(!m_customFields.contains(key));
diff --git a/src/core/Metadata.h b/src/core/Metadata.h
index b2fe3d957..60f8d2d0a 100644
--- a/src/core/Metadata.h
+++ b/src/core/Metadata.h
@@ -20,6 +20,7 @@
#include <QtCore/QDateTime>
#include <QtCore/QHash>
+#include <QtGui/QColor>
#include <QtGui/QImage>
#include "core/Uuid.h"
@@ -42,12 +43,13 @@ public:
QString defaultUserName() const;
QDateTime defaultUserNameChanged() const;
int maintenanceHistoryDays() const;
+ QColor color() const;
bool protectTitle() const;
bool protectUsername() const;
bool protectPassword() const;
bool protectUrl() const;
bool protectNotes() const;
- bool autoEnableVisualHiding() const;
+ // bool autoEnableVisualHiding() const;
QImage customIcon(const Uuid& uuid) const;
QHash<Uuid, QImage> customIcons() const;
bool recycleBinEnabled() const;
@@ -61,6 +63,8 @@ public:
QDateTime masterKeyChanged() const;
int masterKeyChangeRec() const;
int masterKeyChangeForce() const;
+ int historyMaxItems() const;
+ int historyMaxSize() const;
QHash<QString, QString> customFields() const;
void setGenerator(const QString& value);
@@ -71,12 +75,13 @@ public:
void setDefaultUserName(const QString& value);
void setDefaultUserNameChanged(const QDateTime& value);
void setMaintenanceHistoryDays(int value);
+ void setColor(const QColor& value);
void setProtectTitle(bool value);
void setProtectUsername(bool value);
void setProtectPassword(bool value);
void setProtectUrl(bool value);
void setProtectNotes(bool value);
- void setAutoEnableVisualHiding(bool value);
+ // void setAutoEnableVisualHiding(bool value);
void addCustomIcon(const Uuid& uuid, const QImage& icon);
void removeCustomIcon(const Uuid& uuid);
void setRecycleBinEnabled(bool value);
@@ -89,6 +94,8 @@ public:
void setMasterKeyChanged(const QDateTime& value);
void setMasterKeyChangeRec(int value);
void setMasterKeyChangeForce(int value);
+ void setHistoryMaxItems(int value);
+ void setHistoryMaxSize(int value);
void addCustomField(const QString& key, const QString& value);
void removeCustomField(const QString& key);
void setUpdateDatetime(bool value);
@@ -111,13 +118,14 @@ private:
QString m_defaultUserName;
QDateTime m_defaultUserNameChanged;
int m_maintenanceHistoryDays;
+ QColor m_color;
bool m_protectTitle;
bool m_protectUsername;
bool m_protectPassword;
bool m_protectUrl;
bool m_protectNotes;
- bool m_autoEnableVisualHiding;
+ // bool m_autoEnableVisualHiding;
QHash<Uuid, QImage> m_customIcons;
@@ -132,6 +140,8 @@ private:
QDateTime m_masterKeyChanged;
int m_masterKeyChangeRec;
int m_masterKeyChangeForce;
+ int m_historyMaxItems;
+ int m_historyMaxSize;
QHash<QString, QString> m_customFields;
diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h
index 94bb1378b..edb8ec5c9 100644
--- a/src/format/KeePass2.h
+++ b/src/format/KeePass2.h
@@ -26,7 +26,8 @@ namespace KeePass2
{
const quint32 SIGNATURE_1 = 0x9AA2D903;
const quint32 SIGNATURE_2 = 0xB54BFB67;
- const quint32 FILE_VERSION = 0x00020000;
+ const quint32 FILE_VERSION = 0x00030000;
+ const quint32 FILE_VERSION_MIN = 0x00020000;
const quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000;
const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp
index 7fd569935..a4e58a681 100644
--- a/src/format/KeePass2Reader.cpp
+++ b/src/format/KeePass2Reader.cpp
@@ -60,9 +60,8 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
}
quint32 version = Endian::readUInt32(m_device, KeePass2::BYTEORDER, &ok) & KeePass2::FILE_VERSION_CRITICAL_MASK;
- quint32 expectedVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK;
- // TODO do we support old Kdbx versions?
- if (!ok || (version != expectedVersion)) {
+ quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK;
+ if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) {
raiseError(tr("Unsupported KeePass database version."));
return 0;
}
diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp
index d61772825..95ca47796 100644
--- a/src/format/KeePass2XmlReader.cpp
+++ b/src/format/KeePass2XmlReader.cpp
@@ -17,6 +17,7 @@
#include "KeePass2XmlReader.h"
+#include <QtCore/QBuffer>
#include <QtCore/QFile>
#include "core/Database.h"
@@ -24,6 +25,7 @@
#include "core/Group.h"
#include "core/Metadata.h"
#include "format/KeePass2RandomStream.h"
+#include "streams/QtIOCompressor"
KeePass2XmlReader::KeePass2XmlReader()
: m_randomStream(0)
@@ -63,6 +65,27 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra
}
}
+ QSet<QString> poolKeys = m_binaryPool.keys().toSet();
+ QSet<QString> entryKeys = m_binaryMap.keys().toSet();
+ QSet<QString> unmappedKeys = entryKeys - poolKeys;
+ QSet<QString> unusedKeys = poolKeys - entryKeys;
+
+ if (!unmappedKeys.isEmpty()) {
+ raiseError(17);
+ }
+
+ if (!m_xml.error()) {
+ Q_FOREACH (const QString& key, unusedKeys) {
+ qWarning("KeePass2XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key));
+ }
+ }
+
+ QHash<QString, QPair<Entry*, QString> >::const_iterator i;
+ for (i = m_binaryMap.constBegin(); i != m_binaryMap.constEnd(); ++i) {
+ QPair<Entry*, QString> target = i.value();
+ target.first->attachments()->set(target.second, m_binaryPool[i.key()]);
+ }
+
m_meta->setUpdateDatetime(true);
Q_FOREACH (Group* group, m_groups) {
group->setUpdateTimeinfo(true);
@@ -151,6 +174,9 @@ void KeePass2XmlReader::parseMeta()
else if (m_xml.name() == "MaintenanceHistoryDays") {
m_meta->setMaintenanceHistoryDays(readNumber());
}
+ else if (m_xml.name() == "Color") {
+ m_meta->setColor(readColor());
+ }
else if (m_xml.name() == "MasterKeyChanged") {
m_meta->setMasterKeyChanged(readDateTime());
}
@@ -187,6 +213,15 @@ void KeePass2XmlReader::parseMeta()
else if (m_xml.name() == "LastTopVisibleGroup") {
m_meta->setLastTopVisibleGroup(getGroup(readUuid()));
}
+ else if (m_xml.name() == "HistoryMaxItems") {
+ m_meta->setHistoryMaxItems(readNumber());
+ }
+ else if (m_xml.name() == "HistoryMaxSize") {
+ m_meta->setHistoryMaxSize(readNumber());
+ }
+ else if (m_xml.name() == "Binaries") {
+ parseBinaries();
+ }
else if (m_xml.name() == "CustomData") {
parseCustomData();
}
@@ -216,9 +251,9 @@ void KeePass2XmlReader::parseMemoryProtection()
else if (m_xml.name() == "ProtectNotes") {
m_meta->setProtectNotes(readBool());
}
- else if (m_xml.name() == "AutoEnableVisualHiding") {
+ /*else if (m_xml.name() == "AutoEnableVisualHiding") {
m_meta->setAutoEnableVisualHiding(readBool());
- }
+ }*/
else {
skipCurrentElement();
}
@@ -259,6 +294,37 @@ void KeePass2XmlReader::parseIcon()
}
}
+void KeePass2XmlReader::parseBinaries()
+{
+ Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries");
+
+ while (!m_xml.error() && m_xml.readNextStartElement()) {
+ if (m_xml.name() == "Binary") {
+ QXmlStreamAttributes attr = m_xml.attributes();
+
+ QString id = attr.value("ID").toString();
+
+ QByteArray data;
+ if (attr.value("Compressed").compare("True", Qt::CaseInsensitive) == 0) {
+ data = readCompressedBinary();
+ }
+ else {
+ data = readBinary();
+ }
+
+ if (m_binaryPool.contains(id)) {
+ qWarning("KeePass2XmlReader::parseBinaries: overwriting binary item \"%s\"",
+ qPrintable(id));
+ }
+
+ m_binaryPool.insert(id, data);
+ }
+ else {
+ skipCurrentElement();
+ }
+ }
+}
+
void KeePass2XmlReader::parseCustomData()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData");
@@ -547,7 +613,8 @@ void KeePass2XmlReader::parseEntryString(Entry *entry)
QXmlStreamAttributes attr = m_xml.attributes();
QString value = readString();
- bool isProtected = attr.hasAttribute("Protected") && (attr.value("Protected") == "True");
+ bool isProtected = attr.value("Protected") == "True";
+ bool protectInMemory = attr.value("ProtectInMemory") == "True";
if (isProtected && !value.isEmpty()) {
if (m_randomStream) {
@@ -558,7 +625,7 @@ void KeePass2XmlReader::parseEntryString(Entry *entry)
}
}
- entry->attributes()->set(key, value, isProtected);
+ entry->attributes()->set(key, value, isProtected || protectInMemory);
}
else {
skipCurrentElement();
@@ -576,16 +643,24 @@ void KeePass2XmlReader::parseEntryBinary(Entry *entry)
key = readString();
}
else if (m_xml.name() == "Value") {
- QByteArray value = readBinary();
QXmlStreamAttributes attr = m_xml.attributes();
- bool isProtected = attr.hasAttribute("Protected") && (attr.value("Protected") == "True");
-
- if (isProtected && !value.isEmpty()) {
- m_randomStream->processInPlace(value);
+ if (attr.hasAttribute("Ref")) {
+ m_binaryMap.insertMulti(attr.value("Ref").toString(), qMakePair(entry, key));
+ m_xml.skipCurrentElement();
}
+ else {
+ // format compatbility
+ QByteArray value = readBinary();
+ bool isProtected = attr.hasAttribute("Protected")
+ && (attr.value("Protected") == "True");
+
+ if (isProtected && !value.isEmpty()) {
+ m_randomStream->processInPlace(value);
+ }
- entry->attachments()->set(key, value, isProtected);
+ entry->attachments()->set(key, value);
+ }
}
else {
skipCurrentElement();
@@ -782,6 +857,38 @@ QByteArray KeePass2XmlReader::readBinary()
return QByteArray::fromBase64(readString().toAscii());
}
+QByteArray KeePass2XmlReader::readCompressedBinary()
+{
+ QByteArray rawData = readBinary();
+
+ QBuffer buffer(&rawData);
+ buffer.open(QIODevice::ReadOnly);
+
+ QtIOCompressor compressor(&buffer);
+ compressor.setStreamFormat(QtIOCompressor::GzipFormat);
+ compressor.open(QIODevice::ReadOnly);
+
+ QByteArray result;
+ qint64 readBytes = 0;
+ qint64 readResult;
+ do {
+ result.resize(result.size() + 16384);
+ readResult = compressor.read(result.data() + readBytes, result.size() - readBytes);
+ if (readResult > 0) {
+ readBytes += readResult;
+ }
+ } while (readResult > 0);
+
+ if (readResult == -1) {
+ raiseError(16);
+ return QByteArray();
+ }
+ else {
+ result.resize(static_cast<int>(readBytes));
+ return result;
+ }
+}
+
Group* KeePass2XmlReader::getGroup(const Uuid& uuid)
{
if (uuid.isNull()) {
diff --git a/src/format/KeePass2XmlReader.h b/src/format/KeePass2XmlReader.h
index 4e2cb0872..4f7288b62 100644
--- a/src/format/KeePass2XmlReader.h
+++ b/src/format/KeePass2XmlReader.h
@@ -20,6 +20,8 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QDateTime>
+#include <QtCore/QHash>
+#include <QtCore/QPair>
#include <QtCore/QXmlStreamReader>
#include <QtGui/QColor>
@@ -51,6 +53,7 @@ private:
void parseMemoryProtection();
void parseCustomIcons();
void parseIcon();
+ void parseBinaries();
void parseCustomData();
void parseCustomDataItem();
void parseRoot();
@@ -72,6 +75,7 @@ private:
int readNumber();
Uuid readUuid();
QByteArray readBinary();
+ QByteArray readCompressedBinary();
Group* getGroup(const Uuid& uuid);
Entry* getEntry(const Uuid& uuid);
@@ -85,6 +89,8 @@ private:
Group* m_tmpParent;
QList<Group*> m_groups;
QList<Entry*> m_entries;
+ QHash<QString, QByteArray> m_binaryPool;
+ QHash<QString, QPair<Entry*, QString> > m_binaryMap;
};
#endif // KEEPASSX_KEEPASS2XMLREADER_H
diff --git a/src/format/KeePass2XmlWriter.cpp b/src/format/KeePass2XmlWriter.cpp
index 73d80b782..3c03a0aad 100644
--- a/src/format/KeePass2XmlWriter.cpp
+++ b/src/format/KeePass2XmlWriter.cpp
@@ -22,6 +22,7 @@
#include "core/Metadata.h"
#include "format/KeePass2RandomStream.h"
+#include "streams/QtIOCompressor"
KeePass2XmlWriter::KeePass2XmlWriter()
: m_db(0)
@@ -39,6 +40,8 @@ void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2R
m_meta = db->metadata();
m_randomStream = randomStream;
+ generateIdMap();
+
m_xml.setDevice(device);
m_xml.writeStartDocument("1.0", true);
@@ -60,6 +63,21 @@ void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db, Kee
writeDatabase(&file, db, randomStream);
}
+void KeePass2XmlWriter::generateIdMap()
+{
+ QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
+ int nextId = 0;
+
+ Q_FOREACH (Entry* entry, allEntries) {
+ Q_FOREACH (const QString& key, entry->attachments()->keys()) {
+ QByteArray data = entry->attachments()->value(key);
+ if (!m_idMap.contains(data)) {
+ m_idMap.insert(data, nextId++);
+ }
+ }
+ }
+}
+
void KeePass2XmlWriter::writeMetadata()
{
m_xml.writeStartElement("Meta");
@@ -72,6 +90,7 @@ void KeePass2XmlWriter::writeMetadata()
writeString("DefaultUserName", m_meta->defaultUserName());
writeDateTime("DefaultUserNameChanged", m_meta->defaultUserNameChanged());
writeNumber("MaintenanceHistoryDays", m_meta->maintenanceHistoryDays());
+ writeColor("Color", m_meta->color());
writeDateTime("MasterKeyChanged", m_meta->masterKeyChanged());
writeNumber("MasterKeyChangeRec", m_meta->masterKeyChangeRec());
writeNumber("MasterKeyChangeForce", m_meta->masterKeyChangeForce());
@@ -84,6 +103,9 @@ void KeePass2XmlWriter::writeMetadata()
writeDateTime("EntryTemplatesGroupChanged", m_meta->entryTemplatesGroupChanged());
writeUuid("LastSelectedGroup", m_meta->lastSelectedGroup());
writeUuid("LastTopVisibleGroup", m_meta->lastTopVisibleGroup());
+ writeNumber("HistoryMaxItems", m_meta->historyMaxItems());
+ writeNumber("HistoryMaxSize", m_meta->historyMaxSize());
+ writeBinaries();
writeCustomData();
m_xml.writeEndElement();
@@ -98,7 +120,7 @@ void KeePass2XmlWriter::writeMemoryProtection()
writeBool("ProtectPassword", m_meta->protectPassword());
writeBool("ProtectURL", m_meta->protectUrl());
writeBool("ProtectNotes", m_meta->protectNotes());
- writeBool("AutoEnableVisualHiding", m_meta->autoEnableVisualHiding());
+ // writeBool("AutoEnableVisualHiding", m_meta->autoEnableVisualHiding());
m_xml.writeEndElement();
}
@@ -132,6 +154,43 @@ void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
m_xml.writeEndElement();
}
+void KeePass2XmlWriter::writeBinaries()
+{
+ m_xml.writeStartElement("Binaries");
+
+ QHash<QByteArray, int>::const_iterator i;
+ for (i = m_idMap.constBegin(); i != m_idMap.constEnd(); ++i) {
+ m_xml.writeStartElement("Binary");
+
+ m_xml.writeAttribute("ID", QString::number(i.value()));
+
+ if (m_db->compressionAlgo() == Database::CompressionGZip) {
+ m_xml.writeAttribute("Compressed", "True");
+
+ QBuffer buffer;
+ buffer.open(QIODevice::ReadWrite);
+
+ QtIOCompressor compressor(&buffer);
+ compressor.setStreamFormat(QtIOCompressor::GzipFormat);
+ compressor.open(QIODevice::WriteOnly);
+
+ qint64 bytesWritten = compressor.write(i.key());
+ Q_ASSERT(bytesWritten == i.key().size());
+ compressor.close();
+
+ buffer.seek(0);
+ m_xml.writeCharacters(QString::fromAscii(buffer.readAll().toBase64()));
+ }
+ else {
+ m_xml.writeCharacters(QString::fromAscii(i.key().toBase64()));
+ }
+
+ m_xml.writeEndElement();
+ }
+
+ m_xml.writeEndElement();
+}
+
void KeePass2XmlWriter::writeCustomData()
{
m_xml.writeStartElement("CustomData");
@@ -262,8 +321,7 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
((key == "Password") && m_meta->protectPassword()) ||
((key == "URL") && m_meta->protectUrl()) ||
((key == "Notes") && m_meta->protectNotes()) ||
- entry->attributes()->isProtected(key) ) &&
- m_randomStream;
+ entry->attributes()->isProtected(key) );
writeString("Key", key);
@@ -271,9 +329,15 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
QString value;
if (protect) {
- m_xml.writeAttribute("Protected", "True");
- QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8());
- value = QString::fromAscii(rawData.toBase64());
+ if (m_randomStream) {
+ m_xml.writeAttribute("Protected", "True");
+ QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8());
+ value = QString::fromAscii(rawData.toBase64());
+ }
+ else {
+ m_xml.writeAttribute("ProtectInMemory", "True");
+ value = entry->attributes()->value(key);
+ }
}
else {
value = entry->attributes()->value(key);
@@ -288,23 +352,10 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
Q_FOREACH (const QString& key, entry->attachments()->keys()) {
m_xml.writeStartElement("Binary");
- bool protect = entry->attachments()->isProtected(key) && m_randomStream;
-
writeString("Key", key);
m_xml.writeStartElement("Value");
- QString value;
-
- if (protect) {
- m_xml.writeAttribute("Protected", "True");
- QByteArray rawData = m_randomStream->process(entry->attachments()->value(key));
- value = QString::fromAscii(rawData.toBase64());
- }
- else {
- value = entry->attachments()->value(key);
- }
-
- m_xml.writeCharacters(value);
+ m_xml.writeAttribute("Ref", QString::number(m_idMap[entry->attachments()->value(key)]));
m_xml.writeEndElement();
m_xml.writeEndElement();
diff --git a/src/format/KeePass2XmlWriter.h b/src/format/KeePass2XmlWriter.h
index 044f2fd90..14a5d83a3 100644
--- a/src/format/KeePass2XmlWriter.h
+++ b/src/format/KeePass2XmlWriter.h
@@ -43,10 +43,13 @@ public:
QString errorString();
private:
+ void generateIdMap();
+
void writeMetadata();
void writeMemoryProtection();
void writeCustomIcons();
void writeIcon(const Uuid& uuid, const QImage& icon);
+ void writeBinaries();
void writeCustomData();
void writeCustomDataItem(const QString& key, const QString& value);
void writeRoot();
@@ -75,6 +78,7 @@ private:
Database* m_db;
Metadata* m_meta;
KeePass2RandomStream* m_randomStream;
+ QHash<QByteArray, int> m_idMap;
};
#endif // KEEPASSX_KEEPASS2XMLWRITER_H
diff --git a/tests/TestKeePass2Reader.cpp b/tests/TestKeePass2Reader.cpp
index a0897a74c..39f9e0b1a 100644
--- a/tests/TestKeePass2Reader.cpp
+++ b/tests/TestKeePass2Reader.cpp
@@ -82,8 +82,6 @@ void TestKeePass2Reader::testProtectedStrings()
QCOMPARE(entry->attributes()->value("TestProtected"), QString("ABC"));
QCOMPARE(entry->attributes()->value("TestUnprotected"), QString("DEF"));
- QVERIFY(!db->metadata()->protectTitle());
- QVERIFY(db->metadata()->protectUsername());
QVERIFY(db->metadata()->protectPassword());
QVERIFY(entry->attributes()->isProtected("TestProtected"));
QVERIFY(!entry->attributes()->isProtected("TestUnprotected"));
@@ -92,4 +90,36 @@ void TestKeePass2Reader::testProtectedStrings()
delete reader;
}
+void TestKeePass2Reader::testFormat200()
+{
+ QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format200.kdbx");
+ CompositeKey key;
+ key.addKey(PasswordKey("a"));
+ KeePass2Reader* reader = new KeePass2Reader();
+ Database* db = reader->readDatabase(filename, key);
+ QVERIFY(db);
+ QVERIFY(!reader->hasError());
+
+ QCOMPARE(db->rootGroup()->name(), QString("Format200"));
+ QVERIFY(!db->metadata()->protectTitle());
+ QVERIFY(db->metadata()->protectUsername());
+ QVERIFY(!db->metadata()->protectPassword());
+ QVERIFY(db->metadata()->protectUrl());
+ QVERIFY(!db->metadata()->protectNotes());
+
+ QCOMPARE(db->rootGroup()->entries().size(), 1);
+ Entry* entry = db->rootGroup()->entries().at(0);
+
+ QCOMPARE(entry->title(), QString("Sample Entry"));
+ QCOMPARE(entry->username(), QString("User Name"));
+ QCOMPARE(entry->attachments()->keys().size(), 2);
+ QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("abcdefghijk"));
+ QCOMPARE(entry->attachments()->value("test.txt"), QByteArray("this is a test"));
+
+ QCOMPARE(entry->historyItems().size(), 2);
+ QCOMPARE(entry->historyItems().at(0)->attachments()->keys().size(), 0);
+ QCOMPARE(entry->historyItems().at(1)->attachments()->keys().size(), 1);
+ QCOMPARE(entry->historyItems().at(1)->attachments()->value("myattach.txt"), QByteArray("abcdefghijk"));
+}
+
KEEPASSX_QTEST_CORE_MAIN(TestKeePass2Reader)
diff --git a/tests/TestKeePass2Reader.h b/tests/TestKeePass2Reader.h
index b2a75f66e..27680c855 100644
--- a/tests/TestKeePass2Reader.h
+++ b/tests/TestKeePass2Reader.h
@@ -29,6 +29,7 @@ private Q_SLOTS:
void testNonAscii();
void testCompressed();
void testProtectedStrings();
+ void testFormat200();
};
#endif // KEEPASSX_TESTKEEPASS2READER_H
diff --git a/tests/TestKeePass2Writer.cpp b/tests/TestKeePass2Writer.cpp
index 6e3a25838..903de99be 100644
--- a/tests/TestKeePass2Writer.cpp
+++ b/tests/TestKeePass2Writer.cpp
@@ -46,6 +46,8 @@ void TestKeePass2Writer::initTestCase()
entry->setUuid(Uuid::random());
entry->attributes()->set("test", "protectedTest", true);
QVERIFY(entry->attributes()->isProtected("test"));
+ entry->attachments()->set("myattach.txt", QByteArray("this is an attachment"));
+ entry->attachments()->set("aaa.txt", QByteArray("also an attachment"));
entry->setGroup(group);
Group* groupNew = new Group();
groupNew->setUuid(Uuid::random());
@@ -83,4 +85,12 @@ void TestKeePass2Writer::testProtectedAttributes()
QCOMPARE(entry->attributes()->isProtected("test"), true);
}
+void TestKeePass2Writer::testAttachments()
+{
+ Entry* entry = m_dbTest->rootGroup()->entries().at(0);
+ QCOMPARE(entry->attachments()->keys().size(), 2);
+ QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("this is an attachment"));
+ QCOMPARE(entry->attachments()->value("aaa.txt"), QByteArray("also an attachment"));
+}
+
KEEPASSX_QTEST_CORE_MAIN(TestKeePass2Writer)
diff --git a/tests/TestKeePass2Writer.h b/tests/TestKeePass2Writer.h
index e0e333a89..a18433cd5 100644
--- a/tests/TestKeePass2Writer.h
+++ b/tests/TestKeePass2Writer.h
@@ -30,6 +30,7 @@ private Q_SLOTS:
void initTestCase();
void testBasic();
void testProtectedAttributes();
+ void testAttachments();
private:
Database* m_dbOrg;
diff --git a/tests/TestKeePass2XmlReader.cpp b/tests/TestKeePass2XmlReader.cpp
index 146dad65b..b32e45ff1 100644
--- a/tests/TestKeePass2XmlReader.cpp
+++ b/tests/TestKeePass2XmlReader.cpp
@@ -84,12 +84,15 @@ void TestKeePass2XmlReader::testMetadata()
QCOMPARE(m_db->metadata()->defaultUserName(), QString("DEFUSERNAME"));
QCOMPARE(m_db->metadata()->defaultUserNameChanged(), genDT(2010, 8, 8, 17, 27, 45));
QCOMPARE(m_db->metadata()->maintenanceHistoryDays(), 127);
+ QCOMPARE(m_db->metadata()->color(), QColor(0xff, 0xef, 0x00));
+ QCOMPARE(m_db->metadata()->masterKeyChanged(), genDT(2012, 4, 5, 17, 9, 34));
+ QCOMPARE(m_db->metadata()->masterKeyChangeRec(), 101);
+ QCOMPARE(m_db->metadata()->masterKeyChangeForce(), -1);
QCOMPARE(m_db->metadata()->protectTitle(), false);
QCOMPARE(m_db->metadata()->protectUsername(), true);
QCOMPARE(m_db->metadata()->protectPassword(), false);
QCOMPARE(m_db->metadata()->protectUrl(), true);
QCOMPARE(m_db->metadata()->protectNotes(), false);
- QCOMPARE(m_db->metadata()->autoEnableVisualHiding(), false);
QCOMPARE(m_db->metadata()->recycleBinEnabled(), true);
QVERIFY(m_db->metadata()->recycleBin() != 0);
QCOMPARE(m_db->metadata()->recycleBin()->name(), QString("Recycle Bin"));
@@ -99,6 +102,8 @@ void TestKeePass2XmlReader::testMetadata()
QVERIFY(m_db->metadata()->lastSelectedGroup() != 0);
QCOMPARE(m_db->metadata()->lastSelectedGroup()->name(), QString("NewDatabase"));
QVERIFY(m_db->metadata()->lastTopVisibleGroup() == m_db->metadata()->lastSelectedGroup());
+ QCOMPARE(m_db->metadata()->historyMaxItems(), -1);
+ QCOMPARE(m_db->metadata()->historyMaxSize(), 5242880);
}
void TestKeePass2XmlReader::testCustomIcons()
@@ -199,11 +204,13 @@ void TestKeePass2XmlReader::testEntry1()
const Entry* entry = m_db->rootGroup()->entries().at(0);
QCOMPARE(entry->uuid().toBase64(), QString("+wSUOv6qf0OzW8/ZHAs2sA=="));
+ QCOMPARE(entry->historyItems().size(), 2);
QCOMPARE(entry->iconNumber(), 0);
QCOMPARE(entry->iconUuid(), Uuid());
QVERIFY(!entry->foregroundColor().isValid());
QVERIFY(!entry->backgroundColor().isValid());
QCOMPARE(entry->overrideUrl(), QString(""));
+ QCOMPARE(entry->tags(), QString("a b c"));
const TimeInfo ti = entry->timeInfo();
QCOMPARE(ti.lastModificationTime(), genDT(2010, 8, 25, 16, 19, 25));
@@ -216,14 +223,19 @@ void TestKeePass2XmlReader::testEntry1()
QList<QString> attrs = entry->attributes()->keys();
QCOMPARE(entry->attributes()->value("Notes"), QString("Notes"));
+ QVERIFY(!entry->attributes()->isProtected("Notes"));
QVERIFY(attrs.removeOne("Notes"));
QCOMPARE(entry->attributes()->value("Password"), QString("Password"));
+ QVERIFY(!entry->attributes()->isProtected("Password"));
QVERIFY(attrs.removeOne("Password"));
QCOMPARE(entry->attributes()->value("Title"), QString("Sample Entry 1"));
+ QVERIFY(!entry->attributes()->isProtected("Title"));
QVERIFY(attrs.removeOne("Title"));
QCOMPARE(entry->attributes()->value("URL"), QString(""));
+ QVERIFY(entry->attributes()->isProtected("URL"));
QVERIFY(attrs.removeOne("URL"));
QCOMPARE(entry->attributes()->value("UserName"), QString("User Name"));
+ QVERIFY(entry->attributes()->isProtected("UserName"));
QVERIFY(attrs.removeOne("UserName"));
QVERIFY(attrs.isEmpty());
@@ -233,7 +245,13 @@ void TestKeePass2XmlReader::testEntry1()
QCOMPARE(entry->password(), entry->attributes()->value("Password"));
QCOMPARE(entry->notes(), entry->attributes()->value("Notes"));
- QCOMPARE(entry->attachments()->keys().size(), 0);
+ QCOMPARE(entry->attachments()->keys().size(), 1);
+ QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("abcdefghijk"));
+ QCOMPARE(entry->historyItems().at(0)->attachments()->keys().size(), 1);
+ QCOMPARE(entry->historyItems().at(0)->attachments()->value("myattach.txt"), QByteArray("0123456789"));
+ QCOMPARE(entry->historyItems().at(1)->attachments()->keys().size(), 1);
+ QCOMPARE(entry->historyItems().at(1)->attachments()->value("myattach.txt"), QByteArray("abcdefghijk"));
+
QCOMPARE(entry->autoTypeEnabled(), false);
QCOMPARE(entry->autoTypeObfuscation(), 0);
QCOMPARE(entry->defaultAutoTypeSequence(), QString(""));
@@ -254,6 +272,7 @@ void TestKeePass2XmlReader::testEntry2()
QCOMPARE(entry->foregroundColor(), QColor(255, 0, 0));
QCOMPARE(entry->backgroundColor(), QColor(255, 255, 0));
QCOMPARE(entry->overrideUrl(), QString("http://override.net/"));
+ QCOMPARE(entry->tags(), QString(""));
const TimeInfo ti = entry->timeInfo();
QCOMPARE(ti.usageCount(), 7);
@@ -276,7 +295,7 @@ void TestKeePass2XmlReader::testEntry2()
QVERIFY(attrs.isEmpty());
QCOMPARE(entry->attachments()->keys().size(), 1);
- QCOMPARE(QString(entry->attachments()->value("testattach.txt")), QString("42"));
+ QCOMPARE(QString(entry->attachments()->value("myattach.txt")), QString("abcdefghijk"));
QCOMPARE(entry->autoTypeEnabled(), true);
QCOMPARE(entry->autoTypeObfuscation(), 1);
diff --git a/tests/data/Compressed.kdbx b/tests/data/Compressed.kdbx
index fd3108b93..af12d73aa 100644
--- a/tests/data/Compressed.kdbx
+++ b/tests/data/Compressed.kdbx
Binary files differ
diff --git a/tests/data/Format200.kdbx b/tests/data/Format200.kdbx
new file mode 100644
index 000000000..c3b26cdbc
--- /dev/null
+++ b/tests/data/Format200.kdbx
Binary files differ
diff --git a/tests/data/NewDatabase.xml b/tests/data/NewDatabase.xml
index 26b245f24..d07b83693 100644
--- a/tests/data/NewDatabase.xml
+++ b/tests/data/NewDatabase.xml
@@ -9,13 +9,16 @@
<DefaultUserName>DEFUSERNAME</DefaultUserName>
<DefaultUserNameChanged>2010-08-08T17:27:45Z</DefaultUserNameChanged>
<MaintenanceHistoryDays>127</MaintenanceHistoryDays>
+ <Color>#FFEF00</Color>
+ <MasterKeyChanged>2012-04-05T17:09:34Z</MasterKeyChanged>
+ <MasterKeyChangeRec>101</MasterKeyChangeRec>
+ <MasterKeyChangeForce>-1</MasterKeyChangeForce>
<MemoryProtection>
<ProtectTitle>False</ProtectTitle>
<ProtectUserName>True</ProtectUserName>
<ProtectPassword>False</ProtectPassword>
<ProtectURL>True</ProtectURL>
<ProtectNotes>False</ProtectNotes>
- <AutoEnableVisualHiding>False</AutoEnableVisualHiding>
</MemoryProtection>
<CustomIcons>
<Icon>
@@ -30,6 +33,12 @@
<EntryTemplatesGroupChanged>2010-08-08T17:24:19Z</EntryTemplatesGroupChanged>
<LastSelectedGroup>lmU+9n0aeESKZvcEze+bRg==</LastSelectedGroup>
<LastTopVisibleGroup>lmU+9n0aeESKZvcEze+bRg==</LastTopVisibleGroup>
+ <HistoryMaxItems>-1</HistoryMaxItems>
+ <HistoryMaxSize>5242880</HistoryMaxSize>
+ <Binaries>
+ <Binary ID="0" Compressed="True">H4sIAAAAAAAEAO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3mkuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrIHz9+fB8/InZ29+7t3//0wcHD/wfGx4SmCgAAAA==</Binary>
+ <Binary ID="1" Compressed="True">H4sIAAAAAAAEAO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3mkuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrIHz9+fB8/IrLJdJafX8yLn377/wCfD1fOCwAAAA==</Binary>
+ </Binaries>
<CustomData>
<Item>
<Key>A Sample Test Key</Key>
@@ -67,6 +76,7 @@
<ForegroundColor />
<BackgroundColor />
<OverrideURL />
+ <Tags>a b c</Tags>
<Times>
<LastModificationTime>2010-08-25T16:19:25Z</LastModificationTime>
<CreationTime>2010-08-25T16:13:54Z</CreationTime>
@@ -90,12 +100,16 @@
</String>
<String>
<Key>URL</Key>
- <Value />
+ <Value ProtectInMemory="True" />
</String>
<String>
<Key>UserName</Key>
- <Value>User Name</Value>
+ <Value ProtectInMemory="True">User Name</Value>
</String>
+ <Binary>
+ <Key>myattach.txt</Key>
+ <Value Ref="1" />
+ </Binary>
<AutoType>
<Enabled>False</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
@@ -111,6 +125,7 @@
<ForegroundColor />
<BackgroundColor />
<OverrideURL />
+ <Tags />
<Times>
<LastModificationTime>2010-08-25T16:13:54Z</LastModificationTime>
<CreationTime>2010-08-25T16:13:54Z</CreationTime>
@@ -140,6 +155,10 @@
<Key>UserName</Key>
<Value>User Name</Value>
</String>
+ <Binary>
+ <Key>myattach.txt</Key>
+ <Value Ref="0" />
+ </Binary>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
@@ -155,6 +174,7 @@
<ForegroundColor />
<BackgroundColor />
<OverrideURL />
+ <Tags />
<Times>
<LastModificationTime>2010-08-25T16:15:43Z</LastModificationTime>
<CreationTime>2010-08-25T16:13:54Z</CreationTime>
@@ -184,6 +204,10 @@
<Key>UserName</Key>
<Value>User Name</Value>
</String>
+ <Binary>
+ <Key>myattach.txt</Key>
+ <Value Ref="1" />
+ </Binary>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
@@ -202,6 +226,7 @@
<ForegroundColor>#FF0000</ForegroundColor>
<BackgroundColor>#FFFF00</BackgroundColor>
<OverrideURL>http://override.net/</OverrideURL>
+ <Tags />
<Times>
<LastModificationTime>2010-08-25T16:20:24Z</LastModificationTime>
<CreationTime>2010-08-25T16:15:45Z</CreationTime>
@@ -233,15 +258,15 @@
</String>
<String>
<Key>URL</Key>
- <Value>http://www.keepassx.org/</Value>
+ <Value ProtectInMemory="True">http://www.keepassx.org/</Value>
</String>
<String>
<Key>UserName</Key>
- <Value>notDEFUSERNAME</Value>
+ <Value ProtectInMemory="True">notDEFUSERNAME</Value>
</String>
<Binary>
- <Key>testattach.txt</Key>
- <Value>NDI=</Value>
+ <Key>myattach.txt</Key>
+ <Value Ref="1" />
</Binary>
<AutoType>
<Enabled>True</Enabled>
@@ -322,6 +347,7 @@
<ForegroundColor />
<BackgroundColor />
<OverrideURL />
+ <Tags />
<Times>
<LastModificationTime>2010-08-25T16:21:15Z</LastModificationTime>
<CreationTime>2010-08-25T16:20:45Z</CreationTime>
@@ -384,6 +410,7 @@
<ForegroundColor />
<BackgroundColor />
<OverrideURL />
+ <Tags />
<Times>
<LastModificationTime>2010-08-25T16:20:32Z</LastModificationTime>
<CreationTime>2010-08-25T16:20:27Z</CreationTime>
diff --git a/tests/data/NonAscii.kdbx b/tests/data/NonAscii.kdbx
index d4e4ec547..285831f8e 100644
--- a/tests/data/NonAscii.kdbx
+++ b/tests/data/NonAscii.kdbx
Binary files differ
diff --git a/tests/data/NonAscii.kdbx.key b/tests/data/NonAscii.kdbx.key
new file mode 100644
index 000000000..3a3610971
--- /dev/null
+++ b/tests/data/NonAscii.kdbx.key
@@ -0,0 +1 @@
+Δöض
diff --git a/tests/data/ProtectedStrings.kdbx b/tests/data/ProtectedStrings.kdbx
index 9b903f7ae..2614097fc 100644
--- a/tests/data/ProtectedStrings.kdbx
+++ b/tests/data/ProtectedStrings.kdbx
Binary files differ