Refactor Database and Database widgets (#2491)

The Database, DatabaseWidget, and DatabaseTabWidget classes share many responsibilities in inconsistent ways resulting in impenetrable and unmaintainable code and a diverse set of bugs and architecture restrictions. This patch reworks the architecture, responsibilities of, and dependencies between these classes.

The core changes are:

* Move loading and saving logic from widgets into the Database class
* Get rid of the DatabaseManagerStruct and move all the information contained in it into the Database
* Let database objects keep track of modifications and dirty/clean state instead of handing this to external widgets
* Move GUI interactions for loading and saving from the DatabaseTabWidget into the DatabaseWidget (resolves #2494 as a side-effect)
* Heavily clean up DatabaseTabWidget and degrade it to a slightly glorified QTabWidget
* Use QSharedPointers for all Database objects
* Remove the modifiedImmediate signal and replace it with a markAsModified() method
* Implement proper tabName() method instead of reading back titles from GUI widgets (resolves #1389 and its duplicates #2146 #855)
* Fix unwanted AES-KDF downgrade if database uses Argon2 and has CustomData
* Improve code

This patch is also the first major step towards solving issues #476 and #2322.
This commit is contained in:
Janek Bevendorff 2018-11-22 11:47:31 +01:00 committed by GitHub
parent 917c4cc18b
commit d612cad09a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
115 changed files with 2116 additions and 2165 deletions

View File

@ -268,7 +268,7 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
* Global Autotype entry-point function
* Perform global Auto-Type on the active window
*/
void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbList)
{
if (!m_plugin) {
return;
@ -287,7 +287,7 @@ void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
QList<AutoTypeMatch> matchList;
for (Database* db : dbList) {
for (const auto& db : dbList) {
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
for (Entry* entry : dbEntries) {
const QSet<QString> sequences = autoTypeSequences(entry, windowTitle).toSet();

View File

@ -58,7 +58,7 @@ public:
static void createTestInstance();
public slots:
void performGlobalAutoType(const QList<Database*>& dbList);
void performGlobalAutoType(const QList<QSharedPointer<Database>>& dbList);
void raiseWindow();
signals:

View File

@ -19,6 +19,9 @@
#include "BrowserEntrySaveDialog.h"
#include "ui_BrowserEntrySaveDialog.h"
#include "core/Database.h"
#include "gui/DatabaseWidget.h"
BrowserEntrySaveDialog::BrowserEntrySaveDialog(QWidget* parent)
: QDialog(parent)
, m_ui(new Ui::BrowserEntrySaveDialog())
@ -43,10 +46,10 @@ int BrowserEntrySaveDialog::setItems(QList<DatabaseWidget*>& databaseWidgets, Da
uint counter = 0;
int activeIndex = -1;
for (const auto dbWidget : databaseWidgets) {
QString databaseName = dbWidget->getDatabaseName();
QString databaseFileName = dbWidget->getDatabaseFileName();
QString databaseName = dbWidget->database()->metadata()->name();
QString databaseFileName = dbWidget->database()->filePath();
QListWidgetItem* item = new QListWidgetItem();
auto* item = new QListWidgetItem();
item->setData(Qt::UserRole, counter);
// Show database name (and filename if the name has been set in metadata)

View File

@ -69,7 +69,7 @@ bool BrowserService::isDatabaseOpened() const
return false;
}
return dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode;
return dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode || dbWidget->currentMode() == DatabaseWidget::Mode::EditMode;
}
bool BrowserService::openDatabase(bool triggerUnlock)
@ -83,7 +83,7 @@ bool BrowserService::openDatabase(bool triggerUnlock)
return false;
}
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode || dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
return true;
}
@ -106,14 +106,14 @@ void BrowserService::lockDatabase()
return;
}
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode || dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
dbWidget->lock();
}
}
QString BrowserService::getDatabaseRootUuid()
{
Database* db = getDatabase();
auto db = getDatabase();
if (!db) {
return {};
}
@ -128,7 +128,7 @@ QString BrowserService::getDatabaseRootUuid()
QString BrowserService::getDatabaseRecycleBinUuid()
{
Database* db = getDatabase();
auto db = getDatabase();
if (!db) {
return {};
}
@ -150,7 +150,7 @@ QString BrowserService::storeKey(const QString& key)
return id;
}
Database* db = getDatabase();
auto db = getDatabase();
if (!db) {
return {};
}
@ -194,7 +194,7 @@ QString BrowserService::storeKey(const QString& key)
QString BrowserService::getKey(const QString& id)
{
Database* db = getDatabase();
auto db = getDatabase();
if (!db) {
return {};
}
@ -268,13 +268,10 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
return result;
}
void BrowserService::addEntry(const QString& id,
const QString& login,
const QString& password,
const QString& url,
const QString& submitUrl,
const QString& realm,
Database* selectedDb)
void BrowserService::addEntry(const QString& id, const QString& login,
const QString& password, const QString& url,
const QString& submitUrl, const QString& realm,
QSharedPointer<Database> selectedDb)
{
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this,
@ -286,10 +283,10 @@ void BrowserService::addEntry(const QString& id,
Q_ARG(QString, url),
Q_ARG(QString, submitUrl),
Q_ARG(QString, realm),
Q_ARG(Database*, selectedDb));
Q_ARG(QSharedPointer<Database>, selectedDb));
}
Database* db = selectedDb ? selectedDb : selectedDatabase();
auto db = selectedDb ? selectedDb : selectedDatabase();
if (!db) {
return;
}
@ -299,7 +296,7 @@ void BrowserService::addEntry(const QString& id,
return;
}
Entry* entry = new Entry();
auto* entry = new Entry();
entry->setUuid(QUuid::createUuid());
entry->setTitle(QUrl(url).host());
entry->setUrl(url);
@ -341,12 +338,12 @@ void BrowserService::updateEntry(const QString& id,
Q_ARG(QString, submitUrl));
}
Database* db = selectedDatabase();
auto db = selectedDatabase();
if (!db) {
return;
}
Entry* entry = db->resolveEntry(QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1())));
Entry* entry = db->rootGroup()->findEntryByUuid(QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1())));
if (!entry) {
// If entry is not found for update, add a new one to the selected database
addEntry(id, login, password, url, submitUrl, "", db);
@ -382,10 +379,10 @@ void BrowserService::updateEntry(const QString& id,
}
}
QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostname, const QString& url)
QList<Entry*> BrowserService::searchEntries(QSharedPointer<Database> db, const QString& hostname, const QString& url)
{
QList<Entry*> entries;
Group* rootGroup = db->rootGroup();
auto* rootGroup = db->rootGroup();
if (!rootGroup) {
return entries;
}
@ -415,12 +412,12 @@ QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostnam
QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPairList& keyList)
{
// Get the list of databases to search
QList<Database*> databases;
QList<QSharedPointer<Database>> databases;
if (browserSettings()->searchInAllDatabases()) {
const int count = m_dbTabWidget->count();
for (int i = 0; i < count; ++i) {
if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
if (Database* db = dbWidget->database()) {
if (auto* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
if (const auto& db = dbWidget->database()) {
// Check if database is connected with KeePassXC-Browser
for (const StringPair& keyPair : keyList) {
QString key = db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
@ -431,7 +428,7 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
}
}
}
} else if (Database* db = getDatabase()) {
} else if (const auto& db = getDatabase()) {
databases << db;
}
@ -439,7 +436,7 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
QString hostname = QUrl(url).host();
QList<Entry*> entries;
do {
for (Database* db : databases) {
for (const auto& db : databases) {
entries << searchEntries(db, hostname, url);
}
} while (entries.isEmpty() && removeFirstDomain(hostname));
@ -447,9 +444,9 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
return entries;
}
void BrowserService::convertAttributesToCustomData(Database *currentDb)
void BrowserService::convertAttributesToCustomData(QSharedPointer<Database> currentDb)
{
Database* db = currentDb ? currentDb : getDatabase();
auto db = currentDb ? currentDb : getDatabase();
if (!db) {
return;
}
@ -651,9 +648,9 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri
return Unknown;
}
Group* BrowserService::findCreateAddEntryGroup(Database* selectedDb)
Group* BrowserService::findCreateAddEntryGroup(QSharedPointer<Database> selectedDb)
{
Database* db = selectedDb ? selectedDb : getDatabase();
auto db = selectedDb ? selectedDb : getDatabase();
if (!db) {
return nullptr;
}
@ -668,11 +665,11 @@ Group* BrowserService::findCreateAddEntryGroup(Database* selectedDb)
for (const Group* g : rootGroup->groupsRecursive(true)) {
if (g->name() == groupName) {
return db->resolveGroup(g->uuid());
return db->rootGroup()->findGroupByUuid(g->uuid());
}
}
Group* group = new Group();
auto* group = new Group();
group->setUuid(QUuid::createUuid());
group->setName(groupName);
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
@ -775,26 +772,26 @@ QString BrowserService::baseDomain(const QString& url) const
return baseDomain;
}
Database* BrowserService::getDatabase()
QSharedPointer<Database> BrowserService::getDatabase()
{
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
if (Database* db = dbWidget->database()) {
if (const auto& db = dbWidget->database()) {
return db;
}
}
return nullptr;
return {};
}
Database* BrowserService::selectedDatabase()
QSharedPointer<Database> BrowserService::selectedDatabase()
{
QList<DatabaseWidget*> databaseWidgets;
for (int i = 0;; ++i) {
const auto dbStruct = m_dbTabWidget->indexDatabaseManagerStruct(i);
for (int i = 0; ; ++i) {
auto* dbWidget = m_dbTabWidget->databaseWidgetFromIndex(i);
// Add only open databases
if (dbStruct.dbWidget && dbStruct.dbWidget->dbHasKey() &&
(dbStruct.dbWidget->currentMode() == DatabaseWidget::ViewMode ||
dbStruct.dbWidget->currentMode() == DatabaseWidget::EditMode)) {
databaseWidgets.push_back(dbStruct.dbWidget);
if (dbWidget && dbWidget->database()->hasKey() &&
(dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode ||
dbWidget->currentMode() == DatabaseWidget::Mode::EditMode)) {
databaseWidgets.push_back(dbWidget);
continue;
}
@ -813,7 +810,7 @@ Database* BrowserService::selectedDatabase()
return databaseWidgets[index]->database();
}
} else {
return nullptr;
return {};
}
}
@ -836,7 +833,7 @@ bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name)
return false;
}
int BrowserService::moveKeysToCustomData(Entry* entry, Database* db) const
int BrowserService::moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db) const
{
int keyCounter = 0;
for (const auto& key : entry->attributes()->keys()) {
@ -857,7 +854,7 @@ int BrowserService::moveKeysToCustomData(Entry* entry, Database* db) const
bool BrowserService::checkLegacySettings()
{
Database* db = getDatabase();
auto db = getDatabase();
if (!db) {
return false;
}
@ -882,12 +879,8 @@ bool BrowserService::checkLegacySettings()
"Do you want to upgrade the settings to the latest standard?\n"
"This is necessary to maintain compatibility with the browser plugin."),
QMessageBox::Yes | QMessageBox::No);
if (dialogResult == QMessageBox::No) {
return false;
}
return true;
return dialogResult == QMessageBox::Yes;
}
void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
@ -916,7 +909,7 @@ void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
{
if (dbWidget) {
auto currentMode = dbWidget->currentMode();
if (currentMode == DatabaseWidget::ViewMode || currentMode == DatabaseWidget::EditMode) {
if (currentMode == DatabaseWidget::Mode::ViewMode || currentMode == DatabaseWidget::Mode::EditMode) {
emit databaseUnlocked();
} else {
emit databaseLocked();

View File

@ -44,7 +44,6 @@ public:
bool openDatabase(bool triggerUnlock);
QString getDatabaseRootUuid();
QString getDatabaseRecycleBinUuid();
Entry* getConfigEntry(bool create = false);
QString getKey(const QString& id);
void addEntry(const QString& id,
const QString& login,
@ -52,10 +51,10 @@ public:
const QString& url,
const QString& submitUrl,
const QString& realm,
Database* selectedDb = nullptr);
QList<Entry*> searchEntries(Database* db, const QString& hostname, const QString& url);
QSharedPointer<Database> selectedDb = {});
QList<Entry*> searchEntries(QSharedPointer<Database> db, const QString& hostname, const QString& url);
QList<Entry*> searchEntries(const QString& url, const StringPairList& keyList);
void convertAttributesToCustomData(Database *currentDb = nullptr);
void convertAttributesToCustomData(QSharedPointer<Database> currentDb = {});
public:
static const char KEEPASSXCBROWSER_NAME[];
@ -103,16 +102,16 @@ private:
const QString& realm);
QJsonObject prepareEntry(const Entry* entry);
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
Group* findCreateAddEntryGroup(Database* selectedDb = nullptr);
Group* findCreateAddEntryGroup(QSharedPointer<Database> selectedDb = {});
int
sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const;
bool matchUrlScheme(const QString& url);
bool removeFirstDomain(QString& hostname);
QString baseDomain(const QString& url) const;
Database* getDatabase();
Database* selectedDatabase();
QSharedPointer<Database> getDatabase();
QSharedPointer<Database> selectedDatabase();
bool moveSettingsToCustomData(Entry* entry, const QString& name) const;
int moveKeysToCustomData(Entry* entry, Database* db) const;
int moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db) const;
bool checkLegacySettings();
private:

View File

@ -89,7 +89,7 @@ int Add::execute(const QStringList& arguments)
const QString& databasePath = args.at(0);
const QString& entryPath = args.at(1);
Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
auto db = Database::unlockFromStdin(databasePath, parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
@ -126,7 +126,7 @@ int Add::execute(const QStringList& arguments)
if (passwordLength.isEmpty()) {
passwordGenerator.setLength(PasswordGenerator::DefaultLength);
} else {
passwordGenerator.setLength(static_cast<size_t>(passwordLength.toInt()));
passwordGenerator.setLength(passwordLength.toInt());
}
passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset);
@ -135,8 +135,8 @@ int Add::execute(const QStringList& arguments)
entry->setPassword(password);
}
QString errorMessage = db->saveToFile(databasePath);
if (!errorMessage.isEmpty()) {
QString errorMessage;
if (!db->save(databasePath, &errorMessage, true, false)) {
errorTextStream << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl;
return EXIT_FAILURE;
}

View File

@ -66,7 +66,7 @@ int Clip::execute(const QStringList& arguments)
return EXIT_FAILURE;
}
Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
auto db = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
@ -74,7 +74,7 @@ int Clip::execute(const QStringList& arguments)
return clipEntry(db, args.at(1), args.value(2), parser.isSet(totp));
}
int Clip::clipEntry(Database* database, const QString& entryPath, const QString& timeout, bool clipTotp)
int Clip::clipEntry(QSharedPointer<Database> database, const QString& entryPath, const QString& timeout, bool clipTotp)
{
TextStream err(Utils::STDERR);

View File

@ -26,7 +26,7 @@ public:
Clip();
~Clip();
int execute(const QStringList& arguments) override;
int clipEntry(Database* database, const QString& entryPath, const QString& timeout, bool clipTotp);
int clipEntry(QSharedPointer<Database> database, const QString& entryPath, const QString& timeout, bool clipTotp);
};
#endif // KEEPASSXC_CLIP_H

View File

@ -93,7 +93,7 @@ int Edit::execute(const QStringList& arguments)
const QString& databasePath = args.at(0);
const QString& entryPath = args.at(1);
Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
auto db = Database::unlockFromStdin(databasePath, parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
@ -152,8 +152,8 @@ int Edit::execute(const QStringList& arguments)
entry->endUpdate();
QString errorMessage = db->saveToFile(databasePath);
if (!errorMessage.isEmpty()) {
QString errorMessage;
if (!db->save(databasePath, &errorMessage, true, false)) {
err << QObject::tr("Writing the database failed: %1").arg(errorMessage) << endl;
return EXIT_FAILURE;
}

View File

@ -104,7 +104,8 @@ int Extract::execute(const QStringList& arguments)
KeePass2Reader reader;
reader.setSaveXml(true);
QScopedPointer<Database> db(reader.readDatabase(&dbFile, compositeKey));
auto db = QSharedPointer<Database>::create();
reader.readDatabase(&dbFile, compositeKey, db.data());
QByteArray xmlData = reader.reader()->xmlData();

View File

@ -64,7 +64,7 @@ int List::execute(const QStringList& arguments)
bool recursive = parser.isSet(recursiveOption);
QScopedPointer<Database> db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
auto db = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}

View File

@ -61,7 +61,7 @@ int Locate::execute(const QStringList& arguments)
return EXIT_FAILURE;
}
QScopedPointer<Database> db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
auto db = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}

View File

@ -68,27 +68,29 @@ int Merge::execute(const QStringList& arguments)
return EXIT_FAILURE;
}
QScopedPointer<Database> db1(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
auto db1 = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
if (!db1) {
return EXIT_FAILURE;
}
QScopedPointer<Database> db2;
QSharedPointer<Database> db2;
if (!parser.isSet("same-credentials")) {
db2.reset(Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom), Utils::STDOUT, Utils::STDERR));
db2 = Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom), Utils::STDOUT, Utils::STDERR);
} else {
db2.reset(Database::openDatabaseFile(args.at(1), db1->key()));
}
if (!db2) {
return EXIT_FAILURE;
db2 = QSharedPointer<Database>::create();
QString errorMessage;
if (!db2->open(args.at(1), db1->key(), &errorMessage, false)) {
err << QObject::tr("Error reading merge file:\n%1").arg(errorMessage);
return EXIT_FAILURE;
}
}
Merger merger(db2.data(), db1.data());
bool databaseChanged = merger.merge();
if (databaseChanged) {
QString errorMessage = db1->saveToFile(args.at(0));
if (!errorMessage.isEmpty()) {
QString errorMessage;
if (!db1->save(args.at(0), &errorMessage, true, false)) {
err << QObject::tr("Unable to save database to file : %1").arg(errorMessage) << endl;
return EXIT_FAILURE;
}

View File

@ -63,7 +63,7 @@ int Remove::execute(const QStringList& arguments)
return EXIT_FAILURE;
}
QScopedPointer<Database> db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
auto db = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
@ -92,8 +92,8 @@ int Remove::removeEntry(Database* database, const QString& databasePath, const Q
database->recycleEntry(entry);
};
QString errorMessage = database->saveToFile(databasePath);
if (!errorMessage.isEmpty()) {
QString errorMessage;
if (!database->save(databasePath, &errorMessage, true, false)) {
err << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl;
return EXIT_FAILURE;
}

View File

@ -71,7 +71,7 @@ int Show::execute(const QStringList& arguments)
return EXIT_FAILURE;
}
QScopedPointer<Database> db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
auto db = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}

View File

@ -153,6 +153,7 @@ void Config::init(const QString& fileName)
m_defaults.insert("SingleInstance", true);
m_defaults.insert("RememberLastDatabases", true);
m_defaults.insert("NumberOfRememberedLastDatabases", 5);
m_defaults.insert("RememberLastKeyFiles", true);
m_defaults.insert("OpenPreviousDatabasesOnStartup", true);
m_defaults.insert("AutoSaveAfterEveryChange", true);

View File

@ -58,7 +58,7 @@ void CustomData::set(const QString& key, const QString& value)
if (addAttribute || changeValue) {
m_data.insert(key, value);
emit modified();
emit customDataModified();
}
if (addAttribute) {
@ -73,7 +73,7 @@ void CustomData::remove(const QString& key)
m_data.remove(key);
emit removed(key);
emit modified();
emit customDataModified();
}
void CustomData::rename(const QString& oldKey, const QString& newKey)
@ -92,7 +92,7 @@ void CustomData::rename(const QString& oldKey, const QString& newKey)
m_data.remove(oldKey);
m_data.insert(newKey, data);
emit modified();
emit customDataModified();
emit renamed(oldKey, newKey);
}
@ -107,7 +107,7 @@ void CustomData::copyDataFrom(const CustomData* other)
m_data = other->m_data;
emit reset();
emit modified();
emit customDataModified();
}
bool CustomData::operator==(const CustomData& other) const
{
@ -126,7 +126,7 @@ void CustomData::clear()
m_data.clear();
emit reset();
emit modified();
emit customDataModified();
}
bool CustomData::isEmpty() const

View File

@ -46,7 +46,7 @@ public:
bool operator!=(const CustomData& other) const;
signals:
void modified();
void customDataModified();
void aboutToBeAdded(const QString& key);
void added(const QString& key);
void aboutToBeRemoved(const QString& key);

View File

