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

View file

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

View file

@ -19,6 +19,9 @@
#include "BrowserEntrySaveDialog.h" #include "BrowserEntrySaveDialog.h"
#include "ui_BrowserEntrySaveDialog.h" #include "ui_BrowserEntrySaveDialog.h"
#include "core/Database.h"
#include "gui/DatabaseWidget.h"
BrowserEntrySaveDialog::BrowserEntrySaveDialog(QWidget* parent) BrowserEntrySaveDialog::BrowserEntrySaveDialog(QWidget* parent)
: QDialog(parent) : QDialog(parent)
, m_ui(new Ui::BrowserEntrySaveDialog()) , m_ui(new Ui::BrowserEntrySaveDialog())
@ -43,10 +46,10 @@ int BrowserEntrySaveDialog::setItems(QList<DatabaseWidget*>& databaseWidgets, Da
uint counter = 0; uint counter = 0;
int activeIndex = -1; int activeIndex = -1;
for (const auto dbWidget : databaseWidgets) { for (const auto dbWidget : databaseWidgets) {
QString databaseName = dbWidget->getDatabaseName(); QString databaseName = dbWidget->database()->metadata()->name();
QString databaseFileName = dbWidget->getDatabaseFileName(); QString databaseFileName = dbWidget->database()->filePath();
QListWidgetItem* item = new QListWidgetItem(); auto* item = new QListWidgetItem();
item->setData(Qt::UserRole, counter); item->setData(Qt::UserRole, counter);
// Show database name (and filename if the name has been set in metadata) // 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 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) bool BrowserService::openDatabase(bool triggerUnlock)
@ -83,7 +83,7 @@ bool BrowserService::openDatabase(bool triggerUnlock)
return false; return false;
} }
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) { if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode || dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
return true; return true;
} }
@ -106,14 +106,14 @@ void BrowserService::lockDatabase()
return; return;
} }
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) { if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode || dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
dbWidget->lock(); dbWidget->lock();
} }
} }
QString BrowserService::getDatabaseRootUuid() QString BrowserService::getDatabaseRootUuid()
{ {
Database* db = getDatabase(); auto db = getDatabase();
if (!db) { if (!db) {
return {}; return {};
} }
@ -128,7 +128,7 @@ QString BrowserService::getDatabaseRootUuid()
QString BrowserService::getDatabaseRecycleBinUuid() QString BrowserService::getDatabaseRecycleBinUuid()
{ {
Database* db = getDatabase(); auto db = getDatabase();
if (!db) { if (!db) {
return {}; return {};
} }
@ -150,7 +150,7 @@ QString BrowserService::storeKey(const QString& key)
return id; return id;
} }
Database* db = getDatabase(); auto db = getDatabase();
if (!db) { if (!db) {
return {}; return {};
} }
@ -194,7 +194,7 @@ QString BrowserService::storeKey(const QString& key)
QString BrowserService::getKey(const QString& id) QString BrowserService::getKey(const QString& id)
{ {
Database* db = getDatabase(); auto db = getDatabase();
if (!db) { if (!db) {
return {}; return {};
} }
@ -268,13 +268,10 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
return result; return result;
} }
void BrowserService::addEntry(const QString& id, void BrowserService::addEntry(const QString& id, const QString& login,
const QString& login, const QString& password, const QString& url,
const QString& password, const QString& submitUrl, const QString& realm,
const QString& url, QSharedPointer<Database> selectedDb)
const QString& submitUrl,
const QString& realm,
Database* selectedDb)
{ {
if (thread() != QThread::currentThread()) { if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, QMetaObject::invokeMethod(this,
@ -286,10 +283,10 @@ void BrowserService::addEntry(const QString& id,
Q_ARG(QString, url), Q_ARG(QString, url),
Q_ARG(QString, submitUrl), Q_ARG(QString, submitUrl),
Q_ARG(QString, realm), 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) { if (!db) {
return; return;
} }
@ -299,7 +296,7 @@ void BrowserService::addEntry(const QString& id,
return; return;
} }
Entry* entry = new Entry(); auto* entry = new Entry();
entry->setUuid(QUuid::createUuid()); entry->setUuid(QUuid::createUuid());
entry->setTitle(QUrl(url).host()); entry->setTitle(QUrl(url).host());
entry->setUrl(url); entry->setUrl(url);
@ -341,12 +338,12 @@ void BrowserService::updateEntry(const QString& id,
Q_ARG(QString, submitUrl)); Q_ARG(QString, submitUrl));
} }
Database* db = selectedDatabase(); auto db = selectedDatabase();
if (!db) { if (!db) {
return; 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) {
// If entry is not found for update, add a new one to the selected database // If entry is not found for update, add a new one to the selected database
addEntry(id, login, password, url, submitUrl, "", db); 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; QList<Entry*> entries;
Group* rootGroup = db->rootGroup(); auto* rootGroup = db->rootGroup();
if (!rootGroup) { if (!rootGroup) {
return entries; 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) QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPairList& keyList)
{ {
// Get the list of databases to search // Get the list of databases to search
QList<Database*> databases; QList<QSharedPointer<Database>> databases;
if (browserSettings()->searchInAllDatabases()) { if (browserSettings()->searchInAllDatabases()) {
const int count = m_dbTabWidget->count(); const int count = m_dbTabWidget->count();
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) { if (auto* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
if (Database* db = dbWidget->database()) { if (const auto& db = dbWidget->database()) {
// Check if database is connected with KeePassXC-Browser // Check if database is connected with KeePassXC-Browser
for (const StringPair& keyPair : keyList) { for (const StringPair& keyPair : keyList) {
QString key = db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first); 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; databases << db;
} }
@ -439,7 +436,7 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
QString hostname = QUrl(url).host(); QString hostname = QUrl(url).host();
QList<Entry*> entries; QList<Entry*> entries;
do { do {
for (Database* db : databases) { for (const auto& db : databases) {
entries << searchEntries(db, hostname, url); entries << searchEntries(db, hostname, url);
} }
} while (entries.isEmpty() && removeFirstDomain(hostname)); } while (entries.isEmpty() && removeFirstDomain(hostname));
@ -447,9 +444,9 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
return entries; 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) { if (!db) {
return; return;
} }
@ -651,9 +648,9 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri
return Unknown; 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) { if (!db) {
return nullptr; return nullptr;
} }
@ -668,11 +665,11 @@ Group* BrowserService::findCreateAddEntryGroup(Database* selectedDb)
for (const Group* g : rootGroup->groupsRecursive(true)) { for (const Group* g : rootGroup->groupsRecursive(true)) {
if (g->name() == groupName) { 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->setUuid(QUuid::createUuid());
group->setName(groupName); group->setName(groupName);
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON); group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
@ -775,26 +772,26 @@ QString BrowserService::baseDomain(const QString& url) const
return baseDomain; return baseDomain;
} }
Database* BrowserService::getDatabase() QSharedPointer<Database> BrowserService::getDatabase()
{ {
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) { if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
if (Database* db = dbWidget->database()) { if (const auto& db = dbWidget->database()) {
return db; return db;
} }
} }
return nullptr; return {};
} }
Database* BrowserService::selectedDatabase() QSharedPointer<Database> BrowserService::selectedDatabase()
{ {
QList<DatabaseWidget*> databaseWidgets; QList<DatabaseWidget*> databaseWidgets;
for (int i = 0; ; ++i) { for (int i = 0; ; ++i) {
const auto dbStruct = m_dbTabWidget->indexDatabaseManagerStruct(i); auto* dbWidget = m_dbTabWidget->databaseWidgetFromIndex(i);
// Add only open databases // Add only open databases
if (dbStruct.dbWidget && dbStruct.dbWidget->dbHasKey() && if (dbWidget && dbWidget->database()->hasKey() &&
(dbStruct.dbWidget->currentMode() == DatabaseWidget::ViewMode || (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode ||
dbStruct.dbWidget->currentMode() == DatabaseWidget::EditMode)) { dbWidget->currentMode() == DatabaseWidget::Mode::EditMode)) {
databaseWidgets.push_back(dbStruct.dbWidget); databaseWidgets.push_back(dbWidget);
continue; continue;
} }
@ -813,7 +810,7 @@ Database* BrowserService::selectedDatabase()
return databaseWidgets[index]->database(); return databaseWidgets[index]->database();
} }
} else { } else {
return nullptr; return {};
} }
} }
@ -836,7 +833,7 @@ bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name)
return false; return false;
} }
int BrowserService::moveKeysToCustomData(Entry* entry, Database* db) const int BrowserService::moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db) const
{ {
int keyCounter = 0; int keyCounter = 0;
for (const auto& key : entry->attributes()->keys()) { for (const auto& key : entry->attributes()->keys()) {
@ -857,7 +854,7 @@ int BrowserService::moveKeysToCustomData(Entry* entry, Database* db) const
bool BrowserService::checkLegacySettings() bool BrowserService::checkLegacySettings()
{ {
Database* db = getDatabase(); auto db = getDatabase();
if (!db) { if (!db) {
return false; return false;
} }
@ -883,11 +880,7 @@ bool BrowserService::checkLegacySettings()
"This is necessary to maintain compatibility with the browser plugin."), "This is necessary to maintain compatibility with the browser plugin."),
QMessageBox::Yes | QMessageBox::No); QMessageBox::Yes | QMessageBox::No);
if (dialogResult == QMessageBox::No) { return dialogResult == QMessageBox::Yes;
return false;
}
return true;
} }
void BrowserService::databaseLocked(DatabaseWidget* dbWidget) void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
@ -916,7 +909,7 @@ void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
{ {
if (dbWidget) { if (dbWidget) {
auto currentMode = dbWidget->currentMode(); auto currentMode = dbWidget->currentMode();
if (currentMode == DatabaseWidget::ViewMode || currentMode == DatabaseWidget::EditMode) { if (currentMode == DatabaseWidget::Mode::ViewMode || currentMode == DatabaseWidget::Mode::EditMode) {
emit databaseUnlocked(); emit databaseUnlocked();
} else { } else {
emit databaseLocked(); emit databaseLocked();

View file

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

View file

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

View file

@ -66,7 +66,7 @@ int Clip::execute(const QStringList& arguments)
return EXIT_FAILURE; 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) { if (!db) {
return EXIT_FAILURE; 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)); 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); TextStream err(Utils::STDERR);

View file

@ -26,7 +26,7 @@ public:
Clip(); Clip();
~Clip(); ~Clip();
int execute(const QStringList& arguments) override; 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 #endif // KEEPASSXC_CLIP_H

View file

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

View file

@ -104,7 +104,8 @@ int Extract::execute(const QStringList& arguments)
KeePass2Reader reader; KeePass2Reader reader;
reader.setSaveXml(true); 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(); QByteArray xmlData = reader.reader()->xmlData();

View file

@ -64,7 +64,7 @@ int List::execute(const QStringList& arguments)
bool recursive = parser.isSet(recursiveOption); 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) { if (!db) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -61,7 +61,7 @@ int Locate::execute(const QStringList& arguments)
return EXIT_FAILURE; 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) { if (!db) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

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

View file

@ -63,7 +63,7 @@ int Remove::execute(const QStringList& arguments)
return EXIT_FAILURE; 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) { if (!db) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -92,8 +92,8 @@ int Remove::removeEntry(Database* database, const QString& databasePath, const Q
database->recycleEntry(entry); database->recycleEntry(entry);
}; };
QString errorMessage = database->saveToFile(databasePath); QString errorMessage;
if (!errorMessage.isEmpty()) { if (!database->save(databasePath, &errorMessage, true, false)) {
err << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl; err << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -71,7 +71,7 @@ int Show::execute(const QStringList& arguments)
return EXIT_FAILURE; 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) { if (!db) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

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

View file

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

View file

@ -46,7 +46,7 @@ public:
bool operator!=(const CustomData& other) const; bool operator!=(const CustomData& other) const;
signals: signals:
void modified(); void customDataModified();
void aboutToBeAdded(const QString& key); void aboutToBeAdded(const QString& key);
void added(const QString& key); void added(const QString& key);
void aboutToBeRemoved(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) 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 * 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 * it under the terms of the GNU General Public License as published by
@ -18,60 +18,325 @@
#include "Database.h" #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/Utils.h"
#include "cli/TextStream.h"
#include "core/Clock.h" #include "core/Clock.h"
#include "core/Group.h" #include "core/Group.h"
#include "core/Merger.h" #include "core/Merger.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/kdf/AesKdf.h"
#include "format/KeePass2.h"
#include "format/KeePass2Reader.h" #include "format/KeePass2Reader.h"
#include "format/KeePass2Writer.h" #include "format/KeePass2Writer.h"
#include "keys/FileKey.h" #include "keys/FileKey.h"
#include "keys/PasswordKey.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() Database::Database()
: m_metadata(new Metadata(this)) : m_metadata(new Metadata(this))
, m_data()
, m_rootGroup(nullptr) , m_rootGroup(nullptr)
, m_timer(new QTimer(this)) , m_timer(new QTimer(this))
, m_emitModified(false) , m_emitModified(false)
, m_uuid(QUuid::createUuid()) , 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()); setRootGroup(new Group());
rootGroup()->setUuid(QUuid::createUuid()); rootGroup()->setUuid(QUuid::createUuid());
rootGroup()->setName(tr("Root", "Root group name"));
m_timer->setSingleShot(true); 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(metadataModified()), this, SLOT(markAsModified()));
connect(m_metadata, SIGNAL(nameTextChanged()), this, SIGNAL(nameTextChanged())); connect(m_timer, SIGNAL(timeout()), SIGNAL(databaseModified()));
connect(this, SIGNAL(modifiedImmediate()), this, SLOT(startModifiedTimer()));
connect(m_timer, SIGNAL(timeout()), SIGNAL(modified())); m_modified = false;
m_emitModified = true;
}
Database::Database(const QString& filePath)
: Database()
{
setFilePath(filePath);
} }
Database::~Database() 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() Group* Database::rootGroup()
@ -84,10 +349,21 @@ const Group* Database::rootGroup() const
return m_rootGroup; 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) void Database::setRootGroup(Group* group)
{ {
Q_ASSERT(group); Q_ASSERT(group);
if (isInitialized() && m_modified) {
emit databaseDiscarded();
}
m_rootGroup = group; m_rootGroup = group;
m_rootGroup->setParent(this); m_rootGroup->setParent(this);
} }
@ -104,115 +380,23 @@ const Metadata* Database::metadata() const
QString Database::filePath() const QString Database::filePath() const
{ {
return m_filePath; return m_data.filePath;
} }
void Database::setFilePath(const QString& filePath) void Database::setFilePath(const QString& filePath)
{ {
m_filePath = filePath; if (filePath == m_data.filePath) {
return;
} }
Entry* Database::resolveEntry(const QUuid& uuid) if (s_filePathMap.contains(m_data.filePath)) {
{ s_filePathMap.remove(m_data.filePath);
return findEntryRecursive(uuid, m_rootGroup);
} }
QString oldPath = m_data.filePath;
m_data.filePath = filePath;
s_filePathMap.insert(m_data.filePath, this);
Entry* Database::resolveEntry(const QString& text, EntryReferenceType referenceType) emit filePathChanged(oldPath, filePath);
{
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;
}
}
const QList<Group*> children = group->children();
for (Group* child : children) {
Entry* result = findEntryRecursive(uuid, child);
if (result) {
return result;
}
}
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;
} }
QList<DeletedObject> Database::deletedObjects() QList<DeletedObject> Database::deletedObjects()
@ -273,9 +457,9 @@ const QUuid& Database::cipher() const
return m_data.cipher; 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 QByteArray Database::transformedMasterKey() const
@ -301,11 +485,11 @@ void Database::setCipher(const QUuid& cipher)
m_data.cipher = cipher; m_data.cipher = cipher;
} }
void Database::setCompressionAlgo(Database::CompressionAlgorithm algo) void Database::setCompressionAlgorithm(Database::CompressionAlgorithm algo)
{ {
Q_ASSERT(static_cast<quint32>(algo) <= CompressionAlgorithmMax); 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) bool Database::setKey(const QSharedPointer<const CompositeKey>& key, bool updateChangedTime, bool updateTransformSalt)
{ {
Q_ASSERT(!m_data.isReadOnly);
if (!key) { if (!key) {
m_data.key.reset(); m_data.key.reset();
m_data.transformedMasterKey = {}; m_data.transformedMasterKey = {};
@ -344,7 +530,7 @@ bool Database::setKey(const QSharedPointer<const CompositeKey>& key, bool update
} }
if (oldTransformedMasterKey != m_data.transformedMasterKey) { if (oldTransformedMasterKey != m_data.transformedMasterKey) {
emit modifiedImmediate(); markAsModified();
} }
return true; return true;
@ -388,11 +574,13 @@ const QVariantMap& Database::publicCustomData() const
void Database::setPublicCustomData(const QVariantMap& customData) void Database::setPublicCustomData(const QVariantMap& customData)
{ {
Q_ASSERT(!m_data.isReadOnly);
m_data.publicCustomData = customData; m_data.publicCustomData = customData;
} }
void Database::createRecycleBin() void Database::createRecycleBin()
{ {
Q_ASSERT(!m_data.isReadOnly);
Group* recycleBin = Group::createRecycleBin(); Group* recycleBin = Group::createRecycleBin();
recycleBin->setParent(rootGroup()); recycleBin->setParent(rootGroup());
m_metadata->setRecycleBin(recycleBin); m_metadata->setRecycleBin(recycleBin);
@ -400,6 +588,7 @@ void Database::createRecycleBin()
void Database::recycleEntry(Entry* entry) void Database::recycleEntry(Entry* entry)
{ {
Q_ASSERT(!m_data.isReadOnly);
if (m_metadata->recycleBinEnabled()) { if (m_metadata->recycleBinEnabled()) {
if (!m_metadata->recycleBin()) { if (!m_metadata->recycleBin()) {
createRecycleBin(); createRecycleBin();
@ -412,6 +601,7 @@ void Database::recycleEntry(Entry* entry)
void Database::recycleGroup(Group* group) void Database::recycleGroup(Group* group)
{ {
Q_ASSERT(!m_data.isReadOnly);
if (m_metadata->recycleBinEnabled()) { if (m_metadata->recycleBinEnabled()) {
if (!m_metadata->recycleBin()) { if (!m_metadata->recycleBin()) {
createRecycleBin(); createRecycleBin();
@ -424,6 +614,7 @@ void Database::recycleGroup(Group* group)
void Database::emptyRecycleBin() void Database::emptyRecycleBin()
{ {
Q_ASSERT(!m_data.isReadOnly);
if (m_metadata->recycleBinEnabled() && m_metadata->recycleBin()) { if (m_metadata->recycleBinEnabled() && m_metadata->recycleBin()) {
// destroying direct entries of the recycle bin // destroying direct entries of the recycle bin
QList<Entry*> subEntries = m_metadata->recycleBin()->entries(); QList<Entry*> subEntries = m_metadata->recycleBin()->entries();
@ -447,23 +638,54 @@ void Database::setEmitModified(bool value)
m_emitModified = value; m_emitModified = value;
} }
bool Database::isModified() const
{
return m_modified;
}
void Database::markAsModified() void Database::markAsModified()
{ {
emit modified(); if (isReadOnly()) {
return;
} }
const QUuid& Database::uuid() m_modified = true;
if (m_emitModified) {
startModifiedTimer();
}
}
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) 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() void Database::startModifiedTimer()
{ {
Q_ASSERT(!m_data.isReadOnly);
if (!m_emitModified) { if (!m_emitModified) {
return; return;
} }
@ -479,34 +701,12 @@ QSharedPointer<const CompositeKey> Database::key() const
return m_data.key; return m_data.key;
} }
Database* Database::openDatabaseFile(const QString& fileName, QSharedPointer<const CompositeKey> key) QSharedPointer<Database> Database::unlockFromStdin(const QString& databaseFilename, const QString& keyFilename,
{ FILE* outputDescriptor, FILE* errorDescriptor)
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)
{ {
auto compositeKey = QSharedPointer<CompositeKey>::create(); auto compositeKey = QSharedPointer<CompositeKey>::create();
QTextStream out(outputDescriptor); TextStream out(outputDescriptor);
QTextStream err(errorDescriptor); TextStream err(errorDescriptor);
out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename); out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename);
out.flush(); out.flush();
@ -522,7 +722,7 @@ Database* Database::unlockFromStdin(const QString& databaseFilename, const QStri
// LCOV_EXCL_START // LCOV_EXCL_START
if (!fileKey->load(keyFilename, &errorMessage)) { if (!fileKey->load(keyFilename, &errorMessage)) {
err << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage)<< endl; err << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage)<< endl;
return nullptr; return {};
} }
if (fileKey->type() != FileKey::Hashed) { if (fileKey->type() != FileKey::Hashed) {
@ -535,112 +735,9 @@ Database* Database::unlockFromStdin(const QString& databaseFilename, const QStri
compositeKey->addKey(fileKey); compositeKey->addKey(fileKey);
} }
return Database::openDatabaseFile(databaseFilename, compositeKey); auto db = QSharedPointer<Database>::create();
} db->open(databaseFilename, compositeKey, nullptr, false);
return db;
/**
* 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);
} }
QSharedPointer<Kdf> Database::kdf() const QSharedPointer<Kdf> Database::kdf() const
@ -650,11 +747,14 @@ QSharedPointer<Kdf> Database::kdf() const
void Database::setKdf(QSharedPointer<Kdf> kdf) void Database::setKdf(QSharedPointer<Kdf> kdf)
{ {
Q_ASSERT(!m_data.isReadOnly);
m_data.kdf = std::move(kdf); m_data.kdf = std::move(kdf);
} }
bool Database::changeKdf(const QSharedPointer<Kdf>& kdf) bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)
{ {
Q_ASSERT(!m_data.isReadOnly);
kdf->randomizeSeed(); kdf->randomizeSeed();
QByteArray transformedMasterKey; QByteArray transformedMasterKey;
if (!m_data.key) { if (!m_data.key) {
@ -666,7 +766,7 @@ bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)
setKdf(kdf); setKdf(kdf);
m_data.transformedMasterKey = transformedMasterKey; m_data.transformedMasterKey = transformedMasterKey;
emit modifiedImmediate(); markAsModified();
return true; 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) 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 * 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 * it under the terms of the GNU General Public License as published by
@ -22,9 +22,12 @@
#include <QDateTime> #include <QDateTime>
#include <QHash> #include <QHash>
#include <QObject> #include <QObject>
#include <QPointer>
#include "crypto/kdf/Kdf.h" #include "crypto/kdf/Kdf.h"
#include "format/KeePass2.h"
#include "keys/CompositeKey.h" #include "keys/CompositeKey.h"
#include "crypto/kdf/AesKdf.h"
class Entry; class Entry;
enum class EntryReferenceType; enum class EntryReferenceType;
@ -57,40 +60,38 @@ public:
}; };
static const quint32 CompressionAlgorithmMax = CompressionGZip; 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(); Database();
explicit Database(const QString& filePath);
~Database() override; ~Database() override;
Group* rootGroup();
const Group* rootGroup() const;
/** bool open(QSharedPointer<const CompositeKey> key, QString* error = nullptr, bool readOnly = false);
* Sets group as the root group and takes ownership of it. bool open(const QString& filePath, QSharedPointer<const CompositeKey> key, QString* error = nullptr, bool readOnly = false);
* Warning: Be careful when calling this method as it doesn't bool save(QString* error = nullptr, bool atomic = true, bool backup = false);
* emit any notifications so e.g. models aren't updated. bool save(const QString& filePath, QString* error = nullptr, bool atomic = true, bool backup = false);
* The caller is responsible for cleaning up the previous
root group. bool isInitialized() const;
*/ void setInitialized(bool initialized);
void setRootGroup(Group* group); 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(); Metadata* metadata();
const Metadata* metadata() const; const Metadata* metadata() const;
QString filePath() const; Group* rootGroup();
void setFilePath(const QString& filePath); const Group* rootGroup() const;
Entry* resolveEntry(const QUuid& uuid); void setRootGroup(Group* group);
Entry* resolveEntry(const QString& text, EntryReferenceType referenceType); QVariantMap& publicCustomData();
Group* resolveGroup(const QUuid& uuid); const QVariantMap& publicCustomData() const;
void setPublicCustomData(const QVariantMap& customData);
void recycleGroup(Group* group);
void recycleEntry(Entry* entry);
void emptyRecycleBin();
QList<DeletedObject> deletedObjects(); QList<DeletedObject> deletedObjects();
const QList<DeletedObject>& deletedObjects() const; const QList<DeletedObject>& deletedObjects() const;
void addDeletedObject(const DeletedObject& delObj); void addDeletedObject(const DeletedObject& delObj);
@ -99,42 +100,33 @@ public:
bool containsDeletedObject(const DeletedObject& uuid) const; bool containsDeletedObject(const DeletedObject& uuid) const;
void setDeletedObjects(const QList<DeletedObject>& delObjs); void setDeletedObjects(const QList<DeletedObject>& delObjs);
const QUuid& cipher() const; bool hasKey() const;
Database::CompressionAlgorithm compressionAlgo() const;
QSharedPointer<Kdf> kdf() const;
QByteArray transformedMasterKey() const;
QSharedPointer<const CompositeKey> key() const; QSharedPointer<const CompositeKey> key() const;
bool setKey(const QSharedPointer<const CompositeKey>& key, bool updateChangedTime = true, bool updateTransformSalt = false);
QByteArray challengeResponseKey() const; QByteArray challengeResponseKey() const;
bool challengeMasterSeed(const QByteArray& masterSeed); 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; bool verifyKey(const QSharedPointer<CompositeKey>& key) const;
QVariantMap& publicCustomData(); const QUuid& cipher() const;
const QVariantMap& publicCustomData() const; void setCipher(const QUuid& cipher);
void setPublicCustomData(const QVariantMap& customData); Database::CompressionAlgorithm compressionAlgorithm() const;
void recycleEntry(Entry* entry); void setCompressionAlgorithm(Database::CompressionAlgorithm algo);
void recycleGroup(Group* group);
void emptyRecycleBin();
void setEmitModified(bool value);
void markAsModified();
QString saveToFile(const QString& filePath, bool atomic = true, bool backup = false);
/** QSharedPointer<Kdf> kdf() const;
* Returns a unique id that is only valid as long as the Database exists. void setKdf(QSharedPointer<Kdf> kdf);
*/
const QUuid& uuid();
bool changeKdf(const QSharedPointer<Kdf>& kdf); bool changeKdf(const QSharedPointer<Kdf>& kdf);
QByteArray transformedMasterKey() const;
static Database* databaseByUuid(const QUuid& uuid); static Database* databaseByUuid(const QUuid& uuid);
static Database* openDatabaseFile(const QString& fileName, QSharedPointer<const CompositeKey> key); static Database* databaseByFilePath(const QString& filePath);
static Database* unlockFromStdin(const QString& databaseFilename, const QString& keyFilename = {}, static QSharedPointer<Database> unlockFromStdin(const QString& databaseFilename, const QString& keyFilename = {},
FILE* outputDescriptor = stdout, FILE* errorDescriptor = stderr); FILE* outputDescriptor = stdout, FILE* errorDescriptor = stderr);
public slots:
void markAsModified();
void markAsClean();
signals: signals:
void filePathChanged(const QString& oldPath, const QString& newPath);
void groupDataChanged(Group* group); void groupDataChanged(Group* group);
void groupAboutToAdd(Group* group, int index); void groupAboutToAdd(Group* group, int index);
void groupAdded(); void groupAdded();
@ -142,33 +134,51 @@ signals:
void groupRemoved(); void groupRemoved();
void groupAboutToMove(Group* group, Group* toGroup, int index); void groupAboutToMove(Group* group, Group* toGroup, int index);
void groupMoved(); void groupMoved();
void nameTextChanged(); void databaseModified();
void modified(); void databaseSaved();
void modifiedImmediate(); void databaseDiscarded();
private slots: private slots:
void startModifiedTimer(); void startModifiedTimer();
private: private:
Entry* findEntryRecursive(const QUuid& uuid, Group* group); struct DatabaseData
Entry* findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group); {
Group* findGroupRecursive(const QUuid& uuid, Group* group); 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(); void createRecycleBin();
QString writeDatabase(QIODevice* device);
bool writeDatabase(QIODevice* device, QString* error = nullptr);
bool backupDatabase(const QString& filePath); bool backupDatabase(const QString& filePath);
Metadata* const m_metadata; Metadata* const m_metadata;
DatabaseData m_data;
Group* m_rootGroup; Group* m_rootGroup;
QList<DeletedObject> m_deletedObjects; QList<DeletedObject> m_deletedObjects;
QTimer* m_timer; QPointer<QTimer> m_timer;
DatabaseData m_data; bool m_initialized = false;
bool m_modified = false;
bool m_emitModified; bool m_emitModified;
QString m_filePath;
QUuid m_uuid; 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 #endif // KEEPASSX_DATABASE_H

View file

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

View file

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

View file

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

View file

@ -44,7 +44,7 @@ public:
int attachmentsSize() const; int attachmentsSize() const;
signals: signals:
void modified(); void entryAttachmentsModified();
void keyModified(const QString& key); void keyModified(const QString& key);
void aboutToBeAdded(const QString& key); void aboutToBeAdded(const QString& key);
void added(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) { if (emitModified) {
emit modified(); emit entryAttributesModified();
} }
if (defaultAttribute && changeValue) { if (defaultAttribute && changeValue) {
@ -145,7 +145,7 @@ void EntryAttributes::remove(const QString& key)
m_protectedAttributes.remove(key); m_protectedAttributes.remove(key);
emit removed(key); emit removed(key);
emit modified(); emit entryAttributesModified();
} }
void EntryAttributes::rename(const QString& oldKey, const QString& newKey) 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); m_protectedAttributes.insert(newKey);
} }
emit modified(); emit entryAttributesModified();
emit renamed(oldKey, newKey); emit renamed(oldKey, newKey);
} }
@ -207,7 +207,7 @@ void EntryAttributes::copyCustomKeysFrom(const EntryAttributes* other)
} }
emit reset(); emit reset();
emit modified(); emit entryAttributesModified();
} }
bool EntryAttributes::areCustomKeysDifferent(const EntryAttributes* other) bool EntryAttributes::areCustomKeysDifferent(const EntryAttributes* other)
@ -240,7 +240,7 @@ void EntryAttributes::copyDataFrom(const EntryAttributes* other)
m_protectedAttributes = other->m_protectedAttributes; m_protectedAttributes = other->m_protectedAttributes;
emit reset(); emit reset();
emit modified(); emit entryAttributesModified();
} }
} }
@ -275,7 +275,7 @@ void EntryAttributes::clear()
} }
emit reset(); emit reset();
emit modified(); emit entryAttributesModified();
} }
int EntryAttributes::attributesSize() const int EntryAttributes::attributesSize() const

View file

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

View file

@ -43,8 +43,8 @@ Group::Group()
m_data.searchingEnabled = Inherit; m_data.searchingEnabled = Inherit;
m_data.mergeMode = Default; m_data.mergeMode = Default;
connect(m_customData, SIGNAL(modified()), this, SIGNAL(modified())); connect(m_customData, SIGNAL(customDataModified()), this, SIGNAL(groupModified()));
connect(this, SIGNAL(modified()), SLOT(updateTimeinfo())); connect(this, SIGNAL(groupModified()), SLOT(updateTimeinfo()));
} }
Group::~Group() Group::~Group()
@ -87,7 +87,7 @@ template <class P, class V> inline bool Group::set(P& property, const V& value)
{ {
if (property != value) { if (property != value) {
property = value; property = value;
emit modified(); emit groupModified();
return true; return true;
} else { } else {
return false; return false;
@ -310,7 +310,7 @@ void Group::setUuid(const QUuid& uuid)
void Group::setName(const QString& name) void Group::setName(const QString& name)
{ {
if (set(m_data.name, 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())) { if (iconNumber >= 0 && (m_data.iconNumber != iconNumber || !m_data.customIcon.isNull())) {
m_data.iconNumber = iconNumber; m_data.iconNumber = iconNumber;
m_data.customIcon = QUuid(); m_data.customIcon = QUuid();
emit modified(); emit groupModified();
emit dataChanged(this); emit groupDataChanged(this);
} }
} }
@ -334,8 +334,8 @@ void Group::setIcon(const QUuid& uuid)
if (!uuid.isNull() && m_data.customIcon != uuid) { if (!uuid.isNull() && m_data.customIcon != uuid) {
m_data.customIcon = uuid; m_data.customIcon = uuid;
m_data.iconNumber = 0; m_data.iconNumber = 0;
emit modified(); emit groupModified();
emit dataChanged(this); emit groupDataChanged(this);
} }
} }
@ -352,7 +352,7 @@ void Group::setExpanded(bool expanded)
updateTimeinfo(); updateTimeinfo();
return; return;
} }
emit modified(); emit groupModified();
} }
} }
@ -380,7 +380,7 @@ void Group::setExpires(bool value)
{ {
if (m_data.timeInfo.expires() != value) { if (m_data.timeInfo.expires() != value) {
m_data.timeInfo.setExpires(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) { if (m_data.timeInfo.expiryTime() != dateTime) {
m_data.timeInfo.setExpiryTime(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) { if (m_db != parent->m_db) {
recSetDatabase(parent->m_db); connectDatabaseSignalsRecursive(parent->m_db);
} }
QObject::setParent(parent); QObject::setParent(parent);
emit aboutToAdd(this, index); emit groupAboutToAdd(this, index);
Q_ASSERT(index <= parent->m_children.size()); Q_ASSERT(index <= parent->m_children.size());
parent->m_children.insert(index, this); parent->m_children.insert(index, this);
} else { } else {
@ -460,12 +460,12 @@ void Group::setParent(Group* parent, int index)
m_data.timeInfo.setLocationChanged(Clock::currentDateTimeUtc()); m_data.timeInfo.setLocationChanged(Clock::currentDateTimeUtc());
} }
emit modified(); emit groupModified();
if (!moveWithinDatabase) { if (!moveWithinDatabase) {
emit added(); emit groupAdded();
} else { } else {
emit moved(); emit groupMoved();
} }
} }
@ -477,7 +477,7 @@ void Group::setParent(Database* db)
cleanupParent(); cleanupParent();
m_parent = nullptr; m_parent = nullptr;
recSetDatabase(db); connectDatabaseSignalsRecursive(db);
QObject::setParent(db); QObject::setParent(db);
} }
@ -578,6 +578,53 @@ Entry* Group::findEntryByPath(const QString& entryPath)
return findEntryByPathRecursive(normalizedEntryPath, "/"); 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) Entry* Group::findEntryByPathRecursive(const QString& entryPath, const QString& basePath)
{ {
// Return the first entry that matches the full path OR if there is no leading // 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); emit entryAboutToAdd(entry);
m_entries << entry; m_entries << entry;
connect(entry, SIGNAL(dataChanged(Entry*)), SIGNAL(entryDataChanged(Entry*))); connect(entry, SIGNAL(entryDataChanged(Entry*)), SIGNAL(entryDataChanged(Entry*)));
if (m_db) { 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); emit entryAdded(entry);
} }
@ -820,21 +867,21 @@ void Group::removeEntry(Entry* entry)
entry->disconnect(m_db); entry->disconnect(m_db);
} }
m_entries.removeAll(entry); m_entries.removeAll(entry);
emit modified(); emit groupModified();
emit entryRemoved(entry); emit entryRemoved(entry);
} }
void Group::recSetDatabase(Database* db) void Group::connectDatabaseSignalsRecursive(Database* db)
{ {
if (m_db) { if (m_db) {
disconnect(SIGNAL(dataChanged(Group*)), m_db); disconnect(SIGNAL(groupDataChanged(Group*)), m_db);
disconnect(SIGNAL(aboutToRemove(Group*)), m_db); disconnect(SIGNAL(groupAboutToRemove(Group*)), m_db);
disconnect(SIGNAL(removed()), m_db); disconnect(SIGNAL(groupRemoved()), m_db);
disconnect(SIGNAL(aboutToAdd(Group*, int)), m_db); disconnect(SIGNAL(groupAboutToAdd(Group*, int)), m_db);
disconnect(SIGNAL(added()), m_db); disconnect(SIGNAL(groupAdded()), m_db);
disconnect(SIGNAL(aboutToMove(Group*, Group*, int)), m_db); disconnect(SIGNAL(aboutToMove(Group*, Group*, int)), m_db);
disconnect(SIGNAL(moved()), m_db); disconnect(SIGNAL(groupMoved()), m_db);
disconnect(SIGNAL(modified()), m_db); disconnect(SIGNAL(groupModified()), m_db);
} }
for (Entry* entry : asConst(m_entries)) { for (Entry* entry : asConst(m_entries)) {
@ -842,35 +889,35 @@ void Group::recSetDatabase(Database* db)
entry->disconnect(m_db); entry->disconnect(m_db);
} }
if (db) { if (db) {
connect(entry, SIGNAL(modified()), db, SIGNAL(modifiedImmediate())); connect(entry, SIGNAL(entryModified()), db, SLOT(markAsModified()));
} }
} }
if (db) { if (db) {
connect(this, SIGNAL(dataChanged(Group*)), db, SIGNAL(groupDataChanged(Group*))); connect(this, SIGNAL(groupDataChanged(Group*)), db, SIGNAL(groupDataChanged(Group*)));
connect(this, SIGNAL(aboutToRemove(Group*)), db, SIGNAL(groupAboutToRemove(Group*))); connect(this, SIGNAL(groupAboutToRemove(Group*)), db, SIGNAL(groupAboutToRemove(Group*)));
connect(this, SIGNAL(removed()), db, SIGNAL(groupRemoved())); connect(this, SIGNAL(groupRemoved()), db, SIGNAL(groupRemoved()));
connect(this, SIGNAL(aboutToAdd(Group*,int)), db, SIGNAL(groupAboutToAdd(Group*,int))); connect(this, SIGNAL(groupAboutToAdd(Group*, int)), db, SIGNAL(groupAboutToAdd(Group*,int)));
connect(this, SIGNAL(added()), db, SIGNAL(groupAdded())); connect(this, SIGNAL(groupAdded()), db, SIGNAL(groupAdded()));
connect(this, SIGNAL(aboutToMove(Group*,Group*,int)), db, SIGNAL(groupAboutToMove(Group*,Group*,int))); connect(this, SIGNAL(aboutToMove(Group*,Group*,int)), db, SIGNAL(groupAboutToMove(Group*,Group*,int)));
connect(this, SIGNAL(moved()), db, SIGNAL(groupMoved())); connect(this, SIGNAL(groupMoved()), db, SIGNAL(groupMoved()));
connect(this, SIGNAL(modified()), db, SIGNAL(modifiedImmediate())); connect(this, SIGNAL(groupModified()), db, SLOT(markAsModified()));
} }
m_db = db; m_db = db;
for (Group* group : asConst(m_children)) { for (Group* group : asConst(m_children)) {
group->recSetDatabase(db); group->connectDatabaseSignalsRecursive(db);
} }
} }
void Group::cleanupParent() void Group::cleanupParent()
{ {
if (m_parent) { if (m_parent) {
emit aboutToRemove(this); emit groupAboutToRemove(this);
m_parent->m_children.removeAll(this); m_parent->m_children.removeAll(this);
emit modified(); emit groupModified();
emit removed(); emit groupRemoved();
} }
} }
@ -965,7 +1012,7 @@ Entry* Group::addEntryWithPath(const QString& entryPath)
return nullptr; return nullptr;
} }
Entry* entry = new Entry(); auto* entry = new Entry();
entry->setTitle(entryTitle); entry->setTitle(entryTitle);
entry->setUuid(QUuid::createUuid()); entry->setUuid(QUuid::createUuid());
entry->setGroup(group); entry->setGroup(group);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -29,13 +29,13 @@ class Kdbx3Reader : public KdbxReader
Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader) Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader)
public: public:
Database* readDatabaseImpl(QIODevice* device, bool readDatabaseImpl(QIODevice* device,
const QByteArray& headerData, const QByteArray& headerData,
QSharedPointer<const CompositeKey> key, QSharedPointer<const CompositeKey> key,
bool keepDatabase) override; Database* db) override;
protected: protected:
bool readHeaderField(StoreDataStream& headerStream) override; bool readHeaderField(StoreDataStream& headerStream, Database* db) override;
}; };
#endif // KEEPASSX_KDBX3READER_H #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::CipherID, db->cipher().toRfc4122()));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CompressionFlags, CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CompressionFlags,
Endian::sizedIntToBytes<qint32>(db->compressionAlgo(), Endian::sizedIntToBytes<qint32>(db->compressionAlgorithm(),
KeePass2::BYTEORDER))); KeePass2::BYTEORDER)));
auto kdf = db->kdf(); auto kdf = db->kdf();
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed)); CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
@ -112,7 +112,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
QIODevice* outputDevice = nullptr; QIODevice* outputDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor; QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) { if (db->compressionAlgorithm() == Database::CompressionNone) {
outputDevice = &hashedStream; outputDevice = &hashedStream;
} else { } else {
ioCompressor.reset(new QtIOCompressor(&hashedStream)); ioCompressor.reset(new QtIOCompressor(&hashedStream));

View file

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

View file

@ -30,15 +30,15 @@ class Kdbx4Reader : public KdbxReader
Q_DECLARE_TR_FUNCTIONS(Kdbx4Reader) Q_DECLARE_TR_FUNCTIONS(Kdbx4Reader)
public: public:
Database* readDatabaseImpl(QIODevice* device, bool readDatabaseImpl(QIODevice* device,
const QByteArray& headerData, const QByteArray& headerData,
QSharedPointer<const CompositeKey> key, QSharedPointer<const CompositeKey> key,
bool keepDatabase) override; Database* db) override;
QHash<QByteArray, QString> binaryPoolInverse() const; QHash<QByteArray, QString> binaryPoolInverse() const;
QHash<QString, QByteArray> binaryPool() const; QHash<QString, QByteArray> binaryPool() const;
protected: protected:
bool readHeaderField(StoreDataStream& headerStream) override; bool readHeaderField(StoreDataStream& headerStream, Database* db) override;
private: private:
bool readInnerHeaderField(QIODevice* device); 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::CipherID, db->cipher().toRfc4122()));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CompressionFlags, 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))); KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed)); CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV)); CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
@ -138,7 +138,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
QIODevice* outputDevice = nullptr; QIODevice* outputDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor; QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) { if (db->compressionAlgorithm() == Database::CompressionNone) {
outputDevice = cipherStream.data(); outputDevice = cipherStream.data();
} else { } else {
ioCompressor.reset(new QtIOCompressor(cipherStream.data())); 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 device input device
* @param key database encryption composite key * @param key database encryption composite key
* @param keepDatabase keep database in case of read failure * @param db database to read into
* @return pointer to the read database, nullptr on failure * @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); device->seek(0);
m_db.reset(new Database()); m_db = db;
m_xmlData.clear(); m_xmlData.clear();
m_masterSeed.clear(); m_masterSeed.clear();
m_encryptionIV.clear(); m_encryptionIV.clear();
@ -79,7 +79,7 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const Compo
// read KDBX magic numbers // read KDBX magic numbers
quint32 sig1, sig2; quint32 sig1, sig2;
if (!readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion)) { if (!readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion)) {
return nullptr; return false;
} }
m_kdbxSignature = qMakePair(sig1, sig2); m_kdbxSignature = qMakePair(sig1, sig2);
@ -87,24 +87,24 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const Compo
m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK; m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK;
// read header fields // read header fields
while (readHeaderField(headerStream) && !hasError()) { while (readHeaderField(headerStream, m_db) && !hasError()) {
} }
headerStream.close(); headerStream.close();
if (hasError()) { if (hasError()) {
return nullptr; return false;
} }
// read payload // read payload
auto* db = readDatabaseImpl(device, headerStream.storedData(), std::move(key), keepDatabase); bool ok = readDatabaseImpl(device, headerStream.storedData(), std::move(key), db);
if (saveXml()) { if (saveXml()) {
m_xmlData.clear(); m_xmlData.clear();
decryptXmlInnerStream(m_xmlData, db); decryptXmlInnerStream(m_xmlData, db);
} }
return db; return ok;
} }
bool KdbxReader::hasError() const bool KdbxReader::hasError() const
@ -175,7 +175,7 @@ void KdbxReader::setCompressionFlags(const QByteArray& data)
raiseError(tr("Unsupported compression algorithm")); raiseError(tr("Unsupported compression algorithm"));
return; 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; virtual ~KdbxReader() = default;
static bool readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version); 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; bool hasError() const;
QString errorString() const; QString errorString() const;
@ -57,22 +57,22 @@ protected:
* @param device input device at the payload starting position * @param device input device at the payload starting position
* @param KDBX header data as bytes * @param KDBX header data as bytes
* @param key database encryption composite key * @param key database encryption composite key
* @param keepDatabase keep database in case of read failure * @param db database to read into
* @return pointer to the read database, nullptr on failure * @return true on success
*/ */
virtual Database* virtual bool readDatabaseImpl(QIODevice* device,
readDatabaseImpl(QIODevice* device,
const QByteArray& headerData, const QByteArray& headerData,
QSharedPointer<const CompositeKey> key, QSharedPointer<const CompositeKey> key,
bool keepDatabase) = 0; Database* db) = 0;
/** /**
* Read next header field from stream. * Read next header field from stream.
* *
* @param headerStream input header stream * @param headerStream input header stream
* @param database to read header field for
* @return true if there are more header fields * @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 setCipher(const QByteArray& data);
virtual void setCompressionFlags(const QByteArray& data); virtual void setCompressionFlags(const QByteArray& data);
@ -88,9 +88,6 @@ protected:
void decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const; void decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const;
QScopedPointer<Database> m_db;
QPair<quint32, quint32> m_kdbxSignature;
quint32 m_kdbxVersion = 0; quint32 m_kdbxVersion = 0;
QByteArray m_masterSeed; QByteArray m_masterSeed;
@ -102,6 +99,9 @@ protected:
QByteArray m_xmlData; QByteArray m_xmlData;
private: private:
QPair<quint32, quint32> m_kdbxSignature;
QPointer<Database> m_db;
bool m_saveXml = false; bool m_saveXml = false;
bool m_error = false; bool m_error = false;
QString m_errorStr = ""; QString m_errorStr = "";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,31 +21,31 @@
#include "format/KeePass1.h" #include "format/KeePass1.h"
#include <QFile> #include <QFile>
#include <utility>
/** /**
* Read database from file and detect correct file format. * Read database from file and detect correct file format.
* *
* @param filename input file * @param filename input file
* @param key database encryption composite key * @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); QFile file(filename);
if (!file.open(QFile::ReadOnly)) { if (!file.open(QFile::ReadOnly)) {
raiseError(file.errorString()); 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) { if (file.error() != QFile::NoError) {
raiseError(file.errorString()); 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 device input device
* @param key database encryption composite key * @param key database encryption composite key
* @param keepDatabase keep database in case of read failure * @param db Database to read into
* @return pointer to the read database, nullptr on failure * @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_error = false;
m_errorStr.clear(); 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) { if (!ok || signature1 != KeePass2::SIGNATURE_1 || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database.")); raiseError(tr("Not a KeePass database."));
return nullptr; return false;
} }
if (signature2 == KeePass1::SIGNATURE_2) { 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" "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 " "This is a one-way migration. You won't be able to open the imported "
"database with the old KeePassX 0.4 version.")); "database with the old KeePassX 0.4 version."));
return nullptr; return false;
} }
quint32 maxVersion = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK; quint32 maxVersion = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
if (m_version < KeePass2::FILE_VERSION_MIN || m_version > maxVersion) { if (m_version < KeePass2::FILE_VERSION_MIN || m_version > maxVersion) {
raiseError(tr("Unsupported KeePass 2 database version.")); raiseError(tr("Unsupported KeePass 2 database version."));
return nullptr; return false;
} }
// determine file format (KDBX 2/3 or 4) // 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); 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 bool KeePass2Reader::hasError() const

View file

@ -35,8 +35,8 @@ class KeePass2Reader
Q_DECLARE_TR_FUNCTIONS(KdbxReader) Q_DECLARE_TR_FUNCTIONS(KdbxReader)
public: public:
Database* readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key); bool readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key, Database* db);
Database* readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase = false); bool readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db);
bool hasError() const; bool hasError() const;
QString errorString() 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 bool KeePass2Writer::implicitUpgradeNeeded(Database const* db) const
{ {
if (db->kdf()->uuid() != KeePass2::KDF_AES_KDBX3 ) {
return false;
}
if (!db->publicCustomData().isEmpty()) { if (!db->publicCustomData().isEmpty()) {
return true; return true;
} }

View file

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

View file

@ -42,13 +42,13 @@ public:
void load(const QString& filename); void load(const QString& filename);
void clearForms(); void clearForms();
void enterKey(const QString& pw, const QString& keyFile); void enterKey(const QString& pw, const QString& keyFile);
Database* database(); QSharedPointer<Database> database();
public slots: public slots:
void pollYubikey(); void pollYubikey();
signals: signals:
void editFinished(bool accepted); void dialogFinished(bool accepted);
protected: protected:
void showEvent(QShowEvent* event) override; void showEvent(QShowEvent* event) override;
@ -70,7 +70,7 @@ private slots:
protected: protected:
const QScopedPointer<Ui::DatabaseOpenWidget> m_ui; const QScopedPointer<Ui::DatabaseOpenWidget> m_ui;
Database* m_db; QSharedPointer<Database> m_db;
QString m_filename; QString m_filename;
private: 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) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -19,31 +18,16 @@
#ifndef KEEPASSX_DATABASETABWIDGET_H #ifndef KEEPASSX_DATABASETABWIDGET_H
#define KEEPASSX_DATABASETABWIDGET_H #define KEEPASSX_DATABASETABWIDGET_H
#include <QFileInfo>
#include <QHash>
#include <QTabWidget>
#include "gui/DatabaseWidget.h"
#include "gui/MessageWidget.h" #include "gui/MessageWidget.h"
#include <QTabWidget>
#include <QPointer>
class Database;
class DatabaseWidget; class DatabaseWidget;
class DatabaseWidgetStateSync; class DatabaseWidgetStateSync;
class DatabaseOpenWidget; class DatabaseOpenWidget;
class QFile; 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 class DatabaseTabWidget : public QTabWidget
{ {
@ -52,71 +36,62 @@ class DatabaseTabWidget : public QTabWidget
public: public:
explicit DatabaseTabWidget(QWidget* parent = nullptr); explicit DatabaseTabWidget(QWidget* parent = nullptr);
~DatabaseTabWidget() override; ~DatabaseTabWidget() override;
void openDatabase(const QString& fileName, const QString& pw = QString(), const QString& keyFile = QString()); void mergeDatabase(const QString& filePath);
void mergeDatabase(const QString& fileName);
DatabaseWidget* currentDatabaseWidget();
bool hasLockableDatabases() const;
DatabaseManagerStruct indexDatabaseManagerStruct(int index);
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: 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 newDatabase();
void openDatabase(); void openDatabase();
void importCsv();
void mergeDatabase(); void mergeDatabase();
void importCsv();
void importKeePass1Database(); void importKeePass1Database();
bool saveDatabase(int index = -1); bool saveDatabase(int index = -1);
bool saveDatabaseAs(int index = -1); bool saveDatabaseAs(int index = -1);
void exportToCsv(); void exportToCsv();
bool closeDatabase(int index = -1);
void lockDatabases();
void closeDatabaseFromSender(); void closeDatabaseFromSender();
bool closeAllDatabases(); void relockPendingDatabase();
void changeMasterKey(); void changeMasterKey();
void changeDatabaseSettings(); void changeDatabaseSettings();
bool readOnly(int index = -1);
bool canSave(int index = -1);
bool isModified(int index = -1);
void performGlobalAutoType(); void performGlobalAutoType();
void lockDatabases();
void relockPendingDatabase();
QString databasePath(int index = -1);
signals: signals:
void tabNameChanged(); void databaseClosed(const QString& filePath);
void databaseWithFileClosed(QString filePath);
void activateDatabaseChanged(DatabaseWidget* dbWidget);
void databaseLocked(DatabaseWidget* dbWidget);
void databaseUnlocked(DatabaseWidget* dbWidget); void databaseUnlocked(DatabaseWidget* dbWidget);
void databaseLocked(DatabaseWidget* dbWidget);
void activateDatabaseChanged(DatabaseWidget* dbWidget);
void tabNameChanged();
void messageGlobal(const QString&, MessageWidget::MessageType type); void messageGlobal(const QString&, MessageWidget::MessageType type);
void messageTab(const QString&, MessageWidget::MessageType type);
void messageDismissGlobal(); void messageDismissGlobal();
void messageDismissTab();
private slots: private slots:
void updateTabName(Database* db);
void updateTabNameFromDbSender();
void updateTabNameFromDbWidgetSender();
void modified();
void toggleTabbar(); void toggleTabbar();
void changeDatabase(Database* newDb, bool unsavedChanges);
void emitActivateDatabaseChanged(); void emitActivateDatabaseChanged();
void emitDatabaseUnlockedFromDbWidgetSender(); void emitDatabaseLockChanged();
private: private:
Database* execNewDatabaseWizard(); QSharedPointer<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);
void updateLastDatabases(const QString& filename); void updateLastDatabases(const QString& filename);
void connectDatabase(Database* newDb, Database* oldDb = nullptr);
QHash<Database*, DatabaseManagerStruct> m_dbList;
QPointer<DatabaseWidgetStateSync> m_dbWidgetStateSync; QPointer<DatabaseWidgetStateSync> m_dbWidgetStateSync;
QPointer<DatabaseWidget> m_dbPendingLock; 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> * Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -32,6 +32,7 @@
#include <QSplitter> #include <QSplitter>
#include "autotype/AutoType.h" #include "autotype/AutoType.h"
#include "core/Database.h"
#include "core/Config.h" #include "core/Config.h"
#include "core/EntrySearcher.h" #include "core/EntrySearcher.h"
#include "core/FilePath.h" #include "core/FilePath.h"
@ -40,6 +41,7 @@
#include "core/Metadata.h" #include "core/Metadata.h"
#include "core/Tools.h" #include "core/Tools.h"
#include "format/KeePass2Reader.h" #include "format/KeePass2Reader.h"
#include "gui/FileDialog.h"
#include "gui/Clipboard.h" #include "gui/Clipboard.h"
#include "gui/CloneDialog.h" #include "gui/CloneDialog.h"
#include "gui/DatabaseOpenWidget.h" #include "gui/DatabaseOpenWidget.h"
@ -68,131 +70,127 @@
#include "sshagent/SSHAgent.h" #include "sshagent/SSHAgent.h"
#endif #endif
DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
: QStackedWidget(parent) : QStackedWidget(parent)
, m_db(db) , m_db(std::move(db))
, m_newGroup(nullptr)
, m_newEntry(nullptr)
, m_newParent(nullptr)
{
m_mainWidget = new QWidget(this);
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); m_messageWidget->setHidden(true);
auto* mainLayout = new QVBoxLayout(); auto* mainLayout = new QVBoxLayout();
QLayout* layout = new QHBoxLayout();
mainLayout->addWidget(m_messageWidget); mainLayout->addWidget(m_messageWidget);
mainLayout->addLayout(layout); auto* hbox = new QHBoxLayout();
m_mainSplitter = new QSplitter(m_mainWidget); 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_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->setOrientation(Qt::Vertical);
m_previewSplitter->setChildrenCollapsible(true); m_previewSplitter->setChildrenCollapsible(true);
QWidget* rightHandSideWidget = new QWidget(m_mainSplitter);
m_groupView = new GroupView(db, m_mainSplitter);
m_groupView->setObjectName("groupView"); m_groupView->setObjectName("groupView");
m_groupView->setContextMenuPolicy(Qt::CustomContextMenu); m_groupView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_groupView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(emitGroupContextMenuRequested(QPoint))); connect(m_groupView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(emitGroupContextMenuRequested(QPoint)));
m_entryView = new EntryView(rightHandSideWidget);
m_entryView->setObjectName("entryView"); m_entryView->setObjectName("entryView");
m_entryView->setContextMenuPolicy(Qt::CustomContextMenu); 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))); connect(m_entryView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(emitEntryContextMenuRequested(QPoint)));
// Add a notification for when we are searching // Add a notification for when we are searching
m_searchingLabel = new QLabel();
m_searchingLabel->setText(tr("Searching...")); m_searchingLabel->setText(tr("Searching..."));
m_searchingLabel->setAlignment(Qt::AlignCenter); m_searchingLabel->setAlignment(Qt::AlignCenter);
m_searchingLabel->setStyleSheet("color: rgb(0, 0, 0);" m_searchingLabel->setStyleSheet("color: rgb(0, 0, 0);"
"background-color: rgb(255, 253, 160);" "background-color: rgb(255, 253, 160);"
"border: 2px solid rgb(190, 190, 190);" "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(); 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_entryView);
m_previewSplitter->addWidget(m_previewView); m_previewSplitter->addWidget(m_previewView);
m_previewSplitter->setStretchFactor(0, 100); m_previewSplitter->setStretchFactor(0, 100);
m_previewSplitter->setStretchFactor(1, 0); m_previewSplitter->setStretchFactor(1, 0);
m_previewSplitter->setSizes({1, 1}); 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_editEntryWidget->setObjectName("editEntryWidget");
m_historyEditEntryWidget = new EditEntryWidget();
m_editGroupWidget = new EditGroupWidget();
m_editGroupWidget->setObjectName("editGroupWidget"); m_editGroupWidget->setObjectName("editGroupWidget");
m_csvImportWizard = new CsvImportWizard();
m_csvImportWizard->setObjectName("csvImportWizard"); m_csvImportWizard->setObjectName("csvImportWizard");
m_databaseSettingDialog = new DatabaseSettingsDialog();
m_databaseSettingDialog->setObjectName("databaseSettingsDialog"); m_databaseSettingDialog->setObjectName("databaseSettingsDialog");
m_databaseOpenWidget = new DatabaseOpenWidget();
m_databaseOpenWidget->setObjectName("databaseOpenWidget"); m_databaseOpenWidget->setObjectName("databaseOpenWidget");
m_databaseOpenMergeWidget = new DatabaseOpenWidget();
m_databaseOpenMergeWidget->setObjectName("databaseOpenMergeWidget"); m_databaseOpenMergeWidget->setObjectName("databaseOpenMergeWidget");
m_keepass1OpenWidget = new KeePass1OpenWidget();
m_keepass1OpenWidget->setObjectName("keepass1OpenWidget"); m_keepass1OpenWidget->setObjectName("keepass1OpenWidget");
m_unlockDatabaseWidget = new UnlockDatabaseWidget();
m_unlockDatabaseWidget->setObjectName("unlockDatabaseWidget"); m_unlockDatabaseWidget->setObjectName("unlockDatabaseWidget");
m_unlockDatabaseDialog = new UnlockDatabaseDialog();
m_unlockDatabaseDialog->setObjectName("unlockDatabaseDialog"); m_unlockDatabaseDialog->setObjectName("unlockDatabaseDialog");
addWidget(m_mainWidget);
addWidget(m_editEntryWidget); addChildWidget(m_mainWidget);
addWidget(m_editGroupWidget); addChildWidget(m_editEntryWidget);
addWidget(m_databaseSettingDialog); addChildWidget(m_editGroupWidget);
addWidget(m_historyEditEntryWidget); addChildWidget(m_databaseSettingDialog);
addWidget(m_databaseOpenWidget); addChildWidget(m_historyEditEntryWidget);
addWidget(m_csvImportWizard); addChildWidget(m_databaseOpenWidget);
addWidget(m_databaseOpenMergeWidget); addChildWidget(m_csvImportWizard);
addWidget(m_keepass1OpenWidget); addChildWidget(m_databaseOpenMergeWidget);
addWidget(m_unlockDatabaseWidget); addChildWidget(m_keepass1OpenWidget);
addChildWidget(m_unlockDatabaseWidget);
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged())); connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
connect(m_previewSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(previewSplitterSizesChanged())); 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_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged()));
connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(onGroupChanged(Group*))); connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(onGroupChanged(Group*)));
connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged())); connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged()));
connect(m_entryView, connect(m_entryView, SIGNAL(entryActivated(Entry*,EntryModel::ModelColumn)),
SIGNAL(entryActivated(Entry*,EntryModel::ModelColumn)),
SLOT(entryActivationSignalReceived(Entry*,EntryModel::ModelColumn))); SLOT(entryActivationSignalReceived(Entry*,EntryModel::ModelColumn)));
connect(m_entryView, SIGNAL(entrySelectionChanged()), SLOT(emitEntrySelectionChanged())); connect(m_entryView, SIGNAL(entrySelectionChanged()), SIGNAL(entrySelectionChanged()));
connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool))); connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool)));
connect(m_editEntryWidget, SIGNAL(historyEntryActivated(Entry*)), SLOT(switchToHistoryView(Entry*))); connect(m_editEntryWidget, SIGNAL(historyEntryActivated(Entry*)), SLOT(switchToHistoryView(Entry*)));
connect(m_historyEditEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchBackToEntryEdit())); connect(m_historyEditEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchBackToEntryEdit()));
connect(m_editGroupWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool))); connect(m_editGroupWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool)));
connect(m_databaseSettingDialog, 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_databaseOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_databaseOpenMergeWidget, SIGNAL(editFinished(bool)), SLOT(mergeDatabase(bool))); connect(m_databaseOpenMergeWidget, SIGNAL(dialogFinished(bool)), SLOT(mergeDatabase(bool)));
connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool))); connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(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_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool)));
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged())); connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
connect(&m_fileWatchTimer, SIGNAL(timeout()), this, SLOT(reloadDatabaseFile())); 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_groupView, SIGNAL(groupChanged(Group*)), SLOT(emitPressedGroup(Group*)));
connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(emitEntrySelectionChanged())); connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(emitEntrySelectionChanged()));
m_databaseModified = false; connectDatabaseSignals();
m_fileWatchTimer.setSingleShot(true); m_fileWatchTimer.setSingleShot(true);
m_fileWatchUnblockTimer.setSingleShot(true); m_fileWatchUnblockTimer.setSingleShot(true);
@ -225,29 +223,44 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
setCurrentWidget(m_mainWidget); setCurrentWidget(m_mainWidget);
} }
DatabaseWidget::DatabaseWidget(const QString& filePath, QWidget* parent)
: DatabaseWidget(QSharedPointer<Database>::create(filePath), parent)
{
}
DatabaseWidget::~DatabaseWidget() DatabaseWidget::~DatabaseWidget()
{ {
delete m_EntrySearcher; delete m_EntrySearcher;
} }
QSharedPointer<Database> DatabaseWidget::database() const
{
return m_db;
}
DatabaseWidget::Mode DatabaseWidget::currentMode() const DatabaseWidget::Mode DatabaseWidget::currentMode() const
{ {
if (currentWidget() == nullptr) { if (currentWidget() == nullptr) {
return DatabaseWidget::None; return DatabaseWidget::Mode::None;
} else if (currentWidget() == m_csvImportWizard) { } else if (currentWidget() == m_csvImportWizard) {
return DatabaseWidget::ImportMode; return DatabaseWidget::Mode::ImportMode;
} else if (currentWidget() == m_mainWidget) { } else if (currentWidget() == m_mainWidget) {
return DatabaseWidget::ViewMode; return DatabaseWidget::Mode::ViewMode;
} else if (currentWidget() == m_unlockDatabaseWidget || currentWidget() == m_databaseOpenWidget) { } else if (currentWidget() == m_unlockDatabaseWidget || currentWidget() == m_databaseOpenWidget) {
return DatabaseWidget::LockedMode; return DatabaseWidget::Mode::LockedMode;
} else { } 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 bool DatabaseWidget::isEditWidgetModified() const
@ -341,11 +354,6 @@ void DatabaseWidget::emitCurrentModeChanged()
emit currentModeChanged(currentMode()); emit currentModeChanged(currentMode());
} }
Database* DatabaseWidget::database()
{
return m_db;
}
void DatabaseWidget::createEntry() void DatabaseWidget::createEntry()
{ {
Q_ASSERT(m_groupView->currentGroup()); Q_ASSERT(m_groupView->currentGroup());
@ -355,7 +363,7 @@ void DatabaseWidget::createEntry()
m_newEntry = new Entry(); m_newEntry = new Entry();
if (isInSearchMode()) { if (isSearchActive()) {
m_newEntry->setTitle(getCurrentSearch()); m_newEntry->setTitle(getCurrentSearch());
endSearch(); endSearch();
} }
@ -383,13 +391,15 @@ void DatabaseWidget::setIconFromParent()
} }
} }
void DatabaseWidget::replaceDatabase(Database* db) void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
{ {
Database* oldDb = m_db; // TODO: instead of increasing the ref count temporarily, there should be a clean
m_db = db; // 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); m_groupView->changeDatabase(m_db);
emit databaseChanged(m_db, m_databaseModified);
delete oldDb;
} }
void DatabaseWidget::cloneEntry() void DatabaseWidget::cloneEntry()
@ -400,7 +410,7 @@ void DatabaseWidget::cloneEntry()
return; return;
} }
auto cloneDialog = new CloneDialog(this, m_db, currentEntry); auto cloneDialog = new CloneDialog(this, m_db.data(), currentEntry);
cloneDialog->show(); cloneDialog->show();
} }
@ -660,8 +670,8 @@ void DatabaseWidget::deleteGroup()
auto* recycleBin = m_db->metadata()->recycleBin(); auto* recycleBin = m_db->metadata()->recycleBin();
bool inRecycleBin = recycleBin && recycleBin->findGroupByUuid(currentGroup->uuid()); bool inRecycleBin = recycleBin && recycleBin->findGroupByUuid(currentGroup->uuid());
bool isRecycleBin = (currentGroup == m_db->metadata()->recycleBin()); bool isRecycleBin = recycleBin && (currentGroup == recycleBin);
bool isRecycleBinSubgroup = currentGroup->findGroupByUuid(m_db->metadata()->recycleBin()->uuid()); bool isRecycleBinSubgroup = recycleBin && currentGroup->findGroupByUuid(recycleBin->uuid());
if (inRecycleBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) { if (inRecycleBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) {
QMessageBox::StandardButton result = MessageBox::question( QMessageBox::StandardButton result = MessageBox::question(
this, this,
@ -676,40 +686,14 @@ void DatabaseWidget::deleteGroup()
} }
} }
int DatabaseWidget::addWidget(QWidget* w) int DatabaseWidget::addChildWidget(QWidget* w)
{ {
w->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); w->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
int index = QStackedWidget::addWidget(w); int index = QStackedWidget::addWidget(w);
adjustSize(); adjustSize();
return index; 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) void DatabaseWidget::csvImportFinished(bool accepted)
{ {
if (!accepted) { if (!accepted) {
@ -788,24 +772,32 @@ void DatabaseWidget::switchToGroupEdit(Group* group, bool create)
setCurrentWidget(m_editGroupWidget); setCurrentWidget(m_editGroupWidget);
} }
void DatabaseWidget::openDatabase(bool accepted) void DatabaseWidget::connectDatabaseSignals()
{ {
if (accepted) { // relayed Database events
replaceDatabase(static_cast<DatabaseOpenWidget*>(sender())->database()); connect(m_db.data(), SIGNAL(filePathChanged(QString,QString)),
setCurrentWidget(m_mainWidget); this, SIGNAL(databaseFilePathChanged(QString,QString)));
emit unlockedDatabase(); 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 void DatabaseWidget::loadDatabase(bool accepted)
// the file in its dtor. {
delete m_databaseOpenWidget; auto* openWidget = qobject_cast<DatabaseOpenWidget*>(sender());
m_databaseOpenWidget = nullptr; Q_ASSERT(openWidget);
delete m_keepass1OpenWidget; if (!openWidget) {
m_keepass1OpenWidget = nullptr; return;
m_fileWatcher.addPath(m_filePath); }
if (accepted) {
replaceDatabase(openWidget->database());
setCurrentWidget(m_mainWidget);
m_fileWatcher.addPath(m_db->filePath());
emit databaseUnlocked();
} else { } else {
m_fileWatcher.removePath(m_filePath); m_fileWatcher.removePath(m_db->filePath());
if (m_databaseOpenWidget->database()) { if (m_databaseOpenWidget->database()) {
delete m_databaseOpenWidget->database(); m_databaseOpenWidget->database().reset();
} }
emit closeRequest(); emit closeRequest();
} }
@ -815,18 +807,18 @@ void DatabaseWidget::mergeDatabase(bool accepted)
{ {
if (accepted) { if (accepted) {
if (!m_db) { if (!m_db) {
m_messageWidget->showMessage(tr("No current database."), MessageWidget::Error); showMessage(tr("No current database."), MessageWidget::Error);
return; return;
} }
Database* srcDb = static_cast<DatabaseOpenWidget*>(sender())->database(); auto srcDb = qobject_cast<DatabaseOpenWidget*>(sender())->database();
if (!srcDb) { 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; return;
} }
Merger merger(srcDb, m_db); Merger merger(srcDb.data(), m_db.data());
merger.merge(); merger.merge();
} }
@ -842,7 +834,7 @@ void DatabaseWidget::unlockDatabase(bool accepted)
return; return;
} }
Database* db = nullptr; auto db = QSharedPointer<Database>::create();
if (sender() == m_unlockDatabaseDialog) { if (sender() == m_unlockDatabaseDialog) {
db = m_unlockDatabaseDialog->database(); db = m_unlockDatabaseDialog->database();
} else if (sender() == m_unlockDatabaseWidget) { } else if (sender() == m_unlockDatabaseWidget) {
@ -850,6 +842,9 @@ void DatabaseWidget::unlockDatabase(bool accepted)
} }
replaceDatabase(db); replaceDatabase(db);
if (db->isReadOnly()) {
showMessage(tr("File opened in read only mode."), MessageWidget::Warning, false, -1);
}
restoreGroupEntryFocus(m_groupBeforeLock, m_entryBeforeLock); restoreGroupEntryFocus(m_groupBeforeLock, m_entryBeforeLock);
m_groupBeforeLock = QUuid(); m_groupBeforeLock = QUuid();
@ -857,10 +852,10 @@ void DatabaseWidget::unlockDatabase(bool accepted)
setCurrentWidget(m_mainWidget); setCurrentWidget(m_mainWidget);
m_unlockDatabaseWidget->clearForms(); m_unlockDatabaseWidget->clearForms();
emit unlockedDatabase(); emit databaseUnlocked();
if (sender() == m_unlockDatabaseDialog) { if (sender() == m_unlockDatabaseDialog) {
QList<Database*> dbList; QList<QSharedPointer<Database>> dbList;
dbList.append(m_db); dbList.append(m_db);
autoType()->performGlobalAutoType(dbList); autoType()->performGlobalAutoType(dbList);
} }
@ -946,6 +941,11 @@ void DatabaseWidget::switchToDatabaseSettings()
setCurrentWidget(m_databaseSettingDialog); setCurrentWidget(m_databaseSettingDialog);
} }
void DatabaseWidget::switchToOpenDatabase()
{
switchToOpenDatabase(m_db->filePath());
}
void DatabaseWidget::switchToOpenDatabase(const QString& filePath) void DatabaseWidget::switchToOpenDatabase(const QString& filePath)
{ {
updateFilePath(filePath); updateFilePath(filePath);
@ -957,22 +957,10 @@ void DatabaseWidget::switchToOpenDatabase(const QString& filePath)
setCurrentWidget(m_unlockDatabaseWidget); 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) void DatabaseWidget::switchToCsvImport(const QString& filePath)
{ {
setCurrentWidget(m_csvImportWizard); setCurrentWidget(m_csvImportWizard);
m_csvImportWizard->load(filePath, m_db); m_csvImportWizard->load(filePath, m_db.data());
} }
void DatabaseWidget::switchToOpenMergeDatabase(const QString& filePath) void DatabaseWidget::switchToOpenMergeDatabase(const QString& filePath)
@ -995,19 +983,9 @@ void DatabaseWidget::switchToImportKeepass1(const QString& filePath)
setCurrentWidget(m_keepass1OpenWidget); setCurrentWidget(m_keepass1OpenWidget);
} }
void DatabaseWidget::databaseModified()
{
m_databaseModified = true;
}
void DatabaseWidget::databaseSaved()
{
m_databaseModified = false;
}
void DatabaseWidget::refreshSearch() void DatabaseWidget::refreshSearch()
{ {
if (isInSearchMode()) { if (isSearchActive()) {
search(m_lastSearchText); search(m_lastSearchText);
} }
} }
@ -1054,10 +1032,10 @@ void DatabaseWidget::setSearchLimitGroup(bool state)
void DatabaseWidget::onGroupChanged(Group* group) void DatabaseWidget::onGroupChanged(Group* group)
{ {
if (isInSearchMode() && m_searchLimitGroup) { // Intercept group changes if in search mode
// Perform new search if we are limiting search to the current group if (isSearchActive()) {
search(m_lastSearchText); search(m_lastSearchText);
} else if (isInSearchMode()) { } else if (isSearchActive()) {
// Otherwise cancel search // Otherwise cancel search
emit clearSearch(); emit clearSearch();
} else { } else {
@ -1072,7 +1050,7 @@ QString DatabaseWidget::getCurrentSearch()
void DatabaseWidget::endSearch() void DatabaseWidget::endSearch()
{ {
if (isInSearchMode()) { if (isSearchActive()) {
emit listModeAboutToActivate(); emit listModeAboutToActivate();
// Show the normal entry view of the current group // Show the normal entry view of the current group
@ -1117,30 +1095,74 @@ void DatabaseWidget::emitPressedGroup(Group* currentGroup)
emit pressedGroup(currentGroup); emit pressedGroup(currentGroup);
} }
bool DatabaseWidget::dbHasKey() const
{
return m_db->hasKey();
}
bool DatabaseWidget::canDeleteCurrentGroup() const bool DatabaseWidget::canDeleteCurrentGroup() const
{ {
bool isRootGroup = m_db->rootGroup() == m_groupView->currentGroup(); bool isRootGroup = m_db->rootGroup() == m_groupView->currentGroup();
return !isRootGroup; return !isRootGroup;
} }
bool DatabaseWidget::isInSearchMode() const
{
return m_entryView->inSearchMode();
}
Group* DatabaseWidget::currentGroup() const Group* DatabaseWidget::currentGroup() const
{ {
return m_groupView->currentGroup(); 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()) { if (m_groupView->currentGroup()) {
m_groupBeforeLock = m_groupView->currentGroup()->uuid(); m_groupBeforeLock = m_groupView->currentGroup()->uuid();
@ -1154,21 +1176,24 @@ void DatabaseWidget::lock()
endSearch(); endSearch();
clearAllWidgets(); clearAllWidgets();
m_unlockDatabaseWidget->load(m_filePath); m_unlockDatabaseWidget->load(m_db->filePath());
setCurrentWidget(m_unlockDatabaseWidget); setCurrentWidget(m_unlockDatabaseWidget);
Database* newDb = new Database();
newDb->metadata()->setName(m_db->metadata()->name()); auto newDb = QSharedPointer<Database>::create(m_db->filePath());
replaceDatabase(newDb); replaceDatabase(newDb);
emit lockedDatabase();
emit databaseLocked();
return true;
} }
void DatabaseWidget::updateFilePath(const QString& filePath) void DatabaseWidget::updateFilePath(const QString& filePath)
{ {
if (!m_filePath.isEmpty()) { if (!m_db->filePath().isEmpty()) {
m_fileWatcher.removePath(m_filePath); m_fileWatcher.removePath(m_db->filePath());
} }
#if defined(Q_OS_LINUX) #ifdef Q_OS_LINUX
struct statfs statfsBuf; struct statfs statfsBuf;
bool forcePolling = false; bool forcePolling = false;
const auto NFS_SUPER_MAGIC = 0x6969; const auto NFS_SUPER_MAGIC = 0x6969;
@ -1184,7 +1209,6 @@ void DatabaseWidget::updateFilePath(const QString& filePath)
#endif #endif
m_fileWatcher.addPath(filePath); m_fileWatcher.addPath(filePath);
m_filePath = filePath;
m_db->setFilePath(filePath); m_db->setFilePath(filePath);
} }
@ -1201,7 +1225,7 @@ void DatabaseWidget::blockAutoReload(bool block)
void DatabaseWidget::unblockAutoReload() void DatabaseWidget::unblockAutoReload()
{ {
m_ignoreAutoReload = false; m_ignoreAutoReload = false;
updateFilePath(m_filePath); updateFilePath(m_db->filePath());
} }
void DatabaseWidget::onWatchedFileChanged() void DatabaseWidget::onWatchedFileChanged()
@ -1217,55 +1241,40 @@ void DatabaseWidget::onWatchedFileChanged()
void DatabaseWidget::reloadDatabaseFile() void DatabaseWidget::reloadDatabaseFile()
{ {
if (!m_db || currentMode() == DatabaseWidget::LockedMode) { if (!m_db || isLocked()) {
return;
}
if (currentMode() == DatabaseWidget::LockedMode) {
return; return;
} }
if (!config()->get("AutoReloadOnChange").toBool()) { if (!config()->get("AutoReloadOnChange").toBool()) {
// Ask if we want to reload the db // Ask if we want to reload the db
QMessageBox::StandardButton mb = auto result = MessageBox::question(this,
MessageBox::question(this,
tr("File has changed"), tr("File has changed"),
tr("The database file has changed. Do you want to load the changes?"), tr("The database file has changed. Do you want to load the changes?"),
QMessageBox::Yes | QMessageBox::No); QMessageBox::Yes | QMessageBox::No);
if (mb == QMessageBox::No) { if (result == QMessageBox::No) {
// Notify everyone the database does not match the file // Notify everyone the database does not match the file
m_db->markAsModified(); m_db->markAsModified();
m_databaseModified = true;
// Rewatch the database file // Rewatch the database file
m_fileWatcher.addPath(m_filePath); m_fileWatcher.addPath(m_db->filePath());
return; return;
} }
} }
KeePass2Reader reader; QString error;
QFile file(m_filePath); auto db = QSharedPointer<Database>::create(m_db->filePath());
if (file.open(QIODevice::ReadOnly)) { if (db->open(database()->key(), &error, true)) {
Database* db = reader.readDatabase(&file, database()->key()); if (m_db->isModified()) {
if (db != nullptr) {
if (m_databaseModified) {
// Ask if we want to merge changes into new database // Ask if we want to merge changes into new database
QMessageBox::StandardButton mb = auto result = MessageBox::question(this,
MessageBox::question(this,
tr("Merge Request"), tr("Merge Request"),
tr("The database file has changed and you have unsaved changes.\n" tr("The database file has changed and you have unsaved changes.\nDo you want to merge your changes?"),
"Do you want to merge your changes?"),
QMessageBox::Yes | QMessageBox::No); QMessageBox::Yes | QMessageBox::No);
if (mb == QMessageBox::Yes) { if (result == QMessageBox::Yes) {
// Merge the old database into the new one // Merge the old database into the new one
m_db->setEmitModified(false); Merger merger(m_db.data(), db.data());
Merger merger(m_db, db);
merger.merge(); 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;
} }
} }
@ -1281,22 +1290,20 @@ void DatabaseWidget::reloadDatabaseFile()
entryBeforeReload = m_entryView->currentEntry()->uuid(); entryBeforeReload = m_entryView->currentEntry()->uuid();
} }
bool isReadOnly = m_db->isReadOnly();
replaceDatabase(db); replaceDatabase(db);
m_db->setReadOnly(isReadOnly);
restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload); restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload);
}
} else { } else {
m_messageWidget->showMessage( showMessage(
tr("Could not open the new database file while attempting to autoreload this database.") tr("Could not open the new database file while attempting to autoreload.\nError: %1").arg(error),
.append("\n")
.append(file.errorString()),
MessageWidget::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 // Mark db as modified since existing data may differ from file or file was deleted
m_db->markAsModified(); m_db->markAsModified();
} }
// Rewatch the database file // Rewatch the database file
m_fileWatcher.addPath(m_filePath); m_fileWatcher.addPath(m_db->filePath());
} }
int DatabaseWidget::numberOfSelectedEntries() const int DatabaseWidget::numberOfSelectedEntries() const
@ -1319,7 +1326,7 @@ QStringList DatabaseWidget::customEntryAttributes() const
*/ */
void DatabaseWidget::restoreGroupEntryFocus(const QUuid& groupUuid, const QUuid& entryUuid) void DatabaseWidget::restoreGroupEntryFocus(const QUuid& groupUuid, const QUuid& entryUuid)
{ {
auto group = m_db->resolveGroup(groupUuid); auto group = m_db->rootGroup()->findGroupByUuid(groupUuid);
if (group) { if (group) {
m_groupView->setCurrentGroup(group); m_groupView->setCurrentGroup(group);
auto entry = group->findEntryByUuid(entryUuid); auto entry = group->findEntryByUuid(entryUuid);
@ -1409,10 +1416,10 @@ EntryView* DatabaseWidget::entryView()
return m_entryView; return m_entryView;
} }
void DatabaseWidget::showUnlockDialog() void DatabaseWidget::prepareUnlock()
{ {
m_unlockDatabaseDialog->clearForms(); m_unlockDatabaseDialog->clearForms();
m_unlockDatabaseDialog->setFilePath(m_filePath); m_unlockDatabaseDialog->setFilePath(m_db->filePath());
#if defined(Q_OS_MACOS) #if defined(Q_OS_MACOS)
autoType()->raiseWindow(); autoType()->raiseWindow();
@ -1423,15 +1430,101 @@ void DatabaseWidget::showUnlockDialog()
m_unlockDatabaseDialog->activateWindow(); 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;
} }
void DatabaseWidget::showMessage(const QString& text, if (m_db->isReadOnly() || m_db->filePath().isEmpty()) {
MessageWidget::MessageType type, return saveAs();
bool showClosebutton, }
int autoHideTimeout)
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;
}
/**
* 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->setCloseButtonVisible(showClosebutton);
m_messageWidget->showMessage(text, type, autoHideTimeout); 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(); 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() void DatabaseWidget::emptyRecycleBin()
{ {
if (!isRecycleBinSelected()) { 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) 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 * 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 * it under the terms of the GNU General Public License as published by
@ -29,7 +29,6 @@
#include "gui/csvImport/CsvImportWizard.h" #include "gui/csvImport/CsvImportWizard.h"
#include "gui/entry/EntryModel.h" #include "gui/entry/EntryModel.h"
class ChangeMasterKeyWidget;
class DatabaseOpenWidget; class DatabaseOpenWidget;
class DatabaseSettingsDialog; class DatabaseSettingsDialog;
class Database; class Database;
@ -61,7 +60,7 @@ class DatabaseWidget : public QStackedWidget
Q_OBJECT Q_OBJECT
public: public:
enum Mode enum class Mode
{ {
None, None,
ImportMode, ImportMode,
@ -70,35 +69,39 @@ public:
LockedMode 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(); ~DatabaseWidget();
Database* database();
bool dbHasKey() const; QSharedPointer<Database> database() const;
bool canDeleteCurrentGroup() const;
bool isInSearchMode() const; bool lock();
QString getCurrentSearch(); void prepareUnlock();
Group* currentGroup() const; bool save(int attempt = 0);
int addWidget(QWidget* w); bool saveAs();
void setCurrentIndex(int index);
void setCurrentWidget(QWidget* widget);
DatabaseWidget::Mode currentMode() const; DatabaseWidget::Mode currentMode() const;
void lock(); bool isLocked() const;
void updateFilePath(const QString& filePath); bool isSearchActive() const;
int numberOfSelectedEntries() const;
QStringList customEntryAttributes() const; QString getCurrentSearch();
void refreshSearch();
GroupView* groupView();
EntryView* entryView();
Group* currentGroup() const;
bool canDeleteCurrentGroup() const;
bool isGroupSelected() const; bool isGroupSelected() const;
bool isInEditMode() const; bool isRecycleBinSelected() const;
int numberOfSelectedEntries() const;
QStringList customEntryAttributes() const;
bool isEditWidgetModified() 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; bool isUsernamesHidden() const;
void setUsernamesHidden(bool hide); void setUsernamesHidden(bool hide);
bool isPasswordsHidden() const; bool isPasswordsHidden() const;
void setPasswordsHidden(bool hide); void setPasswordsHidden(bool hide);
QByteArray entryViewState() const;
bool setEntryViewState(const QByteArray& state) const;
void clearAllWidgets(); void clearAllWidgets();
bool currentEntryHasFocus(); bool currentEntryHasFocus();
bool currentEntryHasTitle(); bool currentEntryHasTitle();
@ -107,31 +110,33 @@ public:
bool currentEntryHasUrl(); bool currentEntryHasUrl();
bool currentEntryHasNotes(); bool currentEntryHasNotes();
bool currentEntryHasTotp(); bool currentEntryHasTotp();
GroupView* groupView();
EntryView* entryView();
void showUnlockDialog();
void closeUnlockDialog();
void blockAutoReload(bool block = true); void blockAutoReload(bool block = true);
void refreshSearch();
bool isRecycleBinSelected() const; QByteArray entryViewState() const;
QString getDatabaseName() const; bool setEntryViewState(const QByteArray& state) const;
void setDatabaseName(const QString& databaseName); QList<int> mainSplitterSizes() const;
QString getDatabaseFileName() const; void setMainSplitterSizes(const QList<int>& sizes);
void setDatabaseFileName(const QString& databaseFileName); QList<int> previewSplitterSizes() const;
void setPreviewSplitterSizes(const QList<int>& sizes);
signals: signals:
// relayed Database signals
void databaseFilePathChanged(const QString& oldPath, const QString& newPath);
void databaseModified();
void databaseSaved();
void databaseUnlocked();
void databaseLocked();
void closeRequest(); void closeRequest();
void currentModeChanged(DatabaseWidget::Mode mode); void currentModeChanged(DatabaseWidget::Mode mode);
void groupChanged(); void groupChanged();
void entrySelectionChanged(); void entrySelectionChanged();
void databaseChanged(Database* newDb, bool unsavedChanges); void databaseMerged(QSharedPointer<Database> mergedDb);
void databaseMerged(Database* mergedDb);
void groupContextMenuRequested(const QPoint& globalPos); void groupContextMenuRequested(const QPoint& globalPos);
void entryContextMenuRequested(const QPoint& globalPos); void entryContextMenuRequested(const QPoint& globalPos);
void pressedEntry(Entry* selectedEntry); void pressedEntry(Entry* selectedEntry);
void pressedGroup(Group* selectedGroup); void pressedGroup(Group* selectedGroup);
void unlockedDatabase();
void lockedDatabase();
void listModeAboutToActivate(); void listModeAboutToActivate();
void listModeActivated(); void listModeActivated();
void searchModeAboutToActivate(); void searchModeAboutToActivate();
@ -142,6 +147,7 @@ signals:
void clearSearch(); void clearSearch();
public slots: public slots:
void replaceDatabase(QSharedPointer<Database> db);
void createEntry(); void createEntry();
void cloneEntry(); void cloneEntry();
void deleteEntries(); void deleteEntries();
@ -167,15 +173,13 @@ public slots:
void switchToGroupEdit(); void switchToGroupEdit();
void switchToMasterKeyChange(); void switchToMasterKeyChange();
void switchToDatabaseSettings(); void switchToDatabaseSettings();
void switchToOpenDatabase();
void switchToOpenDatabase(const QString& filePath); void switchToOpenDatabase(const QString& filePath);
void switchToOpenDatabase(const QString& filePath, const QString& password, const QString& keyFile);
void switchToCsvImport(const QString& filePath); void switchToCsvImport(const QString& filePath);
void csvImportFinished(bool accepted); void csvImportFinished(bool accepted);
void switchToOpenMergeDatabase(const QString& filePath); void switchToOpenMergeDatabase(const QString& filePath);
void switchToOpenMergeDatabase(const QString& filePath, const QString& password, const QString& keyFile); void switchToOpenMergeDatabase(const QString& filePath, const QString& password, const QString& keyFile);
void switchToImportKeepass1(const QString& filePath); void switchToImportKeepass1(const QString& filePath);
void databaseModified();
void databaseSaved();
void emptyRecycleBin(); void emptyRecycleBin();
// Search related slots // Search related slots
@ -191,18 +195,24 @@ public slots:
void showErrorMessage(const QString& errorMessage); void showErrorMessage(const QString& errorMessage);
void hideMessage(); void hideMessage();
protected:
void closeEvent(QCloseEvent* event) override;
void showEvent(QShowEvent* event) override;
private slots: private slots:
void updateFilePath(const QString& filePath);
void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column); void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column);
void switchBackToEntryEdit(); void switchBackToEntryEdit();
void switchToHistoryView(Entry* entry); void switchToHistoryView(Entry* entry);
void switchToEntryEdit(Entry* entry); void switchToEntryEdit(Entry*);
void switchToEntryEdit(Entry* entry, bool create); void switchToEntryEdit(Entry* entry, bool create);
void switchToGroupEdit(Group* entry, bool create); void switchToGroupEdit(Group* entry, bool create);
void emitGroupContextMenuRequested(const QPoint& pos); void emitGroupContextMenuRequested(const QPoint& pos);
void emitEntryContextMenuRequested(const QPoint& pos); void emitEntryContextMenuRequested(const QPoint& pos);
void emitPressedGroup(Group* currentGroup); void emitPressedGroup(Group* currentGroup);
void emitEntrySelectionChanged(); void emitEntrySelectionChanged();
void openDatabase(bool accepted); void connectDatabaseSignals();
void loadDatabase(bool accepted);
void mergeDatabase(bool accepted); void mergeDatabase(bool accepted);
void unlockDatabase(bool accepted); void unlockDatabase(bool accepted);
void emitCurrentModeChanged(); void emitCurrentModeChanged();
@ -213,36 +223,38 @@ private slots:
void unblockAutoReload(); void unblockAutoReload();
private: private:
int addChildWidget(QWidget* w);
void setClipboardTextAndMinimize(const QString& text); void setClipboardTextAndMinimize(const QString& text);
void setIconFromParent(); void setIconFromParent();
void replaceDatabase(Database* db);
QPointer<Database> m_db; QSharedPointer<Database> m_db;
QWidget* m_mainWidget;
EditEntryWidget* m_editEntryWidget; QPointer<QWidget> m_mainWidget;
EditEntryWidget* m_historyEditEntryWidget; QPointer<QSplitter> m_mainSplitter;
EditGroupWidget* m_editGroupWidget; QPointer<MessageWidget> m_messageWidget;
ChangeMasterKeyWidget* m_changeMasterKeyWidget; QPointer<EntryPreviewWidget> m_previewView;
CsvImportWizard* m_csvImportWizard; QPointer<QSplitter> m_previewSplitter;
DatabaseSettingsDialog* m_databaseSettingDialog; QPointer<QLabel> m_searchingLabel;
DatabaseOpenWidget* m_databaseOpenWidget; QPointer<CsvImportWizard> m_csvImportWizard;
DatabaseOpenWidget* m_databaseOpenMergeWidget; QPointer<EditEntryWidget> m_editEntryWidget;
KeePass1OpenWidget* m_keepass1OpenWidget; QPointer<EditGroupWidget> m_editGroupWidget;
UnlockDatabaseWidget* m_unlockDatabaseWidget; QPointer<EditEntryWidget> m_historyEditEntryWidget;
UnlockDatabaseDialog* m_unlockDatabaseDialog; QPointer<DatabaseSettingsDialog> m_databaseSettingDialog;
QSplitter* m_mainSplitter; QPointer<DatabaseOpenWidget> m_databaseOpenWidget;
QSplitter* m_previewSplitter; QPointer<DatabaseOpenWidget> m_databaseOpenMergeWidget;
GroupView* m_groupView; QPointer<KeePass1OpenWidget> m_keepass1OpenWidget;
EntryView* m_entryView; QPointer<UnlockDatabaseWidget> m_unlockDatabaseWidget;
QLabel* m_searchingLabel; QPointer<UnlockDatabaseDialog> m_unlockDatabaseDialog;
Group* m_newGroup; QPointer<GroupView> m_groupView;
Entry* m_newEntry; QPointer<EntryView> m_entryView;
Group* m_newParent;
QString m_filePath; QPointer<Group> m_newGroup;
QPointer<Entry> m_newEntry;
QPointer<Group> m_newParent;
QUuid m_groupBeforeLock; QUuid m_groupBeforeLock;
QUuid m_entryBeforeLock; QUuid m_entryBeforeLock;
MessageWidget* m_messageWidget;
EntryPreviewWidget* m_previewView;
QString m_databaseName; QString m_databaseName;
QString m_databaseFileName; QString m_databaseFileName;
@ -251,15 +263,11 @@ private:
QString m_lastSearchText; QString m_lastSearchText;
bool m_searchLimitGroup; bool m_searchLimitGroup;
// CSV import state
bool m_importingCsv;
// Autoreload // Autoreload
QFileSystemWatcher m_fileWatcher; QFileSystemWatcher m_fileWatcher;
QTimer m_fileWatchTimer; QTimer m_fileWatchTimer;
QTimer m_fileWatchUnblockTimer; QTimer m_fileWatchUnblockTimer;
bool m_ignoreAutoReload; bool m_ignoreAutoReload;
bool m_databaseModified;
}; };
#endif // KEEPASSX_DATABASEWIDGET_H #endif // KEEPASSX_DATABASEWIDGET_H

View file

@ -74,7 +74,7 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget)
m_activeDbWidget->setPreviewSplitterSizes(m_previewSplitterSizes); m_activeDbWidget->setPreviewSplitterSizes(m_previewSplitterSizes);
} }
if (m_activeDbWidget->isInSearchMode()) { if (m_activeDbWidget->isSearchActive()) {
restoreSearchView(); restoreSearchView();
} else { } else {
restoreListView(); restoreListView();
@ -177,7 +177,7 @@ void DatabaseWidgetStateSync::updateViewState()
m_hideUsernames = m_activeDbWidget->isUsernamesHidden(); m_hideUsernames = m_activeDbWidget->isUsernamesHidden();
m_hidePasswords = m_activeDbWidget->isPasswordsHidden(); m_hidePasswords = m_activeDbWidget->isPasswordsHidden();
if (m_activeDbWidget->isInSearchMode()) { if (m_activeDbWidget->isSearchActive()) {
m_searchViewState = m_activeDbWidget->entryViewState(); m_searchViewState = m_activeDbWidget->entryViewState();
} else { } else {
m_listViewState = m_activeDbWidget->entryViewState(); 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 * 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. * pixels high.
*/ */
QScrollArea* scrollArea = new QScrollArea(m_ui->stackedWidget); auto* scrollArea = new QScrollArea(m_ui->stackedWidget);
scrollArea->setFrameShape(QFrame::NoFrame); scrollArea->setFrameShape(QFrame::NoFrame);
scrollArea->setWidget(widget); scrollArea->setWidget(widget);
scrollArea->setWidgetResizable(true); scrollArea->setWidgetResizable(true);

View file

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

View file

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

View file

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

View file

@ -54,15 +54,13 @@ void KeePass1OpenWidget::openDatabase()
return; return;
} }
delete m_db;
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
m_db = reader.readDatabase(&file, password, keyFileName); m_db = reader.readDatabase(&file, password, keyFileName);
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
if (m_db) { if (m_db) {
m_db->metadata()->setName(QFileInfo(m_filename).completeBaseName()); m_db->metadata()->setName(QFileInfo(m_filename).completeBaseName());
emit editFinished(true); emit dialogFinished(true);
} else { } 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.").append("\n").append(reader.errorString()),
MessageWidget::Error); MessageWidget::Error);

