diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 0ca0cb2879..bd99ae1342 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -168,7 +168,7 @@ QImage Entry::icon() const Q_ASSERT(database()); if (database()) { - return database()->metadata()->customIcon(m_data.customIcon); + return database()->metadata()->customIcon(m_data.customIcon).image; } else { return QImage(); } diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 03ab7fec30..211e2cef17 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -135,7 +135,7 @@ QImage Group::icon() const } else { Q_ASSERT(m_db); if (m_db) { - return m_db->metadata()->customIcon(m_data.customIcon); + return m_db->metadata()->customIcon(m_data.customIcon).image; } else { return QImage(); } diff --git a/src/core/Merger.cpp b/src/core/Merger.cpp index a5f532af2d..556836b991 100644 --- a/src/core/Merger.cpp +++ b/src/core/Merger.cpp @@ -611,7 +611,7 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context) for (const auto& iconUuid : sourceMetadata->customIconsOrder()) { if (!targetMetadata->hasCustomIcon(iconUuid)) { - QImage customIcon = sourceMetadata->customIcon(iconUuid); + QImage customIcon = sourceMetadata->customIcon(iconUuid).image; targetMetadata->addCustomIcon(iconUuid, customIcon); changes << tr("Adding missing icon %1").arg(QString::fromLatin1(iconUuid.toRfc4122().toHex())); } diff --git a/src/core/Metadata.cpp b/src/core/Metadata.cpp index f99f424993..e09a0f35bd 100644 --- a/src/core/Metadata.cpp +++ b/src/core/Metadata.cpp @@ -175,7 +175,7 @@ bool Metadata::protectNotes() const return m_data.protectNotes; } -QImage Metadata::customIcon(const QUuid& uuid) const +Metadata::CustomIconData Metadata::customIcon(const QUuid& uuid) const { return m_customIconsRaw.value(uuid); } @@ -357,12 +357,12 @@ void Metadata::setProtectNotes(bool value) set(m_data.protectNotes, value); } -void Metadata::addCustomIcon(const QUuid& uuid, const QImage& image) +void Metadata::addCustomIcon(const QUuid& uuid, const QImage& image, const QString& name, const QDateTime& lastModified) { Q_ASSERT(!uuid.isNull()); Q_ASSERT(!m_customIconsRaw.contains(uuid)); - m_customIconsRaw[uuid] = image; + m_customIconsRaw[uuid] = {image, name, lastModified}; // remove all uuids to prevent duplicates in release mode m_customIconsOrder.removeAll(uuid); m_customIconsOrder.append(uuid); @@ -393,7 +393,7 @@ void Metadata::removeCustomIcon(const QUuid& uuid) Q_ASSERT(m_customIconsRaw.contains(uuid)); // Remove hash record only if this is the same uuid - QByteArray hash = hashImage(m_customIconsRaw[uuid]); + QByteArray hash = hashImage(m_customIconsRaw[uuid].image); if (m_customIconsHashes.contains(hash) && m_customIconsHashes[hash] == uuid) { m_customIconsHashes.remove(hash); } @@ -402,6 +402,7 @@ void Metadata::removeCustomIcon(const QUuid& uuid) m_customIconsRaw.remove(uuid); m_customIconsOrder.removeAll(uuid); Q_ASSERT(m_customIconsRaw.count() == m_customIconsOrder.count()); + dynamic_cast(parent())->addDeletedObject(uuid); emitModified(); } @@ -417,7 +418,7 @@ void Metadata::copyCustomIcons(const QSet& iconList, const Metadata* othe Q_ASSERT(otherMetadata->hasCustomIcon(uuid)); if (!hasCustomIcon(uuid) && otherMetadata->hasCustomIcon(uuid)) { - addCustomIcon(uuid, otherMetadata->customIcon(uuid)); + addCustomIcon(uuid, otherMetadata->customIcon(uuid).image); } } } diff --git a/src/core/Metadata.h b/src/core/Metadata.h index 51276ec617..26aab9d3ad 100644 --- a/src/core/Metadata.h +++ b/src/core/Metadata.h @@ -60,6 +60,13 @@ class Metadata : public ModifiableObject bool protectNotes; }; + struct CustomIconData + { + QImage image; + QString name; + QDateTime lastModified; + }; + void init(); void clear(); @@ -78,7 +85,7 @@ class Metadata : public ModifiableObject bool protectPassword() const; bool protectUrl() const; bool protectNotes() const; - QImage customIcon(const QUuid& uuid) const; + CustomIconData customIcon(const QUuid& uuid) const; bool hasCustomIcon(const QUuid& uuid) const; QPixmap customIconPixmap(const QUuid& uuid, IconSize size = IconSize::Default) const; QHash customIconsPixmaps(IconSize size = IconSize::Default) const; @@ -117,7 +124,8 @@ class Metadata : public ModifiableObject void setProtectPassword(bool value); void setProtectUrl(bool value); void setProtectNotes(bool value); - void addCustomIcon(const QUuid& uuid, const QImage& image); + void + addCustomIcon(const QUuid& uuid, const QImage& image, const QString& name = {}, const QDateTime& lastModified = {}); void removeCustomIcon(const QUuid& uuid); void copyCustomIcons(const QSet& iconList, const Metadata* otherMetadata); QUuid findCustomIcon(const QImage& candidate); @@ -153,7 +161,7 @@ class Metadata : public ModifiableObject MetadataData m_data; QHash m_customIcons; - QHash m_customIconsRaw; + QHash m_customIconsRaw; QList m_customIconsOrder; QHash m_customIconsHashes; diff --git a/src/format/KdbxXmlReader.cpp b/src/format/KdbxXmlReader.cpp index 07b63dfb39..0b2aac83f4 100644 --- a/src/format/KdbxXmlReader.cpp +++ b/src/format/KdbxXmlReader.cpp @@ -347,6 +347,8 @@ void KdbxXmlReader::parseIcon() QUuid uuid; QImage icon; + QString name; + QDateTime lastModified; bool uuidSet = false; bool iconSet = false; @@ -357,6 +359,10 @@ void KdbxXmlReader::parseIcon() } else if (m_xml.name() == "Data") { icon.loadFromData(readBinary()); iconSet = true; + } else if (m_xml.name() == "Name") { + name = readString(); + } else if (m_xml.name() == "LastModificationTime") { + lastModified = readDateTime(); } else { skipCurrentElement(); } @@ -367,7 +373,7 @@ void KdbxXmlReader::parseIcon() if (m_meta->hasCustomIcon(uuid)) { uuid = QUuid::createUuid(); } - m_meta->addCustomIcon(uuid, icon); + m_meta->addCustomIcon(uuid, icon, name, lastModified); return; } diff --git a/src/format/KdbxXmlWriter.cpp b/src/format/KdbxXmlWriter.cpp index 6a266dafdd..d870f346be 100644 --- a/src/format/KdbxXmlWriter.cpp +++ b/src/format/KdbxXmlWriter.cpp @@ -21,7 +21,6 @@ #include #include "core/Endian.h" -#include "core/Metadata.h" #include "format/KeePass2RandomStream.h" #include "streams/qtiocompressor.h" @@ -162,17 +161,26 @@ void KdbxXmlWriter::writeCustomIcons() m_xml.writeEndElement(); } -void KdbxXmlWriter::writeIcon(const QUuid& uuid, const QImage& icon) +void KdbxXmlWriter::writeIcon(const QUuid& uuid, const Metadata::CustomIconData& iconData) { m_xml.writeStartElement("Icon"); writeUuid("UUID", uuid); + if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) { + if (!iconData.name.isEmpty()) { + writeString("Name", iconData.name); + } + if (iconData.lastModified.isValid()) { + writeDateTime("LastModificationTime", iconData.lastModified); + } + } + QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); // TODO: check !icon.save() - icon.save(&buffer, "PNG"); + iconData.image.save(&buffer, "PNG"); buffer.close(); writeBinary("Data", ba); diff --git a/src/format/KdbxXmlWriter.h b/src/format/KdbxXmlWriter.h index c0774fc6cb..af131a63a1 100644 --- a/src/format/KdbxXmlWriter.h +++ b/src/format/KdbxXmlWriter.h @@ -21,9 +21,9 @@ #include #include "core/Group.h" +#include "core/Metadata.h" class KeePass2RandomStream; -class Metadata; class KdbxXmlWriter { @@ -46,7 +46,7 @@ class KdbxXmlWriter void writeMetadata(); void writeMemoryProtection(); void writeCustomIcons(); - void writeIcon(const QUuid& uuid, const QImage& icon); + void writeIcon(const QUuid& uuid, const Metadata::CustomIconData& iconData); void writeBinaries(); void writeCustomData(const CustomData* customData); void writeCustomDataItem(const QString& key, const QString& value); diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 3158a6c9d4..9c7dc28ba1 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -242,7 +242,7 @@ void EditWidgetIcons::addCustomIconFromFile() auto icon = QImage(filename); if (icon.isNull()) { errornames << filename; - } else if (!addCustomIcon(icon)) { + } else if (!addCustomIcon(icon, QFileInfo(filename).baseName())) { // Icon already exists in database ++numexisting; } @@ -279,7 +279,7 @@ void EditWidgetIcons::addCustomIconFromFile() } } -bool EditWidgetIcons::addCustomIcon(const QImage& icon) +bool EditWidgetIcons::addCustomIcon(const QImage& icon, const QString& name) { bool added = false; if (m_db) { @@ -292,7 +292,7 @@ bool EditWidgetIcons::addCustomIcon(const QImage& icon) QUuid uuid = m_db->metadata()->findCustomIcon(scaledicon); if (uuid.isNull()) { uuid = QUuid::createUuid(); - m_db->metadata()->addCustomIcon(uuid, scaledicon); + m_db->metadata()->addCustomIcon(uuid, scaledicon, name, QDateTime::currentDateTimeUtc()); m_customIconModel->setIcons(m_db->metadata()->customIconsPixmaps(IconSize::Default), m_db->metadata()->customIconsOrder()); added = true; diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index e0e662848c..6e134ced60 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -85,7 +85,7 @@ private slots: void downloadFavicon(); void iconReceived(const QString& url, const QImage& icon); void addCustomIconFromFile(); - bool addCustomIcon(const QImage& icon); + bool addCustomIcon(const QImage& icon, const QString& name = {}); void updateWidgetsDefaultIcons(bool checked); void updateWidgetsCustomIcons(bool checked); void updateRadioButtonDefaultIcons(); diff --git a/src/gui/group/GroupModel.cpp b/src/gui/group/GroupModel.cpp index 9a2b745eaa..49cd45666b 100644 --- a/src/gui/group/GroupModel.cpp +++ b/src/gui/group/GroupModel.cpp @@ -296,7 +296,7 @@ bool GroupModel::dropMimeData(const QMimeData* data, if (sourceDb != targetDb) { QUuid customIcon = entry->iconUuid(); if (!customIcon.isNull() && !targetDb->metadata()->hasCustomIcon(customIcon)) { - targetDb->metadata()->addCustomIcon(customIcon, sourceDb->metadata()->customIcon(customIcon)); + targetDb->metadata()->addCustomIcon(customIcon, sourceDb->metadata()->customIcon(customIcon).image); } // Reset the UUID when moving across db boundary diff --git a/tests/TestDatabase.cpp b/tests/TestDatabase.cpp index 8a05fcbfa3..7604873f5b 100644 --- a/tests/TestDatabase.cpp +++ b/tests/TestDatabase.cpp @@ -28,6 +28,7 @@ #include "core/Tools.h" #include "crypto/Crypto.h" #include "format/KeePass2Writer.h" +#include "gui/Icons.h" #include "keys/PasswordKey.h" #include "util/TemporaryFile.h" @@ -214,3 +215,27 @@ void TestDatabase::testEmptyRecycleBinWithHierarchicalData() writer.writeDatabase(&afterCleanup, db.data()); QVERIFY(afterCleanup.size() < initialSize); } + +void TestDatabase::testCustomIcons() +{ + Database db; + + QUuid uuid1 = QUuid::createUuid(); + QImage img1 = QImage(":/icons/application/scalable/actions/document-close.svg"); + Q_ASSERT(!img1.isNull()); + db.metadata()->addCustomIcon(uuid1, img1); + Metadata::CustomIconData iconData = db.metadata()->customIcon(uuid1); + QCOMPARE(iconData.image, img1); + QVERIFY(iconData.name.isNull()); + QVERIFY(iconData.lastModified.isNull()); + + QUuid uuid2 = QUuid::createUuid(); + QImage img2 = QImage(":/icons/application/scalable/actions/document-new.svg"); + QDateTime date = QDateTime::currentDateTimeUtc(); + db.metadata()->addCustomIcon(uuid2, img2, "Test", date); + iconData = db.metadata()->customIcon(uuid2); + QCOMPARE(iconData.image, img2); + QCOMPARE(iconData.name, QString("Test")); + QCOMPARE(iconData.lastModified, date); +} +git \ No newline at end of file diff --git a/tests/TestDatabase.h b/tests/TestDatabase.h index dc377ef057..5117038495 100644 --- a/tests/TestDatabase.h +++ b/tests/TestDatabase.h @@ -34,6 +34,7 @@ private slots: void testEmptyRecycleBinOnNotCreated(); void testEmptyRecycleBinOnEmpty(); void testEmptyRecycleBinWithHierarchicalData(); + void testCustomIcons(); }; #endif // KEEPASSX_TESTDATABASE_H diff --git a/tests/TestDeletedObjects.cpp b/tests/TestDeletedObjects.cpp index b86902efc6..881aa31624 100644 --- a/tests/TestDeletedObjects.cpp +++ b/tests/TestDeletedObjects.cpp @@ -152,3 +152,15 @@ void TestDeletedObjects::testDatabaseChange() delete group; } + +void TestDeletedObjects::testCustomIconDeletion() +{ + Database db; + QCOMPARE(db.deletedObjects().size(), 0); + + QUuid uuid = QUuid::createUuid(); + db.metadata()->addCustomIcon(uuid, QImage()); + db.metadata()->removeCustomIcon(uuid); + QCOMPARE(db.deletedObjects().size(), 1); + QCOMPARE(db.deletedObjects().at(0).uuid, uuid); +} diff --git a/tests/TestDeletedObjects.h b/tests/TestDeletedObjects.h index 61d864a673..862b34636d 100644 --- a/tests/TestDeletedObjects.h +++ b/tests/TestDeletedObjects.h @@ -34,6 +34,7 @@ private slots: void testDeletedObjectsFromFile(); void testDeletedObjectsFromNewDb(); void testDatabaseChange(); + void testCustomIconDeletion(); }; #endif // KEEPASSX_TESTDELETEDOBJECTS_H diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index 8eb409ed1d..bdf270b5bf 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -338,12 +338,12 @@ void TestGroup::testCopyCustomIcon() group->setParent(dbTarget->rootGroup()); QVERIFY(dbTarget->metadata()->hasCustomIcon(groupIconUuid)); - QCOMPARE(dbTarget->metadata()->customIcon(groupIconUuid), groupIcon); + QCOMPARE(dbTarget->metadata()->customIcon(groupIconUuid).image, groupIcon); QCOMPARE(group->icon(), groupIcon); entry->setGroup(dbTarget->rootGroup()); QVERIFY(dbTarget->metadata()->hasCustomIcon(entryIconUuid)); - QCOMPARE(dbTarget->metadata()->customIcon(entryIconUuid), entryIcon); + QCOMPARE(dbTarget->metadata()->customIcon(entryIconUuid).image, entryIcon); QCOMPARE(entry->icon(), entryIcon); } @@ -469,8 +469,8 @@ void TestGroup::testCopyCustomIcons() QVERIFY(metaTarget->hasCustomIcon(entry1IconOld)); QVERIFY(metaTarget->hasCustomIcon(entry1IconNew)); - QCOMPARE(metaTarget->customIcon(group1Icon).pixel(0, 0), qRgb(1, 2, 3)); - QCOMPARE(metaTarget->customIcon(group2Icon).pixel(0, 0), qRgb(4, 5, 6)); + QCOMPARE(metaTarget->customIcon(group1Icon).image.pixel(0, 0), qRgb(1, 2, 3)); + QCOMPARE(metaTarget->customIcon(group2Icon).image.pixel(0, 0), qRgb(4, 5, 6)); } void TestGroup::testFindEntry() diff --git a/tests/TestKeePass2Format.cpp b/tests/TestKeePass2Format.cpp index bbe9f3c3e3..801ef66dd3 100644 --- a/tests/TestKeePass2Format.cpp +++ b/tests/TestKeePass2Format.cpp @@ -113,7 +113,7 @@ void TestKeePass2Format::testXmlCustomIcons() QCOMPARE(m_xmlDb->metadata()->customIconsOrder().size(), 1); QUuid uuid = QUuid::fromRfc4122(QByteArray::fromBase64("++vyI+daLk6omox4a6kQGA==")); QVERIFY(m_xmlDb->metadata()->hasCustomIcon(uuid)); - QImage icon = m_xmlDb->metadata()->customIcon(uuid); + QImage icon = m_xmlDb->metadata()->customIcon(uuid).image; QCOMPARE(icon.width(), 16); QCOMPARE(icon.height(), 16);