@ -1,6 +1,6 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,60 +18,325 @@
#include "Database.h"
#include <QDebug>
#include <QFile>
#include <QSaveFile>
#include <QTemporaryFile>
#include <QTextStream>
#include <QTimer>
#include <QXmlStreamReader>
#include <utility>
#include "cli/Utils.h"
#include "cli/TextStream.h"
#include "core/Clock.h"
#include "core/Group.h"
#include "core/Merger.h"
#include "core/Metadata.h"
#include "crypto/kdf/AesKdf.h"
#include "format/KeePass2.h"
#include "format/KeePass2Reader.h"
#include "format/KeePass2Writer.h"
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
QHash<QUuid, Database*> Database::m_uuidMap;
#include <QFile>
#include <QSaveFile>
#include <QTemporaryFile>
#include <QTimer>
#include <QXmlStreamReader>
#include <QFileInfo>
QHash<QUuid, QPointer<Database>> Database::s_uuidMap;
QHash<QString, QPointer<Database>> Database::s_filePathMap;
Database::Database()
: m_metadata(new Metadata(this))
, m_data()
, m_rootGroup(nullptr)
, m_timer(new QTimer(this))
, m_emitModified(false)
, m_uuid(QUuid::createUuid())
{
m_data.cipher = KeePass2::CIPHER_AES256;
m_data.compressionAlgo = CompressionGZip;
// instantiate default AES-KDF with legacy KDBX3 flag set
// KDBX4+ will re-initialize the KDF using parameters read from the KDBX file
m_data.kdf = QSharedPointer<AesKdf>::create(true);
m_data.kdf->randomizeSeed();
m_data.hasKey = false;
setRootGroup(new Group());
rootGroup()->setUuid(QUuid::createUuid());
rootGroup()->setName(tr("Root", "Root group name"));
m_timer->setSingleShot(true);
m_uuidMap.insert(m_uuid, this);
s_uuidMap.insert(m_uuid, this);
connect(m_metadata, SIGNAL(modified()), this, SIGNAL(modifiedImmediate()));
connect(m_metadata, SIGNAL(nameTextChanged()), this, SIGNAL(nameTextChanged()));
connect(this, SIGNAL(modifiedImmediate()), this, SLOT(startModifiedTimer()));
connect(m_timer, SIGNAL(timeout()), SIGNAL(modified()));
connect(m_metadata, SIGNAL(metadataModified()), this, SLOT(markAsModified()));
connect(m_timer, SIGNAL(timeout()), SIGNAL(databaseModified()));
m_modified = false;
m_emitModified = true;
}
Database::Database(const QString& filePath)
: Database()
{
setFilePath(filePath);
}
Database::~Database()
{
m_uuidMap.remove(m_uuid);
s_uuidMap.remove(m_uuid);
if (m_modified) {
emit databaseDiscarded();
}
}
QUuid Database::uuid() const
{
return m_uuid;
}
/**
* Open the database from a previously specified file.
* Unless `readOnly` is set to false, the database will be opened in
* read-write mode and fall back to read-only if that is not possible.
*
* @param key composite key for unlocking the database
* @param readOnly open in read-only mode
* @param error error message in case of failure
* @return true on success
*/
bool Database::open(QSharedPointer<const CompositeKey> key, QString* error, bool readOnly)
{
Q_ASSERT(!m_data.filePath.isEmpty());
if (m_data.filePath.isEmpty()) {
return false;
}
return open(m_data.filePath, std::move(key), error, readOnly);
}
/**
* Open the database from a file.
* Unless `readOnly` is set to false, the database will be opened in
* read-write mode and fall back to read-only if that is not possible.
*
* @param filePath path to the file
* @param key composite key for unlocking the database
* @param readOnly open in read-only mode
* @param error error message in case of failure
* @return true on success
*/
bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey> key, QString* error, bool readOnly)
{
if (isInitialized() && m_modified) {
emit databaseDiscarded();
}
setEmitModified(false);
QFile dbFile(filePath);
if (!dbFile.exists()) {
if (error) {
*error = tr("File %1 does not exist.").arg(filePath);
}
return false;
}
if (!readOnly && !dbFile.open(QIODevice::ReadWrite)) {
readOnly = true;
}
if (!dbFile.isOpen() && !dbFile.open(QIODevice::ReadOnly)) {
if (error) {
*error = tr("Unable to open file %1.").arg(filePath);
}
return false;
}
KeePass2Reader reader;
bool ok = reader.readDatabase(&dbFile, std::move(key), this);
if (reader.hasError()) {
if (error) {
*error = tr("Error while reading the database: %1").arg(reader.errorString());
}
return false;
}
setReadOnly(readOnly);
setFilePath(filePath);
dbFile.close();
setInitialized(ok);
markAsClean();
setEmitModified(true);
return ok;
}
/**
* Save the database back to the file is has been opened from.
* This method behaves the same as its overloads.
*
* @see Database::save(const QString&, bool, bool, QString*)
*
* @param atomic Use atomic file transactions
* @param backup Backup the existing database file, if exists
* @param error error message in case of failure
* @return true on success
*/
bool Database::save(QString* error, bool atomic, bool backup)
{
Q_ASSERT(!m_data.filePath.isEmpty());
if (m_data.filePath.isEmpty()) {
if (error) {
*error = tr("Could not save, database has no file name.");
}
return false;
}
return save(m_data.filePath, error, atomic, backup);
}
/**
* Save the database to a file.
*
* This function uses QTemporaryFile instead of QSaveFile due to a bug
* in Qt (https://bugreports.qt.io/browse/QTBUG-57299) that may prevent
* the QSaveFile from renaming itself when using Dropbox, Drive, or OneDrive.
*
* The risk in using QTemporaryFile is that the rename function is not atomic
* and may result in loss of data if there is a crash or power loss at the
* wrong moment.
*
* @param filePath Absolute path of the file to save
* @param atomic Use atomic file transactions
* @param backup Backup the existing database file, if exists
* @param error error message in case of failure
* @return true on success
*/
bool Database::save(const QString& filePath, QString* error, bool atomic, bool backup)
{
Q_ASSERT(!m_data.isReadOnly);
if (m_data.isReadOnly) {
return false;
}
if (atomic) {
QSaveFile saveFile(filePath);
if (saveFile.open(QIODevice::WriteOnly)) {
// write the database to the file
if (!writeDatabase(&saveFile, error)) {
return false;
}
if (backup) {
backupDatabase(filePath);
}
if (saveFile.commit()) {
// successfully saved database file
setFilePath(filePath);
return true;
}
}
if (error) {
*error = saveFile.errorString();
}
} else {
QTemporaryFile tempFile;
if (tempFile.open()) {
// write the database to the file
if (!writeDatabase(&tempFile, error)) {
return false;
}
tempFile.close(); // flush to disk
if (backup) {
backupDatabase(filePath);
}
// Delete the original db and move the temp file in place
QFile::remove(filePath);
#ifdef Q_OS_LINUX
// workaround to make this workaround work, see: https://bugreports.qt.io/browse/QTBUG-64008
if (tempFile.copy(filePath)) {
// successfully saved database file
return true;
}
#else
if (tempFile.rename(filePath)) {
// successfully saved database file
tempFile.setAutoRemove(false);
setFilePath(filePath);
return true;
}
#endif
}
if (error) {
*error = tempFile.errorString();
}
}
// Saving failed
return false;
}
bool Database::writeDatabase(QIODevice* device, QString* error)
{
Q_ASSERT(!m_data.isReadOnly);
if (m_data.isReadOnly) {
if (error) {
*error = tr("File cannot be written as it is opened in read-only mode.");
}
return false;
}
KeePass2Writer writer;
setEmitModified(false);
writer.writeDatabase(device, this);
setEmitModified(true);
if (writer.hasError()) {
// the writer failed
if (error) {
*error = writer.errorString();
}
return false;
}
markAsClean();
return true;
}
/**
* Remove the old backup and replace it with a new one
* backups are named <filename>.old.kdbx
*
* @param filePath Path to the file to backup
* @return true on success
*/
bool Database::backupDatabase(const QString& filePath)
{
QString backupFilePath = filePath;
auto re = QRegularExpression("\\.kdbx$|(?<!\\.kdbx)$", QRegularExpression::CaseInsensitiveOption);
backupFilePath.replace(re, ".old.kdbx");
QFile::remove(backupFilePath);
return QFile::copy(filePath, backupFilePath);
}
bool Database::isReadOnly() const
{
return m_data.isReadOnly;
}
void Database::setReadOnly(bool readOnly)
{
m_data.isReadOnly = readOnly;
}
/**
* Returns true if database has been fully decrypted and populated, i.e. if
* it's not just an empty default instance.
*
* @return true if database has been fully initialized
*/
bool Database::isInitialized() const
{
return m_initialized;
}
/**
* @param initialized true to mark database as initialized
*/
void Database::setInitialized(bool initialized)
{
m_initialized = initialized;
}
Group* Database::rootGroup()
@ -84,10 +349,21 @@ const Group* Database::rootGroup() const
return m_rootGroup;
}
/**
* Sets group as the root group and takes ownership of it.
* Warning: Be careful when calling this method as it doesn't
* emit any notifications so e.g. models aren't updated.
* The caller is responsible for cleaning up the previous
root group.
*/
void Database::setRootGroup(Group* group)
{
Q_ASSERT(group);
if (isInitialized() && m_modified) {
emit databaseDiscarded();
}
m_rootGroup = group;
m_rootGroup->setParent(this);
}
@ -104,115 +380,23 @@ const Metadata* Database::metadata() const
QString Database::filePath() const
{
return m_filePath;
return m_data.filePath;
}
void Database::setFilePath(const QString& filePath)
{
m_filePath = filePath;
}
Entry* Database::resolveEntry(const QUuid& uuid)
{
return findEntryRecursive(uuid, m_rootGroup);
}
Entry* Database::resolveEntry(const QString& text, EntryReferenceType referenceType)
{
return findEntryRecursive(text, referenceType, m_rootGroup);
}
Entry* Database::findEntryRecursive(const QUuid& uuid, Group* group)
{
const QList<Entry*> entryList = group->entries();
for (Entry* entry : entryList) {
if (entry->uuid() == uuid) {
return entry;
}
if (filePath == m_data.filePath) {
return;
}
const QList<Group*> children = group->children();
for (Group* child : children) {
Entry* result = findEntryRecursive(uuid, child);
if (result) {
return result;
}
if (s_filePathMap.contains(m_data.filePath)) {
s_filePathMap.remove(m_data.filePath);
}
QString oldPath = m_data.filePath;
m_data.filePath = filePath;
s_filePathMap.insert(m_data.filePath, this);
return nullptr;
}
Entry* Database::findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group)
{
Q_ASSERT_X(referenceType != EntryReferenceType::Unknown,
"Database::findEntryRecursive",
"Can't search entry with \"referenceType\" parameter equal to \"Unknown\"");
bool found = false;
const QList<Entry*> entryList = group->entries();
for (Entry* entry : entryList) {
switch (referenceType) {
case EntryReferenceType::Unknown:
return nullptr;
case EntryReferenceType::Title:
found = entry->title() == text;
break;
case EntryReferenceType::UserName:
found = entry->username() == text;
break;
case EntryReferenceType::Password:
found = entry->password() == text;
break;
case EntryReferenceType::Url:
found = entry->url() == text;
break;
case EntryReferenceType::Notes:
found = entry->notes() == text;
break;
case EntryReferenceType::QUuid:
found = entry->uuid() == QUuid::fromRfc4122(QByteArray::fromHex(text.toLatin1()));
break;
case EntryReferenceType::CustomAttributes:
found = entry->attributes()->containsValue(text);
break;
}
if (found) {
return entry;
}
}
const QList<Group*> children = group->children();
for (Group* child : children) {
Entry* result = findEntryRecursive(text, referenceType, child);
if (result) {
return result;
}
}
return nullptr;
}
Group* Database::resolveGroup(const QUuid& uuid)
{
return findGroupRecursive(uuid, m_rootGroup);
}
Group* Database::findGroupRecursive(const QUuid& uuid, Group* group)
{
if (group->uuid() == uuid) {
return group;
}
const QList<Group*> children = group->children();
for (Group* child : children) {
Group* result = findGroupRecursive(uuid, child);
if (result) {
return result;
}
}
return nullptr;
emit filePathChanged(oldPath, filePath);
}
QList<DeletedObject> Database::deletedObjects()
@ -273,9 +457,9 @@ const QUuid& Database::cipher() const
return m_data.cipher;
}
Database::CompressionAlgorithm Database::compressionAlgo() const
Database::CompressionAlgorithm Database::compressionAlgorithm() const
{
return m_data.compressionAlgo;
return m_data.compressionAlgorithm;
}
QByteArray Database::transformedMasterKey() const
@ -301,11 +485,11 @@ void Database::setCipher(const QUuid& cipher)
m_data.cipher = cipher;
}
void Database::setCompressionAlgo(Database::CompressionAlgorithm algo)
void Database::setCompressionAlgorithm(Database::CompressionAlgorithm algo)
{
Q_ASSERT(static_cast<quint32>(algo) <= CompressionAlgorithmMax);
m_data.compressionAlgo = algo;
m_data.compressionAlgorithm = algo;
}
/**
@ -318,6 +502,8 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo)
*/
bool Database::setKey(const QSharedPointer<const CompositeKey>& key, bool updateChangedTime, bool updateTransformSalt)
{
Q_ASSERT(!m_data.isReadOnly);
if (!key) {
m_data.key.reset();
m_data.transformedMasterKey = {};
@ -344,7 +530,7 @@ bool Database::setKey(const QSharedPointer<const CompositeKey>& key, bool update
}
if (oldTransformedMasterKey != m_data.transformedMasterKey) {
emit modifiedImmediate();
markAsModified();
}
return true;
@ -388,11 +574,13 @@ const QVariantMap& Database::publicCustomData() const
void Database::setPublicCustomData(const QVariantMap& customData)
{
Q_ASSERT(!m_data.isReadOnly);
m_data.publicCustomData = customData;
}
void Database::createRecycleBin()
{
Q_ASSERT(!m_data.isReadOnly);
Group* recycleBin = Group::createRecycleBin();
recycleBin->setParent(rootGroup());
m_metadata->setRecycleBin(recycleBin);
@ -400,6 +588,7 @@ void Database::createRecycleBin()
void Database::recycleEntry(Entry* entry)
{
Q_ASSERT(!m_data.isReadOnly);
if (m_metadata->recycleBinEnabled()) {
if (!m_metadata->recycleBin()) {
createRecycleBin();
@ -412,6 +601,7 @@ void Database::recycleEntry(Entry* entry)
void Database::recycleGroup(Group* group)
{
Q_ASSERT(!m_data.isReadOnly);
if (m_metadata->recycleBinEnabled()) {
if (!m_metadata->recycleBin()) {
createRecycleBin();
@ -424,6 +614,7 @@ void Database::recycleGroup(Group* group)
void Database::emptyRecycleBin()
{
Q_ASSERT(!m_data.isReadOnly);
if (m_metadata->recycleBinEnabled() && m_metadata->recycleBin()) {
// destroying direct entries of the recycle bin
QList<Entry*> subEntries = m_metadata->recycleBin()->entries();
@ -447,23 +638,54 @@ void Database::setEmitModified(bool value)
m_emitModified = value;
}
bool Database::isModified() const
{
return m_modified;
}
void Database::markAsModified()
{
emit modified();
if (isReadOnly()) {
return;
}
m_modified = true;
if (m_emitModified) {
startModifiedTimer();
}
}
const QUuid& Database::uuid()
void Database::markAsClean()
{
return m_uuid;
bool emitSignal = m_modified;
m_modified = false;
if (emitSignal) {
emit databaseSaved();
}
}
/**
* @param uuid UUID of the database
* @return pointer to the database or nullptr if no such database exists
*/
Database* Database::databaseByUuid(const QUuid& uuid)
{
return m_uuidMap.value(uuid, 0);
return s_uuidMap.value(uuid, nullptr);
}
/**
* @param filePath file path of the database
* @return pointer to the database or nullptr if the database has not been opened
*/
Database* Database::databaseByFilePath(const QString& filePath)
{
return s_filePathMap.value(filePath, nullptr);
}
void Database::startModifiedTimer()
{
Q_ASSERT(!m_data.isReadOnly);
if (!m_emitModified) {
return;
}
@ -479,34 +701,12 @@ QSharedPointer<const CompositeKey> Database::key() const
return m_data.key;
}
Database* Database::openDatabaseFile(const QString& fileName, QSharedPointer<const CompositeKey> key)
{
QFile dbFile(fileName);
if (!dbFile.exists()) {
qCritical("Database file %s does not exist.", qPrintable(fileName));
return nullptr;
}
if (!dbFile.open(QIODevice::ReadOnly)) {
qCritical("Unable to open database file %s.", qPrintable(fileName));
return nullptr;
}
KeePass2Reader reader;
Database* db = reader.readDatabase(&dbFile, std::move(key));
if (reader.hasError()) {
qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
return nullptr;
}
return db;
}
Database* Database::unlockFromStdin(const QString& databaseFilename, const QString& keyFilename, FILE* outputDescriptor, FILE* errorDescriptor)
QSharedPointer<Database> Database::unlockFromStdin(const QString& databaseFilename, const QString& keyFilename,
FILE* outputDescriptor, FILE* errorDescriptor)
{
auto compositeKey = QSharedPointer<CompositeKey>::create();
QTextStream out(outputDescriptor);
QTextStream err(errorDescriptor);
TextStream out(outputDescriptor);
TextStream err(errorDescriptor);
out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename);
out.flush();
@ -522,7 +722,7 @@ Database* Database::unlockFromStdin(const QString& databaseFilename, const QStri
// LCOV_EXCL_START
if (!fileKey->load(keyFilename, &errorMessage)) {
err << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage)<< endl;
return nullptr;
return {};
}
if (fileKey->type() != FileKey::Hashed) {
@ -535,112 +735,9 @@ Database* Database::unlockFromStdin(const QString& databaseFilename, const QStri
compositeKey->addKey(fileKey);
}
return Database::openDatabaseFile(databaseFilename, compositeKey);
}
/**
* Save the database to a file.
*
* This function uses QTemporaryFile instead of QSaveFile due to a bug
* in Qt (https://bugreports.qt.io/browse/QTBUG-57299) that may prevent
* the QSaveFile from renaming itself when using Dropbox, Drive, or OneDrive.
*
* The risk in using QTemporaryFile is that the rename function is not atomic
* and may result in loss of data if there is a crash or power loss at the
* wrong moment.
*
* @param filePath Absolute path of the file to save
* @param atomic Use atomic file transactions
* @param backup Backup the existing database file, if exists
* @return error string, if any
*/
QString Database::saveToFile(const QString& filePath, bool atomic, bool backup)
{
QString error;
if (atomic) {
QSaveFile saveFile(filePath);
if (saveFile.open(QIODevice::WriteOnly)) {
// write the database to the file
error = writeDatabase(&saveFile);
if (!error.isEmpty()) {
return error;
}
if (backup) {
backupDatabase(filePath);
}
if (saveFile.commit()) {
// successfully saved database file
return {};
}
}
error = saveFile.errorString();
} else {
QTemporaryFile tempFile;
if (tempFile.open()) {
// write the database to the file
error = writeDatabase(&tempFile);
if (!error.isEmpty()) {
return error;
}
tempFile.close(); // flush to disk
if (backup) {
backupDatabase(filePath);
}
// Delete the original db and move the temp file in place
QFile::remove(filePath);
#ifdef Q_OS_LINUX
// workaround to make this workaround work, see: https://bugreports.qt.io/browse/QTBUG-64008
if (tempFile.copy(filePath)) {
// successfully saved database file
return {};
}
#else
if (tempFile.rename(filePath)) {
// successfully saved database file
tempFile.setAutoRemove(false);
return {};
}
#endif
}
error = tempFile.errorString();
}
// Saving failed
return error;
}
QString Database::writeDatabase(QIODevice* device)
{
KeePass2Writer writer;
setEmitModified(false);
writer.writeDatabase(device, this);
setEmitModified(true);
if (writer.hasError()) {
// the writer failed
return writer.errorString();
}
return {};
}
/**
* Remove the old backup and replace it with a new one
* backups are named <filename>.old.kdbx
*
* @param filePath Path to the file to backup
* @return
*/
bool Database::backupDatabase(const QString& filePath)
{
QString backupFilePath = filePath;
auto re = QRegularExpression("\\.kdbx$|(?<!\\.kdbx)$", QRegularExpression::CaseInsensitiveOption);
backupFilePath.replace(re, ".old.kdbx");
QFile::remove(backupFilePath);
return QFile::copy(filePath, backupFilePath);
auto db = QSharedPointer<Database>::create();
db->open(databaseFilename, compositeKey, nullptr, false);
return db;
}
QSharedPointer<Kdf> Database::kdf() const
@ -650,11 +747,14 @@ QSharedPointer<Kdf> Database::kdf() const
void Database::setKdf(QSharedPointer<Kdf> kdf)
{
Q_ASSERT(!m_data.isReadOnly);
m_data.kdf = std::move(kdf);
}
bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)
{
Q_ASSERT(!m_data.isReadOnly);
kdf->randomizeSeed();
QByteArray transformedMasterKey;
if (!m_data.key) {
@ -666,7 +766,7 @@ bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)
setKdf(kdf);
m_data.transformedMasterKey = transformedMasterKey;
emit modifiedImmediate();
markAsModified();
return true;
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -22,9 +22,12 @@
#include <QDateTime>
#include <QHash>
#include <QObject>
#include <QPointer>
#include "crypto/kdf/Kdf.h"
#include "format/KeePass2.h"
#include "keys/CompositeKey.h"
#include "crypto/kdf/AesKdf.h"
class Entry;
enum class EntryReferenceType;
@ -57,40 +60,38 @@ public:
};
static const quint32 CompressionAlgorithmMax = CompressionGZip;
struct DatabaseData
{
QUuid cipher;
CompressionAlgorithm compressionAlgo;
QByteArray transformedMasterKey;
QSharedPointer<Kdf> kdf;
QSharedPointer<const CompositeKey> key;
bool hasKey;
QByteArray masterSeed;
QByteArray challengeResponseKey;
QVariantMap publicCustomData;
};
Database();
explicit Database(const QString& filePath);
~Database() override;
Group* rootGroup();
const Group* rootGroup() const;
/**
* Sets group as the root group and takes ownership of it.
* Warning: Be careful when calling this method as it doesn't
* emit any notifications so e.g. models aren't updated.
* The caller is responsible for cleaning up the previous
root group.
*/
void setRootGroup(Group* group);
bool open(QSharedPointer<const CompositeKey> key, QString* error = nullptr, bool readOnly = false);
bool open(const QString& filePath, QSharedPointer<const CompositeKey> key, QString* error = nullptr, bool readOnly = false);
bool save(QString* error = nullptr, bool atomic = true, bool backup = false);
bool save(const QString& filePath, QString* error = nullptr, bool atomic = true, bool backup = false);
bool isInitialized() const;
void setInitialized(bool initialized);
bool isModified() const;
void setEmitModified(bool value);
bool isReadOnly() const;
void setReadOnly(bool readOnly);
QUuid uuid() const;
QString filePath() const;
void setFilePath(const QString& filePath);
Metadata* metadata();
const Metadata* metadata() const;
QString filePath() const;
void setFilePath(const QString& filePath);
Entry* resolveEntry(const QUuid& uuid);
Entry* resolveEntry(const QString& text, EntryReferenceType referenceType);
Group* resolveGroup(const QUuid& uuid);
Group* rootGroup();
const Group* rootGroup() const;
void setRootGroup(Group* group);
QVariantMap& publicCustomData();
const QVariantMap& publicCustomData() const;
void setPublicCustomData(const QVariantMap& customData);
void recycleGroup(Group* group);
void recycleEntry(Entry* entry);
void emptyRecycleBin();
QList<DeletedObject> deletedObjects();
const QList<DeletedObject>& deletedObjects() const;
void addDeletedObject(const DeletedObject& delObj);
@ -99,42 +100,33 @@ public:
bool containsDeletedObject(const DeletedObject& uuid) const;
void setDeletedObjects(const QList<DeletedObject>& delObjs);
const QUuid& cipher() const;
Database::CompressionAlgorithm compressionAlgo() const;
QSharedPointer<Kdf> kdf() const;
QByteArray transformedMasterKey() const;
bool hasKey() const;
QSharedPointer<const CompositeKey> key() const;
bool setKey(const QSharedPointer<const CompositeKey>& key, bool updateChangedTime = true, bool updateTransformSalt = false);
QByteArray challengeResponseKey() const;
bool challengeMasterSeed(const QByteArray& masterSeed);
void setCipher(const QUuid& cipher);
void setCompressionAlgo(Database::CompressionAlgorithm algo);
void setKdf(QSharedPointer<Kdf> kdf);
bool setKey(const QSharedPointer<const CompositeKey>& key, bool updateChangedTime = true, bool updateTransformSalt = false);
bool hasKey() const;
bool verifyKey(const QSharedPointer<CompositeKey>& key) const;
QVariantMap& publicCustomData();
const QVariantMap& publicCustomData() const;
void setPublicCustomData(const QVariantMap& customData);
void recycleEntry(Entry* entry);
void recycleGroup(Group* group);
void emptyRecycleBin();
void setEmitModified(bool value);
void markAsModified();
QString saveToFile(const QString& filePath, bool atomic = true, bool backup = false);
const QUuid& cipher() const;
void setCipher(const QUuid& cipher);
Database::CompressionAlgorithm compressionAlgorithm() const;
void setCompressionAlgorithm(Database::CompressionAlgorithm algo);
/**
* Returns a unique id that is only valid as long as the Database exists.
*/
const QUuid& uuid();
QSharedPointer<Kdf> kdf() const;
void setKdf(QSharedPointer<Kdf> kdf);
bool changeKdf(const QSharedPointer<Kdf>& kdf);
QByteArray transformedMasterKey() const;
static Database* databaseByUuid(const QUuid& uuid);
static Database* openDatabaseFile(const QString& fileName, QSharedPointer<const CompositeKey> key);
static Database* unlockFromStdin(const QString& databaseFilename, const QString& keyFilename = {},
FILE* outputDescriptor = stdout, FILE* errorDescriptor = stderr);
static Database* databaseByFilePath(const QString& filePath);
static QSharedPointer<Database> unlockFromStdin(const QString& databaseFilename, const QString& keyFilename = {},
FILE* outputDescriptor = stdout, FILE* errorDescriptor = stderr);
public slots:
void markAsModified();
void markAsClean();
signals:
void filePathChanged(const QString& oldPath, const QString& newPath);
void groupDataChanged(Group* group);
void groupAboutToAdd(Group* group, int index);
void groupAdded();
@ -142,33 +134,51 @@ signals:
void groupRemoved();
void groupAboutToMove(Group* group, Group* toGroup, int index);
void groupMoved();
void nameTextChanged();
void modified();
void modifiedImmediate();
void databaseModified();
void databaseSaved();
void databaseDiscarded();
private slots:
void startModifiedTimer();
private:
Entry* findEntryRecursive(const QUuid& uuid, Group* group);
Entry* findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group);
Group* findGroupRecursive(const QUuid& uuid, Group* group);
struct DatabaseData
{
QString filePath;
bool isReadOnly = false;
QUuid cipher = KeePass2::CIPHER_AES256;
CompressionAlgorithm compressionAlgorithm = CompressionGZip;
QByteArray transformedMasterKey;
QSharedPointer<Kdf> kdf = QSharedPointer<AesKdf>::create(true);
QSharedPointer<const CompositeKey> key;
bool hasKey = false;
QByteArray masterSeed;
QByteArray challengeResponseKey;
QVariantMap publicCustomData;
DatabaseData()
{
kdf->randomizeSeed();
}
};
void createRecycleBin();
QString writeDatabase(QIODevice* device);
bool writeDatabase(QIODevice* device, QString* error = nullptr);
bool backupDatabase(const QString& filePath);
Metadata* const m_metadata;
DatabaseData m_data;
Group* m_rootGroup;
QList<DeletedObject> m_deletedObjects;
QTimer* m_timer;
DatabaseData m_data;
QPointer<QTimer> m_timer;
bool m_initialized = false;
bool m_modified = false;
bool m_emitModified;
QString m_filePath;
QUuid m_uuid;
static QHash<QUuid, Database*> m_uuidMap;
static QHash<QUuid, QPointer<Database>> s_uuidMap;
static QHash<QString, QPointer<Database>> s_filePathMap;
};
#endif // KEEPASSX_DATABASE_H

View File

@ -26,7 +26,6 @@
#include "core/Metadata.h"
#include "totp/totp.h"
#include <QDebug>
#include <QDir>
#include <QRegularExpression>
#include <utility>
@ -49,15 +48,15 @@ Entry::Entry()
m_data.autoTypeEnabled = true;
m_data.autoTypeObfuscation = 0;
connect(m_attributes, SIGNAL(modified()), SLOT(updateTotp()));
connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified()));
connect(m_attributes, SIGNAL(entryAttributesModified()), SLOT(updateTotp()));
connect(m_attributes, SIGNAL(entryAttributesModified()), this, SIGNAL(entryModified()));
connect(m_attributes, SIGNAL(defaultKeyModified()), SLOT(emitDataChanged()));
connect(m_attachments, SIGNAL(modified()), this, SIGNAL(modified()));
connect(m_autoTypeAssociations, SIGNAL(modified()), SIGNAL(modified()));
connect(m_customData, SIGNAL(modified()), this, SIGNAL(modified()));
connect(m_attachments, SIGNAL(entryAttachmentsModified()), this, SIGNAL(entryModified()));
connect(m_autoTypeAssociations, SIGNAL(modified()), SIGNAL(entryModified()));
connect(m_customData, SIGNAL(customDataModified()), this, SIGNAL(entryModified()));
connect(this, SIGNAL(modified()), SLOT(updateTimeinfo()));
connect(this, SIGNAL(modified()), SLOT(updateModifiedSinceBegin()));
connect(this, SIGNAL(entryModified()), SLOT(updateTimeinfo()));
connect(this, SIGNAL(entryModified()), SLOT(updateModifiedSinceBegin()));
}
Entry::~Entry()
@ -78,7 +77,7 @@ template <class T> inline bool Entry::set(T& property, const T& value)
{
if (property != value) {
property = value;
emit modified();
emit entryModified();
return true;
}
return false;
@ -409,7 +408,7 @@ void Entry::setIcon(int iconNumber)
m_data.iconNumber = iconNumber;
m_data.customIcon = QUuid();
emit modified();
emit entryModified();
emitDataChanged();
}
}
@ -422,7 +421,7 @@ void Entry::setIcon(const QUuid& uuid)
m_data.customIcon = uuid;
m_data.iconNumber = 0;
emit modified();
emit entryModified();
emitDataChanged();
}
}
@ -502,7 +501,7 @@ void Entry::setExpires(const bool& value)
{
if (m_data.timeInfo.expires() != value) {
m_data.timeInfo.setExpires(value);
emit modified();
emit entryModified();
}
}
@ -510,7 +509,7 @@ void Entry::setExpiryTime(const QDateTime& dateTime)
{
if (m_data.timeInfo.expiryTime() != dateTime) {
m_data.timeInfo.setExpiryTime(dateTime);
emit modified();
emit entryModified();
}
}
@ -529,7 +528,7 @@ void Entry::addHistoryItem(Entry* entry)
Q_ASSERT(!entry->parent());
m_history.append(entry);
emit modified();
emit entryModified();
}
void Entry::removeHistoryItems(const QList<Entry*>& historyEntries)
@ -547,7 +546,7 @@ void Entry::removeHistoryItems(const QList<Entry*>& historyEntries)
delete entry;
}
emit modified();
emit entryModified();
}
void Entry::truncateHistory()
@ -742,7 +741,7 @@ void Entry::updateModifiedSinceBegin()
QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const
{
if (maxDepth <= 0) {
qWarning() << QString("Maximum depth of replacement has been reached. Entry uuid: %1").arg(uuid().toString());
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
return str;
}
@ -766,7 +765,7 @@ QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxD
QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDepth) const
{
if (maxDepth <= 0) {
qWarning() << QString("Maximum depth of replacement has been reached. Entry uuid: %1").arg(uuid().toString());
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
return placeholder;
}
@ -830,7 +829,7 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder, int maxDepth) const
{
if (maxDepth <= 0) {
qWarning() << QString("Maximum depth of replacement has been reached. Entry uuid: %1").arg(uuid().toString());
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
return placeholder;
}
@ -850,7 +849,7 @@ QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder,
Q_ASSERT(m_group);
Q_ASSERT(m_group->database());
const Entry* refEntry = m_group->database()->resolveEntry(searchText, searchInType);
const Entry* refEntry = m_group->findEntryBySearchTerm(searchText, searchInType);
if (refEntry) {
const QString wantedField = match.captured(EntryAttributes::WantedFieldGroupName);
@ -930,7 +929,7 @@ void Entry::setGroup(Group* group)
void Entry::emitDataChanged()
{
emit dataChanged(this);
emit entryDataChanged(this);
}
const Database* Entry::database() const

View File

@ -221,9 +221,8 @@ signals:
/**
* Emitted when a default attribute has been changed.
*/
void dataChanged(Entry* entry);
void modified();
void entryDataChanged(Entry* entry);
void entryModified();
private slots:
void emitDataChanged();

View File

@ -66,7 +66,7 @@ void EntryAttachments::set(const QString& key, const QByteArray& value)
}
if (emitModified) {
emit modified();
emit entryAttachmentsModified();
}
}
@ -82,7 +82,7 @@ void EntryAttachments::remove(const QString& key)
m_attachments.remove(key);
emit removed(key);
emit modified();
emit entryAttachmentsModified();
}
void EntryAttachments::remove(const QStringList& keys)
@ -106,7 +106,7 @@ void EntryAttachments::remove(const QStringList& keys)
}
if (isModified) {
emit modified();
emit entryAttachmentsModified();
}
}
@ -126,7 +126,7 @@ void EntryAttachments::clear()
m_attachments.clear();
emit reset();
emit modified();
emit entryAttachmentsModified();
}
void EntryAttachments::copyDataFrom(const EntryAttachments* other)
@ -137,7 +137,7 @@ void EntryAttachments::copyDataFrom(const EntryAttachments* other)
m_attachments = other->m_attachments;
emit reset();
emit modified();
emit entryAttachmentsModified();
}
}