View file

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

View file

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

View file

@ -135,7 +135,7 @@ void SearchWidget::databaseChanged(DatabaseWidget* dbWidget)
// Set current search text from this database // Set current search text from this database
m_ui->searchEdit->setText(dbWidget->getCurrentSearch()); m_ui->searchEdit->setText(dbWidget->getCurrentSearch());
// Keyboard focus on search widget at database unlocking // 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 // Enforce search policy
emit caseSensitiveChanged(m_actionCaseSensitive->isChecked()); emit caseSensitiveChanged(m_actionCaseSensitive->isChecked());
emit limitGroupChanged(m_actionLimitGroup->isChecked()); emit limitGroupChanged(m_actionLimitGroup->isChecked());

View file

@ -27,7 +27,7 @@ UnlockDatabaseDialog::UnlockDatabaseDialog(QWidget* parent)
, m_view(new UnlockDatabaseWidget(this)) , m_view(new UnlockDatabaseWidget(this))
{ {
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); 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) void UnlockDatabaseDialog::setFilePath(const QString& filePath)
@ -40,7 +40,7 @@ void UnlockDatabaseDialog::clearForms()
m_view->clearForms(); m_view->clearForms();
} }
Database* UnlockDatabaseDialog::database() QSharedPointer<Database> UnlockDatabaseDialog::database()
{ {
return m_view->database(); return m_view->database();
} }

