Skip to content

Commit

Permalink
Implement KDBX 4.1 extended custom icons
Browse files Browse the repository at this point in the history
  • Loading branch information
phoerious committed Nov 10, 2021
1 parent b6601f7 commit 81a20fd
Show file tree
Hide file tree
Showing 17 changed files with 88 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/core/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/Group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/Merger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
}
Expand Down
11 changes: 6 additions & 5 deletions src/core/Metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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<Database*>(parent())->addDeletedObject(uuid);
emitModified();
}

Expand All @@ -417,7 +418,7 @@ void Metadata::copyCustomIcons(const QSet<QUuid>& 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);
}
}
}
Expand Down
14 changes: 11 additions & 3 deletions src/core/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ class Metadata : public ModifiableObject
bool protectNotes;
};

struct CustomIconData
{
QImage image;
QString name;
QDateTime lastModified;
};

void init();
void clear();

Expand All @@ -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<QUuid, QPixmap> customIconsPixmaps(IconSize size = IconSize::Default) const;
Expand Down Expand Up @@ -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<QUuid>& iconList, const Metadata* otherMetadata);
QUuid findCustomIcon(const QImage& candidate);
Expand Down Expand Up @@ -153,7 +161,7 @@ class Metadata : public ModifiableObject
MetadataData m_data;

QHash<QUuid, QIcon> m_customIcons;
QHash<QUuid, QImage> m_customIconsRaw;
QHash<QUuid, CustomIconData> m_customIconsRaw;
QList<QUuid> m_customIconsOrder;
QHash<QByteArray, QUuid> m_customIconsHashes;

Expand Down
8 changes: 7 additions & 1 deletion src/format/KdbxXmlReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ void KdbxXmlReader::parseIcon()

QUuid uuid;
QImage icon;
QString name;
QDateTime lastModified;
bool uuidSet = false;
bool iconSet = false;

Expand All @@ -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();
}
Expand All @@ -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;
}

Expand Down
14 changes: 11 additions & 3 deletions src/format/KdbxXmlWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
#include <QFile>

#include "core/Endian.h"
#include "core/Metadata.h"
#include "format/KeePass2RandomStream.h"
#include "streams/qtiocompressor.h"

Expand Down Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions src/format/KdbxXmlWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
#include <QXmlStreamWriter>

#include "core/Group.h"
#include "core/Metadata.h"

class KeePass2RandomStream;
class Metadata;

class KdbxXmlWriter
{
Expand All @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/gui/EditWidgetIcons.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/gui/EditWidgetIcons.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion src/gui/group/GroupModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions tests/TestDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -214,3 +215,26 @@ 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);
}
1 change: 1 addition & 0 deletions tests/TestDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ private slots:
void testEmptyRecycleBinOnNotCreated();
void testEmptyRecycleBinOnEmpty();
void testEmptyRecycleBinWithHierarchicalData();
void testCustomIcons();
};

#endif // KEEPASSX_TESTDATABASE_H
12 changes: 12 additions & 0 deletions tests/TestDeletedObjects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions tests/TestDeletedObjects.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ private slots:
void testDeletedObjectsFromFile();
void testDeletedObjectsFromNewDb();
void testDatabaseChange();
void testCustomIconDeletion();
};

#endif // KEEPASSX_TESTDELETEDOBJECTS_H
8 changes: 4 additions & 4 deletions tests/TestGroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion tests/TestKeePass2Format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit 81a20fd

Please sign in to comment.