View File

@ -44,7 +44,7 @@ public:
int attachmentsSize() const;
signals:
void modified();
void entryAttachmentsModified();
void keyModified(const QString& key);
void aboutToBeAdded(const QString& key);
void added(const QString& key);

View File

@ -118,7 +118,7 @@ void EntryAttributes::set(const QString& key, const QString& value, bool protect
}
if (emitModified) {
emit modified();
emit entryAttributesModified();
}
if (defaultAttribute && changeValue) {
@ -145,7 +145,7 @@ void EntryAttributes::remove(const QString& key)
m_protectedAttributes.remove(key);
emit removed(key);
emit modified();
emit entryAttributesModified();
}
void EntryAttributes::rename(const QString& oldKey, const QString& newKey)
@ -175,7 +175,7 @@ void EntryAttributes::rename(const QString& oldKey, const QString& newKey)
m_protectedAttributes.insert(newKey);
}
emit modified();
emit entryAttributesModified();
emit renamed(oldKey, newKey);
}
@ -207,7 +207,7 @@ void EntryAttributes::copyCustomKeysFrom(const EntryAttributes* other)
}
emit reset();
emit modified();
emit entryAttributesModified();
}
bool EntryAttributes::areCustomKeysDifferent(const EntryAttributes* other)
@ -240,7 +240,7 @@ void EntryAttributes::copyDataFrom(const EntryAttributes* other)
m_protectedAttributes = other->m_protectedAttributes;
emit reset();
emit modified();
emit entryAttributesModified();
}
}
@ -275,7 +275,7 @@ void EntryAttributes::clear()
}
emit reset();
emit modified();
emit entryAttributesModified();
}
int EntryAttributes::attributesSize() const

View File

@ -66,7 +66,7 @@ public:
static const QString SearchTextGroupName;
signals:
void modified();
void entryAttributesModified();
void defaultKeyModified();
void customKeyModified(const QString& key);
void aboutToBeAdded(const QString& key);

View File

@ -43,8 +43,8 @@ Group::Group()
m_data.searchingEnabled = Inherit;
m_data.mergeMode = Default;
connect(m_customData, SIGNAL(modified()), this, SIGNAL(modified()));
connect(this, SIGNAL(modified()), SLOT(updateTimeinfo()));
connect(m_customData, SIGNAL(customDataModified()), this, SIGNAL(groupModified()));
connect(this, SIGNAL(groupModified()), SLOT(updateTimeinfo()));
}
Group::~Group()
@ -87,7 +87,7 @@ template <class P, class V> inline bool Group::set(P& property, const V& value)
{
if (property != value) {
property = value;
emit modified();
emit groupModified();
return true;
} else {
return false;
@ -310,7 +310,7 @@ void Group::setUuid(const QUuid& uuid)
void Group::setName(const QString& name)
{
if (set(m_data.name, name)) {
emit dataChanged(this);
emit groupDataChanged(this);
}
}
@ -324,8 +324,8 @@ void Group::setIcon(int iconNumber)
if (iconNumber >= 0 && (m_data.iconNumber != iconNumber || !m_data.customIcon.isNull())) {
m_data.iconNumber = iconNumber;
m_data.customIcon = QUuid();
emit modified();
emit dataChanged(this);
emit groupModified();
emit groupDataChanged(this);
}
}
@ -334,8 +334,8 @@ void Group::setIcon(const QUuid& uuid)
if (!uuid.isNull() && m_data.customIcon != uuid) {
m_data.customIcon = uuid;
m_data.iconNumber = 0;
emit modified();
emit dataChanged(this);
emit groupModified();
emit groupDataChanged(this);
}
}
@ -352,7 +352,7 @@ void Group::setExpanded(bool expanded)
updateTimeinfo();
return;
}
emit modified();
emit groupModified();
}
}
@ -380,7 +380,7 @@ void Group::setExpires(bool value)
{
if (m_data.timeInfo.expires() != value) {
m_data.timeInfo.setExpires(value);
emit modified();
emit groupModified();
}
}
@ -388,7 +388,7 @@ void Group::setExpiryTime(const QDateTime& dateTime)
{
if (m_data.timeInfo.expiryTime() != dateTime) {
m_data.timeInfo.setExpiryTime(dateTime);
emit modified();
emit groupModified();
}
}
@ -441,10 +441,10 @@ void Group::setParent(Group* parent, int index)
}
}
if (m_db != parent->m_db) {
recSetDatabase(parent->m_db);
connectDatabaseSignalsRecursive(parent->m_db);
}
QObject::setParent(parent);
emit aboutToAdd(this, index);
emit groupAboutToAdd(this, index);
Q_ASSERT(index <= parent->m_children.size());
parent->m_children.insert(index, this);
} else {
@ -460,12 +460,12 @@ void Group::setParent(Group* parent, int index)
m_data.timeInfo.setLocationChanged(Clock::currentDateTimeUtc());
}
emit modified();
emit groupModified();
if (!moveWithinDatabase) {
emit added();
emit groupAdded();
} else {
emit moved();
emit groupMoved();
}
}
@ -477,7 +477,7 @@ void Group::setParent(Database* db)
cleanupParent();
m_parent = nullptr;
recSetDatabase(db);
connectDatabaseSignalsRecursive(db);
QObject::setParent(db);
}
@ -578,6 +578,53 @@ Entry* Group::findEntryByPath(const QString& entryPath)
return findEntryByPathRecursive(normalizedEntryPath, "/");
}
Entry* Group::findEntryBySearchTerm(const QString& term, EntryReferenceType referenceType)
{
Q_ASSERT_X(referenceType != EntryReferenceType::Unknown,
"Database::findEntryRecursive",
"Can't search entry with \"referenceType\" parameter equal to \"Unknown\"");
const QList<Group*> groups = groupsRecursive(true);
for (const Group* group : groups) {
bool found = false;
const QList<Entry*>& entryList = group->entries();
for (Entry* entry : entryList) {
switch (referenceType) {
case EntryReferenceType::Unknown:
return nullptr;
case EntryReferenceType::Title:
found = entry->title() == term;
break;
case EntryReferenceType::UserName:
found = entry->username() == term;
break;
case EntryReferenceType::Password:
found = entry->password() == term;
break;
case EntryReferenceType::Url:
found = entry->url() == term;
break;
case EntryReferenceType::Notes:
found = entry->notes() == term;
break;
case EntryReferenceType::QUuid:
found = entry->uuid() == QUuid::fromRfc4122(QByteArray::fromHex(term.toLatin1()));
break;
case EntryReferenceType::CustomAttributes:
found = entry->attributes()->containsValue(term);
break;
}
if (found) {
return entry;
}
}
}
return nullptr;
}
Entry* Group::findEntryByPathRecursive(const QString& entryPath, const QString& basePath)
{
// Return the first entry that matches the full path OR if there is no leading
@ -798,12 +845,12 @@ void Group::addEntry(Entry* entry)
emit entryAboutToAdd(entry);
m_entries << entry;
connect(entry, SIGNAL(dataChanged(Entry*)), SIGNAL(entryDataChanged(Entry*)));
connect(entry, SIGNAL(entryDataChanged(Entry*)), SIGNAL(entryDataChanged(Entry*)));
if (m_db) {
connect(entry, SIGNAL(modified()), m_db, SIGNAL(modifiedImmediate()));
connect(entry, SIGNAL(entryModified()), m_db, SLOT(markAsModified()));
}
emit modified();
emit groupModified();
emit entryAdded(entry);
}
@ -820,21 +867,21 @@ void Group::removeEntry(Entry* entry)
entry->disconnect(m_db);
}
m_entries.removeAll(entry);
emit modified();
emit groupModified();
emit entryRemoved(entry);
}
void Group::recSetDatabase(Database* db)
void Group::connectDatabaseSignalsRecursive(Database* db)
{
if (m_db) {
disconnect(SIGNAL(dataChanged(Group*)), m_db);
disconnect(SIGNAL(aboutToRemove(Group*)), m_db);
disconnect(SIGNAL(removed()), m_db);
disconnect(SIGNAL(aboutToAdd(Group*, int)), m_db);
disconnect(SIGNAL(added()), m_db);
disconnect(SIGNAL(groupDataChanged(Group*)), m_db);
disconnect(SIGNAL(groupAboutToRemove(Group*)), m_db);
disconnect(SIGNAL(groupRemoved()), m_db);
disconnect(SIGNAL(groupAboutToAdd(Group*, int)), m_db);
disconnect(SIGNAL(groupAdded()), m_db);
disconnect(SIGNAL(aboutToMove(Group*, Group*, int)), m_db);
disconnect(SIGNAL(moved()), m_db);
disconnect(SIGNAL(modified()), m_db);
disconnect(SIGNAL(groupMoved()), m_db);
disconnect(SIGNAL(groupModified()), m_db);
}
for (Entry* entry : asConst(m_entries)) {
@ -842,35 +889,35 @@ void Group::recSetDatabase(Database* db)
entry->disconnect(m_db);
}
if (db) {
connect(entry, SIGNAL(modified()), db, SIGNAL(modifiedImmediate()));
connect(entry, SIGNAL(entryModified()), db, SLOT(markAsModified()));
}
}
if (db) {
connect(this, SIGNAL(dataChanged(Group*)), db, SIGNAL(groupDataChanged(Group*)));
connect(this, SIGNAL(aboutToRemove(Group*)), db, SIGNAL(groupAboutToRemove(Group*)));
connect(this, SIGNAL(removed()), db, SIGNAL(groupRemoved()));
connect(this, SIGNAL(aboutToAdd(Group*,int)), db, SIGNAL(groupAboutToAdd(Group*,int)));
connect(this, SIGNAL(added()), db, SIGNAL(groupAdded()));
connect(this, SIGNAL(groupDataChanged(Group*)), db, SIGNAL(groupDataChanged(Group*)));
connect(this, SIGNAL(groupAboutToRemove(Group*)), db, SIGNAL(groupAboutToRemove(Group*)));
connect(this, SIGNAL(groupRemoved()), db, SIGNAL(groupRemoved()));
connect(this, SIGNAL(groupAboutToAdd(Group*, int)), db, SIGNAL(groupAboutToAdd(Group*,int)));
connect(this, SIGNAL(groupAdded()), db, SIGNAL(groupAdded()));
connect(this, SIGNAL(aboutToMove(Group*,Group*,int)), db, SIGNAL(groupAboutToMove(Group*,Group*,int)));
connect(this, SIGNAL(moved()), db, SIGNAL(groupMoved()));
connect(this, SIGNAL(modified()), db, SIGNAL(modifiedImmediate()));
connect(this, SIGNAL(groupMoved()), db, SIGNAL(groupMoved()));
connect(this, SIGNAL(groupModified()), db, SLOT(markAsModified()));
}
m_db = db;
for (Group* group : asConst(m_children)) {
group->recSetDatabase(db);
group->connectDatabaseSignalsRecursive(db);
}
}
void Group::cleanupParent()
{
if (m_parent) {
emit aboutToRemove(this);
emit groupAboutToRemove(this);
m_parent->m_children.removeAll(this);
emit modified();
emit removed();
emit groupModified();
emit groupRemoved();
}
}
@ -965,7 +1012,7 @@ Entry* Group::addEntryWithPath(const QString& entryPath)
return nullptr;
}
Entry* entry = new Entry();
auto* entry = new Entry();
entry->setTitle(entryTitle);
entry->setUuid(QUuid::createUuid());
entry->setGroup(group);

View File

@ -116,6 +116,7 @@ public:
Group* findChildByName(const QString& name);
Entry* findEntryByUuid(const QUuid& uuid) const;
Entry* findEntryByPath(const QString& entryPath);
Entry* findEntryBySearchTerm(const QString& term, EntryReferenceType referenceType);
Group* findGroupByUuid(const QUuid& uuid);
Group* findGroupByPath(const QString& groupPath);
QStringList locate(const QString& locateTerm, const QString& currentPath = {"/"}) const;
@ -149,6 +150,7 @@ public:
const QList<Group*>& children() const;
QList<Entry*> entries();
const QList<Entry*>& entries() const;
Entry* findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group = nullptr);
QList<Entry*> entriesRecursive(bool includeHistoryItems = false) const;
QList<const Group*> groupsRecursive(bool includeSelf) const;
QList<Group*> groupsRecursive(bool includeSelf);
@ -164,14 +166,14 @@ public:
void removeEntry(Entry* entry);
signals:
void dataChanged(Group* group);
void aboutToAdd(Group* group, int index);
void added();
void aboutToRemove(Group* group);
void removed();
void groupDataChanged(Group* group);
void groupAboutToAdd(Group* group, int index);
void groupAdded();
void groupAboutToRemove(Group* group);
void groupRemoved();
void aboutToMove(Group* group, Group* toGroup, int index);
void moved();
void modified();
void groupMoved();
void groupModified();
void entryAboutToAdd(Entry* entry);
void entryAdded(Entry* entry);
void entryAboutToRemove(Entry* entry);
@ -186,7 +188,7 @@ private:
void setParent(Database* db);
void recSetDatabase(Database* db);
void connectDatabaseSignalsRecursive(Database* db);
void cleanupParent();
void recCreateDelObjects();

View File

@ -53,14 +53,14 @@ Metadata::Metadata(QObject* parent)
m_masterKeyChanged = now;
m_settingsChanged = now;
connect(m_customData, SIGNAL(modified()), this, SIGNAL(modified()));
connect(m_customData, SIGNAL(customDataModified()), this, SIGNAL(metadataModified()));
}
template <class P, class V> bool Metadata::set(P& property, const V& value)
{
if (property != value) {
property = value;
emit modified();
emit metadataModified();
return true;
} else {
return false;
@ -74,7 +74,7 @@ template <class P, class V> bool Metadata::set(P& property, const V& value, QDat
if (m_updateDatetime) {
dateTime = Clock::currentDateTimeUtc();
}
emit modified();
emit metadataModified();
return true;
} else {
return false;
@ -311,9 +311,7 @@ void Metadata::setGenerator(const QString& value)
void Metadata::setName(const QString& value)
{
if (set(m_data.name, value, m_data.nameChanged)) {
emit nameTextChanged();
}
set(m_data.name, value, m_data.nameChanged);
}
void Metadata::setNameChanged(const QDateTime& value)
@ -393,7 +391,7 @@ void Metadata::addCustomIcon(const QUuid& uuid, const QImage& icon)
QByteArray hash = hashImage(icon);
m_customIconsHashes[hash] = uuid;
Q_ASSERT(m_customIcons.count() == m_customIconsOrder.count());
emit modified();
emit metadataModified();
}
void Metadata::addCustomIconScaled(const QUuid& uuid, const QImage& icon)
@ -428,7 +426,7 @@ void Metadata::removeCustomIcon(const QUuid& uuid)
m_customIconScaledCacheKeys.remove(uuid);
m_customIconsOrder.removeAll(uuid);
Q_ASSERT(m_customIcons.count() == m_customIconsOrder.count());
emit modified();
emit metadataModified();
}
QUuid Metadata::findCustomIcon(const QImage &candidate)

View File

@ -148,8 +148,7 @@ public:
void copyAttributesFrom(const Metadata* other);
signals:
void nameTextChanged();
void modified();
void metadataModified();
private:
template <class P, class V> bool set(P& property, const V& value);

View File

@ -20,8 +20,6 @@
#include "config-keepassx.h"
#include "crypto/SymmetricCipherGcrypt.h"
#include <QDebug>
SymmetricCipher::SymmetricCipher(Algorithm algo, Mode mode, Direction direction)
: m_backend(createBackend(algo, mode, direction))
, m_initialized(false)
@ -102,7 +100,7 @@ SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(const QUuid& ciphe
return Twofish;
}
qWarning() << "SymmetricCipher::cipherToAlgorithm: invalid UUID " << cipher;
qWarning("SymmetricCipher::cipherToAlgorithm: invalid UUID %s", cipher.toString().toLatin1().data());
return InvalidAlgorithm;
}

View File

@ -23,7 +23,7 @@
#include "core/Database.h"
#include "core/Group.h"
bool CsvExporter::exportDatabase(const QString& filename, const Database* db)
bool CsvExporter::exportDatabase(const QString& filename, QSharedPointer<const Database> db)
{
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
@ -33,7 +33,7 @@ bool CsvExporter::exportDatabase(const QString& filename, const Database* db)
return exportDatabase(&file, db);
}
bool CsvExporter::exportDatabase(QIODevice* device, const Database* db)
bool CsvExporter::exportDatabase(QIODevice* device, QSharedPointer<const Database> db)
{
QString header;
addColumn(header, "Group");

View File

@ -20,6 +20,7 @@
#define KEEPASSX_CSVEXPORTER_H
#include <QString>
#include <QtCore/QSharedPointer>
class Database;
class Group;
@ -28,8 +29,8 @@ class QIODevice;
class CsvExporter
{
public:
bool exportDatabase(const QString& filename, const Database* db);
bool exportDatabase(QIODevice* device, const Database* db);
bool exportDatabase(const QString& filename, QSharedPointer<const Database> db);
bool exportDatabase(QIODevice* device, QSharedPointer<const Database> db);
QString errorString() const;
private:

View File

@ -29,77 +29,77 @@
#include <QBuffer>
Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
bool keepDatabase)
bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
Database* db)
{
Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3_1);
if (hasError()) {
return nullptr;
return false;
}
// check if all required headers were present
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_streamStartBytes.isEmpty()
|| m_protectedStreamKey.isEmpty()
|| m_db->cipher().isNull()) {
|| db->cipher().isNull()) {
raiseError(tr("missing database headers"));
return nullptr;
return false;
}
if (!m_db->setKey(key, false)) {
if (!db->setKey(key, false)) {
raiseError(tr("Unable to calculate master key"));
return nullptr;
return false;
}
if (!m_db->challengeMasterSeed(m_masterSeed)) {
if (!db->challengeMasterSeed(m_masterSeed)) {
raiseError(tr("Unable to issue challenge-response."));
return nullptr;
return false;
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
hash.addData(m_db->challengeResponseKey());
hash.addData(m_db->transformedMasterKey());
hash.addData(db->challengeResponseKey());
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher());
SymmetricCipherStream cipherStream(
device, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString());
return nullptr;
return false;
}
if (!cipherStream.open(QIODevice::ReadOnly)) {
raiseError(cipherStream.errorString());
return nullptr;
return false;
}
QByteArray realStart = cipherStream.read(32);
if (realStart != m_streamStartBytes) {
raiseError(tr("Wrong key or database file is corrupt."));
return nullptr;
return false;
}
HashedBlockStream hashedStream(&cipherStream);
if (!hashedStream.open(QIODevice::ReadOnly)) {
raiseError(hashedStream.errorString());
return nullptr;
return false;
}
QIODevice* xmlDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (m_db->compressionAlgo() == Database::CompressionNone) {
if (db->compressionAlgorithm() == Database::CompressionNone) {
xmlDevice = &hashedStream;
} else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
if (!ioCompressor->open(QIODevice::ReadOnly)) {
raiseError(ioCompressor->errorString());
return nullptr;
return false;
}
xmlDevice = ioCompressor.data();
}
@ -107,20 +107,17 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20);
if (!randomStream.init(m_protectedStreamKey)) {
raiseError(randomStream.errorString());
return nullptr;
return false;
}
Q_ASSERT(xmlDevice);
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3_1);
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
xmlReader.readDatabase(xmlDevice, db, &randomStream);
if (xmlReader.hasError()) {
raiseError(xmlReader.errorString());
if (keepDatabase) {
return m_db.take();
}
return nullptr;
return false;
}
Q_ASSERT(!xmlReader.headerHash().isEmpty() || m_kdbxVersion < KeePass2::FILE_VERSION_3_1);
@ -129,15 +126,17 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);
if (headerHash != xmlReader.headerHash()) {
raiseError(tr("Header doesn't match hash"));
return nullptr;
return false;
}
}
return m_db.take();
return true;
}
bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream)
bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream, Database* db)
{
Q_UNUSED(db);
QByteArray fieldIDArray = headerStream.read(1);
if (fieldIDArray.size() != 1) {
raiseError(tr("Invalid header id size"));

View File

@ -29,13 +29,13 @@ class Kdbx3Reader : public KdbxReader
Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader)
public:
Database* readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
bool keepDatabase) override;
bool readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
Database* db) override;
protected:
bool readHeaderField(StoreDataStream& headerStream) override;
bool readHeaderField(StoreDataStream& headerStream, Database* db) override;
};
#endif // KEEPASSX_KDBX3READER_H