View file

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

View file

@ -92,9 +92,9 @@ void CsvParserModel::setSkippedRows(int skipped)
emit layoutChanged(); 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 int CsvParserModel::rowCount(const QModelIndex& parent) const

View file

@ -36,7 +36,7 @@ public:
QString getFileInfo(); QString getFileInfo();
bool parse(); bool parse();
void setHeaderLabels(QStringList l); void setHeaderLabels(const QStringList& labels);
void mapColumns(int csvColumn, int dbColumn); void mapColumns(int csvColumn, int dbColumn);
int rowCount(const QModelIndex& parent = QModelIndex()) const override; 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_ui->categoryList->setCurrentCategory(0);
m_generalWidget->load(db); m_generalWidget->load(db);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -32,7 +32,7 @@ class AutoTypeAssociationsModel : public QAbstractListModel
public: public:
explicit AutoTypeAssociationsModel(QObject* parent = nullptr); explicit AutoTypeAssociationsModel(QObject* parent = nullptr);
void setAutoTypeAssociations(AutoTypeAssociations* autoTypeAssociations); void setAutoTypeAssociations(AutoTypeAssociations* autoTypeAssociations);
void setEntry(const Entry* entry); void setEntry(Entry* entry);
int rowCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(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; 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) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> * 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->decryptButton, SIGNAL(clicked()), SLOT(decryptPrivateKey()));
connect(m_sshAgentUi->copyToClipboardButton, SIGNAL(clicked()), SLOT(copyPublicKey())); 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); 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_entry = entry;
m_database = database; m_db = std::move(database);
m_create = create; m_create = create;
m_history = history; m_history = history;
@ -667,7 +669,7 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const Q
setUnsavedChanges(m_create); 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->titleEdit->setReadOnly(m_history);
m_mainUi->usernameEdit->setReadOnly(m_history); m_mainUi->usernameEdit->setReadOnly(m_history);
@ -734,7 +736,7 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore)
IconStruct iconStruct; IconStruct iconStruct;
iconStruct.uuid = entry->iconUuid(); iconStruct.uuid = entry->iconUuid();
iconStruct.number = entry->iconNumber(); 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))); connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
m_autoTypeUi->enableButton->setChecked(entry->autoTypeEnabled()); m_autoTypeUi->enableButton->setChecked(entry->autoTypeEnabled());
@ -924,7 +926,7 @@ void EditEntryWidget::cancel()
return; 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); m_entry->setIcon(Entry::DefaultIconNumber);
} }
@ -952,7 +954,7 @@ void EditEntryWidget::cancel()
void EditEntryWidget::clear() void EditEntryWidget::clear()
{ {
m_entry = nullptr; m_entry = nullptr;
m_database = nullptr; m_db.reset();
m_entryAttributes->clear(); m_entryAttributes->clear();
m_advancedUi->attachmentsWidget->clearAttachments(); m_advancedUi->attachmentsWidget->clearAttachments();
m_autoTypeAssoc->clear(); m_autoTypeAssoc->clear();
@ -969,11 +971,11 @@ bool EditEntryWidget::hasBeenModified() const
} }
// check if updating the entry would modify it // check if updating the entry would modify it
QScopedPointer<Entry> entry(new Entry()); auto* entry = new Entry();
entry->copyDataFrom(m_entry); entry->copyDataFrom(m_entry.data());
entry->beginUpdate(); entry->beginUpdate();
updateEntryData(entry.data()); updateEntryData(entry);
return entry->endUpdate(); return entry->endUpdate();
} }
@ -1256,17 +1258,13 @@ void EditEntryWidget::deleteHistoryEntry()
void EditEntryWidget::deleteAllHistoryEntries() void EditEntryWidget::deleteAllHistoryEntries()
{ {
m_historyModel->deleteAll(); m_historyModel->deleteAll();
if (m_historyModel->rowCount() > 0) { m_historyUi->deleteAllButton->setEnabled(m_historyModel->rowCount() > 0);
m_historyUi->deleteAllButton->setEnabled(true);
} else {
m_historyUi->deleteAllButton->setEnabled(false);
}
setUnsavedChanges(true); setUnsavedChanges(true);
} }
QMenu* EditEntryWidget::createPresetsMenu() QMenu* EditEntryWidget::createPresetsMenu()
{ {
QMenu* expirePresetsMenu = new QMenu(this); auto* expirePresetsMenu = new QMenu(this);
expirePresetsMenu->addAction(tr("Tomorrow"))->setData(QVariant::fromValue(TimeDelta::fromDays(1))); expirePresetsMenu->addAction(tr("Tomorrow"))->setData(QVariant::fromValue(TimeDelta::fromDays(1)));
expirePresetsMenu->addSeparator(); expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromDays(7))); 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, 3))->setData(QVariant::fromValue(TimeDelta::fromMonths(3)));
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 6))->setData(QVariant::fromValue(TimeDelta::fromMonths(6))); expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 6))->setData(QVariant::fromValue(TimeDelta::fromMonths(6)));
expirePresetsMenu->addSeparator(); expirePresetsMenu->addSeparator();
expirePresetsMenu->addAction(tr("%n year(s)", 0, 1))->setData(QVariant::fromValue(TimeDelta::fromYears(1))); expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 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)", nullptr, 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, 3))->setData(QVariant::fromValue(TimeDelta::fromYears(3)));
return expirePresetsMenu; return expirePresetsMenu;
} }

