Refactor Database and Database widgets (#2491)

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

The core changes are:

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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