View File

@ -67,7 +67,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CompressionFlags,
Endian::sizedIntToBytes<qint32>(db->compressionAlgo(),
Endian::sizedIntToBytes<qint32>(db->compressionAlgorithm(),
KeePass2::BYTEORDER)));
auto kdf = db->kdf();
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
@ -112,7 +112,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
QIODevice* outputDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) {
if (db->compressionAlgorithm() == Database::CompressionNone) {
outputDevice = &hashedStream;
} else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));

View File

@ -28,85 +28,85 @@
#include "streams/QtIOCompressor"
#include "streams/SymmetricCipherStream.h"
Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
bool keepDatabase)
bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
Database* db)
{
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
m_binaryPoolInverse.clear();
if (hasError()) {
return nullptr;
return false;
}
// check if all required headers were present
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_db->cipher().isNull()) {
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || db->cipher().isNull()) {
raiseError(tr("missing database headers"));
return nullptr;
return false;
}
if (!m_db->setKey(key, false, false)) {
if (!db->setKey(key, false, false)) {
raiseError(tr("Unable to calculate master key"));
return nullptr;
return false;
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
hash.addData(m_db->transformedMasterKey());
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
QByteArray headerSha256 = device->read(32);
QByteArray headerHmac = device->read(32);
if (headerSha256.size() != 32 || headerHmac.size() != 32) {
raiseError(tr("Invalid header checksum size"));
return nullptr;
return false;
}
if (headerSha256 != CryptoHash::hash(headerData, CryptoHash::Sha256)) {
raiseError(tr("Header SHA256 mismatch"));
return nullptr;
return false;
}
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, m_db->transformedMasterKey());
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, db->transformedMasterKey());
if (headerHmac
!= CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
raiseError(tr("Wrong key or database file is corrupt. (HMAC mismatch)"));
return nullptr;
return false;
}
HmacBlockStream hmacStream(device, hmacKey);
if (!hmacStream.open(QIODevice::ReadOnly)) {
raiseError(hmacStream.errorString());
return nullptr;
return false;
}
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher());
if (cipher == SymmetricCipher::InvalidAlgorithm) {
raiseError(tr("Unknown cipher"));
return nullptr;
return false;
}
SymmetricCipherStream cipherStream(
&hmacStream, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString());
return nullptr;
return false;
}
if (!cipherStream.open(QIODevice::ReadOnly)) {
raiseError(cipherStream.errorString());
return nullptr;
return false;
}
QIODevice* xmlDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (m_db->compressionAlgo() == Database::CompressionNone) {
if (db->compressionAlgorithm() == Database::CompressionNone) {
xmlDevice = &cipherStream;
} else {
ioCompressor.reset(new QtIOCompressor(&cipherStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
if (!ioCompressor->open(QIODevice::ReadOnly)) {
raiseError(ioCompressor->errorString());
return nullptr;
return false;
}
xmlDevice = ioCompressor.data();
}
@ -115,32 +115,29 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
}
if (hasError()) {
return nullptr;
return false;
}
KeePass2RandomStream randomStream(m_irsAlgo);
if (!randomStream.init(m_protectedStreamKey)) {
raiseError(randomStream.errorString());
return nullptr;
return false;
}
Q_ASSERT(xmlDevice);
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, binaryPool());
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
xmlReader.readDatabase(xmlDevice, db, &randomStream);
if (xmlReader.hasError()) {
raiseError(xmlReader.errorString());
if (keepDatabase) {
return m_db.take();
}
return nullptr;
return false;
}
return m_db.take();
return true;
}
bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
bool Kdbx4Reader::readHeaderField(StoreDataStream& device, Database* db)
{
QByteArray fieldIDArray = device.read(1);
if (fieldIDArray.size() != 1) {
@ -197,7 +194,7 @@ bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
raiseError(tr("Unsupported key derivation function (KDF) or invalid parameters"));
return false;
}
m_db->setKdf(kdf);
db->setKdf(kdf);
break;
}
@ -206,7 +203,7 @@ bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
variantBuffer.setBuffer(&fieldData);
variantBuffer.open(QBuffer::ReadOnly);
QVariantMap data = readVariantMap(&variantBuffer);
m_db->setPublicCustomData(data);
db->setPublicCustomData(data);
break;
}

View File

@ -30,15 +30,15 @@ class Kdbx4Reader : public KdbxReader
Q_DECLARE_TR_FUNCTIONS(Kdbx4Reader)
public:
Database* readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
bool keepDatabase) override;
bool readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
Database* db) override;
QHash<QByteArray, QString> binaryPoolInverse() const;
QHash<QString, QByteArray> binaryPool() const;
protected:
bool readHeaderField(StoreDataStream& headerStream) override;
bool readHeaderField(StoreDataStream& headerStream, Database* db) override;
private:
bool readInnerHeaderField(QIODevice* device);

View File

@ -75,7 +75,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CompressionFlags,
Endian::sizedIntToBytes(static_cast<int>(db->compressionAlgo()),
Endian::sizedIntToBytes(static_cast<int>(db->compressionAlgorithm()),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
@ -138,7 +138,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
QIODevice* outputDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) {
if (db->compressionAlgorithm() == Database::CompressionNone) {
outputDevice = cipherStream.data();
} else {
ioCompressor.reset(new QtIOCompressor(cipherStream.data()));

View File

@ -59,14 +59,14 @@ bool KdbxReader::readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig
*
* @param device input device
* @param key database encryption composite key
* @param keepDatabase keep database in case of read failure
* @return pointer to the read database, nullptr on failure
* @param db database to read into
* @return true on success
*/
Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase)
bool KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db)
{
device->seek(0);
m_db.reset(new Database());
m_db = db;
m_xmlData.clear();
m_masterSeed.clear();
m_encryptionIV.clear();
@ -79,7 +79,7 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const Compo
// read KDBX magic numbers
quint32 sig1, sig2;
if (!readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion)) {
return nullptr;
return false;
}
m_kdbxSignature = qMakePair(sig1, sig2);
@ -87,24 +87,24 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const Compo
m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK;
// read header fields
while (readHeaderField(headerStream) && !hasError()) {
while (readHeaderField(headerStream, m_db) && !hasError()) {
}
headerStream.close();
if (hasError()) {
return nullptr;
return false;
}
// read payload
auto* db = readDatabaseImpl(device, headerStream.storedData(), std::move(key), keepDatabase);
bool ok = readDatabaseImpl(device, headerStream.storedData(), std::move(key), db);
if (saveXml()) {
m_xmlData.clear();
decryptXmlInnerStream(m_xmlData, db);
}
return db;
return ok;
}
bool KdbxReader::hasError() const
@ -175,7 +175,7 @@ void KdbxReader::setCompressionFlags(const QByteArray& data)
raiseError(tr("Unsupported compression algorithm"));
return;
}
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
m_db->setCompressionAlgorithm(static_cast<Database::CompressionAlgorithm>(id));
}
/**

View File

@ -40,7 +40,7 @@ public:
virtual ~KdbxReader() = default;
static bool readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version);
Database* readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase = false);
bool readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db);
bool hasError() const;
QString errorString() const;
@ -57,22 +57,22 @@ protected:
* @param device input device at the payload starting position
* @param KDBX header data as bytes
* @param key database encryption composite key
* @param keepDatabase keep database in case of read failure
* @return pointer to the read database, nullptr on failure
* @param db database to read into
* @return true on success
*/
virtual Database*
readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
bool keepDatabase) = 0;
virtual bool readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
Database* db) = 0;
/**
* Read next header field from stream.
*
* @param headerStream input header stream
* @param database to read header field for
* @return true if there are more header fields
*/
virtual bool readHeaderField(StoreDataStream& headerStream) = 0;
virtual bool readHeaderField(StoreDataStream& headerStream, Database* db) = 0;
virtual void setCipher(const QByteArray& data);
virtual void setCompressionFlags(const QByteArray& data);
@ -88,9 +88,6 @@ protected:
void decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const;
QScopedPointer<Database> m_db;
QPair<quint32, quint32> m_kdbxSignature;
quint32 m_kdbxVersion = 0;
QByteArray m_masterSeed;
@ -102,6 +99,9 @@ protected:
QByteArray m_xmlData;
private:
QPair<quint32, quint32> m_kdbxSignature;
QPointer<Database> m_db;
bool m_saveXml = false;
bool m_error = false;
QString m_errorStr = "";

View File

@ -56,7 +56,7 @@ KdbxXmlReader::KdbxXmlReader(quint32 version, QHash<QString, QByteArray> binary
* @param device input file
* @return pointer to the new database
*/
Database* KdbxXmlReader::readDatabase(const QString& filename)
QSharedPointer<Database> KdbxXmlReader::readDatabase(const QString& filename)
{
QFile file(filename);
file.open(QIODevice::ReadOnly);
@ -69,10 +69,10 @@ Database* KdbxXmlReader::readDatabase(const QString& filename)
* @param device input device
* @return pointer to the new database
*/
Database* KdbxXmlReader::readDatabase(QIODevice* device)
QSharedPointer<Database> KdbxXmlReader::readDatabase(QIODevice* device)
{
auto db = new Database();
readDatabase(device, db);
auto db = QSharedPointer<Database>::create();
readDatabase(device, db.data());
return db;
}

View File

@ -45,8 +45,8 @@ public:
explicit KdbxXmlReader(quint32 version, QHash<QString, QByteArray> binaryPool);
virtual ~KdbxXmlReader() = default;
virtual Database* readDatabase(const QString& filename);
virtual Database* readDatabase(QIODevice* device);
virtual QSharedPointer<Database> readDatabase(const QString& filename);
virtual QSharedPointer<Database> readDatabase(QIODevice* device);
virtual void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
bool hasError() const;

View File

@ -190,7 +190,7 @@ void KdbxXmlWriter::writeBinaries()
m_xml.writeAttribute("ID", QString::number(i.value()));
QByteArray data;
if (m_db->compressionAlgo() == Database::CompressionGZip) {
if (m_db->compressionAlgorithm() == Database::CompressionGZip) {
m_xml.writeAttribute("Compressed", "True");
QBuffer buffer;

View File

@ -48,8 +48,7 @@ private:
};
KeePass1Reader::KeePass1Reader()
: m_db(nullptr)
, m_tmpParent(nullptr)
: m_tmpParent(nullptr)
, m_device(nullptr)
, m_encryptionFlags(0)
, m_transformRounds(0)
@ -57,7 +56,7 @@ KeePass1Reader::KeePass1Reader()
{
}
Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice)
QSharedPointer<Database> KeePass1Reader::readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice)
{
m_error = false;
m_errorStr.clear();
@ -70,22 +69,22 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
if (keyfileData.isEmpty()) {
raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
return nullptr;
return {};
}
if (!keyfileDevice->seek(0)) {
raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
return nullptr;
return {};
}
if (!newFileKey->load(keyfileDevice)) {
raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
return nullptr;
return {};
}
}
QScopedPointer<Database> db(new Database());
auto db = QSharedPointer<Database>::create();
QScopedPointer<Group> tmpParent(new Group());
m_db = db.data();
m_db = db;
m_tmpParent = tmpParent.data();
m_device = device;
@ -94,19 +93,19 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
auto signature1 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok || signature1 != KeePass1::SIGNATURE_1) {
raiseError(tr("Not a KeePass database."));
return nullptr;
return {};
}
auto signature2 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok || signature2 != KeePass1::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
return {};
}
m_encryptionFlags = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok || !(m_encryptionFlags & KeePass1::Rijndael || m_encryptionFlags & KeePass1::Twofish)) {
raiseError(tr("Unsupported encryption algorithm."));
return nullptr;
return {};
}
auto version = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
@ -114,49 +113,49 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|| (version & KeePass1::FILE_VERSION_CRITICAL_MASK)
!= (KeePass1::FILE_VERSION & KeePass1::FILE_VERSION_CRITICAL_MASK)) {
raiseError(tr("Unsupported KeePass database version."));
return nullptr;
return {};
}
m_masterSeed = m_device->read(16);
if (m_masterSeed.size() != 16) {
raiseError("Unable to read master seed");
return nullptr;
return {};
}
m_encryptionIV = m_device->read(16);
if (m_encryptionIV.size() != 16) {
raiseError(tr("Unable to read encryption IV", "IV = Initialization Vector for symmetric cipher"));
return nullptr;
return {};
}
auto numGroups = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok) {
raiseError(tr("Invalid number of groups"));
return nullptr;
return {};
}
auto numEntries = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok) {
raiseError(tr("Invalid number of entries"));
return nullptr;
return {};
}
m_contentHashHeader = m_device->read(32);
if (m_contentHashHeader.size() != 32) {
raiseError(tr("Invalid content hash size"));
return nullptr;
return {};
}
m_transformSeed = m_device->read(32);
if (m_transformSeed.size() != 32) {
raiseError(tr("Invalid transform seed size"));
return nullptr;
return {};
}
m_transformRounds = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok) {
raiseError(tr("Invalid number of transform rounds"));
return nullptr;
return {};
}
auto kdf = QSharedPointer<AesKdf>::create(true);
kdf->setRounds(m_transformRounds);
@ -168,14 +167,14 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
QScopedPointer<SymmetricCipherStream> cipherStream(testKeys(password, keyfileData, contentPos));
if (!cipherStream) {
return nullptr;
return {};
}
QList<Group*> groups;
for (quint32 i = 0; i < numGroups; i++) {
Group* group = readGroup(cipherStream.data());
if (!group) {
return nullptr;
return {};
}
groups.append(group);
}
@ -184,14 +183,14 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
for (quint32 i = 0; i < numEntries; i++) {
Entry* entry = readEntry(cipherStream.data());
if (!entry) {
return nullptr;
return {};
}
entries.append(entry);
}
if (!constructGroupTree(groups)) {
raiseError(tr("Unable to construct group tree"));
return nullptr;
return {};
}
for (Entry* entry : asConst(entries)) {
@ -243,41 +242,39 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
if (!db->setKey(key)) {
raiseError(tr("Unable to calculate master key"));
return nullptr;
return {};
}
return db.take();
return db;
}
Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& password, const QString& keyfileName)
QSharedPointer<Database> KeePass1Reader::readDatabase(QIODevice* device, const QString& password, const QString& keyfileName)
{
QScopedPointer<QFile> keyFile;
if (!keyfileName.isEmpty()) {
keyFile.reset(new QFile(keyfileName));
if (!keyFile->open(QFile::ReadOnly)) {
raiseError(keyFile->errorString());
return nullptr;
return {};
}
}
QScopedPointer<Database> db(readDatabase(device, password, keyFile.data()));
return db.take();
return QSharedPointer<Database>(readDatabase(device, password, keyFile.data()));
}
Database* KeePass1Reader::readDatabase(const QString& filename, const QString& password, const QString& keyfileName)
QSharedPointer<Database> KeePass1Reader::readDatabase(const QString& filename, const QString& password, const QString& keyfileName)
{
QFile dbFile(filename);
if (!dbFile.open(QFile::ReadOnly)) {
raiseError(dbFile.errorString());
return nullptr;
return {};
}
Database* db = readDatabase(&dbFile, password, keyfileName);
auto db = readDatabase(&dbFile, password, keyfileName);
if (dbFile.error() != QFile::NoError) {
raiseError(dbFile.errorString());
return nullptr;
return {};
}
return db;
@ -293,8 +290,7 @@ QString KeePass1Reader::errorString()
return m_errorStr;
}
SymmetricCipherStream*
KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData, qint64 contentPos)
SymmetricCipherStream* KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData, qint64 contentPos)
{
const QList<PasswordEncoding> encodings = {Windows1252, Latin1, UTF8};

View File

@ -21,6 +21,7 @@
#include <QCoreApplication>
#include <QDateTime>
#include <QHash>
#include <QSharedPointer>
class Database;
class Entry;
@ -34,9 +35,9 @@ class KeePass1Reader
public:
KeePass1Reader();
Database* readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice);
Database* readDatabase(QIODevice* device, const QString& password, const QString& keyfileName);
Database* readDatabase(const QString& filename, const QString& password, const QString& keyfileName);
QSharedPointer<Database> readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice);
QSharedPointer<Database> readDatabase(QIODevice* device, const QString& password, const QString& keyfileName);
QSharedPointer<Database> readDatabase(const QString& filename, const QString& password, const QString& keyfileName);
bool hasError();
QString errorString();
@ -63,7 +64,7 @@ private:
static QDateTime dateFromPackedStruct(const QByteArray& data);
static bool isMetaStream(const Entry* entry);
Database* m_db;
QSharedPointer<Database> m_db;
Group* m_tmpParent;
QIODevice* m_device;
quint32 m_encryptionFlags;

View File

@ -21,31 +21,31 @@
#include "format/KeePass1.h"
#include <QFile>
#include <utility>
/**
* Read database from file and detect correct file format.
*
* @param filename input file
* @param key database encryption composite key
* @return pointer to the read database, nullptr on failure
* @param db Database to read into
* @return true on success
*/
Database* KeePass2Reader::readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key)
bool KeePass2Reader::readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key, Database* db)
{
QFile file(filename);
if (!file.open(QFile::ReadOnly)) {
raiseError(file.errorString());
return nullptr;
return false;
}
QScopedPointer<Database> db(readDatabase(&file, std::move(key)));
bool ok = readDatabase(&file, std::move(key), db);
if (file.error() != QFile::NoError) {
raiseError(file.errorString());
return nullptr;
return false;
}
return db.take();
return ok;
}
/**
@ -53,10 +53,10 @@ Database* KeePass2Reader::readDatabase(const QString& filename, QSharedPointer<c
*
* @param device input device
* @param key database encryption composite key
* @param keepDatabase keep database in case of read failure
* @return pointer to the read database, nullptr on failure
* @param db Database to read into
* @return true on success
*/
Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase)
bool KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db)
{
m_error = false;
m_errorStr.clear();
@ -69,7 +69,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const C
if (!ok || signature1 != KeePass2::SIGNATURE_1 || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
return false;
}
if (signature2 == KeePass1::SIGNATURE_2) {
@ -77,13 +77,13 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const C
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
"This is a one-way migration. You won't be able to open the imported "
"database with the old KeePassX 0.4 version."));
return nullptr;
return false;
}
quint32 maxVersion = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
if (m_version < KeePass2::FILE_VERSION_MIN || m_version > maxVersion) {
raiseError(tr("Unsupported KeePass 2 database version."));
return nullptr;
return false;
}
// determine file format (KDBX 2/3 or 4)
@ -94,7 +94,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const C
}
m_reader->setSaveXml(m_saveXml);
return m_reader->readDatabase(device, std::move(key), keepDatabase);
return m_reader->readDatabase(device, std::move(key), db);
}
bool KeePass2Reader::hasError() const

View File

@ -35,8 +35,8 @@ class KeePass2Reader
Q_DECLARE_TR_FUNCTIONS(KdbxReader)
public:
Database* readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key);
Database* readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase = false);
bool readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key, Database* db);
bool readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db);
bool hasError() const;
QString errorString() const;

View File