View file

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

View file

@ -30,7 +30,6 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
, m_editGroupWidgetIcons(new EditWidgetIcons()) , m_editGroupWidgetIcons(new EditWidgetIcons())
, m_editWidgetProperties(new EditWidgetProperties()) , m_editWidgetProperties(new EditWidgetProperties())
, m_group(nullptr) , m_group(nullptr)
, m_database(nullptr)
{ {
m_mainUi->setupUi(m_editGroupWidgetMain); 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_group = group;
m_database = database; m_db = database;
if (create) { if (create) {
setHeadline(tr("Add group")); setHeadline(tr("Add group"));
@ -141,7 +140,7 @@ void EditGroupWidget::apply()
void EditGroupWidget::cancel() 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); m_group->setIcon(Entry::DefaultIconNumber);
} }
@ -152,7 +151,7 @@ void EditGroupWidget::cancel()
void EditGroupWidget::clear() void EditGroupWidget::clear()
{ {
m_group = nullptr; m_group = nullptr;
m_database = nullptr; m_db.reset();
m_editGroupWidgetIcons->reset(); m_editGroupWidgetIcons->reset();
} }

View file

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

View file

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

View file

@ -51,9 +51,9 @@ GroupView::GroupView(Database* db, QWidget* parent)
setDefaultDropAction(Qt::MoveAction); 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) void GroupView::dragMoveEvent(QDragMoveEvent* event)

