From 8acd6f74d8c4ef396787c2e7b1f4ef91b5670a1e Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Sat, 21 Apr 2012 16:45:46 +0200 Subject: [PATCH] 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. --- src/core/EntryAttachments.cpp | 26 +------ src/core/EntryAttachments.h | 4 +- src/core/Group.cpp | 19 +++++ src/core/Group.h | 1 + src/core/Metadata.cpp | 42 ++++++++-- src/core/Metadata.h | 16 +++- src/format/KeePass2.h | 3 +- src/format/KeePass2Reader.cpp | 5 +- src/format/KeePass2XmlReader.cpp | 127 ++++++++++++++++++++++++++++--- src/format/KeePass2XmlReader.h | 6 ++ src/format/KeePass2XmlWriter.cpp | 91 +++++++++++++++++----- src/format/KeePass2XmlWriter.h | 4 + tests/TestKeePass2Reader.cpp | 34 ++++++++- tests/TestKeePass2Reader.h | 1 + tests/TestKeePass2Writer.cpp | 10 +++ tests/TestKeePass2Writer.h | 1 + tests/TestKeePass2XmlReader.cpp | 25 +++++- tests/data/Compressed.kdbx | Bin 1870 -> 1918 bytes tests/data/Format200.kdbx | Bin 0 -> 2302 bytes tests/data/NewDatabase.xml | 41 ++++++++-- tests/data/NonAscii.kdbx | Bin 2574 -> 2798 bytes tests/data/NonAscii.kdbx.key | 1 + tests/data/ProtectedStrings.kdbx | Bin 1950 -> 1934 bytes 23 files changed, 376 insertions(+), 81 deletions(-) create mode 100644 tests/data/Format200.kdbx create mode 100644 tests/data/NonAscii.kdbx.key 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 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 m_attachments; - QSet 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& Group::entries() const return m_entries; } +QList Group::entriesRecursive(bool includeHistoryItems) +{ + QList 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& children() const; QList entries(); const QList& entries() const; + QList 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 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 #include +#include #include #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 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 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 m_customIcons; @@ -132,6 +140,8 @@ private: QDateTime m_masterKeyChanged; int m_masterKeyChangeRec; int m_masterKeyChangeForce; + int m_historyMaxItems; + int m_historyMaxSize; QHash 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 #include #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 poolKeys = m_binaryPool.keys().toSet(); + QSet entryKeys = m_binaryMap.keys().toSet(); + QSet unmappedKeys = entryKeys - poolKeys; + QSet 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 >::const_iterator i; + for (i = m_binaryMap.constBegin(); i != m_binaryMap.constEnd(); ++i) { + QPair 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"); - entry->attachments()->set(key, value, isProtected); + if (isProtected && !value.isEmpty()) { + m_randomStream->processInPlace(value); + } + + 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(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 #include +#include +#include #include #include @@ -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 m_groups; QList m_entries; + QHash m_binaryPool; + QHash > 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 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::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 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 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 fd3108b934a6b1b8c2ebca783c146183fb287b0c..af12d73aaac7ddcdc8b8e00dec5b15be57df659f 100644 GIT binary patch delta 1911 zcmV--2Z;F24*m`c1KFaQXZuUF000A#3m$)Kv}(W^viTg(ai~F8#=q4pKFWuci1@2G zMW>zx^lpR&AOMs>u?foiOtsv0l)gBKGX zw?KgKOxJ3f5xor$6lcqEu-r{zWxjt{MLBG@lWd!xIU8%$5BH&sMy3d!5Y<=qc3|7f zGW-mZ5sAr~KtZ(5u(qHIe1t@AkFmB-ZagJS~sa z55Yb}OoBN|(@4Ug+C$&k=;J*w_Y)$SQQg5PTixiY(RNLU8^h!vcmdXf6U%=BQ%=%t z5WF0%B->iZ^0jH-sUpg6rWlE@z)c)r!)JSwGsc}8(-}t({UNzi!OCXlzr)d2-y1!) zriCFvAC8Z=mBxx$$08oY8%oY((nPTW*+qYOr<38Gva5#VfJs{~vjjIGeGFGUqk|62 zCo=93{m^jr^~j$M_=d72P5OWMve5R=b$XQnP%GyG3^wVK09|;NPqsn3#4XbS@0l^?@t&RvedCOa9|fEBe?cUJmWjCTqd-Id3kL; zn0XT8s|{YFw#l?0j7NVSr)Um1-s5hr)Z>IQpE1)7m zWk07}T-!8Ig*R=%Moo`jAR-G~KJgR^qtyjt`oN*$U{%;I(;0pOlx|yoTi+>S>rpHI z8uJw=#bpZq6X|~{wazHlkQ@#!1QWZ+AvrXuDJMp8v5Y_sxy5){^uDjcYjK#K(k6|N zi-cMrW4guLL(8^0v-#00-=!I~Xyb5uSLyTGbk2z;7`O9x%uL6=JpBtJut=`uNhiQW zbkEM_vRn{k554dP21~?~)+CXv05#Zr8(|n>c#-?^8Vi4?NlWdP55Rth9USO1FHM^S z==ZirgRZ7o+Y8=6X~dbjnuk>QuWi}HHBLzPvdKkVQ?G|XA4Y-^i|B*lv?swGtSut$ zSj<*wEba#@;P6I!&t;v}~+GUAxgE$l2bP}$#lGOug@2ge(7Z24_ zR`<21e+)r+;#ul4tjrOPrTXs)wxpusbcLYCsdgCE}<$; zu9FDW*gjQ+VBA$?>uk$3W3gG~bL{mg8Oubcpw zsz`r+*He|dejCin!6K(*5KRMaE|siGCcZJ;%5mEVM8vW8RA#SK3J!xL3@%>bwL&D% z$OXCs;_ZHQ7{#Wg+&7B_|9vZ&Isw5GEPE*WT%NY?m?fqHvB1anfvW$MLT4X#AAbc0d$NarKh(x)T_fR`?*1B#3jap-a zT+|=RB6-{AhsDtJ+-SJ8=;b`V3k-N6nBh1H>~r|)Jlvl&r|sMPb{g;1Zjz7@B`pf} x`;l`CXN3)1VX_I#SEcC|s|%*0c%$cb?nc}atEk2mIWymJvh{@*r)!;PKu$J>q&WZp delta 1863 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

-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 Yg2A&`3WQ?A1AFO!o&7-^>ZzDyXeerT5dZ)H literal 0 HcmV?d00001 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 @@ DEFUSERNAME 2010-08-08T17:27:45Z 127 + #FFEF00 + 2012-04-05T17:09:34Z + 101 + -1 False True False True False - False @@ -30,6 +33,12 @@ 2010-08-08T17:24:19Z lmU+9n0aeESKZvcEze+bRg== lmU+9n0aeESKZvcEze+bRg== + -1 + 5242880 + + H4sIAAAAAAAEAO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3mkuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrIHz9+fB8/InZ29+7t3//0wcHD/wfGx4SmCgAAAA== + H4sIAAAAAAAEAO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3mkuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrIHz9+fB8/IrLJdJafX8yLn377/wCfD1fOCwAAAA== + A Sample Test Key @@ -67,6 +76,7 @@ + a b c 2010-08-25T16:19:25Z 2010-08-25T16:13:54Z @@ -90,12 +100,16 @@ URL - + UserName - User Name + User Name + + myattach.txt + + False 0 @@ -111,6 +125,7 @@ + 2010-08-25T16:13:54Z 2010-08-25T16:13:54Z @@ -140,6 +155,10 @@ UserName User Name + + myattach.txt + + True 0 @@ -155,6 +174,7 @@ + 2010-08-25T16:15:43Z 2010-08-25T16:13:54Z @@ -184,6 +204,10 @@ UserName User Name + + myattach.txt + + True 0 @@ -202,6 +226,7 @@ #FF0000 #FFFF00 http://override.net/ + 2010-08-25T16:20:24Z 2010-08-25T16:15:45Z @@ -233,15 +258,15 @@ URL - http://www.keepassx.org/ + http://www.keepassx.org/ UserName - notDEFUSERNAME + notDEFUSERNAME - testattach.txt - NDI= + myattach.txt + True @@ -322,6 +347,7 @@ + 2010-08-25T16:21:15Z 2010-08-25T16:20:45Z @@ -384,6 +410,7 @@ + 2010-08-25T16:20:32Z 2010-08-25T16:20:27Z diff --git a/tests/data/NonAscii.kdbx b/tests/data/NonAscii.kdbx index d4e4ec547a9333194007156559cd3d9aafccf6ea..285831f8eff0a5c0eb6c2a141b4eedb19b4f57fb 100644 GIT binary patch literal 2798 zcmV z0YKF1ONg60000401XNa3VubbWUxl#ZRe!COVIYM0TW7E6jv3m(6m3{?O!lU ze}=_dUg*u>7#F<>buf@|TUiY-T3K#qOV(p1wCiOVpw<}aQ|lMR z{(+_{@~2$ViD~0&t&HEDI@l<;VpaJ#9|G!Ekq*ct4^1|gDFdXDE&vP`hrmZW-DKuV zD5nmI8Ur4OxF2Y3wFC{4ouA1AIZJx%D}PC-7X^L6;%^y;cK*IU-%$dAv{s81`bE?J0$hG=BQT0|O63z93(k;PmlTa5N`T=(`e zBIq|w{5f+_Pf274!Mo`XDe3Z2w?qBzknD$?*zg8|GNo}D?>{Z)UbNPXs{Q-t?@lLS z(<6P`?KdMxA~YRqPao3HH*_ZOM#B{KOd1fkfkB%i5z*<T&l>5N&B=k2J*_db5DD?>x`wcb+G|gG zPasWy+np}f)dR|ayuJ)L21wYD3jH25x34D!?Y&x6L{HQvh4m3CaPX+9y(z3(K)c@3 zgo7tDW5y;y2Hm;0h|l<$%Kn3kAMh6D=tzHL78gE#APA-g?VDTrsyVf))DfcL4Dxh4 z*-(E#Ps_!1j zt(hPjC0&0~fi>Ex!8WB48YJ!gPdhGK=WsBst8xlNTkQ6fv^`D_KrxOSFs#&c-B)BC1(NP_DE?L>41<|WJ zUgZp$-|vgx4_vuV1w0ZmAoLmL4!)i@rd0+Z7~_%#?mq{q+BhjoUuK=}17nv#-RASf zKRO&^$5k+&HI5*`PljcKX1-BwR+R;Vy|6G1$0($5$E#mzjRt1m$O>bn2z?#sj*>E) z@o%#&0_>_e1g%Y`{2-`JNUT^q6$)~6fDC|72^YF(h`f&x7;UgKQd9$-_<2)9k9G5>NcRnm*YnI^FLOL1m0?n<8+GY zQA91{M`ta8xK(}$vWm>ho+FJH!GXaLPV%W4e#&UzM2VusBol1UmITUu!~{5|7PTO| zMeJCR25Ay>P$&niLp~wQYQo)#C79m_(3Kdr=-tYPuiEN5kV}=#VP7y4t9P%-5Mk~b zk@h@;itGjd)De1chReUOw*@$PI*NBg`2U0RqTaZ_`oa7s6ckYa9|Nw8)3k>bQyh>M zsJ@f-R`v=uRDdPl}0r^WTA(Q(|kWRwE{;s5jMW>pv_^c8P4A*cUX5Z!+er`F@yaQg%+`)8%wH>K(q&~ zq)%9mir%=4V&}I!OiLEwWhXQZ84`IdHRQ)dYmSXngLdJtDmyw@J9QHXPFyoVp;}C@ z#=d#XhonrPYE}*53f*GGuz+xDYB;R#*mk8iY(Gxy_+h=~uoB2e-;>rl2j4M+Ei+0M zA<>{?KZ!7oZMX0p-fmve_RA*UCS(pI$^bm;Fh6Epy1XfzToz{yKoUG4So3L;JSoF} z@iEX)pZ-?ex_zB8tW?J_1d|LV#c-_ z=9x)jA$3ErYNiB>$Xq)oC?R&uiBE$`6eZYT0^*CM>S_5P%2%qd=%<`sYry5dHqf&- zVtr{dCwMSxilp~lB51ku7svwXviWPHD^OSL@R1KT7js71PmTaOY~6s!^ScP@Jd%AE z!yDFp^-#LU&AgnPc=Im>SI%haAs#mal-megvjDA}?USoa`7aT$RHu}4i+oAg+Io*9 ze$xApiG0}3oXLvD(7Rjjw=r3c1YOL#@k;Z%l44G@ydIOHvHIad!0XU+7gMGZLO5}C zQ8xMf1Sm{!!_0rAnfCBM#U2hrm?wDYN^$g)HkVl*-I`o&_!W@L6)4=e=WUMz; zHNU^mzlsc@t**Y{2)ro$a8ZR80lmPZ(BNz~G8PA=*cLPXAiqWBBH7pRPWB)sRCE(* z2=&dTWWyl1YM$%69_aO+Xj;oGo(^GlCb5E_kMr6>g-|Opd$*%4$~}b%4QJMD;E%y` zmtfvq*lNY8;-3xQd)L6NgwquJ)DbET)SE*0FfMV4>?^XbWg-+5u9k6}8^#p@O%=Wv z{B|&)4N%gS6B?Voz$U3l)je30@SUHO>U7P^ZK-Tpi#o@1aaY)AVY3 zI&Nn(-G7;%Qp_UZ9Oda}%W$do_0~w!d=?45Nd+~W`u{}@-ZGZci3wttS+7&+Tee^1 z+Ehu37>OP<-5G?y$)JcLdX7b)XTj4tTI){A=AIiW;>!Y&Arv0%j5UGAoM!aS7cZvS zkh|*y#Pn~xTwqK~K?X?7>Su*r3TaW-FMe(Fvl@A&0n(x}U;xDF-}PY7?R#Pb%TSw< zZHs5%B9O>-=09HOV2(0QqCXtxq}z8E&BVb@K5Lq=%myJC=P7J?$`TAFHgaq*X#i AO#lD@ delta 2572 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 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 9b903f7aeb2852a6111451768dd7ebb04f0a335e..2614097fc2bb8f60086ad3fae1e4daabcbf39296 100644 GIT binary patch delta 1927 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