@ -48,6 +48,10 @@ bool KeePass2Writer::writeDatabase(const QString& filename, Database* db)
*/
bool KeePass2Writer::implicitUpgradeNeeded(Database const* db) const
{
if (db->kdf()->uuid() != KeePass2::KDF_AES_KDBX3 ) {
return false;
}
if (!db->publicCustomData().isEmpty()) {
return true;
}

View File

@ -105,8 +105,7 @@ void DatabaseOpenWidget::showEvent(QShowEvent* event)
#ifdef WITH_XC_YUBIKEY
// showEvent() may be called twice, so make sure we are only polling once
if (!m_yubiKeyBeingPolled) {
connect(
YubiKey::instance(), SIGNAL(detected(int,bool)), SLOT(yubikeyDetected(int,bool)), Qt::QueuedConnection);
connect(YubiKey::instance(), SIGNAL(detected(int,bool)), SLOT(yubikeyDetected(int,bool)), Qt::QueuedConnection);
connect(YubiKey::instance(), SIGNAL(detectComplete()), SLOT(yubikeyDetectComplete()), Qt::QueuedConnection);
connect(YubiKey::instance(), SIGNAL(notFound()), SLOT(noYubikeyFound()), Qt::QueuedConnection);
@ -156,10 +155,10 @@ void DatabaseOpenWidget::clearForms()
m_ui->checkChallengeResponse->setChecked(false);
m_ui->checkTouchID->setChecked(false);
m_ui->buttonTogglePassword->setChecked(false);
m_db = nullptr;
m_db.reset();
}
Database* DatabaseOpenWidget::database()
QSharedPointer<Database> DatabaseOpenWidget::database()
{
return m_db;
}
@ -178,9 +177,8 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
void DatabaseOpenWidget::openDatabase()
{
KeePass2Reader reader;
QSharedPointer<CompositeKey> masterKey = databaseKey();
if (masterKey.isNull()) {
if (!masterKey) {
return;
}
@ -189,19 +187,18 @@ void DatabaseOpenWidget::openDatabase()
}
QCoreApplication::processEvents();
QFile file(m_filename);
if (!file.open(QIODevice::ReadOnly)) {
m_ui->messageWidget->showMessage(tr("Unable to open the database.").append("\n").append(file.errorString()),
MessageWidget::Error);
m_db.reset(new Database());
QString error;
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
bool ok = m_db->open(m_filename, masterKey, &error, false);
QApplication::restoreOverrideCursor();
if (!ok) {
m_ui->messageWidget->showMessage(
tr("Unable to open the database:\n%1").arg(error),
MessageWidget::MessageType::Error);
return;
}
delete m_db;
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
m_db = reader.readDatabase(&file, masterKey);
QApplication::restoreOverrideCursor();
if (m_db) {
#ifdef WITH_XC_TOUCHID
QHash<QString, QVariant> useTouchID = config()->get("UseTouchID").toHash();
@ -224,9 +221,9 @@ void DatabaseOpenWidget::openDatabase()
if (m_ui->messageWidget->isVisible()) {
m_ui->messageWidget->animatedHide();
}
emit editFinished(true);
emit dialogFinished(true);
} else {
m_ui->messageWidget->showMessage(tr("Unable to open the database.").append("\n").append(reader.errorString()),
m_ui->messageWidget->showMessage(tr("Unable to open the database:\n%1").arg(error),
MessageWidget::Error);
m_ui->editPassword->clear();
@ -326,7 +323,7 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::databaseKey()
void DatabaseOpenWidget::reject()
{
emit editFinished(false);
emit dialogFinished(false);
}
void DatabaseOpenWidget::activatePassword()

View File

@ -42,13 +42,13 @@ public:
void load(const QString& filename);
void clearForms();
void enterKey(const QString& pw, const QString& keyFile);
Database* database();
QSharedPointer<Database> database();
public slots:
void pollYubikey();
signals:
void editFinished(bool accepted);
void dialogFinished(bool accepted);
protected:
void showEvent(QShowEvent* event) override;
@ -70,7 +70,7 @@ private slots:
protected:
const QScopedPointer<Ui::DatabaseOpenWidget> m_ui;
Database* m_db;
QSharedPointer<Database> m_db;
QString m_filename;
private:

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,31 +18,16 @@
#ifndef KEEPASSX_DATABASETABWIDGET_H
#define KEEPASSX_DATABASETABWIDGET_H
#include <QFileInfo>
#include <QHash>
#include <QTabWidget>
#include "gui/DatabaseWidget.h"
#include "gui/MessageWidget.h"
#include <QTabWidget>
#include <QPointer>
class Database;
class DatabaseWidget;
class DatabaseWidgetStateSync;
class DatabaseOpenWidget;
class QFile;
class MessageWidget;
struct DatabaseManagerStruct
{
DatabaseManagerStruct();
DatabaseWidget* dbWidget;
QFileInfo fileInfo;
bool modified;
bool readOnly;
int saveAttempts;
};
Q_DECLARE_TYPEINFO(DatabaseManagerStruct, Q_MOVABLE_TYPE);
class DatabaseTabWidget : public QTabWidget
{
@ -52,71 +36,62 @@ class DatabaseTabWidget : public QTabWidget
public:
explicit DatabaseTabWidget(QWidget* parent = nullptr);
~DatabaseTabWidget() override;
void openDatabase(const QString& fileName, const QString& pw = QString(), const QString& keyFile = QString());
void mergeDatabase(const QString& fileName);
DatabaseWidget* currentDatabaseWidget();
bool hasLockableDatabases() const;
DatabaseManagerStruct indexDatabaseManagerStruct(int index);
void mergeDatabase(const QString& filePath);
static const int LastDatabasesCount;
QString tabName(int index);
DatabaseWidget* currentDatabaseWidget();
DatabaseWidget* databaseWidgetFromIndex(int index) const;
bool isReadOnly(int index = -1) const;
bool canSave(int index = -1) const;
bool isModified(int index = -1) const;
bool hasLockableDatabases() const;
public slots:
void addDatabaseTab(const QString& filePath);
void addDatabaseTab(DatabaseWidget* dbWidget);
bool closeDatabaseTab(int index);
bool closeDatabaseTab(DatabaseWidget* dbWidget);
bool closeAllDatabaseTabs();
bool closeCurrentDatabaseTab();
bool closeDatabaseTabFromSender();
void updateTabName(int index = -1);
void newDatabase();
void openDatabase();
void importCsv();
void mergeDatabase();
void importCsv();
void importKeePass1Database();
bool saveDatabase(int index = -1);
bool saveDatabaseAs(int index = -1);
void exportToCsv();
bool closeDatabase(int index = -1);
void lockDatabases();
void closeDatabaseFromSender();
bool closeAllDatabases();
void relockPendingDatabase();
void changeMasterKey();
void changeDatabaseSettings();
bool readOnly(int index = -1);
bool canSave(int index = -1);
bool isModified(int index = -1);
void performGlobalAutoType();
void lockDatabases();
void relockPendingDatabase();
QString databasePath(int index = -1);
signals:
void tabNameChanged();
void databaseWithFileClosed(QString filePath);
void activateDatabaseChanged(DatabaseWidget* dbWidget);
void databaseLocked(DatabaseWidget* dbWidget);
void databaseClosed(const QString& filePath);
void databaseUnlocked(DatabaseWidget* dbWidget);
void databaseLocked(DatabaseWidget* dbWidget);
void activateDatabaseChanged(DatabaseWidget* dbWidget);
void tabNameChanged();
void messageGlobal(const QString&, MessageWidget::MessageType type);
void messageTab(const QString&, MessageWidget::MessageType type);
void messageDismissGlobal();
void messageDismissTab();
private slots:
void updateTabName(Database* db);
void updateTabNameFromDbSender();
void updateTabNameFromDbWidgetSender();
void modified();
void toggleTabbar();
void changeDatabase(Database* newDb, bool unsavedChanges);
void emitActivateDatabaseChanged();
void emitDatabaseUnlockedFromDbWidgetSender();
void emitDatabaseLockChanged();
private:
Database* execNewDatabaseWizard();
bool saveDatabase(Database* db, QString filePath = "");
bool saveDatabaseAs(Database* db);
bool closeDatabase(Database* db);
void deleteDatabase(Database* db);
int databaseIndex(Database* db);
Database* indexDatabase(int index);
Database* databaseFromDatabaseWidget(DatabaseWidget* dbWidget);
void insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct);
QSharedPointer<Database> execNewDatabaseWizard();
void updateLastDatabases(const QString& filename);
void connectDatabase(Database* newDb, Database* oldDb = nullptr);
QHash<Database*, DatabaseManagerStruct> m_dbList;
QPointer<DatabaseWidgetStateSync> m_dbWidgetStateSync;
QPointer<DatabaseWidget> m_dbPendingLock;
};

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
@ -32,6 +32,7 @@
#include <QSplitter>
#include "autotype/AutoType.h"
#include "core/Database.h"
#include "core/Config.h"
#include "core/EntrySearcher.h"
#include "core/FilePath.h"
@ -40,6 +41,7 @@
#include "core/Metadata.h"
#include "core/Tools.h"
#include "format/KeePass2Reader.h"
#include "gui/FileDialog.h"
#include "gui/Clipboard.h"
#include "gui/CloneDialog.h"
#include "gui/DatabaseOpenWidget.h"
@ -68,131 +70,127 @@
#include "sshagent/SSHAgent.h"
#endif
DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
: QStackedWidget(parent)
, m_db(db)
, m_newGroup(nullptr)
, m_newEntry(nullptr)
, m_newParent(nullptr)
{
m_mainWidget = new QWidget(this);
, m_db(std::move(db))
m_messageWidget = new MessageWidget(this);
, m_mainWidget(new QWidget(this))
, m_mainSplitter(new QSplitter(m_mainWidget))
, m_messageWidget(new MessageWidget(this))
, m_previewView(new EntryPreviewWidget(this))
, m_previewSplitter(new QSplitter(m_mainWidget))
, m_searchingLabel(new QLabel(this))
, m_csvImportWizard(new CsvImportWizard(this))
, m_editEntryWidget(new EditEntryWidget(this))
, m_editGroupWidget(new EditGroupWidget(this))
, m_historyEditEntryWidget(new EditEntryWidget(this))
, m_databaseSettingDialog(new DatabaseSettingsDialog(this))
, m_databaseOpenWidget(new DatabaseOpenWidget(this))
, m_databaseOpenMergeWidget(new DatabaseOpenWidget(this))
, m_keepass1OpenWidget(new KeePass1OpenWidget(this))
, m_unlockDatabaseWidget(new UnlockDatabaseWidget(this))
, m_unlockDatabaseDialog(new UnlockDatabaseDialog(this))
, m_groupView(new GroupView(m_db.data(), m_mainSplitter))
, m_entryView(nullptr)
, m_newGroup()
, m_newEntry()
, m_newParent()
{
m_messageWidget->setHidden(true);
auto* mainLayout = new QVBoxLayout();
QLayout* layout = new QHBoxLayout();
mainLayout->addWidget(m_messageWidget);
mainLayout->addLayout(layout);
m_mainSplitter = new QSplitter(m_mainWidget);
auto* hbox = new QHBoxLayout();
mainLayout->addLayout(hbox);
hbox->addWidget(m_mainSplitter);
m_mainWidget->setLayout(mainLayout);
auto* rightHandSideWidget = new QWidget(m_mainSplitter);
auto* vbox = new QVBoxLayout();
vbox->setMargin(0);
vbox->addWidget(m_searchingLabel);
vbox->addWidget(m_previewSplitter);
rightHandSideWidget->setLayout(vbox);
m_entryView = new EntryView(rightHandSideWidget);
m_mainSplitter->setChildrenCollapsible(false);
m_previewSplitter = new QSplitter(m_mainWidget);
m_mainSplitter->addWidget(m_groupView);
m_mainSplitter->addWidget(rightHandSideWidget);
m_mainSplitter->setStretchFactor(0, 30);
m_mainSplitter->setStretchFactor(1, 70);
m_previewSplitter->setOrientation(Qt::Vertical);
m_previewSplitter->setChildrenCollapsible(true);
QWidget* rightHandSideWidget = new QWidget(m_mainSplitter);
m_groupView = new GroupView(db, m_mainSplitter);
m_groupView->setObjectName("groupView");
m_groupView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_groupView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(emitGroupContextMenuRequested(QPoint)));
m_entryView = new EntryView(rightHandSideWidget);
m_entryView->setObjectName("entryView");
m_entryView->setContextMenuPolicy(Qt::CustomContextMenu);
m_entryView->displayGroup(db->rootGroup());
m_entryView->displayGroup(m_db->rootGroup());
connect(m_entryView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(emitEntryContextMenuRequested(QPoint)));
// Add a notification for when we are searching
m_searchingLabel = new QLabel();
m_searchingLabel->setText(tr("Searching..."));
m_searchingLabel->setAlignment(Qt::AlignCenter);
m_searchingLabel->setStyleSheet("color: rgb(0, 0, 0);"
"background-color: rgb(255, 253, 160);"
"border: 2px solid rgb(190, 190, 190);"
"border-radius: 5px;");
"border-radius: 2px;");
m_searchingLabel->setVisible(false);
m_previewView = new EntryPreviewWidget(this);
m_previewView->hide();
connect(this, SIGNAL(pressedGroup(Group*)), m_previewView, SLOT(setGroup(Group*)));
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)),
m_previewView, SLOT(setDatabaseMode(DatabaseWidget::Mode)));
connect(m_previewView, SIGNAL(errorOccurred(QString)), this, SLOT(showErrorMessage(QString)));
auto* vLayout = new QVBoxLayout(rightHandSideWidget);
vLayout->setMargin(0);
vLayout->addWidget(m_searchingLabel);
vLayout->addWidget(m_previewSplitter);
m_previewSplitter->addWidget(m_entryView);
m_previewSplitter->addWidget(m_previewView);
m_previewSplitter->setStretchFactor(0, 100);
m_previewSplitter->setStretchFactor(1, 0);
m_previewSplitter->setSizes({1, 1});
m_searchingLabel->setVisible(false);
rightHandSideWidget->setLayout(vLayout);
m_mainSplitter->addWidget(m_groupView);
m_mainSplitter->addWidget(rightHandSideWidget);
m_mainSplitter->setStretchFactor(0, 30);
m_mainSplitter->setStretchFactor(1, 70);
layout->addWidget(m_mainSplitter);
m_mainWidget->setLayout(mainLayout);
m_editEntryWidget = new EditEntryWidget();
m_editEntryWidget->setObjectName("editEntryWidget");
m_historyEditEntryWidget = new EditEntryWidget();
m_editGroupWidget = new EditGroupWidget();
m_editGroupWidget->setObjectName("editGroupWidget");
m_csvImportWizard = new CsvImportWizard();
m_csvImportWizard->setObjectName("csvImportWizard");
m_databaseSettingDialog = new DatabaseSettingsDialog();
m_databaseSettingDialog->setObjectName("databaseSettingsDialog");
m_databaseOpenWidget = new DatabaseOpenWidget();
m_databaseOpenWidget->setObjectName("databaseOpenWidget");
m_databaseOpenMergeWidget = new DatabaseOpenWidget();
m_databaseOpenMergeWidget->setObjectName("databaseOpenMergeWidget");
m_keepass1OpenWidget = new KeePass1OpenWidget();
m_keepass1OpenWidget->setObjectName("keepass1OpenWidget");
m_unlockDatabaseWidget = new UnlockDatabaseWidget();
m_unlockDatabaseWidget->setObjectName("unlockDatabaseWidget");
m_unlockDatabaseDialog = new UnlockDatabaseDialog();
m_unlockDatabaseDialog->setObjectName("unlockDatabaseDialog");
addWidget(m_mainWidget);
addWidget(m_editEntryWidget);
addWidget(m_editGroupWidget);
addWidget(m_databaseSettingDialog);
addWidget(m_historyEditEntryWidget);
addWidget(m_databaseOpenWidget);
addWidget(m_csvImportWizard);
addWidget(m_databaseOpenMergeWidget);
addWidget(m_keepass1OpenWidget);
addWidget(m_unlockDatabaseWidget);
addChildWidget(m_mainWidget);
addChildWidget(m_editEntryWidget);
addChildWidget(m_editGroupWidget);
addChildWidget(m_databaseSettingDialog);
addChildWidget(m_historyEditEntryWidget);
addChildWidget(m_databaseOpenWidget);
addChildWidget(m_csvImportWizard);
addChildWidget(m_databaseOpenMergeWidget);
addChildWidget(m_keepass1OpenWidget);
addChildWidget(m_unlockDatabaseWidget);
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
connect(m_previewSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(previewSplitterSizesChanged()));
connect(this, SIGNAL(pressedEntry(Entry*)), m_previewView, SLOT(setEntry(Entry*)));
connect(this, SIGNAL(pressedGroup(Group*)), m_previewView, SLOT(setGroup(Group*)));
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), m_previewView, SLOT(setDatabaseMode(DatabaseWidget::Mode)));
connect(m_previewView, SIGNAL(errorOccurred(QString)), this, SLOT(showErrorMessage(QString)));
connect(m_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged()));
connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(onGroupChanged(Group*)));
connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged()));
connect(m_entryView,
SIGNAL(entryActivated(Entry*,EntryModel::ModelColumn)),
SLOT(entryActivationSignalReceived(Entry*,EntryModel::ModelColumn)));
connect(m_entryView, SIGNAL(entrySelectionChanged()), SLOT(emitEntrySelectionChanged()));
connect(m_entryView, SIGNAL(entryActivated(Entry*,EntryModel::ModelColumn)),
SLOT(entryActivationSignalReceived(Entry*,EntryModel::ModelColumn)));
connect(m_entryView, SIGNAL(entrySelectionChanged()), SIGNAL(entrySelectionChanged()));
connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool)));
connect(m_editEntryWidget, SIGNAL(historyEntryActivated(Entry*)), SLOT(switchToHistoryView(Entry*)));
connect(m_historyEditEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchBackToEntryEdit()));
connect(m_editGroupWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool)));
connect(m_databaseSettingDialog, SIGNAL(editFinished(bool)), SLOT(switchToView(bool)));
connect(m_databaseOpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
connect(m_databaseOpenMergeWidget, SIGNAL(editFinished(bool)), SLOT(mergeDatabase(bool)));
connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
connect(m_databaseOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_databaseOpenMergeWidget, SIGNAL(dialogFinished(bool)), SLOT(mergeDatabase(bool)));
connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool)));
connect(m_unlockDatabaseWidget, SIGNAL(dialogFinished(bool)), SLOT(unlockDatabase(bool)));
connect(m_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool)));
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
connect(&m_fileWatchTimer, SIGNAL(timeout()), this, SLOT(reloadDatabaseFile()));
@ -203,7 +201,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
connect(m_groupView, SIGNAL(groupChanged(Group*)), SLOT(emitPressedGroup(Group*)));
connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(emitEntrySelectionChanged()));
m_databaseModified = false;
connectDatabaseSignals();
m_fileWatchTimer.setSingleShot(true);
m_fileWatchUnblockTimer.setSingleShot(true);
@ -225,29 +223,44 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
setCurrentWidget(m_mainWidget);
}
DatabaseWidget::DatabaseWidget(const QString& filePath, QWidget* parent)
: DatabaseWidget(QSharedPointer<Database>::create(filePath), parent)
{
}
DatabaseWidget::~DatabaseWidget()
{
delete m_EntrySearcher;
}
QSharedPointer<Database> DatabaseWidget::database() const
{
return m_db;
}
DatabaseWidget::Mode DatabaseWidget::currentMode() const
{
if (currentWidget() == nullptr) {
return DatabaseWidget::None;
return DatabaseWidget::Mode::None;
} else if (currentWidget() == m_csvImportWizard) {
return DatabaseWidget::ImportMode;
return DatabaseWidget::Mode::ImportMode;
} else if (currentWidget() == m_mainWidget) {
return DatabaseWidget::ViewMode;
return DatabaseWidget::Mode::ViewMode;
} else if (currentWidget() == m_unlockDatabaseWidget || currentWidget() == m_databaseOpenWidget) {
return DatabaseWidget::LockedMode;
return DatabaseWidget::Mode::LockedMode;
} else {
return DatabaseWidget::EditMode;
return DatabaseWidget::Mode::EditMode;
}
}
bool DatabaseWidget::isInEditMode() const
bool DatabaseWidget::isLocked() const
{
return currentMode() == DatabaseWidget::EditMode;
return currentMode() == Mode::LockedMode;
}
bool DatabaseWidget::isSearchActive() const
{
return m_entryView->inSearchMode();
}
bool DatabaseWidget::isEditWidgetModified() const
@ -341,11 +354,6 @@ void DatabaseWidget::emitCurrentModeChanged()
emit currentModeChanged(currentMode());
}
Database* DatabaseWidget::database()
{
return m_db;
}
void DatabaseWidget::createEntry()
{
Q_ASSERT(m_groupView->currentGroup());
@ -355,7 +363,7 @@ void DatabaseWidget::createEntry()
m_newEntry = new Entry();
if (isInSearchMode()) {
if (isSearchActive()) {
m_newEntry->setTitle(getCurrentSearch());
endSearch();
}
@ -383,13 +391,15 @@ void DatabaseWidget::setIconFromParent()
}
}
void DatabaseWidget::replaceDatabase(Database* db)
void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
{
Database* oldDb = m_db;
m_db = db;
// TODO: instead of increasing the ref count temporarily, there should be a clean
// break from the old database. Without this crashes occur due to the change
// signals triggering dangling pointers.
auto oldDb = m_db;
m_db = std::move(db);
connectDatabaseSignals();
m_groupView->changeDatabase(m_db);
emit databaseChanged(m_db, m_databaseModified);
delete oldDb;
}
void DatabaseWidget::cloneEntry()
@ -400,7 +410,7 @@ void DatabaseWidget::cloneEntry()
return;
}
auto cloneDialog = new CloneDialog(this, m_db, currentEntry);
auto cloneDialog = new CloneDialog(this, m_db.data(), currentEntry);
cloneDialog->show();
}
@ -660,8 +670,8 @@ void DatabaseWidget::deleteGroup()
auto* recycleBin = m_db->metadata()->recycleBin();
bool inRecycleBin = recycleBin && recycleBin->findGroupByUuid(currentGroup->uuid());
bool isRecycleBin = (currentGroup == m_db->metadata()->recycleBin());
bool isRecycleBinSubgroup = currentGroup->findGroupByUuid(m_db->metadata()->recycleBin()->uuid());
bool isRecycleBin = recycleBin && (currentGroup == recycleBin);
bool isRecycleBinSubgroup = recycleBin && currentGroup->findGroupByUuid(recycleBin->uuid());
if (inRecycleBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) {
QMessageBox::StandardButton result = MessageBox::question(
this,
@ -676,40 +686,14 @@ void DatabaseWidget::deleteGroup()
}
}
int DatabaseWidget::addWidget(QWidget* w)
int DatabaseWidget::addChildWidget(QWidget* w)
{
w->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
int index = QStackedWidget::addWidget(w);
adjustSize();
return index;
}
void DatabaseWidget::setCurrentIndex(int index)
{
// use setCurrentWidget() instead
// index is not reliable
Q_UNUSED(index);
Q_ASSERT(false);
}
void DatabaseWidget::setCurrentWidget(QWidget* widget)
{
if (currentWidget()) {
currentWidget()->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
}
QStackedWidget::setCurrentWidget(widget);
if (currentWidget()) {
currentWidget()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
}
adjustSize();
}
void DatabaseWidget::csvImportFinished(bool accepted)
{
if (!accepted) {
@ -788,24 +772,32 @@ void DatabaseWidget::switchToGroupEdit(Group* group, bool create)
setCurrentWidget(m_editGroupWidget);
}
void DatabaseWidget::openDatabase(bool accepted)
void DatabaseWidget::connectDatabaseSignals()
{
if (accepted) {
replaceDatabase(static_cast<DatabaseOpenWidget*>(sender())->database());
setCurrentWidget(m_mainWidget);
emit unlockedDatabase();
// relayed Database events
connect(m_db.data(), SIGNAL(filePathChanged(QString,QString)),
this, SIGNAL(databaseFilePathChanged(QString,QString)));
connect(m_db.data(), SIGNAL(databaseModified()), this, SIGNAL(databaseModified()));
connect(m_db.data(), SIGNAL(databaseSaved()), this, SIGNAL(databaseSaved()));
}
// We won't need those anymore and KeePass1OpenWidget closes
// the file in its dtor.
delete m_databaseOpenWidget;
m_databaseOpenWidget = nullptr;
delete m_keepass1OpenWidget;
m_keepass1OpenWidget = nullptr;
m_fileWatcher.addPath(m_filePath);
void DatabaseWidget::loadDatabase(bool accepted)
{
auto* openWidget = qobject_cast<DatabaseOpenWidget*>(sender());
Q_ASSERT(openWidget);
if (!openWidget) {
return;
}
if (accepted) {
replaceDatabase(openWidget->database());
setCurrentWidget(m_mainWidget);
m_fileWatcher.addPath(m_db->filePath());
emit databaseUnlocked();
} else {
m_fileWatcher.removePath(m_filePath);
m_fileWatcher.removePath(m_db->filePath());
if (m_databaseOpenWidget->database()) {
delete m_databaseOpenWidget->database();
m_databaseOpenWidget->database().reset();
}
emit closeRequest();
}
@ -815,18 +807,18 @@ void DatabaseWidget::mergeDatabase(bool accepted)
{
if (accepted) {
if (!m_db) {
m_messageWidget->showMessage(tr("No current database."), MessageWidget::Error);
showMessage(tr("No current database."), MessageWidget::Error);
return;
}
Database* srcDb = static_cast<DatabaseOpenWidget*>(sender())->database();
auto srcDb = qobject_cast<DatabaseOpenWidget*>(sender())->database();
if (!srcDb) {
m_messageWidget->showMessage(tr("No source database, nothing to do."), MessageWidget::Error);
showMessage(tr("No source database, nothing to do."), MessageWidget::Error);
return;
}
Merger merger(srcDb, m_db);
Merger merger(srcDb.data(), m_db.data());
merger.merge();
}
@ -842,7 +834,7 @@ void DatabaseWidget::unlockDatabase(bool accepted)
return;
}
Database* db = nullptr;
auto db = QSharedPointer<Database>::create();
if (sender() == m_unlockDatabaseDialog) {
db = m_unlockDatabaseDialog->database();
} else if (sender() == m_unlockDatabaseWidget) {
@ -850,6 +842,9 @@ void DatabaseWidget::unlockDatabase(bool accepted)
}
replaceDatabase(db);
if (db->isReadOnly()) {
showMessage(tr("File opened in read only mode."), MessageWidget::Warning, false, -1);
}
restoreGroupEntryFocus(m_groupBeforeLock, m_entryBeforeLock);
m_groupBeforeLock = QUuid();
@ -857,10 +852,10 @@ void DatabaseWidget::unlockDatabase(bool accepted)
setCurrentWidget(m_mainWidget);
m_unlockDatabaseWidget->clearForms();
emit unlockedDatabase();
emit databaseUnlocked();
if (sender() == m_unlockDatabaseDialog) {
QList<Database*> dbList;
QList<QSharedPointer<Database>> dbList;
dbList.append(m_db);
autoType()->performGlobalAutoType(dbList);
}
@ -946,6 +941,11 @@ void DatabaseWidget::switchToDatabaseSettings()
setCurrentWidget(m_databaseSettingDialog);
}
void DatabaseWidget::switchToOpenDatabase()
{
switchToOpenDatabase(m_db->filePath());
}
void DatabaseWidget::switchToOpenDatabase(const QString& filePath)
{
updateFilePath(filePath);
@ -957,22 +957,10 @@ void DatabaseWidget::switchToOpenDatabase(const QString& filePath)
setCurrentWidget(m_unlockDatabaseWidget);
}
}
void DatabaseWidget::switchToOpenDatabase(const QString& filePath, const QString& password, const QString& keyFile)
{
updateFilePath(filePath);
switchToOpenDatabase(filePath);
if (m_databaseOpenWidget) {
m_databaseOpenWidget->enterKey(password, keyFile);
} else if (m_unlockDatabaseWidget) {
m_unlockDatabaseWidget->enterKey(password, keyFile);
}
}
void DatabaseWidget::switchToCsvImport(const QString& filePath)
{
setCurrentWidget(m_csvImportWizard);
m_csvImportWizard->load(filePath, m_db);
m_csvImportWizard->load(filePath, m_db.data());
}
void DatabaseWidget::switchToOpenMergeDatabase(const QString& filePath)
@ -995,19 +983,9 @@ void DatabaseWidget::switchToImportKeepass1(const QString& filePath)
setCurrentWidget(m_keepass1OpenWidget);
}
void DatabaseWidget::databaseModified()
{
m_databaseModified = true;
}
void DatabaseWidget::databaseSaved()
{
m_databaseModified = false;
}
void DatabaseWidget::refreshSearch()
{
if (isInSearchMode()) {
if (isSearchActive()) {
search(m_lastSearchText);
}
}
@ -1054,10 +1032,10 @@ void DatabaseWidget::setSearchLimitGroup(bool state)
void DatabaseWidget::onGroupChanged(Group* group)
{
if (isInSearchMode() && m_searchLimitGroup) {
// Perform new search if we are limiting search to the current group
// Intercept group changes if in search mode
if (isSearchActive()) {
search(m_lastSearchText);
} else if (isInSearchMode()) {
} else if (isSearchActive()) {
// Otherwise cancel search
emit clearSearch();
} else {
@ -1072,7 +1050,7 @@ QString DatabaseWidget::getCurrentSearch()
void DatabaseWidget::endSearch()
{
if (isInSearchMode()) {
if (isSearchActive()) {
emit listModeAboutToActivate();
// Show the normal entry view of the current group
@ -1117,30 +1095,74 @@ void DatabaseWidget::emitPressedGroup(Group* currentGroup)
emit pressedGroup(currentGroup);
}
bool DatabaseWidget::dbHasKey() const
{
return m_db->hasKey();
}
bool DatabaseWidget::canDeleteCurrentGroup() const
{
bool isRootGroup = m_db->rootGroup() == m_groupView->currentGroup();
return !isRootGroup;
}
bool DatabaseWidget::isInSearchMode() const
{
return m_entryView->inSearchMode();
}
Group* DatabaseWidget::currentGroup() const
{
return m_groupView->currentGroup();
}
void DatabaseWidget::lock()
void DatabaseWidget::closeEvent(QCloseEvent* event)
{
Q_ASSERT(currentMode() != DatabaseWidget::LockedMode);
if (!isLocked() && !lock()) {
event->ignore();
return;
}
event->accept();
}
void DatabaseWidget::showEvent(QShowEvent* event)
{
if (!m_db->isInitialized() || isLocked()) {
switchToOpenDatabase();
}
event->accept();
}
bool DatabaseWidget::lock()
{
if (isLocked()) {
return true;
}
clipboard()->clearCopiedText();
if (currentMode() == DatabaseWidget::Mode::EditMode) {
auto result = MessageBox::question(this, tr("Lock Database?"),
tr("You are editing an entry. Discard changes and lock anyway?"),
QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
if (result == QMessageBox::Cancel) {
return false;
}
}
if (m_db->isModified()) {
if (config()->get("AutoSaveOnExit").toBool()) {
if (!m_db->save(nullptr, false, false)) {
return false;
}
} else if (isLocked()) {
QString msg;
if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) {
msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped());
} else {
msg = tr("Database was modified.\nSave changes?");
}
auto result = MessageBox::question(this, tr("Save changes?"), msg,
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
if (result == QMessageBox::Yes && !m_db->save(nullptr, false, false)) {
return false;
} else if (result == QMessageBox::Cancel) {
return false;
}
}
}
if (m_groupView->currentGroup()) {
m_groupBeforeLock = m_groupView->currentGroup()->uuid();
@ -1154,21 +1176,24 @@ void DatabaseWidget::lock()
endSearch();
clearAllWidgets();
m_unlockDatabaseWidget->load(m_filePath);
m_unlockDatabaseWidget->load(m_db->filePath());
setCurrentWidget(m_unlockDatabaseWidget);
Database* newDb = new Database();
newDb->metadata()->setName(m_db->metadata()->name());
auto newDb = QSharedPointer<Database>::create(m_db->filePath());
replaceDatabase(newDb);
emit lockedDatabase();
emit databaseLocked();
return true;
}
void DatabaseWidget::updateFilePath(const QString& filePath)
{
if (!m_filePath.isEmpty()) {
m_fileWatcher.removePath(m_filePath);
if (!m_db->filePath().isEmpty()) {
m_fileWatcher.removePath(m_db->filePath());
}
#if defined(Q_OS_LINUX)
#ifdef Q_OS_LINUX
struct statfs statfsBuf;
bool forcePolling = false;
const auto NFS_SUPER_MAGIC = 0x6969;
@ -1184,7 +1209,6 @@ void DatabaseWidget::updateFilePath(const QString& filePath)
#endif
m_fileWatcher.addPath(filePath);
m_filePath = filePath;
m_db->setFilePath(filePath);
}
@ -1201,7 +1225,7 @@ void DatabaseWidget::blockAutoReload(bool block)
void DatabaseWidget::unblockAutoReload()
{
m_ignoreAutoReload = false;
updateFilePath(m_filePath);
updateFilePath(m_db->filePath());
}
void DatabaseWidget::onWatchedFileChanged()
@ -1217,86 +1241,69 @@ void DatabaseWidget::onWatchedFileChanged()
void DatabaseWidget::reloadDatabaseFile()
{
if (!m_db || currentMode() == DatabaseWidget::LockedMode) {
return;
}
if (currentMode() == DatabaseWidget::LockedMode) {
if (!m_db || isLocked()) {
return;
}
if (!config()->get("AutoReloadOnChange").toBool()) {
// Ask if we want to reload the db
QMessageBox::StandardButton mb =
MessageBox::question(this,
tr("File has changed"),
tr("The database file has changed. Do you want to load the changes?"),
QMessageBox::Yes | QMessageBox::No);
auto result = MessageBox::question(this,
tr("File has changed"),
tr("The database file has changed. Do you want to load the changes?"),
QMessageBox::Yes | QMessageBox::No);
if (mb == QMessageBox::No) {
if (result == QMessageBox::No) {
// Notify everyone the database does not match the file
m_db->markAsModified();
m_databaseModified = true;
// Rewatch the database file
m_fileWatcher.addPath(m_filePath);
m_fileWatcher.addPath(m_db->filePath());
return;
}
}
KeePass2Reader reader;
QFile file(m_filePath);
if (file.open(QIODevice::ReadOnly)) {
Database* db = reader.readDatabase(&file, database()->key());
if (db != nullptr) {
if (m_databaseModified) {
// Ask if we want to merge changes into new database
QMessageBox::StandardButton mb =
MessageBox::question(this,
tr("Merge Request"),
tr("The database file has changed and you have unsaved changes.\n"
"Do you want to merge your changes?"),
QMessageBox::Yes | QMessageBox::No);
QString error;
auto db = QSharedPointer<Database>::create(m_db->filePath());
if (db->open(database()->key(), &error, true)) {
if (m_db->isModified()) {
// Ask if we want to merge changes into new database
auto result = MessageBox::question(this,
tr("Merge Request"),
tr("The database file has changed and you have unsaved changes.\nDo you want to merge your changes?"),
QMessageBox::Yes | QMessageBox::No);
if (mb == QMessageBox::Yes) {
// Merge the old database into the new one
m_db->setEmitModified(false);
Merger merger(m_db, db);
merger.merge();
} else {
// Since we are accepting the new file as-is, internally mark as unmodified
// TODO: when saving is moved out of DatabaseTabWidget, this should be replaced
m_databaseModified = false;
}
if (result == QMessageBox::Yes) {
// Merge the old database into the new one
Merger merger(m_db.data(), db.data());
merger.merge();
}
QUuid groupBeforeReload;
if (m_groupView && m_groupView->currentGroup()) {
groupBeforeReload = m_groupView->currentGroup()->uuid();
} else {
groupBeforeReload = m_db->rootGroup()->uuid();
}
QUuid entryBeforeReload;
if (m_entryView && m_entryView->currentEntry()) {
entryBeforeReload = m_entryView->currentEntry()->uuid();
}
replaceDatabase(db);
restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload);
}
QUuid groupBeforeReload;
if (m_groupView && m_groupView->currentGroup()) {
groupBeforeReload = m_groupView->currentGroup()->uuid();
} else {
groupBeforeReload = m_db->rootGroup()->uuid();
}
QUuid entryBeforeReload;
if (m_entryView && m_entryView->currentEntry()) {
entryBeforeReload = m_entryView->currentEntry()->uuid();
}
bool isReadOnly = m_db->isReadOnly();
replaceDatabase(db);
m_db->setReadOnly(isReadOnly);
restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload);
} else {
m_messageWidget->showMessage(
tr("Could not open the new database file while attempting to autoreload this database.")
.append("\n")
.append(file.errorString()),
showMessage(
tr("Could not open the new database file while attempting to autoreload.\nError: %1").arg(error),
MessageWidget::Error);
// HACK: Directly calling the database's signal
// Mark db as modified since existing data may differ from file or file was deleted
m_db->markAsModified();
}
// Rewatch the database file
m_fileWatcher.addPath(m_filePath);
m_fileWatcher.addPath(m_db->filePath());
}
int DatabaseWidget::numberOfSelectedEntries() const
@ -1319,7 +1326,7 @@ QStringList DatabaseWidget::customEntryAttributes() const
*/
void DatabaseWidget::restoreGroupEntryFocus(const QUuid& groupUuid, const QUuid& entryUuid)
{
auto group = m_db->resolveGroup(groupUuid);
auto group = m_db->rootGroup()->findGroupByUuid(groupUuid);
if (group) {
m_groupView->setCurrentGroup(group);
auto entry = group->findEntryByUuid(entryUuid);
@ -1409,10 +1416,10 @@ EntryView* DatabaseWidget::entryView()
return m_entryView;
}
void DatabaseWidget::showUnlockDialog()
void DatabaseWidget::prepareUnlock()
{
m_unlockDatabaseDialog->clearForms();
m_unlockDatabaseDialog->setFilePath(m_filePath);
m_unlockDatabaseDialog->setFilePath(m_db->filePath());
#if defined(Q_OS_MACOS)
autoType()->raiseWindow();
@ -1423,15 +1430,101 @@ void DatabaseWidget::showUnlockDialog()
m_unlockDatabaseDialog->activateWindow();
}
void DatabaseWidget::closeUnlockDialog()
/**
* Save the database to disk.
*
* This method will try to save several times in case of failure and
* ask to disable safe saves if it is unable to save after the third attempt.
* Set `attempt` to -1 to disable this behavior.
*
* @param attempt current save attempt or -1 to disable attempts
* @return true on success
*/
bool DatabaseWidget::save(int attempt)
{
m_unlockDatabaseDialog->close();
// Never allow saving a locked database; it causes corruption
Q_ASSERT(!isLocked());
// Release build interlock
if (isLocked()) {
// We return true since a save is not required
return true;
}
if (m_db->isReadOnly() || m_db->filePath().isEmpty()) {
return saveAs();
}
blockAutoReload(true);
// TODO: Make this async, but lock out the database widget to prevent re-entrance
bool useAtomicSaves = config()->get("UseAtomicSaves", true).toBool();
QString errorMessage;
bool ok = m_db->save(&errorMessage, useAtomicSaves, config()->get("BackupBeforeSave").toBool());
blockAutoReload(false);
if (ok) {
return true;
}
if (attempt >= 0 && attempt <= 2) {
return save(attempt + 1);
}
if (attempt > 2 && useAtomicSaves) {
// Saving failed 3 times, issue a warning and attempt to resolve
auto choice = MessageBox::question(this,
tr("Disable safe saves?"),
tr("KeePassXC has failed to save the database multiple times. "
"This is likely caused by file sync services holding a lock on "
"the save file.\nDisable safe saves and try again?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
if (choice == QMessageBox::Yes) {
config()->set("UseAtomicSaves", false);
return save(attempt + 1);
}
}
showMessage(tr("Writing the database failed.\n%1").arg(errorMessage), MessageWidget::Error);
return false;
}
void DatabaseWidget::showMessage(const QString& text,
MessageWidget::MessageType type,
bool showClosebutton,
int autoHideTimeout)
/**
* Save database under a new user-selected filename.
*
* @return true on success
*/
bool DatabaseWidget::saveAs()
{
while (true) {
QString oldFilePath = m_db->filePath();
if (!QFileInfo(oldFilePath).exists()) {
oldFilePath = QDir::toNativeSeparators(config()->get("LastDir", QDir::homePath()).toString()
+ "/" + tr("Passwords").append(".kdbx"));
}
QString newFilePath = fileDialog()->getSaveFileName(
this, tr("Save database as"), oldFilePath,
tr("KeePass 2 Database").append(" (*.kdbx)"), nullptr, nullptr, "kdbx");
if (!newFilePath.isEmpty()) {
// Ensure we don't recurse back into this function
m_db->setReadOnly(false);
m_db->setFilePath(newFilePath);
if (!save(-1)) {
// Failed to save, try again
continue;
}
return true;
}
// Canceled file selection
return false;
}
}
void DatabaseWidget::showMessage(const QString& text, MessageWidget::MessageType type,
bool showClosebutton, int autoHideTimeout)
{
m_messageWidget->setCloseButtonVisible(showClosebutton);
m_messageWidget->showMessage(text, type, autoHideTimeout);
@ -1454,26 +1547,6 @@ bool DatabaseWidget::isRecycleBinSelected() const
return m_groupView->currentGroup() && m_groupView->currentGroup() == m_db->metadata()->recycleBin();
}
QString DatabaseWidget::getDatabaseName() const
{
return m_databaseName;
}
void DatabaseWidget::setDatabaseName(const QString& databaseName)
{
m_databaseName = databaseName;
}
QString DatabaseWidget::getDatabaseFileName() const
{
return m_databaseFileName;
}
void DatabaseWidget::setDatabaseFileName(const QString& databaseFileName)
{
m_databaseFileName = databaseFileName;
}
void DatabaseWidget::emptyRecycleBin()
{
if (!isRecycleBinSelected()) {

View File

@ -1,6 +1,6 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -29,7 +29,6 @@
#include "gui/csvImport/CsvImportWizard.h"
#include "gui/entry/EntryModel.h"
class ChangeMasterKeyWidget;
class DatabaseOpenWidget;
class DatabaseSettingsDialog;
class Database;
@ -61,7 +60,7 @@ class DatabaseWidget : public QStackedWidget
Q_OBJECT
public:
enum Mode
enum class Mode
{
None,
ImportMode,
@ -70,35 +69,39 @@ public:
LockedMode
};
explicit DatabaseWidget(Database* db, QWidget* parent = nullptr);
explicit DatabaseWidget(QSharedPointer<Database> db, QWidget* parent = nullptr);
explicit DatabaseWidget(const QString& filePath, QWidget* parent = nullptr);
~DatabaseWidget();
Database* database();
bool dbHasKey() const;
bool canDeleteCurrentGroup() const;
bool isInSearchMode() const;
QString getCurrentSearch();
Group* currentGroup() const;
int addWidget(QWidget* w);
void setCurrentIndex(int index);
void setCurrentWidget(QWidget* widget);
QSharedPointer<Database> database() const;
bool lock();
void prepareUnlock();
bool save(int attempt = 0);
bool saveAs();
DatabaseWidget::Mode currentMode() const;
void lock();
void updateFilePath(const QString& filePath);
int numberOfSelectedEntries() const;
QStringList customEntryAttributes() const;
bool isLocked() const;
bool isSearchActive() const;
QString getCurrentSearch();
void refreshSearch();
GroupView* groupView();
EntryView* entryView();
Group* currentGroup() const;
bool canDeleteCurrentGroup() const;
bool isGroupSelected() const;
bool isInEditMode() const;
bool isRecycleBinSelected() const;
int numberOfSelectedEntries() const;
QStringList customEntryAttributes() const;
bool isEditWidgetModified() const;
QList<int> mainSplitterSizes() const;
void setMainSplitterSizes(const QList<int>& sizes);
QList<int> previewSplitterSizes() const;
void setPreviewSplitterSizes(const QList<int>& sizes);
bool isUsernamesHidden() const;
void setUsernamesHidden(bool hide);
bool isPasswordsHidden() const;
void setPasswordsHidden(bool hide);
QByteArray entryViewState() const;
bool setEntryViewState(const QByteArray& state) const;
void clearAllWidgets();
bool currentEntryHasFocus();
bool currentEntryHasTitle();
@ -107,31 +110,33 @@ public:
bool currentEntryHasUrl();
bool currentEntryHasNotes();
bool currentEntryHasTotp();
GroupView* groupView();
EntryView* entryView();
void showUnlockDialog();
void closeUnlockDialog();
void blockAutoReload(bool block = true);
void refreshSearch();
bool isRecycleBinSelected() const;
QString getDatabaseName() const;
void setDatabaseName(const QString& databaseName);
QString getDatabaseFileName() const;
void setDatabaseFileName(const QString& databaseFileName);
QByteArray entryViewState() const;
bool setEntryViewState(const QByteArray& state) const;
QList<int> mainSplitterSizes() const;
void setMainSplitterSizes(const QList<int>& sizes);
QList<int> previewSplitterSizes() const;
void setPreviewSplitterSizes(const QList<int>& sizes);
signals:
// relayed Database signals
void databaseFilePathChanged(const QString& oldPath, const QString& newPath);
void databaseModified();
void databaseSaved();
void databaseUnlocked();
void databaseLocked();
void closeRequest();
void currentModeChanged(DatabaseWidget::Mode mode);
void groupChanged();
void entrySelectionChanged();
void databaseChanged(Database* newDb, bool unsavedChanges);
void databaseMerged(Database* mergedDb);
void databaseMerged(QSharedPointer<Database> mergedDb);
void groupContextMenuRequested(const QPoint& globalPos);
void entryContextMenuRequested(const QPoint& globalPos);
void pressedEntry(Entry* selectedEntry);
void pressedGroup(Group* selectedGroup);
void unlockedDatabase();
void lockedDatabase();
void listModeAboutToActivate();
void listModeActivated();
void searchModeAboutToActivate();
@ -142,6 +147,7 @@ signals:
void clearSearch();
public slots:
void replaceDatabase(QSharedPointer<Database> db);
void createEntry();
void cloneEntry();
void deleteEntries();
@ -167,15 +173,13 @@ public slots:
void switchToGroupEdit();
void switchToMasterKeyChange();
void switchToDatabaseSettings();
void switchToOpenDatabase();
void switchToOpenDatabase(const QString& filePath);
void switchToOpenDatabase(const QString& filePath, const QString& password, const QString& keyFile);
void switchToCsvImport(const QString& filePath);
void csvImportFinished(bool accepted);
void switchToOpenMergeDatabase(const QString& filePath);
void switchToOpenMergeDatabase(const QString& filePath, const QString& password, const QString& keyFile);
void switchToImportKeepass1(const QString& filePath);
void databaseModified();
void databaseSaved();
void emptyRecycleBin();
// Search related slots
@ -191,18 +195,24 @@ public slots:
void showErrorMessage(const QString& errorMessage);
void hideMessage();
protected:
void closeEvent(QCloseEvent* event) override;
void showEvent(QShowEvent* event) override;
private slots:
void updateFilePath(const QString& filePath);
void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column);
void switchBackToEntryEdit();
void switchToHistoryView(Entry* entry);
void switchToEntryEdit(Entry* entry);
void switchToEntryEdit(Entry*);
void switchToEntryEdit(Entry* entry, bool create);
void switchToGroupEdit(Group* entry, bool create);
void emitGroupContextMenuRequested(const QPoint& pos);
void emitEntryContextMenuRequested(const QPoint& pos);
void emitPressedGroup(Group* currentGroup);
void emitEntrySelectionChanged();
void openDatabase(bool accepted);
void connectDatabaseSignals();
void loadDatabase(bool accepted);
void mergeDatabase(bool accepted);
void unlockDatabase(bool accepted);
void emitCurrentModeChanged();
@ -213,36 +223,38 @@ private slots:
void unblockAutoReload();
private:
int addChildWidget(QWidget* w);
void setClipboardTextAndMinimize(const QString& text);
void setIconFromParent();
void replaceDatabase(Database* db);
QPointer<Database> m_db;
QWidget* m_mainWidget;
EditEntryWidget* m_editEntryWidget;
EditEntryWidget* m_historyEditEntryWidget;
EditGroupWidget* m_editGroupWidget;
ChangeMasterKeyWidget* m_changeMasterKeyWidget;
CsvImportWizard* m_csvImportWizard;
DatabaseSettingsDialog* m_databaseSettingDialog;
DatabaseOpenWidget* m_databaseOpenWidget;
DatabaseOpenWidget* m_databaseOpenMergeWidget;
KeePass1OpenWidget* m_keepass1OpenWidget;
UnlockDatabaseWidget* m_unlockDatabaseWidget;
UnlockDatabaseDialog* m_unlockDatabaseDialog;
QSplitter* m_mainSplitter;
QSplitter* m_previewSplitter;
GroupView* m_groupView;
EntryView* m_entryView;
QLabel* m_searchingLabel;
Group* m_newGroup;
Entry* m_newEntry;
Group* m_newParent;
QString m_filePath;
QSharedPointer<Database> m_db;
QPointer<QWidget> m_mainWidget;
QPointer<QSplitter> m_mainSplitter;
QPointer<MessageWidget> m_messageWidget;
QPointer<EntryPreviewWidget> m_previewView;
QPointer<QSplitter> m_previewSplitter;
QPointer<QLabel> m_searchingLabel;
QPointer<CsvImportWizard> m_csvImportWizard;
QPointer<EditEntryWidget> m_editEntryWidget;
QPointer<EditGroupWidget> m_editGroupWidget;
QPointer<EditEntryWidget> m_historyEditEntryWidget;
QPointer<DatabaseSettingsDialog> m_databaseSettingDialog;
QPointer<DatabaseOpenWidget> m_databaseOpenWidget;
QPointer<DatabaseOpenWidget> m_databaseOpenMergeWidget;
QPointer<KeePass1OpenWidget> m_keepass1OpenWidget;
QPointer<UnlockDatabaseWidget> m_unlockDatabaseWidget;
QPointer<UnlockDatabaseDialog> m_unlockDatabaseDialog;
QPointer<GroupView> m_groupView;
QPointer<EntryView> m_entryView;
QPointer<Group> m_newGroup;
QPointer<Entry> m_newEntry;
QPointer<Group> m_newParent;
QUuid m_groupBeforeLock;
QUuid m_entryBeforeLock;
MessageWidget* m_messageWidget;
EntryPreviewWidget* m_previewView;
QString m_databaseName;
QString m_databaseFileName;
@ -251,15 +263,11 @@ private:
QString m_lastSearchText;
bool m_searchLimitGroup;
// CSV import state
bool m_importingCsv;
// Autoreload
QFileSystemWatcher m_fileWatcher;
QTimer m_fileWatchTimer;
QTimer m_fileWatchUnblockTimer;
bool m_ignoreAutoReload;
bool m_databaseModified;
};
#endif // KEEPASSX_DATABASEWIDGET_H

View File

@ -74,7 +74,7 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget)
m_activeDbWidget->setPreviewSplitterSizes(m_previewSplitterSizes);
}
if (m_activeDbWidget->isInSearchMode()) {
if (m_activeDbWidget->isSearchActive()) {
restoreSearchView();
} else {
restoreListView();
@ -177,7 +177,7 @@ void DatabaseWidgetStateSync::updateViewState()
m_hideUsernames = m_activeDbWidget->isUsernamesHidden();
m_hidePasswords = m_activeDbWidget->isPasswordsHidden();
if (m_activeDbWidget->isInSearchMode()) {
if (m_activeDbWidget->isSearchActive()) {
m_searchViewState = m_activeDbWidget->entryViewState();
} else {
m_listViewState = m_activeDbWidget->entryViewState();

View File

@ -56,7 +56,7 @@ void EditWidget::addPage(const QString& labelText, const QIcon& icon, QWidget* w
* from automatic resizing and it now should be able to fit into a user's monitor even if the monitor is only 768
* pixels high.
*/
QScrollArea* scrollArea = new QScrollArea(m_ui->stackedWidget);
auto* scrollArea = new QScrollArea(m_ui->stackedWidget);
scrollArea->setFrameShape(QFrame::NoFrame);
scrollArea->setWidget(widget);
scrollArea->setWidgetResizable(true);

View File

@ -61,7 +61,7 @@ void UrlFetchProgressDialog::networkReplyProgress(qint64 bytesRead, qint64 total
EditWidgetIcons::EditWidgetIcons(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::EditWidgetIcons())
, m_database(nullptr)
, m_db(nullptr)
#ifdef WITH_XC_NETWORKING
, m_reply(nullptr)
#endif
@ -102,7 +102,7 @@ EditWidgetIcons::~EditWidgetIcons()
IconStruct EditWidgetIcons::state()
{
Q_ASSERT(m_database);
Q_ASSERT(m_db);
Q_ASSERT(!m_currentUuid.isNull());
IconStruct iconStruct;
@ -127,16 +127,19 @@ IconStruct EditWidgetIcons::state()
void EditWidgetIcons::reset()
{
m_database = nullptr;
m_db.reset();
m_currentUuid = QUuid();
}
void EditWidgetIcons::load(const QUuid& currentUuid, Database* database, const IconStruct& iconStruct, const QString& url)
void EditWidgetIcons::load(const QUuid& currentUuid,
QSharedPointer<Database> database,
const IconStruct& iconStruct,
const QString& url)
{
Q_ASSERT(database);
Q_ASSERT(!currentUuid.isNull());
m_database = database;
m_db = database;
m_currentUuid = currentUuid;
setUrl(url);
@ -329,7 +332,7 @@ void EditWidgetIcons::startFetchFavicon(const QUrl& url)
void EditWidgetIcons::addCustomIconFromFile()
{
if (m_database) {
if (m_db) {
QString filter = QString("%1 (%2);;%3 (*)").arg(tr("Images"), Tools::imageReaderFilter(), tr("All files"));
auto filenames = QFileDialog::getOpenFileNames(this, tr("Select Image(s)"), "", filter);
@ -378,19 +381,19 @@ void EditWidgetIcons::addCustomIconFromFile()
bool EditWidgetIcons::addCustomIcon(const QImage& icon)
{
bool added = false;
if (m_database) {
if (m_db) {
// Don't add an icon larger than 128x128, but retain original size if smaller
auto scaledicon = icon;
if (icon.width() > 128 || icon.height() > 128) {
scaledicon = icon.scaled(128, 128);
}
QUuid uuid = m_database->metadata()->findCustomIcon(scaledicon);
QUuid uuid = m_db->metadata()->findCustomIcon(scaledicon);
if (uuid.isNull()) {
uuid = QUuid::createUuid();
m_database->metadata()->addCustomIcon(uuid, scaledicon);
m_customIconModel->setIcons(m_database->metadata()->customIconsScaledPixmaps(),
m_database->metadata()->customIconsOrder());
m_db->metadata()->addCustomIcon(uuid, scaledicon);
m_customIconModel->setIcons(m_db->metadata()->customIconsScaledPixmaps(),
m_db->metadata()->customIconsOrder());
added = true;
}
@ -407,12 +410,12 @@ bool EditWidgetIcons::addCustomIcon(const QImage& icon)
void EditWidgetIcons::removeCustomIcon()
{
if (m_database) {
if (m_db) {
QModelIndex index = m_ui->customIconsView->currentIndex();
if (index.isValid()) {
QUuid iconUuid = m_customIconModel->uuidFromIndex(index);
const QList<Entry*> allEntries = m_database->rootGroup()->entriesRecursive(true);
const QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
QList<Entry*> entriesWithSameIcon;
QList<Entry*> historyEntriesWithSameIcon;
@ -427,7 +430,7 @@ void EditWidgetIcons::removeCustomIcon()
}
}
const QList<Group*> allGroups = m_database->rootGroup()->groupsRecursive(true);
const QList<Group*> allGroups = m_db->rootGroup()->groupsRecursive(true);
QList<Group*> groupsWithSameIcon;
for (Group* group : allGroups) {
@ -471,14 +474,14 @@ void EditWidgetIcons::removeCustomIcon()
}
// Remove the icon from the database
m_database->metadata()->removeCustomIcon(iconUuid);
m_customIconModel->setIcons(m_database->metadata()->customIconsScaledPixmaps(),
m_database->metadata()->customIconsOrder());
m_db->metadata()->removeCustomIcon(iconUuid);
m_customIconModel->setIcons(m_db->metadata()->customIconsScaledPixmaps(),
m_db->metadata()->customIconsOrder());
// Reset the current icon view
updateRadioButtonDefaultIcons();
if (m_database->resolveEntry(m_currentUuid) != nullptr) {
if (m_db->rootGroup()->findEntryByUuid(m_currentUuid) != nullptr) {
m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(Entry::DefaultIconNumber));
} else {
m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(Group::DefaultIconNumber));

View File

@ -71,7 +71,10 @@ public:
IconStruct state();
void reset();
void load(const QUuid& currentUuid, Database* database, const IconStruct& iconStruct, const QString& url = "");
void load(const QUuid& currentUuid,
QSharedPointer<Database> database,
const IconStruct& iconStruct,
const QString& url = "");
public slots:
void setUrl(const QString& url);
@ -97,7 +100,7 @@ private slots:
private:
const QScopedPointer<Ui::EditWidgetIcons> m_ui;
Database* m_database;
QSharedPointer<Database> m_db;
QUuid m_currentUuid;
#ifdef WITH_XC_NETWORKING
QUrl m_url;

View File

@ -19,7 +19,6 @@
#include "EntryPreviewWidget.h"
#include "ui_EntryPreviewWidget.h"
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
@ -115,12 +114,12 @@ void EntryPreviewWidget::setGroup(Group* selectedGroup)
void EntryPreviewWidget::setDatabaseMode(DatabaseWidget::Mode mode)
{
m_locked = mode == DatabaseWidget::LockedMode;
m_locked = mode == DatabaseWidget::Mode::LockedMode;
if (m_locked) {
return;
}
if (mode == DatabaseWidget::ViewMode) {
if (mode == DatabaseWidget::Mode::ViewMode) {
if (m_ui->stackedWidget->currentWidget() == m_ui->pageGroup) {
setGroup(m_currentGroup);
} else {

View File

@ -54,15 +54,13 @@ void KeePass1OpenWidget::openDatabase()
return;
}
delete m_db;
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
m_db = reader.readDatabase(&file, password, keyFileName);
QApplication::restoreOverrideCursor();
if (m_db) {
m_db->metadata()->setName(QFileInfo(m_filename).completeBaseName());
emit editFinished(true);
emit dialogFinished(true);
} else {
m_ui->messageWidget->showMessage(tr("Unable to open the database.").append("\n").append(reader.errorString()),
MessageWidget::Error);

View File

@ -32,6 +32,9 @@
#include "core/FilePath.h"
#include "core/InactivityTimer.h"
#include "core/Metadata.h"
#include "keys/CompositeKey.h"
#include "keys/PasswordKey.h"
#include "keys/FileKey.h"
#include "gui/AboutDialog.h"
#include "gui/DatabaseWidget.h"
#include "gui/SearchWidget.h"
@ -132,7 +135,7 @@ MainWindow::MainWindow()
setAcceptDrops(true);
// Setup the search widget in the toolbar
SearchWidget* search = new SearchWidget();
auto* search = new SearchWidget();
search->connectSignals(m_actionMultiplexer);
m_searchWidgetAction = m_ui->toolBar->addWidget(search);
m_searchWidgetAction->setEnabled(false);
@ -293,7 +296,7 @@ MainWindow::MainWindow()
connect(m_ui->actionDatabaseOpen, SIGNAL(triggered()), m_ui->tabWidget, SLOT(openDatabase()));
connect(m_ui->actionDatabaseSave, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabase()));
connect(m_ui->actionDatabaseSaveAs, SIGNAL(triggered()), m_ui->tabWidget, SLOT(saveDatabaseAs()));
connect(m_ui->actionDatabaseClose, SIGNAL(triggered()), m_ui->tabWidget, SLOT(closeDatabase()));
connect(m_ui->actionDatabaseClose, SIGNAL(triggered()), m_ui->tabWidget, SLOT(closeCurrentDatabaseTab()));
connect(m_ui->actionDatabaseMerge, SIGNAL(triggered()), m_ui->tabWidget, SLOT(mergeDatabase()));
connect(m_ui->actionChangeMasterKey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeMasterKey()));
connect(m_ui->actionChangeDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeDatabaseSettings()));
@ -349,11 +352,6 @@ MainWindow::MainWindow()
this,
SLOT(displayGlobalMessage(QString,MessageWidget::MessageType)));
connect(m_ui->tabWidget, SIGNAL(messageDismissGlobal()), this, SLOT(hideGlobalMessage()));
connect(m_ui->tabWidget,
SIGNAL(messageTab(QString,MessageWidget::MessageType)),
this,
SLOT(displayTabMessage(QString,MessageWidget::MessageType)));
connect(m_ui->tabWidget, SIGNAL(messageDismissTab()), this, SLOT(hideTabMessage()));
m_screenLockListener = new ScreenLockListener(this);
connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock()));
@ -450,9 +448,28 @@ void MainWindow::clearLastDatabases()
}
}
void MainWindow::openDatabase(const QString& fileName, const QString& pw, const QString& keyFile)
void MainWindow::openDatabase(const QString& filePath, const QString& pw, const QString& keyFile)
{
m_ui->tabWidget->openDatabase(fileName, pw, keyFile);
if (pw.isEmpty() && keyFile.isEmpty()) {
m_ui->tabWidget->addDatabaseTab(filePath);
return;
}
auto db = QSharedPointer<Database>::create();
auto key = QSharedPointer<CompositeKey>::create();
if (!pw.isEmpty()) {
key->addKey(QSharedPointer<PasswordKey>::create(pw));
}
if (!keyFile.isEmpty()) {
auto fileKey = QSharedPointer<FileKey>::create();
fileKey->load(keyFile);
key->addKey(fileKey);
}
if (db->open(filePath, key, nullptr, false)) {
auto* dbWidget = new DatabaseWidget(db, this);
m_ui->tabWidget->addDatabaseTab(dbWidget);
dbWidget->switchToView(true);
}
}
void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
@ -476,12 +493,12 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
DatabaseWidget* dbWidget = m_ui->tabWidget->currentDatabaseWidget();
Q_ASSERT(dbWidget);
if (mode == DatabaseWidget::None) {
if (mode == DatabaseWidget::Mode::None) {
mode = dbWidget->currentMode();
}
switch (mode) {
case DatabaseWidget::ViewMode: {
case DatabaseWidget::Mode::ViewMode: {
// bool inSearch = dbWidget->isInSearchMode();
bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1 && dbWidget->currentEntryHasFocus();
bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0 && dbWidget->currentEntryHasFocus();
@ -521,9 +538,9 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
break;
}
case DatabaseWidget::EditMode:
case DatabaseWidget::ImportMode:
case DatabaseWidget::LockedMode: {
case DatabaseWidget::Mode::EditMode:
case DatabaseWidget::Mode::ImportMode:
case DatabaseWidget::Mode::LockedMode: {
const QList<QAction*> entryActions = m_ui->menuEntries->actions();
for (QAction* action : entryActions) {
action->setEnabled(false);
@ -589,14 +606,11 @@ void MainWindow::updateWindowTitle()
bool isModified = m_ui->tabWidget->isModified(tabWidgetIndex);
if (stackedWidgetIndex == DatabaseTabScreen && tabWidgetIndex != -1) {
customWindowTitlePart = m_ui->tabWidget->tabText(tabWidgetIndex);
customWindowTitlePart = m_ui->tabWidget->tabName(tabWidgetIndex);
if (isModified) {
// remove asterisk '*' from title
customWindowTitlePart.remove(customWindowTitlePart.size() - 1, 1);
}
if (m_ui->tabWidget->readOnly(tabWidgetIndex)) {
customWindowTitlePart = tr("%1 [read-only]", "window title modifier").arg(customWindowTitlePart);
}
m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave(tabWidgetIndex));
} else if (stackedWidgetIndex == 1) {
customWindowTitlePart = tr("Settings");
@ -612,17 +626,16 @@ void MainWindow::updateWindowTitle()
if (customWindowTitlePart.isEmpty() || stackedWidgetIndex == 1) {
setWindowFilePath("");
} else {
setWindowFilePath(m_ui->tabWidget->databasePath(tabWidgetIndex));
setWindowFilePath(m_ui->tabWidget->databaseWidgetFromIndex(tabWidgetIndex)->database()->filePath());
}
setWindowModified(isModified);
setWindowTitle(windowTitle);
setWindowModified(isModified);
}
void MainWindow::showAboutDialog()
{
AboutDialog* aboutDialog = new AboutDialog(this);
auto* aboutDialog = new AboutDialog(this);
aboutDialog->open();
}
@ -687,7 +700,7 @@ void MainWindow::switchToOpenDatabase()
void MainWindow::switchToDatabaseFile(const QString& file)
{
m_ui->tabWidget->openDatabase(file);
m_ui->tabWidget->addDatabaseTab(file);
switchToDatabases();
}
@ -703,8 +716,9 @@ void MainWindow::switchToCsvImport()
switchToDatabases();
}
void MainWindow::databaseStatusChanged(DatabaseWidget*)
void MainWindow::databaseStatusChanged(DatabaseWidget* dbWidget)
{
Q_UNUSED(dbWidget);
updateTrayIcon();
}
@ -817,18 +831,14 @@ bool MainWindow::saveLastDatabases()
bool openPreviousDatabasesOnStartup = config()->get("OpenPreviousDatabasesOnStartup").toBool();
if (openPreviousDatabasesOnStartup) {
connect(m_ui->tabWidget, SIGNAL(databaseWithFileClosed(QString)), this, SLOT(rememberOpenDatabases(QString)));
connect(m_ui->tabWidget, SIGNAL(databaseClosed(const QString&)), this, SLOT(rememberOpenDatabases(const QString&)));
}
if (!m_ui->tabWidget->closeAllDatabases()) {
accept = false;
} else {
accept = true;
}
accept = m_ui->tabWidget->closeAllDatabaseTabs();
if (openPreviousDatabasesOnStartup) {
disconnect(
m_ui->tabWidget, SIGNAL(databaseWithFileClosed(QString)), this, SLOT(rememberOpenDatabases(QString)));
m_ui->tabWidget, SIGNAL(databaseClosed(const QString&)), this, SLOT(rememberOpenDatabases(const QString&)));
config()->set("LastOpenedDatabases", m_openDatabases);
}
@ -1036,13 +1046,6 @@ void MainWindow::hideGlobalMessage()
m_ui->globalMessageWidget->hideMessage();
}
void MainWindow::hideTabMessage()
{
if (m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) {
m_ui->tabWidget->currentDatabaseWidget()->hideMessage();
}
}
void MainWindow::showYubiKeyPopup()
{
displayGlobalMessage(tr("Please touch the button on your YubiKey!"),
@ -1121,7 +1124,7 @@ void MainWindow::dropEvent(QDropEvent* event)
void MainWindow::closeAllDatabases()
{
m_ui->tabWidget->closeAllDatabases();
m_ui->tabWidget->closeAllDatabaseTabs();
}
void MainWindow::lockAllDatabases()

View File

@ -56,7 +56,7 @@ public:
};
public slots:
void openDatabase(const QString& fileName, const QString& pw = QString(), const QString& keyFile = QString());
void openDatabase(const QString& filePath, const QString& pw = {}, const QString& keyFile = {});
void appExit();
void displayGlobalMessage(const QString& text,
MessageWidget::MessageType type,
@ -80,7 +80,7 @@ protected:
void changeEvent(QEvent* event) override;
private slots:
void setMenuActionState(DatabaseWidget::Mode mode = DatabaseWidget::None);
void setMenuActionState(DatabaseWidget::Mode mode = DatabaseWidget::Mode::None);
void updateWindowTitle();
void showAboutDialog();
void openDonateUrl();
@ -107,7 +107,6 @@ private slots:
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
void lockDatabasesAfterInactivity();
void forgetTouchIDAfterInactivity();
void hideTabMessage();
void handleScreenLock();
void showErrorMessage(const QString& message);
void selectNextDatabaseTab();

View File

@ -135,7 +135,7 @@ void SearchWidget::databaseChanged(DatabaseWidget* dbWidget)
// Set current search text from this database
m_ui->searchEdit->setText(dbWidget->getCurrentSearch());
// Keyboard focus on search widget at database unlocking
connect(dbWidget, SIGNAL(unlockedDatabase()), this, SLOT(searchFocus()));
connect(dbWidget, SIGNAL(databaseUnlocked()), this, SLOT(searchFocus()));
// Enforce search policy
emit caseSensitiveChanged(m_actionCaseSensitive->isChecked());
emit limitGroupChanged(m_actionLimitGroup->isChecked());

View File

@ -27,7 +27,7 @@ UnlockDatabaseDialog::UnlockDatabaseDialog(QWidget* parent)
, m_view(new UnlockDatabaseWidget(this))
{
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
connect(m_view, SIGNAL(editFinished(bool)), this, SLOT(complete(bool)));
connect(m_view, SIGNAL(dialogFinished(bool)), this, SLOT(complete(bool)));
}
void UnlockDatabaseDialog::setFilePath(const QString& filePath)
@ -40,7 +40,7 @@ void UnlockDatabaseDialog::clearForms()
m_view->clearForms();
}
Database* UnlockDatabaseDialog::database()
QSharedPointer<Database> UnlockDatabaseDialog::database()
{
return m_view->database();
}

View File

@ -34,7 +34,7 @@ public:
explicit UnlockDatabaseDialog(QWidget* parent = nullptr);
void setFilePath(const QString& filePath);
void clearForms();
Database* database();
QSharedPointer<Database> database();
signals:
void unlockDone(bool);

View File

@ -92,9 +92,9 @@ void CsvParserModel::setSkippedRows(int skipped)
emit layoutChanged();
}
void CsvParserModel::setHeaderLabels(QStringList l)
void CsvParserModel::setHeaderLabels(const QStringList& labels)
{
m_columnHeader = std::move(l);
m_columnHeader = labels;
}
int CsvParserModel::rowCount(const QModelIndex& parent) const

View File

@ -36,7 +36,7 @@ public:
QString getFileInfo();
bool parse();
void setHeaderLabels(QStringList l);
void setHeaderLabels(const QStringList& labels);
void mapColumns(int csvColumn, int dbColumn);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;

View File

@ -73,7 +73,7 @@ DatabaseSettingsDialog::~DatabaseSettingsDialog()
{
}
void DatabaseSettingsDialog::load(Database* db)
void DatabaseSettingsDialog::load(QSharedPointer<Database> db)
{
m_ui->categoryList->setCurrentCategory(0);
m_generalWidget->load(db);

View File

@ -21,8 +21,9 @@
#include "gui/DialogyWidget.h"
#include "config-keepassx.h"
#include <QScopedPointer>
#include <QPointer>
#include <QScopedPointer>
#include <QSharedPointer>
class Database;
class DatabaseSettingsWidgetGeneral;
@ -47,7 +48,7 @@ public:
~DatabaseSettingsDialog() override;
Q_DISABLE_COPY(DatabaseSettingsDialog);
void load(Database* db);
void load(QSharedPointer<Database> db);
void showMasterKeySettings();
signals:
@ -66,7 +67,7 @@ private:
Security = 1
};
QPointer<Database> m_db;
QSharedPointer<Database> m_db;
const QScopedPointer<Ui::DatabaseSettingsDialog> m_ui;
QPointer<DatabaseSettingsWidgetGeneral> m_generalWidget;
QPointer<QTabWidget> m_securityTabWidget;

View File

@ -36,7 +36,7 @@ DatabaseSettingsWidget::~DatabaseSettingsWidget()
*
* @param db database object to be configured
*/
void DatabaseSettingsWidget::load(Database* db)
void DatabaseSettingsWidget::load(QSharedPointer<Database> db)
{
m_db = db;
initialize();

View File

@ -20,7 +20,7 @@
#include "gui/settings/SettingsWidget.h"
#include <QPointer>
#include <QSharedPointer>
class Database;
@ -36,7 +36,7 @@ public:
Q_DISABLE_COPY(DatabaseSettingsWidget);
~DatabaseSettingsWidget() override;
virtual void load(Database* db);
virtual void load(QSharedPointer<Database> db);
signals:
/**
@ -45,7 +45,7 @@ signals:
void sizeChanged();
protected:
QPointer<Database> m_db;
QSharedPointer<Database> m_db;
};
#endif //KEEPASSXC_DATABASESETTINGSWIDGET_H

View File

@ -44,7 +44,7 @@ void DatabaseSettingsWidgetGeneral::initialize()
m_ui->dbDescriptionEdit->setText(meta->description());
m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled());
m_ui->defaultUsernameEdit->setText(meta->defaultUserName());
m_ui->compressionCheckbox->setChecked(m_db->compressionAlgo() != Database::CompressionNone);
m_ui->compressionCheckbox->setChecked(m_db->compressionAlgorithm() != Database::CompressionNone);
if (meta->historyMaxItems() > -1) {
m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems());
@ -75,8 +75,8 @@ void DatabaseSettingsWidgetGeneral::showEvent(QShowEvent* event)
bool DatabaseSettingsWidgetGeneral::save()
{
m_db->setCompressionAlgo(m_ui->compressionCheckbox->isChecked() ? Database::CompressionGZip
: Database::CompressionNone);
m_db->setCompressionAlgorithm(m_ui->compressionCheckbox->isChecked() ? Database::CompressionGZip
: Database::CompressionNone);
Metadata* meta = m_db->metadata();
meta->setName(m_ui->dbNameEdit->text());

View File

@ -68,7 +68,7 @@ DatabaseSettingsWidgetMasterKey::~DatabaseSettingsWidgetMasterKey()
{
}
void DatabaseSettingsWidgetMasterKey::load(Database* db)
void DatabaseSettingsWidgetMasterKey::load(QSharedPointer<Database> db)
{
DatabaseSettingsWidget::load(db);

View File

@ -41,7 +41,7 @@ public:
Q_DISABLE_COPY(DatabaseSettingsWidgetMasterKey);
~DatabaseSettingsWidgetMasterKey() override;
void load(Database* db) override;
void load(QSharedPointer<Database> db) override;
inline bool hasAdvancedMode() const override { return false; }

View File

@ -49,7 +49,7 @@ void AutoTypeAssociationsModel::setAutoTypeAssociations(AutoTypeAssociations* au
endResetModel();
}
void AutoTypeAssociationsModel::setEntry(const Entry* entry)
void AutoTypeAssociationsModel::setEntry(Entry* entry)
{
m_entry = entry;
}

View File

@ -32,7 +32,7 @@ class AutoTypeAssociationsModel : public QAbstractListModel
public:
explicit AutoTypeAssociationsModel(QObject* parent = nullptr);
void setAutoTypeAssociations(AutoTypeAssociations* autoTypeAssociations);
void setEntry(const Entry* entry);
void setEntry(Entry* entry);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

View File

@ -1,3 +1,5 @@
#include <utility>
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
@ -340,7 +342,7 @@ void EditEntryWidget::setupSSHAgent()
connect(m_sshAgentUi->decryptButton, SIGNAL(clicked()), SLOT(decryptPrivateKey()));
connect(m_sshAgentUi->copyToClipboardButton, SIGNAL(clicked()), SLOT(copyPublicKey()));
connect(m_advancedUi->attachmentsWidget->entryAttachments(), SIGNAL(modified()), SLOT(updateSSHAgentAttachments()));
connect(m_advancedUi->attachmentsWidget->entryAttachments(), SIGNAL(entryAttachmentsModified()), SLOT(updateSSHAgentAttachments()));
addPage(tr("SSH Agent"), FilePath::instance()->icon("apps", "utilities-terminal"), m_sshAgentWidget);
}
@ -640,10 +642,10 @@ QString EditEntryWidget::entryTitle() const
}
}
void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const QString& parentName, Database* database)
void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const QString& parentName, QSharedPointer<Database> database)
{
m_entry = entry;
m_database = database;
m_db = std::move(database);
m_create = create;
m_history = history;
@ -667,7 +669,7 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const Q
setUnsavedChanges(m_create);
}
void EditEntryWidget::setForms(const Entry* entry, bool restore)
void EditEntryWidget::setForms(Entry* entry, bool restore)
{
m_mainUi->titleEdit->setReadOnly(m_history);
m_mainUi->usernameEdit->setReadOnly(m_history);
@ -734,7 +736,7 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore)
IconStruct iconStruct;
iconStruct.uuid = entry->iconUuid();
iconStruct.number = entry->iconNumber();
m_iconsWidget->load(entry->uuid(), m_database, iconStruct, entry->webUrl());
m_iconsWidget->load(entry->uuid(), m_db, iconStruct, entry->webUrl());
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
m_autoTypeUi->enableButton->setChecked(entry->autoTypeEnabled());
@ -924,7 +926,7 @@ void EditEntryWidget::cancel()
return;
}
if (!m_entry->iconUuid().isNull() && !m_database->metadata()->containsCustomIcon(m_entry->iconUuid())) {
if (!m_entry->iconUuid().isNull() && !m_db->metadata()->containsCustomIcon(m_entry->iconUuid())) {
m_entry->setIcon(Entry::DefaultIconNumber);
}
@ -952,7 +954,7 @@ void EditEntryWidget::cancel()
void EditEntryWidget::clear()
{
m_entry = nullptr;
m_database = nullptr;
m_db.reset();
m_entryAttributes->clear();
m_advancedUi->attachmentsWidget->clearAttachments();
m_autoTypeAssoc->clear();
@ -969,11 +971,11 @@ bool EditEntryWidget::hasBeenModified() const
}
// check if updating the entry would modify it
QScopedPointer<Entry> entry(new Entry());
entry->copyDataFrom(m_entry);
auto* entry = new Entry();
entry->copyDataFrom(m_entry.data());
entry->beginUpdate();
updateEntryData(entry.data());
updateEntryData(entry);
return entry->endUpdate();
}
@ -1256,17 +1258,13 @@ void EditEntryWidget::deleteHistoryEntry()
void EditEntryWidget::deleteAllHistoryEntries()
{
m_historyModel->deleteAll();
if (m_historyModel->rowCount() > 0) {
m_historyUi->deleteAllButton->setEnabled(true);
} else {
m_historyUi->deleteAllButton->setEnabled(false);
}
m_historyUi->deleteAllButton->setEnabled(m_historyModel->rowCount() > 0);
setUnsavedChanges(true);
}
QMenu* EditEntryWidget::createPresetsMenu()
{
QMenu* expirePresetsMenu = new QMenu(this);
auto* expirePresetsMenu = new QMenu(this);
expirePresetsMenu->addAction(tr("Tomorrow"))->setData(QVariant::fromValue(TimeDelta::fromDays(1)));
expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromDays(7)));
@ -1277,9 +1275,9 @@ QMenu* EditEntryWidget::createPresetsMenu()
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromMonths(3)));
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 6))->setData(QVariant::fromValue(TimeDelta::fromMonths(6)));
expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("%n year(s)", 0, 1))->setData(QVariant::fromValue(TimeDelta::fromYears(1)));
expirePresetsMenu->addAction(tr("%n year(s)", 0, 2))->setData(QVariant::fromValue(TimeDelta::fromYears(2)));
expirePresetsMenu->addAction(tr("%n year(s)", 0, 3))->setData(QVariant::fromValue(TimeDelta::fromYears(3)));
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromYears(1)));
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromYears(2)));
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromYears(3)));
return expirePresetsMenu;
}

View File

@ -38,7 +38,6 @@ class EntryHistoryModel;
class QButtonGroup;
class QMenu;
class QSortFilterProxyModel;
class QStackedLayout;
#ifdef WITH_XC_SSHAGENT
#include "sshagent/KeeAgentSettings.h"
class OpenSSHKey;
@ -60,11 +59,11 @@ class EditEntryWidget : public EditWidget
public:
explicit EditEntryWidget(QWidget* parent = nullptr);
~EditEntryWidget();
~EditEntryWidget() override;
void loadEntry(Entry* entry, bool create, bool history, const QString& parentName, Database* database);
void loadEntry(Entry* entry, bool create, bool history, const QString& parentName,
QSharedPointer<Database> database);
void createPresetsMenu(QMenu* expirePresetsMenu);
QString entryTitle() const;
void clear();
bool hasBeenModified() const;
@ -128,7 +127,7 @@ private:
void setupColorButton(bool foreground, const QColor& color);
bool passwordsEqual();
void setForms(const Entry* entry, bool restore = false);
void setForms(Entry* entry, bool restore = false);
QMenu* createPresetsMenu();
void updateEntryData(Entry* entry) const;
#ifdef WITH_XC_SSHAGENT
@ -138,8 +137,8 @@ private:
void displayAttribute(QModelIndex index, bool showProtected);
Entry* m_entry;
Database* m_database;
QPointer<Entry> m_entry;
QSharedPointer<Database> m_db;
bool m_create;
bool m_history;

View File

@ -30,7 +30,6 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
, m_editGroupWidgetIcons(new EditWidgetIcons())
, m_editWidgetProperties(new EditWidgetProperties())
, m_group(nullptr)
, m_database(nullptr)
{
m_mainUi->setupUi(m_editGroupWidgetMain);
@ -58,10 +57,10 @@ EditGroupWidget::~EditGroupWidget()
{
}
void EditGroupWidget::loadGroup(Group* group, bool create, Database* database)
void EditGroupWidget::loadGroup(Group* group, bool create, QSharedPointer<Database> database)
{
m_group = group;
m_database = database;
m_db = database;
if (create) {
setHeadline(tr("Add group"));
@ -141,7 +140,7 @@ void EditGroupWidget::apply()
void EditGroupWidget::cancel()
{
if (!m_group->iconUuid().isNull() && !m_database->metadata()->containsCustomIcon(m_group->iconUuid())) {
if (!m_group->iconUuid().isNull() && !m_db->metadata()->containsCustomIcon(m_group->iconUuid())) {
m_group->setIcon(Entry::DefaultIconNumber);
}
@ -152,7 +151,7 @@ void EditGroupWidget::cancel()
void EditGroupWidget::clear()
{
m_group = nullptr;
m_database = nullptr;
m_db.reset();
m_editGroupWidgetIcons->reset();
}

View File

@ -41,7 +41,7 @@ public:
explicit EditGroupWidget(QWidget* parent = nullptr);
~EditGroupWidget();
void loadGroup(Group* group, bool create, Database* database);
void loadGroup(Group* group, bool create, QSharedPointer<Database> database);
void clear();
signals:
@ -65,7 +65,7 @@ private:
QPointer<EditWidgetProperties> m_editWidgetProperties;
QPointer<Group> m_group;
QPointer<Database> m_database;
QSharedPointer<Database> m_db;
Q_DISABLE_COPY(EditGroupWidget)
};

View File

@ -37,10 +37,6 @@ void GroupModel::changeDatabase(Database* newDb)
{
beginResetModel();
if (m_db) {
m_db->disconnect(this);
}
m_db = newDb;
connect(m_db, SIGNAL(groupDataChanged(Group*)), SLOT(groupDataChanged(Group*)));
@ -233,7 +229,7 @@ bool GroupModel::dropMimeData(const QMimeData* data,
return false;
}
Group* dragGroup = db->resolveGroup(groupUuid);
Group* dragGroup = db->rootGroup()->findGroupByUuid(groupUuid);
if (!dragGroup || !db->rootGroup()->findGroupByUuid(dragGroup->uuid()) || dragGroup == db->rootGroup()) {
return false;
}
@ -277,7 +273,7 @@ bool GroupModel::dropMimeData(const QMimeData* data,
continue;
}
Entry* dragEntry = db->resolveEntry(entryUuid);
Entry* dragEntry = db->rootGroup()->findEntryByUuid(entryUuid);
if (!dragEntry || !db->rootGroup()->findEntryByUuid(dragEntry->uuid())) {
continue;
}

View File

@ -51,9 +51,9 @@ GroupView::GroupView(Database* db, QWidget* parent)
setDefaultDropAction(Qt::MoveAction);
}
void GroupView::changeDatabase(Database* newDb)
void GroupView::changeDatabase(QSharedPointer<Database> newDb)
{
m_model->changeDatabase(newDb);
m_model->changeDatabase(newDb.data());
}
void GroupView::dragMoveEvent(QDragMoveEvent* event)

View File

@ -30,7 +30,7 @@ class GroupView : public QTreeView
public:
explicit GroupView(Database* db, QWidget* parent = nullptr);
void changeDatabase(Database* newDb);
void changeDatabase(QSharedPointer<Database> newDb);
void setModel(QAbstractItemModel* model) override;
Group* currentGroup();
void setCurrentGroup(Group* group);

View File

@ -17,7 +17,6 @@
#include "ElidedLabel.h"
#include <QDebug>
#include <QResizeEvent>
namespace

View File

@ -39,7 +39,7 @@ NewDatabaseWizard::NewDatabaseWizard(QWidget* parent)
<< new NewDatabaseWizardPageEncryption()
<< new NewDatabaseWizardPageMasterKey();
for (auto const& page: asConst(m_pages)) {
for (const auto& page: asConst(m_pages)) {
addPage(page);
}
@ -54,23 +54,34 @@ NewDatabaseWizard::~NewDatabaseWizard()
bool NewDatabaseWizard::validateCurrentPage()
{
return m_pages[currentId()]->validatePage();
bool ok = m_pages[currentId()]->validatePage();
if (ok && currentId() == m_pages.size() - 1) {
m_db->setInitialized(true);
}
return ok;
}
Database* NewDatabaseWizard::takeDatabase()
/**
* Take configured database and reset internal pointer.
*
* @return the configured database
*/
QSharedPointer<Database> NewDatabaseWizard::takeDatabase()
{
return m_db.take();
auto tmpPointer = m_db;
m_db.reset();
return tmpPointer;
}
void NewDatabaseWizard::initializePage(int id)
{
if (id == startId()) {
m_db.reset(new Database());
m_db = QSharedPointer<Database>::create();
m_db->rootGroup()->setName(tr("Root", "Root group"));
m_db->setKdf({});
m_db->setKey({});
}
m_pages[id]->setDatabase(m_db.data());
m_pages[id]->setDatabase(m_db);
m_pages[id]->initializePage();
}

View File

@ -19,7 +19,7 @@
#define KEEPASSXC_NEWDATABASEWIZARD_H
#include <QPointer>
#include <QScopedPointer>
#include <QSharedPointer>
#include <QWizard>
class Database;
@ -36,14 +36,14 @@ public:
explicit NewDatabaseWizard(QWidget* parent = nullptr);
~NewDatabaseWizard() override;
Database* takeDatabase();
QSharedPointer<Database> takeDatabase();
bool validateCurrentPage() override;
protected:
void initializePage(int id) override;
private:
QScopedPointer<Database> m_db;
QSharedPointer<Database> m_db;
QList<QPointer<NewDatabaseWizardPage>> m_pages;
};

View File

@ -1,3 +1,5 @@
#include <utility>
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
@ -66,9 +68,9 @@ DatabaseSettingsWidget* NewDatabaseWizardPage::pageWidget()
*
* @param db database object to be configured
*/
void NewDatabaseWizardPage::setDatabase(Database* db)
void NewDatabaseWizardPage::setDatabase(QSharedPointer<Database> db)
{
m_db = db;
m_db = std::move(db);
}
void NewDatabaseWizardPage::initializePage()

View File

@ -43,7 +43,7 @@ public:
void setPageWidget(DatabaseSettingsWidget* page);
DatabaseSettingsWidget* pageWidget();
void setDatabase(Database* db);
void setDatabase(QSharedPointer<Database> db);
void initializePage() override;
bool validatePage() override;
@ -53,7 +53,7 @@ public slots:
protected:
QPointer<DatabaseSettingsWidget> m_pageWidget;
QPointer<Database> m_db;
QSharedPointer<Database> m_db;
const QScopedPointer<Ui::NewDatabaseWizardPage> m_ui;
};

View File

@ -18,8 +18,6 @@
#include <stdio.h>
#include <QDebug>
#include <ykcore.h>
#include <ykdef.h>
#include <ykstatus.h>
@ -215,9 +213,9 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte
*/
if (yk_errno == YK_EUSBERR) {
qWarning() << "USB error:" << yk_usb_strerror();
qWarning("USB error: %s", yk_usb_strerror());
} else {
qWarning() << "YubiKey core error:" << yk_strerror(yk_errno);
qWarning("YubiKey core error: %s", yk_strerror(yk_errno));
}
return ERROR;

View File

@ -251,7 +251,7 @@ void SSHAgent::databaseModeChanged(DatabaseWidget::Mode mode)
const QUuid& uuid = widget->database()->uuid();
if (mode == DatabaseWidget::LockedMode && m_keys.contains(uuid)) {
if (mode == DatabaseWidget::Mode::LockedMode && m_keys.contains(uuid)) {
QSet<OpenSSHKey> keys = m_keys.take(uuid);
for (OpenSSHKey key : keys) {
@ -259,7 +259,7 @@ void SSHAgent::databaseModeChanged(DatabaseWidget::Mode mode)
emit error(m_error);
}
}
} else if (mode == DatabaseWidget::ViewMode && !m_keys.contains(uuid)) {
} else if (mode == DatabaseWidget::Mode::ViewMode && !m_keys.contains(uuid)) {
for (Entry* e : widget->database()->rootGroup()->entriesRecursive()) {
if (widget->database()->metadata()->recycleBinEnabled()

View File

@ -43,7 +43,7 @@ signals:
void error(const QString& message);
public slots:
void databaseModeChanged(DatabaseWidget::Mode mode = DatabaseWidget::LockedMode);
void databaseModeChanged(DatabaseWidget::Mode mode = DatabaseWidget::Mode::LockedMode);
private:
const quint8 SSH_AGENT_FAILURE = 5;

View File

@ -57,7 +57,7 @@ void TestAutoType::init()
config()->set("AutoTypeEntryTitleMatch", false);
m_test->clearActions();
m_db = new Database();
m_db = QSharedPointer<Database>::create();
m_dbList.clear();
m_dbList.append(m_db);
m_group = new Group();
@ -126,7 +126,6 @@ void TestAutoType::init()
void TestAutoType::cleanup()
{
delete m_db;
}
void TestAutoType::testInternal()

View File

@ -20,6 +20,7 @@
#define KEEPASSX_TESTAUTOTYPE_H
#include <QObject>
#include <QSharedPointer>
class AutoType;
class AutoTypePlatformInterface;
@ -53,8 +54,8 @@ private:
AutoTypePlatformInterface* m_platform;
AutoTypeTestInterface* m_test;
AutoType* m_autoType;
Database* m_db;
QList<Database*> m_dbList;
QSharedPointer<Database> m_db;
QList<QSharedPointer<Database>> m_dbList;
Group* m_group;
Entry* m_entry1;
Entry* m_entry2;

View File

@ -671,7 +671,8 @@ void TestCli::testMerge()
QFile readBack(targetFile1.fileName());
readBack.open(QIODevice::ReadOnly);
QScopedPointer<Database> mergedDb(reader.readDatabase(&readBack, oldKey));
auto mergedDb = QSharedPointer<Database>::create();
reader.readDatabase(&readBack, oldKey, mergedDb.data());
readBack.close();
QVERIFY(mergedDb);
auto* entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website");
@ -691,7 +692,8 @@ void TestCli::testMerge()
readBack.setFileName(targetFile2.fileName());
readBack.open(QIODevice::ReadOnly);
mergedDb.reset(reader.readDatabase(&readBack, key));
mergedDb = QSharedPointer<Database>::create();
reader.readDatabase(&readBack, key, mergedDb.data());
readBack.close();
QVERIFY(mergedDb);
entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website");
@ -740,7 +742,8 @@ void TestCli::testRemove()
key->addKey(QSharedPointer<PasswordKey>::create("a"));
QFile readBack(m_dbFile->fileName());
readBack.open(QIODevice::ReadOnly);
QScopedPointer<Database> readBackDb(reader.readDatabase(&readBack, key));
auto readBackDb = QSharedPointer<Database>::create();
reader.readDatabase(&readBack, key, readBackDb.data());
readBack.close();
QVERIFY(readBackDb);
QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry"));
@ -757,7 +760,8 @@ void TestCli::testRemove()
readBack.setFileName(fileCopy.fileName());
readBack.open(QIODevice::ReadOnly);
readBackDb.reset(reader.readDatabase(&readBack, key));
readBackDb = QSharedPointer<Database>::create();
reader.readDatabase(&readBack, key, readBackDb.data());
readBack.close();
QVERIFY(readBackDb);
QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry"));

View File

@ -31,8 +31,8 @@ const QString TestCsvExporter::ExpectedHeaderLine =
void TestCsvExporter::init()
{
m_db = new Database();
m_csvExporter = new CsvExporter();
m_db = QSharedPointer<Database>::create();
m_csvExporter = QSharedPointer<CsvExporter>::create();
}
void TestCsvExporter::initTestCase()
@ -42,17 +42,15 @@ void TestCsvExporter::initTestCase()
void TestCsvExporter::cleanup()
{
delete m_db;
delete m_csvExporter;
}
void TestCsvExporter::testExport()
{
Group* groupRoot = m_db->rootGroup();
Group* group = new Group();
auto* group = new Group();
group->setName("Test Group Name");
group->setParent(groupRoot);
Entry* entry = new Entry();
auto* entry = new Entry();
entry->setGroup(group);
entry->setTitle("Test Entry Title");
entry->setUsername("Test Username");
@ -65,7 +63,7 @@ void TestCsvExporter::testExport()
m_csvExporter->exportDatabase(&buffer, m_db);
QString expectedResult =
QString().append(ExpectedHeaderLine).append("\"Test Group Name\",\"Test Entry Title\",\"Test Username\",\"Test "
QString().append(ExpectedHeaderLine).append("\"Root/Test Group Name\",\"Test Entry Title\",\"Test Username\",\"Test "
"Password\",\"http://test.url\",\"Test Notes\"\n");
QCOMPARE(QString::fromUtf8(buffer.buffer().constData()), expectedResult);
@ -83,13 +81,13 @@ void TestCsvExporter::testEmptyDatabase()
void TestCsvExporter::testNestedGroups()
{
Group* groupRoot = m_db->rootGroup();
Group* group = new Group();
auto* group = new Group();
group->setName("Test Group Name");
group->setParent(groupRoot);
Group* childGroup = new Group();
auto* childGroup = new Group();
childGroup->setName("Test Sub Group Name");
childGroup->setParent(group);
Entry* entry = new Entry();
auto* entry = new Entry();
entry->setGroup(childGroup);
entry->setTitle("Test Entry Title");
@ -100,5 +98,5 @@ void TestCsvExporter::testNestedGroups()
QCOMPARE(QString::fromUtf8(buffer.buffer().constData()),
QString()
.append(ExpectedHeaderLine)
.append("\"Test Group Name/Test Sub Group Name\",\"Test Entry Title\",\"\",\"\",\"\",\"\"\n"));
.append("\"Root/Test Group Name/Test Sub Group Name\",\"Test Entry Title\",\"\",\"\",\"\",\"\"\n"));
}

View File

@ -20,6 +20,7 @@
#define KEEPASSX_TESTCSVEXPORTER_H
#include <QObject>
#include <QSharedPointer>
class Database;
class CsvExporter;
@ -40,8 +41,8 @@ private slots:
void testNestedGroups();
private:
Database* m_db;
CsvExporter* m_csvExporter;
QSharedPointer<Database> m_db;
QSharedPointer<CsvExporter> m_csvExporter;
};
#endif // KEEPASSX_TESTCSVEXPORTER_H

View File

@ -40,14 +40,18 @@ void TestDatabase::testEmptyRecycleBinOnDisabled()
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinDisabled.kdbx");
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("123"));
QScopedPointer<Database> db(Database::openDatabaseFile(filename, key));
QVERIFY(db);
auto db = QSharedPointer<Database>::create();
QVERIFY(db->open(filename, key, nullptr, false));
QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate()));
// Explicitly mark DB as read-write in case it was opened from a read-only drive.
// Prevents assertion failures on CI systems when the data dir is not writable
db->setReadOnly(false);
QSignalSpy spyModified(db.data(), SIGNAL(databaseModified()));
db->emptyRecycleBin();
// The database must be unmodified in this test after emptying the recycle bin.
QCOMPARE(spyModified.count(), 0);
QTRY_COMPARE(spyModified.count(), 0);
}
void TestDatabase::testEmptyRecycleBinOnNotCreated()
@ -55,14 +59,15 @@ void TestDatabase::testEmptyRecycleBinOnNotCreated()
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinNotYetCreated.kdbx");
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("123"));
QScopedPointer<Database> db(Database::openDatabaseFile(filename, key));
QVERIFY(db);
auto db = QSharedPointer<Database>::create();
QVERIFY(db->open(filename, key, nullptr, false));
db->setReadOnly(false);
QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate()));
QSignalSpy spyModified(db.data(), SIGNAL(databaseModified()));
db->emptyRecycleBin();
// The database must be unmodified in this test after emptying the recycle bin.
QCOMPARE(spyModified.count(), 0);
QTRY_COMPARE(spyModified.count(), 0);
}
void TestDatabase::testEmptyRecycleBinOnEmpty()
@ -70,14 +75,15 @@ void TestDatabase::testEmptyRecycleBinOnEmpty()
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinEmpty.kdbx");
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("123"));
QScopedPointer<Database> db(Database::openDatabaseFile(filename, key));
QVERIFY(db);
auto db = QSharedPointer<Database>::create();
QVERIFY(db->open(filename, key, nullptr, false));
db->setReadOnly(false);
QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate()));
QSignalSpy spyModified(db.data(), SIGNAL(databaseModified()));
db->emptyRecycleBin();
// The database must be unmodified in this test after emptying the recycle bin.
QCOMPARE(spyModified.count(), 0);
QTRY_COMPARE(spyModified.count(), 0);
}
void TestDatabase::testEmptyRecycleBinWithHierarchicalData()
@ -85,8 +91,9 @@ void TestDatabase::testEmptyRecycleBinWithHierarchicalData()
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinWithData.kdbx");
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("123"));
QScopedPointer<Database> db(Database::openDatabaseFile(filename, key));
QVERIFY(db);
auto db = QSharedPointer<Database>::create();
QVERIFY(db->open(filename, key, nullptr, false));
db->setReadOnly(false);
QFile originalFile(filename);
qint64 initialSize = originalFile.size();
@ -97,6 +104,8 @@ void TestDatabase::testEmptyRecycleBinWithHierarchicalData()
QVERIFY(db->metadata()->recycleBin()->children().empty());
QTemporaryFile afterCleanup;
afterCleanup.open();
KeePass2Writer writer;
writer.writeDatabase(&afterCleanup, db.data());
QVERIFY(afterCleanup.size() < initialSize);

View File

@ -30,7 +30,7 @@ void TestDeletedObjects::initTestCase()
QVERIFY(Crypto::init());
}
void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize)
void TestDeletedObjects::createAndDelete(QSharedPointer<Database> db, int delObjectsSize)
{
QCOMPARE(db->deletedObjects().size(), delObjectsSize);
Group* root = db->rootGroup();
@ -89,32 +89,27 @@ void TestDeletedObjects::testDeletedObjectsFromFile()
KdbxXmlReader reader(KeePass2::FILE_VERSION_3_1);
reader.setStrictMode(true);
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
Database* db = reader.readDatabase(xmlFile);
auto db = reader.readDatabase(xmlFile);
createAndDelete(db, 2);
delete db;
}
void TestDeletedObjects::testDeletedObjectsFromNewDb()
{
Database* db = new Database();
auto db = QSharedPointer<Database>::create();
createAndDelete(db, 0);
delete db;
}
void TestDeletedObjects::testDatabaseChange()
{
Database* db = new Database();
auto db = QSharedPointer<Database>::create();
Group* root = db->rootGroup();
int delObjectsSize = 0;
Database* db2 = new Database();
auto db2 = QSharedPointer<Database>::create();
Group* root2 = db2->rootGroup();
int delObjectsSize2 = 0;
Entry* e = new Entry();
auto* e = new Entry();
e->setGroup(root);
QCOMPARE(db->deletedObjects().size(), delObjectsSize);
@ -130,11 +125,11 @@ void TestDeletedObjects::testDatabaseChange()
QCOMPARE(db->deletedObjects().size(), delObjectsSize);
QCOMPARE(db2->deletedObjects().size(), ++delObjectsSize2);
Group* g1 = new Group();
auto* g1 = new Group();
g1->setParent(root);
QUuid g1Uuid = QUuid::createUuid();
g1->setUuid(g1Uuid);
Entry* e1 = new Entry();
auto* e1 = new Entry();
e1->setGroup(g1);
QUuid e1Uuid = QUuid::createUuid();
e1->setUuid(e1Uuid);
@ -146,8 +141,8 @@ void TestDeletedObjects::testDatabaseChange()
QCOMPARE(db->deletedObjects().at(delObjectsSize - 2).uuid, e1Uuid);
QCOMPARE(db->deletedObjects().at(delObjectsSize - 1).uuid, g1Uuid);
Group* group = new Group();
Entry* entry = new Entry();
auto* group = new Group();
auto* entry = new Entry();
entry->setGroup(group);
entry->setGroup(root);
@ -155,6 +150,4 @@ void TestDeletedObjects::testDatabaseChange()
QCOMPARE(db2->deletedObjects().size(), delObjectsSize2);
delete group;
delete db;
delete db2;
}

View File

@ -27,7 +27,7 @@ class TestDeletedObjects : public QObject
Q_OBJECT
private:
void createAndDelete(Database* db, int delObjectsSize);
void createAndDelete(QSharedPointer<Database> db, int delObjectsSize);
private slots:
void initTestCase();

Some files were not shown because too many files have changed in this diff Show More