View file

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

View file

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

View file

@ -39,7 +39,7 @@ NewDatabaseWizard::NewDatabaseWizard(QWidget* parent)
<< new NewDatabaseWizardPageEncryption() << new NewDatabaseWizardPageEncryption()
<< new NewDatabaseWizardPageMasterKey(); << new NewDatabaseWizardPageMasterKey();
for (auto const& page: asConst(m_pages)) { for (const auto& page: asConst(m_pages)) {
addPage(page); addPage(page);
} }
@ -54,23 +54,34 @@ NewDatabaseWizard::~NewDatabaseWizard()
bool NewDatabaseWizard::validateCurrentPage() 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) void NewDatabaseWizard::initializePage(int id)
{ {
if (id == startId()) { if (id == startId()) {
m_db.reset(new Database()); m_db = QSharedPointer<Database>::create();
m_db->rootGroup()->setName(tr("Root", "Root group")); m_db->rootGroup()->setName(tr("Root", "Root group"));
m_db->setKdf({}); m_db->setKdf({});
m_db->setKey({}); m_db->setKey({});
} }
m_pages[id]->setDatabase(m_db.data()); m_pages[id]->setDatabase(m_db);
m_pages[id]->initializePage(); m_pages[id]->initializePage();
} }

View file

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

View file

@ -1,3 +1,5 @@
#include <utility>
/* /*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* *
@ -66,9 +68,9 @@ DatabaseSettingsWidget* NewDatabaseWizardPage::pageWidget()
* *
* @param db database object to be configured * @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() void NewDatabaseWizardPage::initializePage()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -40,14 +40,18 @@ void TestDatabase::testEmptyRecycleBinOnDisabled()
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinDisabled.kdbx"); QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinDisabled.kdbx");
auto key = QSharedPointer<CompositeKey>::create(); auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("123")); key->addKey(QSharedPointer<PasswordKey>::create("123"));
QScopedPointer<Database> db(Database::openDatabaseFile(filename, key)); auto db = QSharedPointer<Database>::create();
QVERIFY(db); 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(); db->emptyRecycleBin();
// The database must be unmodified in this test after emptying the recycle bin. // 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() void TestDatabase::testEmptyRecycleBinOnNotCreated()
@ -55,14 +59,15 @@ void TestDatabase::testEmptyRecycleBinOnNotCreated()
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinNotYetCreated.kdbx"); QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinNotYetCreated.kdbx");
auto key = QSharedPointer<CompositeKey>::create(); auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("123")); key->addKey(QSharedPointer<PasswordKey>::create("123"));
QScopedPointer<Database> db(Database::openDatabaseFile(filename, key)); auto db = QSharedPointer<Database>::create();
QVERIFY(db); QVERIFY(db->open(filename, key, nullptr, false));
db->setReadOnly(false);
QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate())); QSignalSpy spyModified(db.data(), SIGNAL(databaseModified()));
db->emptyRecycleBin(); db->emptyRecycleBin();
// The database must be unmodified in this test after emptying the recycle bin. // 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() void TestDatabase::testEmptyRecycleBinOnEmpty()
@ -70,14 +75,15 @@ void TestDatabase::testEmptyRecycleBinOnEmpty()
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinEmpty.kdbx"); QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinEmpty.kdbx");
auto key = QSharedPointer<CompositeKey>::create(); auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("123")); key->addKey(QSharedPointer<PasswordKey>::create("123"));
QScopedPointer<Database> db(Database::openDatabaseFile(filename, key)); auto db = QSharedPointer<Database>::create();
QVERIFY(db); QVERIFY(db->open(filename, key, nullptr, false));
db->setReadOnly(false);
QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate())); QSignalSpy spyModified(db.data(), SIGNAL(databaseModified()));
db->emptyRecycleBin(); db->emptyRecycleBin();
// The database must be unmodified in this test after emptying the recycle bin. // 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() void TestDatabase::testEmptyRecycleBinWithHierarchicalData()
@ -85,8 +91,9 @@ void TestDatabase::testEmptyRecycleBinWithHierarchicalData()
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinWithData.kdbx"); QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinWithData.kdbx");
auto key = QSharedPointer<CompositeKey>::create(); auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("123")); key->addKey(QSharedPointer<PasswordKey>::create("123"));
QScopedPointer<Database> db(Database::openDatabaseFile(filename, key)); auto db = QSharedPointer<Database>::create();
QVERIFY(db); QVERIFY(db->open(filename, key, nullptr, false));
db->setReadOnly(false);
QFile originalFile(filename); QFile originalFile(filename);
qint64 initialSize = originalFile.size(); qint64 initialSize = originalFile.size();
@ -97,6 +104,8 @@ void TestDatabase::testEmptyRecycleBinWithHierarchicalData()
QVERIFY(db->metadata()->recycleBin()->children().empty()); QVERIFY(db->metadata()->recycleBin()->children().empty());
QTemporaryFile afterCleanup; QTemporaryFile afterCleanup;
afterCleanup.open();
KeePass2Writer writer; KeePass2Writer writer;
writer.writeDatabase(&afterCleanup, db.data()); writer.writeDatabase(&afterCleanup, db.data());
QVERIFY(afterCleanup.size() < initialSize); QVERIFY(afterCleanup.size() < initialSize);

View file

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

View file

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

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