mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
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:
parent
917c4cc18b
commit
d612cad09a
@ -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();
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -69,7 +69,7 @@ bool BrowserService::isDatabaseOpened() const
|
||||
return false;
|
||||
}
|
||||
|
||||
return dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode;
|
||||
return dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode || dbWidget->currentMode() == DatabaseWidget::Mode::EditMode;
|
||||
}
|
||||
|
||||
bool BrowserService::openDatabase(bool triggerUnlock)
|
||||
@ -83,7 +83,7 @@ bool BrowserService::openDatabase(bool triggerUnlock)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
|
||||
if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode || dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -106,14 +106,14 @@ void BrowserService::lockDatabase()
|
||||
return;
|
||||
}
|
||||
|
||||
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
|
||||
if (dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode || dbWidget->currentMode() == DatabaseWidget::Mode::EditMode) {
|
||||
dbWidget->lock();
|
||||
}
|
||||
}
|
||||
|
||||
QString BrowserService::getDatabaseRootUuid()
|
||||
{
|
||||
Database* db = getDatabase();
|
||||
auto db = getDatabase();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
@ -128,7 +128,7 @@ QString BrowserService::getDatabaseRootUuid()
|
||||
|
||||
QString BrowserService::getDatabaseRecycleBinUuid()
|
||||
{
|
||||
Database* db = getDatabase();
|
||||
auto db = getDatabase();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
@ -150,7 +150,7 @@ QString BrowserService::storeKey(const QString& key)
|
||||
return id;
|
||||
}
|
||||
|
||||
Database* db = getDatabase();
|
||||
auto db = getDatabase();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
@ -194,7 +194,7 @@ QString BrowserService::storeKey(const QString& key)
|
||||
|
||||
QString BrowserService::getKey(const QString& id)
|
||||
{
|
||||
Database* db = getDatabase();
|
||||
auto db = getDatabase();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
@ -268,13 +268,10 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
||||
return result;
|
||||
}
|
||||
|
||||
void BrowserService::addEntry(const QString& id,
|
||||
const QString& login,
|
||||
const QString& password,
|
||||
const QString& url,
|
||||
const QString& submitUrl,
|
||||
const QString& realm,
|
||||
Database* selectedDb)
|
||||
void BrowserService::addEntry(const QString& id, const QString& login,
|
||||
const QString& password, const QString& url,
|
||||
const QString& submitUrl, const QString& realm,
|
||||
QSharedPointer<Database> selectedDb)
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this,
|
||||
@ -286,10 +283,10 @@ void BrowserService::addEntry(const QString& id,
|
||||
Q_ARG(QString, url),
|
||||
Q_ARG(QString, submitUrl),
|
||||
Q_ARG(QString, realm),
|
||||
Q_ARG(Database*, selectedDb));
|
||||
Q_ARG(QSharedPointer<Database>, selectedDb));
|
||||
}
|
||||
|
||||
Database* db = selectedDb ? selectedDb : selectedDatabase();
|
||||
auto db = selectedDb ? selectedDb : selectedDatabase();
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
@ -299,7 +296,7 @@ void BrowserService::addEntry(const QString& id,
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = new Entry();
|
||||
auto* entry = new Entry();
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setTitle(QUrl(url).host());
|
||||
entry->setUrl(url);
|
||||
@ -341,12 +338,12 @@ void BrowserService::updateEntry(const QString& id,
|
||||
Q_ARG(QString, submitUrl));
|
||||
}
|
||||
|
||||
Database* db = selectedDatabase();
|
||||
auto db = selectedDatabase();
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = db->resolveEntry(QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1())));
|
||||
Entry* entry = db->rootGroup()->findEntryByUuid(QUuid::fromRfc4122(QByteArray::fromHex(uuid.toLatin1())));
|
||||
if (!entry) {
|
||||
// If entry is not found for update, add a new one to the selected database
|
||||
addEntry(id, login, password, url, submitUrl, "", db);
|
||||
@ -382,10 +379,10 @@ void BrowserService::updateEntry(const QString& id,
|
||||
}
|
||||
}
|
||||
|
||||
QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostname, const QString& url)
|
||||
QList<Entry*> BrowserService::searchEntries(QSharedPointer<Database> db, const QString& hostname, const QString& url)
|
||||
{
|
||||
QList<Entry*> entries;
|
||||
Group* rootGroup = db->rootGroup();
|
||||
auto* rootGroup = db->rootGroup();
|
||||
if (!rootGroup) {
|
||||
return entries;
|
||||
}
|
||||
@ -415,12 +412,12 @@ QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostnam
|
||||
QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPairList& keyList)
|
||||
{
|
||||
// Get the list of databases to search
|
||||
QList<Database*> databases;
|
||||
QList<QSharedPointer<Database>> databases;
|
||||
if (browserSettings()->searchInAllDatabases()) {
|
||||
const int count = m_dbTabWidget->count();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
|
||||
if (Database* db = dbWidget->database()) {
|
||||
if (auto* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
|
||||
if (const auto& db = dbWidget->database()) {
|
||||
// Check if database is connected with KeePassXC-Browser
|
||||
for (const StringPair& keyPair : keyList) {
|
||||
QString key = db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
|
||||
@ -431,7 +428,7 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (Database* db = getDatabase()) {
|
||||
} else if (const auto& db = getDatabase()) {
|
||||
databases << db;
|
||||
}
|
||||
|
||||
@ -439,7 +436,7 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
|
||||
QString hostname = QUrl(url).host();
|
||||
QList<Entry*> entries;
|
||||
do {
|
||||
for (Database* db : databases) {
|
||||
for (const auto& db : databases) {
|
||||
entries << searchEntries(db, hostname, url);
|
||||
}
|
||||
} while (entries.isEmpty() && removeFirstDomain(hostname));
|
||||
@ -447,9 +444,9 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
|
||||
return entries;
|
||||
}
|
||||
|
||||
void BrowserService::convertAttributesToCustomData(Database *currentDb)
|
||||
void BrowserService::convertAttributesToCustomData(QSharedPointer<Database> currentDb)
|
||||
{
|
||||
Database* db = currentDb ? currentDb : getDatabase();
|
||||
auto db = currentDb ? currentDb : getDatabase();
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
@ -651,9 +648,9 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
Group* BrowserService::findCreateAddEntryGroup(Database* selectedDb)
|
||||
Group* BrowserService::findCreateAddEntryGroup(QSharedPointer<Database> selectedDb)
|
||||
{
|
||||
Database* db = selectedDb ? selectedDb : getDatabase();
|
||||
auto db = selectedDb ? selectedDb : getDatabase();
|
||||
if (!db) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -668,11 +665,11 @@ Group* BrowserService::findCreateAddEntryGroup(Database* selectedDb)
|
||||
|
||||
for (const Group* g : rootGroup->groupsRecursive(true)) {
|
||||
if (g->name() == groupName) {
|
||||
return db->resolveGroup(g->uuid());
|
||||
return db->rootGroup()->findGroupByUuid(g->uuid());
|
||||
}
|
||||
}
|
||||
|
||||
Group* group = new Group();
|
||||
auto* group = new Group();
|
||||
group->setUuid(QUuid::createUuid());
|
||||
group->setName(groupName);
|
||||
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
||||
@ -775,26 +772,26 @@ QString BrowserService::baseDomain(const QString& url) const
|
||||
return baseDomain;
|
||||
}
|
||||
|
||||
Database* BrowserService::getDatabase()
|
||||
QSharedPointer<Database> BrowserService::getDatabase()
|
||||
{
|
||||
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
|
||||
if (Database* db = dbWidget->database()) {
|
||||
if (const auto& db = dbWidget->database()) {
|
||||
return db;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
Database* BrowserService::selectedDatabase()
|
||||
QSharedPointer<Database> BrowserService::selectedDatabase()
|
||||
{
|
||||
QList<DatabaseWidget*> databaseWidgets;
|
||||
for (int i = 0;; ++i) {
|
||||
const auto dbStruct = m_dbTabWidget->indexDatabaseManagerStruct(i);
|
||||
for (int i = 0; ; ++i) {
|
||||
auto* dbWidget = m_dbTabWidget->databaseWidgetFromIndex(i);
|
||||
// Add only open databases
|
||||
if (dbStruct.dbWidget && dbStruct.dbWidget->dbHasKey() &&
|
||||
(dbStruct.dbWidget->currentMode() == DatabaseWidget::ViewMode ||
|
||||
dbStruct.dbWidget->currentMode() == DatabaseWidget::EditMode)) {
|
||||
databaseWidgets.push_back(dbStruct.dbWidget);
|
||||
if (dbWidget && dbWidget->database()->hasKey() &&
|
||||
(dbWidget->currentMode() == DatabaseWidget::Mode::ViewMode ||
|
||||
dbWidget->currentMode() == DatabaseWidget::Mode::EditMode)) {
|
||||
databaseWidgets.push_back(dbWidget);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -813,7 +810,7 @@ Database* BrowserService::selectedDatabase()
|
||||
return databaseWidgets[index]->database();
|
||||
}
|
||||
} else {
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -836,7 +833,7 @@ bool BrowserService::moveSettingsToCustomData(Entry* entry, const QString& name)
|
||||
return false;
|
||||
}
|
||||
|
||||
int BrowserService::moveKeysToCustomData(Entry* entry, Database* db) const
|
||||
int BrowserService::moveKeysToCustomData(Entry* entry, QSharedPointer<Database> db) const
|
||||
{
|
||||
int keyCounter = 0;
|
||||
for (const auto& key : entry->attributes()->keys()) {
|
||||
@ -857,7 +854,7 @@ int BrowserService::moveKeysToCustomData(Entry* entry, Database* db) const
|
||||
|
||||
bool BrowserService::checkLegacySettings()
|
||||
{
|
||||
Database* db = getDatabase();
|
||||
auto db = getDatabase();
|
||||
if (!db) {
|
||||
return false;
|
||||
}
|
||||
@ -882,12 +879,8 @@ bool BrowserService::checkLegacySettings()
|
||||
"Do you want to upgrade the settings to the latest standard?\n"
|
||||
"This is necessary to maintain compatibility with the browser plugin."),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (dialogResult == QMessageBox::No) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return dialogResult == QMessageBox::Yes;
|
||||
}
|
||||
|
||||
void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
|
||||
@ -916,7 +909,7 @@ void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
|
||||
{
|
||||
if (dbWidget) {
|
||||
auto currentMode = dbWidget->currentMode();
|
||||
if (currentMode == DatabaseWidget::ViewMode || currentMode == DatabaseWidget::EditMode) {
|
||||
if (currentMode == DatabaseWidget::Mode::ViewMode || currentMode == DatabaseWidget::Mode::EditMode) {
|
||||
emit databaseUnlocked();
|
||||
} else {
|
||||
emit databaseLocked();
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -68,27 +68,29 @@ int Merge::execute(const QStringList& arguments)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QScopedPointer<Database> db1(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
|
||||
auto db1 = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR);
|
||||
if (!db1) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QScopedPointer<Database> db2;
|
||||
QSharedPointer<Database> db2;
|
||||
if (!parser.isSet("same-credentials")) {
|
||||
db2.reset(Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom), Utils::STDOUT, Utils::STDERR));
|
||||
db2 = Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom), Utils::STDOUT, Utils::STDERR);
|
||||
} else {
|
||||
db2.reset(Database::openDatabaseFile(args.at(1), db1->key()));
|
||||
}
|
||||
if (!db2) {
|
||||
return EXIT_FAILURE;
|
||||
db2 = QSharedPointer<Database>::create();
|
||||
QString errorMessage;
|
||||
if (!db2->open(args.at(1), db1->key(), &errorMessage, false)) {
|
||||
err << QObject::tr("Error reading merge file:\n%1").arg(errorMessage);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
Merger merger(db2.data(), db1.data());
|
||||
bool databaseChanged = merger.merge();
|
||||
|
||||
if (databaseChanged) {
|
||||
QString errorMessage = db1->saveToFile(args.at(0));
|
||||
if (!errorMessage.isEmpty()) {
|
||||
QString errorMessage;
|
||||
if (!db1->save(args.at(0), &errorMessage, true, false)) {
|
||||
err << QObject::tr("Unable to save database to file : %1").arg(errorMessage) << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -18,60 +18,325 @@
|
||||
|
||||
#include "Database.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTextStream>
|
||||
#include <QTimer>
|
||||
#include <QXmlStreamReader>
|
||||
#include <utility>
|
||||
|
||||
#include "cli/Utils.h"
|
||||
#include "cli/TextStream.h"
|
||||
#include "core/Clock.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Merger.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "keys/FileKey.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
QHash<QUuid, Database*> Database::m_uuidMap;
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTimer>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QFileInfo>
|
||||
|
||||
QHash<QUuid, QPointer<Database>> Database::s_uuidMap;
|
||||
QHash<QString, QPointer<Database>> Database::s_filePathMap;
|
||||
|
||||
Database::Database()
|
||||
: m_metadata(new Metadata(this))
|
||||
, m_data()
|
||||
, m_rootGroup(nullptr)
|
||||
, m_timer(new QTimer(this))
|
||||
, m_emitModified(false)
|
||||
, m_uuid(QUuid::createUuid())
|
||||
{
|
||||
m_data.cipher = KeePass2::CIPHER_AES256;
|
||||
m_data.compressionAlgo = CompressionGZip;
|
||||
|
||||
// instantiate default AES-KDF with legacy KDBX3 flag set
|
||||
// KDBX4+ will re-initialize the KDF using parameters read from the KDBX file
|
||||
m_data.kdf = QSharedPointer<AesKdf>::create(true);
|
||||
m_data.kdf->randomizeSeed();
|
||||
m_data.hasKey = false;
|
||||
|
||||
setRootGroup(new Group());
|
||||
rootGroup()->setUuid(QUuid::createUuid());
|
||||
rootGroup()->setName(tr("Root", "Root group name"));
|
||||
m_timer->setSingleShot(true);
|
||||
|
||||
m_uuidMap.insert(m_uuid, this);
|
||||
s_uuidMap.insert(m_uuid, this);
|
||||
|
||||
connect(m_metadata, SIGNAL(modified()), this, SIGNAL(modifiedImmediate()));
|
||||
connect(m_metadata, SIGNAL(nameTextChanged()), this, SIGNAL(nameTextChanged()));
|
||||
connect(this, SIGNAL(modifiedImmediate()), this, SLOT(startModifiedTimer()));
|
||||
connect(m_timer, SIGNAL(timeout()), SIGNAL(modified()));
|
||||
connect(m_metadata, SIGNAL(metadataModified()), this, SLOT(markAsModified()));
|
||||
connect(m_timer, SIGNAL(timeout()), SIGNAL(databaseModified()));
|
||||
|
||||
m_modified = false;
|
||||
m_emitModified = true;
|
||||
}
|
||||
|
||||
Database::Database(const QString& filePath)
|
||||
: Database()
|
||||
{
|
||||
setFilePath(filePath);
|
||||
}
|
||||
|
||||
Database::~Database()
|
||||
{
|
||||
m_uuidMap.remove(m_uuid);
|
||||
s_uuidMap.remove(m_uuid);
|
||||
|
||||
if (m_modified) {
|
||||
emit databaseDiscarded();
|
||||
}
|
||||
}
|
||||
|
||||
QUuid Database::uuid() const
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the database from a previously specified file.
|
||||
* Unless `readOnly` is set to false, the database will be opened in
|
||||
* read-write mode and fall back to read-only if that is not possible.
|
||||
*
|
||||
* @param key composite key for unlocking the database
|
||||
* @param readOnly open in read-only mode
|
||||
* @param error error message in case of failure
|
||||
* @return true on success
|
||||
*/
|
||||
bool Database::open(QSharedPointer<const CompositeKey> key, QString* error, bool readOnly)
|
||||
{
|
||||
Q_ASSERT(!m_data.filePath.isEmpty());
|
||||
if (m_data.filePath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return open(m_data.filePath, std::move(key), error, readOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the database from a file.
|
||||
* Unless `readOnly` is set to false, the database will be opened in
|
||||
* read-write mode and fall back to read-only if that is not possible.
|
||||
*
|
||||
* @param filePath path to the file
|
||||
* @param key composite key for unlocking the database
|
||||
* @param readOnly open in read-only mode
|
||||
* @param error error message in case of failure
|
||||
* @return true on success
|
||||
*/
|
||||
bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey> key, QString* error, bool readOnly)
|
||||
{
|
||||
if (isInitialized() && m_modified) {
|
||||
emit databaseDiscarded();
|
||||
}
|
||||
|
||||
setEmitModified(false);
|
||||
|
||||
QFile dbFile(filePath);
|
||||
if (!dbFile.exists()) {
|
||||
if (error) {
|
||||
*error = tr("File %1 does not exist.").arg(filePath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!readOnly && !dbFile.open(QIODevice::ReadWrite)) {
|
||||
readOnly = true;
|
||||
}
|
||||
|
||||
if (!dbFile.isOpen() && !dbFile.open(QIODevice::ReadOnly)) {
|
||||
if (error) {
|
||||
*error = tr("Unable to open file %1.").arg(filePath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
KeePass2Reader reader;
|
||||
bool ok = reader.readDatabase(&dbFile, std::move(key), this);
|
||||
if (reader.hasError()) {
|
||||
if (error) {
|
||||
*error = tr("Error while reading the database: %1").arg(reader.errorString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setReadOnly(readOnly);
|
||||
setFilePath(filePath);
|
||||
dbFile.close();
|
||||
|
||||
setInitialized(ok);
|
||||
markAsClean();
|
||||
|
||||
setEmitModified(true);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the database back to the file is has been opened from.
|
||||
* This method behaves the same as its overloads.
|
||||
*
|
||||
* @see Database::save(const QString&, bool, bool, QString*)
|
||||
*
|
||||
* @param atomic Use atomic file transactions
|
||||
* @param backup Backup the existing database file, if exists
|
||||
* @param error error message in case of failure
|
||||
* @return true on success
|
||||
*/
|
||||
bool Database::save(QString* error, bool atomic, bool backup)
|
||||
{
|
||||
Q_ASSERT(!m_data.filePath.isEmpty());
|
||||
if (m_data.filePath.isEmpty()) {
|
||||
if (error) {
|
||||
*error = tr("Could not save, database has no file name.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return save(m_data.filePath, error, atomic, backup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the database to a file.
|
||||
*
|
||||
* This function uses QTemporaryFile instead of QSaveFile due to a bug
|
||||
* in Qt (https://bugreports.qt.io/browse/QTBUG-57299) that may prevent
|
||||
* the QSaveFile from renaming itself when using Dropbox, Drive, or OneDrive.
|
||||
*
|
||||
* The risk in using QTemporaryFile is that the rename function is not atomic
|
||||
* and may result in loss of data if there is a crash or power loss at the
|
||||
* wrong moment.
|
||||
*
|
||||
* @param filePath Absolute path of the file to save
|
||||
* @param atomic Use atomic file transactions
|
||||
* @param backup Backup the existing database file, if exists
|
||||
* @param error error message in case of failure
|
||||
* @return true on success
|
||||
*/
|
||||
bool Database::save(const QString& filePath, QString* error, bool atomic, bool backup)
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
if (m_data.isReadOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (atomic) {
|
||||
QSaveFile saveFile(filePath);
|
||||
if (saveFile.open(QIODevice::WriteOnly)) {
|
||||
// write the database to the file
|
||||
if (!writeDatabase(&saveFile, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (backup) {
|
||||
backupDatabase(filePath);
|
||||
}
|
||||
|
||||
if (saveFile.commit()) {
|
||||
// successfully saved database file
|
||||
setFilePath(filePath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
*error = saveFile.errorString();
|
||||
}
|
||||
} else {
|
||||
QTemporaryFile tempFile;
|
||||
if (tempFile.open()) {
|
||||
// write the database to the file
|
||||
if (!writeDatabase(&tempFile, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tempFile.close(); // flush to disk
|
||||
|
||||
if (backup) {
|
||||
backupDatabase(filePath);
|
||||
}
|
||||
|
||||
// Delete the original db and move the temp file in place
|
||||
QFile::remove(filePath);
|
||||
#ifdef Q_OS_LINUX
|
||||
// workaround to make this workaround work, see: https://bugreports.qt.io/browse/QTBUG-64008
|
||||
if (tempFile.copy(filePath)) {
|
||||
// successfully saved database file
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
if (tempFile.rename(filePath)) {
|
||||
// successfully saved database file
|
||||
tempFile.setAutoRemove(false);
|
||||
setFilePath(filePath);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (error) {
|
||||
*error = tempFile.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
// Saving failed
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Database::writeDatabase(QIODevice* device, QString* error)
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
if (m_data.isReadOnly) {
|
||||
if (error) {
|
||||
*error = tr("File cannot be written as it is opened in read-only mode.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
KeePass2Writer writer;
|
||||
setEmitModified(false);
|
||||
writer.writeDatabase(device, this);
|
||||
setEmitModified(true);
|
||||
|
||||
if (writer.hasError()) {
|
||||
// the writer failed
|
||||
if (error) {
|
||||
*error = writer.errorString();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
markAsClean();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the old backup and replace it with a new one
|
||||
* backups are named <filename>.old.kdbx
|
||||
*
|
||||
* @param filePath Path to the file to backup
|
||||
* @return true on success
|
||||
*/
|
||||
bool Database::backupDatabase(const QString& filePath)
|
||||
{
|
||||
QString backupFilePath = filePath;
|
||||
auto re = QRegularExpression("\\.kdbx$|(?<!\\.kdbx)$", QRegularExpression::CaseInsensitiveOption);
|
||||
backupFilePath.replace(re, ".old.kdbx");
|
||||
QFile::remove(backupFilePath);
|
||||
return QFile::copy(filePath, backupFilePath);
|
||||
}
|
||||
|
||||
bool Database::isReadOnly() const
|
||||
{
|
||||
return m_data.isReadOnly;
|
||||
}
|
||||
|
||||
void Database::setReadOnly(bool readOnly)
|
||||
{
|
||||
m_data.isReadOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if database has been fully decrypted and populated, i.e. if
|
||||
* it's not just an empty default instance.
|
||||
*
|
||||
* @return true if database has been fully initialized
|
||||
*/
|
||||
bool Database::isInitialized() const
|
||||
{
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param initialized true to mark database as initialized
|
||||
*/
|
||||
void Database::setInitialized(bool initialized)
|
||||
{
|
||||
m_initialized = initialized;
|
||||
}
|
||||
|
||||
Group* Database::rootGroup()
|
||||
@ -84,10 +349,21 @@ const Group* Database::rootGroup() const
|
||||
return m_rootGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets group as the root group and takes ownership of it.
|
||||
* Warning: Be careful when calling this method as it doesn't
|
||||
* emit any notifications so e.g. models aren't updated.
|
||||
* The caller is responsible for cleaning up the previous
|
||||
root group.
|
||||
*/
|
||||
void Database::setRootGroup(Group* group)
|
||||
{
|
||||
Q_ASSERT(group);
|
||||
|
||||
if (isInitialized() && m_modified) {
|
||||
emit databaseDiscarded();
|
||||
}
|
||||
|
||||
m_rootGroup = group;
|
||||
m_rootGroup->setParent(this);
|
||||
}
|
||||
@ -104,115 +380,23 @@ const Metadata* Database::metadata() const
|
||||
|
||||
QString Database::filePath() const
|
||||
{
|
||||
return m_filePath;
|
||||
return m_data.filePath;
|
||||
}
|
||||
|
||||
void Database::setFilePath(const QString& filePath)
|
||||
{
|
||||
m_filePath = filePath;
|
||||
}
|
||||
|
||||
Entry* Database::resolveEntry(const QUuid& uuid)
|
||||
{
|
||||
return findEntryRecursive(uuid, m_rootGroup);
|
||||
}
|
||||
|
||||
Entry* Database::resolveEntry(const QString& text, EntryReferenceType referenceType)
|
||||
{
|
||||
return findEntryRecursive(text, referenceType, m_rootGroup);
|
||||
}
|
||||
|
||||
Entry* Database::findEntryRecursive(const QUuid& uuid, Group* group)
|
||||
{
|
||||
const QList<Entry*> entryList = group->entries();
|
||||
for (Entry* entry : entryList) {
|
||||
if (entry->uuid() == uuid) {
|
||||
return entry;
|
||||
}
|
||||
if (filePath == m_data.filePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QList<Group*> children = group->children();
|
||||
for (Group* child : children) {
|
||||
Entry* result = findEntryRecursive(uuid, child);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
if (s_filePathMap.contains(m_data.filePath)) {
|
||||
s_filePathMap.remove(m_data.filePath);
|
||||
}
|
||||
QString oldPath = m_data.filePath;
|
||||
m_data.filePath = filePath;
|
||||
s_filePathMap.insert(m_data.filePath, this);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Entry* Database::findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group)
|
||||
{
|
||||
Q_ASSERT_X(referenceType != EntryReferenceType::Unknown,
|
||||
"Database::findEntryRecursive",
|
||||
"Can't search entry with \"referenceType\" parameter equal to \"Unknown\"");
|
||||
|
||||
bool found = false;
|
||||
const QList<Entry*> entryList = group->entries();
|
||||
for (Entry* entry : entryList) {
|
||||
switch (referenceType) {
|
||||
case EntryReferenceType::Unknown:
|
||||
return nullptr;
|
||||
case EntryReferenceType::Title:
|
||||
found = entry->title() == text;
|
||||
break;
|
||||
case EntryReferenceType::UserName:
|
||||
found = entry->username() == text;
|
||||
break;
|
||||
case EntryReferenceType::Password:
|
||||
found = entry->password() == text;
|
||||
break;
|
||||
case EntryReferenceType::Url:
|
||||
found = entry->url() == text;
|
||||
break;
|
||||
case EntryReferenceType::Notes:
|
||||
found = entry->notes() == text;
|
||||
break;
|
||||
case EntryReferenceType::QUuid:
|
||||
found = entry->uuid() == QUuid::fromRfc4122(QByteArray::fromHex(text.toLatin1()));
|
||||
break;
|
||||
case EntryReferenceType::CustomAttributes:
|
||||
found = entry->attributes()->containsValue(text);
|
||||
break;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
const QList<Group*> children = group->children();
|
||||
for (Group* child : children) {
|
||||
Entry* result = findEntryRecursive(text, referenceType, child);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Group* Database::resolveGroup(const QUuid& uuid)
|
||||
{
|
||||
return findGroupRecursive(uuid, m_rootGroup);
|
||||
}
|
||||
|
||||
Group* Database::findGroupRecursive(const QUuid& uuid, Group* group)
|
||||
{
|
||||
if (group->uuid() == uuid) {
|
||||
return group;
|
||||
}
|
||||
|
||||
const QList<Group*> children = group->children();
|
||||
for (Group* child : children) {
|
||||
Group* result = findGroupRecursive(uuid, child);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
emit filePathChanged(oldPath, filePath);
|
||||
}
|
||||
|
||||
QList<DeletedObject> Database::deletedObjects()
|
||||
@ -273,9 +457,9 @@ const QUuid& Database::cipher() const
|
||||
return m_data.cipher;
|
||||
}
|
||||
|
||||
Database::CompressionAlgorithm Database::compressionAlgo() const
|
||||
Database::CompressionAlgorithm Database::compressionAlgorithm() const
|
||||
{
|
||||
return m_data.compressionAlgo;
|
||||
return m_data.compressionAlgorithm;
|
||||
}
|
||||
|
||||
QByteArray Database::transformedMasterKey() const
|
||||
@ -301,11 +485,11 @@ void Database::setCipher(const QUuid& cipher)
|
||||
m_data.cipher = cipher;
|
||||
}
|
||||
|
||||
void Database::setCompressionAlgo(Database::CompressionAlgorithm algo)
|
||||
void Database::setCompressionAlgorithm(Database::CompressionAlgorithm algo)
|
||||
{
|
||||
Q_ASSERT(static_cast<quint32>(algo) <= CompressionAlgorithmMax);
|
||||
|
||||
m_data.compressionAlgo = algo;
|
||||
m_data.compressionAlgorithm = algo;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -318,6 +502,8 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo)
|
||||
*/
|
||||
bool Database::setKey(const QSharedPointer<const CompositeKey>& key, bool updateChangedTime, bool updateTransformSalt)
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
|
||||
if (!key) {
|
||||
m_data.key.reset();
|
||||
m_data.transformedMasterKey = {};
|
||||
@ -344,7 +530,7 @@ bool Database::setKey(const QSharedPointer<const CompositeKey>& key, bool update
|
||||
}
|
||||
|
||||
if (oldTransformedMasterKey != m_data.transformedMasterKey) {
|
||||
emit modifiedImmediate();
|
||||
markAsModified();
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -388,11 +574,13 @@ const QVariantMap& Database::publicCustomData() const
|
||||
|
||||
void Database::setPublicCustomData(const QVariantMap& customData)
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
m_data.publicCustomData = customData;
|
||||
}
|
||||
|
||||
void Database::createRecycleBin()
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
Group* recycleBin = Group::createRecycleBin();
|
||||
recycleBin->setParent(rootGroup());
|
||||
m_metadata->setRecycleBin(recycleBin);
|
||||
@ -400,6 +588,7 @@ void Database::createRecycleBin()
|
||||
|
||||
void Database::recycleEntry(Entry* entry)
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
if (m_metadata->recycleBinEnabled()) {
|
||||
if (!m_metadata->recycleBin()) {
|
||||
createRecycleBin();
|
||||
@ -412,6 +601,7 @@ void Database::recycleEntry(Entry* entry)
|
||||
|
||||
void Database::recycleGroup(Group* group)
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
if (m_metadata->recycleBinEnabled()) {
|
||||
if (!m_metadata->recycleBin()) {
|
||||
createRecycleBin();
|
||||
@ -424,6 +614,7 @@ void Database::recycleGroup(Group* group)
|
||||
|
||||
void Database::emptyRecycleBin()
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
if (m_metadata->recycleBinEnabled() && m_metadata->recycleBin()) {
|
||||
// destroying direct entries of the recycle bin
|
||||
QList<Entry*> subEntries = m_metadata->recycleBin()->entries();
|
||||
@ -447,23 +638,54 @@ void Database::setEmitModified(bool value)
|
||||
m_emitModified = value;
|
||||
}
|
||||
|
||||
bool Database::isModified() const
|
||||
{
|
||||
return m_modified;
|
||||
}
|
||||
|
||||
void Database::markAsModified()
|
||||
{
|
||||
emit modified();
|
||||
if (isReadOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_modified = true;
|
||||
if (m_emitModified) {
|
||||
startModifiedTimer();
|
||||
}
|
||||
}
|
||||
|
||||
const QUuid& Database::uuid()
|
||||
void Database::markAsClean()
|
||||
{
|
||||
return m_uuid;
|
||||
bool emitSignal = m_modified;
|
||||
m_modified = false;
|
||||
if (emitSignal) {
|
||||
emit databaseSaved();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uuid UUID of the database
|
||||
* @return pointer to the database or nullptr if no such database exists
|
||||
*/
|
||||
Database* Database::databaseByUuid(const QUuid& uuid)
|
||||
{
|
||||
return m_uuidMap.value(uuid, 0);
|
||||
return s_uuidMap.value(uuid, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param filePath file path of the database
|
||||
* @return pointer to the database or nullptr if the database has not been opened
|
||||
*/
|
||||
Database* Database::databaseByFilePath(const QString& filePath)
|
||||
{
|
||||
return s_filePathMap.value(filePath, nullptr);
|
||||
}
|
||||
|
||||
void Database::startModifiedTimer()
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
|
||||
if (!m_emitModified) {
|
||||
return;
|
||||
}
|
||||
@ -479,34 +701,12 @@ QSharedPointer<const CompositeKey> Database::key() const
|
||||
return m_data.key;
|
||||
}
|
||||
|
||||
Database* Database::openDatabaseFile(const QString& fileName, QSharedPointer<const CompositeKey> key)
|
||||
{
|
||||
|
||||
QFile dbFile(fileName);
|
||||
if (!dbFile.exists()) {
|
||||
qCritical("Database file %s does not exist.", qPrintable(fileName));
|
||||
return nullptr;
|
||||
}
|
||||
if (!dbFile.open(QIODevice::ReadOnly)) {
|
||||
qCritical("Unable to open database file %s.", qPrintable(fileName));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KeePass2Reader reader;
|
||||
Database* db = reader.readDatabase(&dbFile, std::move(key));
|
||||
if (reader.hasError()) {
|
||||
qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
Database* Database::unlockFromStdin(const QString& databaseFilename, const QString& keyFilename, FILE* outputDescriptor, FILE* errorDescriptor)
|
||||
QSharedPointer<Database> Database::unlockFromStdin(const QString& databaseFilename, const QString& keyFilename,
|
||||
FILE* outputDescriptor, FILE* errorDescriptor)
|
||||
{
|
||||
auto compositeKey = QSharedPointer<CompositeKey>::create();
|
||||
QTextStream out(outputDescriptor);
|
||||
QTextStream err(errorDescriptor);
|
||||
TextStream out(outputDescriptor);
|
||||
TextStream err(errorDescriptor);
|
||||
|
||||
out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename);
|
||||
out.flush();
|
||||
@ -522,7 +722,7 @@ Database* Database::unlockFromStdin(const QString& databaseFilename, const QStri
|
||||
// LCOV_EXCL_START
|
||||
if (!fileKey->load(keyFilename, &errorMessage)) {
|
||||
err << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage)<< endl;
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (fileKey->type() != FileKey::Hashed) {
|
||||
@ -535,112 +735,9 @@ Database* Database::unlockFromStdin(const QString& databaseFilename, const QStri
|
||||
compositeKey->addKey(fileKey);
|
||||
}
|
||||
|
||||
return Database::openDatabaseFile(databaseFilename, compositeKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the database to a file.
|
||||
*
|
||||
* This function uses QTemporaryFile instead of QSaveFile due to a bug
|
||||
* in Qt (https://bugreports.qt.io/browse/QTBUG-57299) that may prevent
|
||||
* the QSaveFile from renaming itself when using Dropbox, Drive, or OneDrive.
|
||||
*
|
||||
* The risk in using QTemporaryFile is that the rename function is not atomic
|
||||
* and may result in loss of data if there is a crash or power loss at the
|
||||
* wrong moment.
|
||||
*
|
||||
* @param filePath Absolute path of the file to save
|
||||
* @param atomic Use atomic file transactions
|
||||
* @param backup Backup the existing database file, if exists
|
||||
* @return error string, if any
|
||||
*/
|
||||
QString Database::saveToFile(const QString& filePath, bool atomic, bool backup)
|
||||
{
|
||||
QString error;
|
||||
if (atomic) {
|
||||
QSaveFile saveFile(filePath);
|
||||
if (saveFile.open(QIODevice::WriteOnly)) {
|
||||
// write the database to the file
|
||||
error = writeDatabase(&saveFile);
|
||||
if (!error.isEmpty()) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (backup) {
|
||||
backupDatabase(filePath);
|
||||
}
|
||||
|
||||
if (saveFile.commit()) {
|
||||
// successfully saved database file
|
||||
return {};
|
||||
}
|
||||
}
|
||||
error = saveFile.errorString();
|
||||
} else {
|
||||
QTemporaryFile tempFile;
|
||||
if (tempFile.open()) {
|
||||
// write the database to the file
|
||||
error = writeDatabase(&tempFile);
|
||||
if (!error.isEmpty()) {
|
||||
return error;
|
||||
}
|
||||
|
||||
tempFile.close(); // flush to disk
|
||||
|
||||
if (backup) {
|
||||
backupDatabase(filePath);
|
||||
}
|
||||
|
||||
// Delete the original db and move the temp file in place
|
||||
QFile::remove(filePath);
|
||||
#ifdef Q_OS_LINUX
|
||||
// workaround to make this workaround work, see: https://bugreports.qt.io/browse/QTBUG-64008
|
||||
if (tempFile.copy(filePath)) {
|
||||
// successfully saved database file
|
||||
return {};
|
||||
}
|
||||
#else
|
||||
if (tempFile.rename(filePath)) {
|
||||
// successfully saved database file
|
||||
tempFile.setAutoRemove(false);
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
error = tempFile.errorString();
|
||||
}
|
||||
// Saving failed
|
||||
return error;
|
||||
}
|
||||
|
||||
QString Database::writeDatabase(QIODevice* device)
|
||||
{
|
||||
KeePass2Writer writer;
|
||||
setEmitModified(false);
|
||||
writer.writeDatabase(device, this);
|
||||
setEmitModified(true);
|
||||
|
||||
if (writer.hasError()) {
|
||||
// the writer failed
|
||||
return writer.errorString();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the old backup and replace it with a new one
|
||||
* backups are named <filename>.old.kdbx
|
||||
*
|
||||
* @param filePath Path to the file to backup
|
||||
* @return
|
||||
*/
|
||||
bool Database::backupDatabase(const QString& filePath)
|
||||
{
|
||||
QString backupFilePath = filePath;
|
||||
auto re = QRegularExpression("\\.kdbx$|(?<!\\.kdbx)$", QRegularExpression::CaseInsensitiveOption);
|
||||
backupFilePath.replace(re, ".old.kdbx");
|
||||
QFile::remove(backupFilePath);
|
||||
return QFile::copy(filePath, backupFilePath);
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
db->open(databaseFilename, compositeKey, nullptr, false);
|
||||
return db;
|
||||
}
|
||||
|
||||
QSharedPointer<Kdf> Database::kdf() const
|
||||
@ -650,11 +747,14 @@ QSharedPointer<Kdf> Database::kdf() const
|
||||
|
||||
void Database::setKdf(QSharedPointer<Kdf> kdf)
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
m_data.kdf = std::move(kdf);
|
||||
}
|
||||
|
||||
bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
|
||||
kdf->randomizeSeed();
|
||||
QByteArray transformedMasterKey;
|
||||
if (!m_data.key) {
|
||||
@ -666,7 +766,7 @@ bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)
|
||||
|
||||
setKdf(kdf);
|
||||
m_data.transformedMasterKey = transformedMasterKey;
|
||||
emit modifiedImmediate();
|
||||
markAsModified();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -22,9 +22,12 @@
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
#include "crypto/kdf/Kdf.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
|
||||
class Entry;
|
||||
enum class EntryReferenceType;
|
||||
@ -57,40 +60,38 @@ public:
|
||||
};
|
||||
static const quint32 CompressionAlgorithmMax = CompressionGZip;
|
||||
|
||||
struct DatabaseData
|
||||
{
|
||||
QUuid cipher;
|
||||
CompressionAlgorithm compressionAlgo;
|
||||
QByteArray transformedMasterKey;
|
||||
QSharedPointer<Kdf> kdf;
|
||||
QSharedPointer<const CompositeKey> key;
|
||||
bool hasKey;
|
||||
QByteArray masterSeed;
|
||||
QByteArray challengeResponseKey;
|
||||
QVariantMap publicCustomData;
|
||||
};
|
||||
|
||||
Database();
|
||||
explicit Database(const QString& filePath);
|
||||
~Database() override;
|
||||
Group* rootGroup();
|
||||
const Group* rootGroup() const;
|
||||
|
||||
/**
|
||||
* Sets group as the root group and takes ownership of it.
|
||||
* Warning: Be careful when calling this method as it doesn't
|
||||
* emit any notifications so e.g. models aren't updated.
|
||||
* The caller is responsible for cleaning up the previous
|
||||
root group.
|
||||
*/
|
||||
void setRootGroup(Group* group);
|
||||
bool open(QSharedPointer<const CompositeKey> key, QString* error = nullptr, bool readOnly = false);
|
||||
bool open(const QString& filePath, QSharedPointer<const CompositeKey> key, QString* error = nullptr, bool readOnly = false);
|
||||
bool save(QString* error = nullptr, bool atomic = true, bool backup = false);
|
||||
bool save(const QString& filePath, QString* error = nullptr, bool atomic = true, bool backup = false);
|
||||
|
||||
bool isInitialized() const;
|
||||
void setInitialized(bool initialized);
|
||||
bool isModified() const;
|
||||
void setEmitModified(bool value);
|
||||
bool isReadOnly() const;
|
||||
void setReadOnly(bool readOnly);
|
||||
|
||||
QUuid uuid() const;
|
||||
QString filePath() const;
|
||||
void setFilePath(const QString& filePath);
|
||||
|
||||
Metadata* metadata();
|
||||
const Metadata* metadata() const;
|
||||
QString filePath() const;
|
||||
void setFilePath(const QString& filePath);
|
||||
Entry* resolveEntry(const QUuid& uuid);
|
||||
Entry* resolveEntry(const QString& text, EntryReferenceType referenceType);
|
||||
Group* resolveGroup(const QUuid& uuid);
|
||||
Group* rootGroup();
|
||||
const Group* rootGroup() const;
|
||||
void setRootGroup(Group* group);
|
||||
QVariantMap& publicCustomData();
|
||||
const QVariantMap& publicCustomData() const;
|
||||
void setPublicCustomData(const QVariantMap& customData);
|
||||
|
||||
void recycleGroup(Group* group);
|
||||
void recycleEntry(Entry* entry);
|
||||
void emptyRecycleBin();
|
||||
QList<DeletedObject> deletedObjects();
|
||||
const QList<DeletedObject>& deletedObjects() const;
|
||||
void addDeletedObject(const DeletedObject& delObj);
|
||||
@ -99,42 +100,33 @@ public:
|
||||
bool containsDeletedObject(const DeletedObject& uuid) const;
|
||||
void setDeletedObjects(const QList<DeletedObject>& delObjs);
|
||||
|
||||
const QUuid& cipher() const;
|
||||
Database::CompressionAlgorithm compressionAlgo() const;
|
||||
QSharedPointer<Kdf> kdf() const;
|
||||
QByteArray transformedMasterKey() const;
|
||||
bool hasKey() const;
|
||||
QSharedPointer<const CompositeKey> key() const;
|
||||
bool setKey(const QSharedPointer<const CompositeKey>& key, bool updateChangedTime = true, bool updateTransformSalt = false);
|
||||
QByteArray challengeResponseKey() const;
|
||||
bool challengeMasterSeed(const QByteArray& masterSeed);
|
||||
|
||||
void setCipher(const QUuid& cipher);
|
||||
void setCompressionAlgo(Database::CompressionAlgorithm algo);
|
||||
void setKdf(QSharedPointer<Kdf> kdf);
|
||||
bool setKey(const QSharedPointer<const CompositeKey>& key, bool updateChangedTime = true, bool updateTransformSalt = false);
|
||||
bool hasKey() const;
|
||||
bool verifyKey(const QSharedPointer<CompositeKey>& key) const;
|
||||
QVariantMap& publicCustomData();
|
||||
const QVariantMap& publicCustomData() const;
|
||||
void setPublicCustomData(const QVariantMap& customData);
|
||||
void recycleEntry(Entry* entry);
|
||||
void recycleGroup(Group* group);
|
||||
void emptyRecycleBin();
|
||||
void setEmitModified(bool value);
|
||||
void markAsModified();
|
||||
QString saveToFile(const QString& filePath, bool atomic = true, bool backup = false);
|
||||
const QUuid& cipher() const;
|
||||
void setCipher(const QUuid& cipher);
|
||||
Database::CompressionAlgorithm compressionAlgorithm() const;
|
||||
void setCompressionAlgorithm(Database::CompressionAlgorithm algo);
|
||||
|
||||
/**
|
||||
* Returns a unique id that is only valid as long as the Database exists.
|
||||
*/
|
||||
const QUuid& uuid();
|
||||
QSharedPointer<Kdf> kdf() const;
|
||||
void setKdf(QSharedPointer<Kdf> kdf);
|
||||
bool changeKdf(const QSharedPointer<Kdf>& kdf);
|
||||
QByteArray transformedMasterKey() const;
|
||||
|
||||
static Database* databaseByUuid(const QUuid& uuid);
|
||||
static Database* openDatabaseFile(const QString& fileName, QSharedPointer<const CompositeKey> key);
|
||||
static Database* unlockFromStdin(const QString& databaseFilename, const QString& keyFilename = {},
|
||||
FILE* outputDescriptor = stdout, FILE* errorDescriptor = stderr);
|
||||
static Database* databaseByFilePath(const QString& filePath);
|
||||
static QSharedPointer<Database> unlockFromStdin(const QString& databaseFilename, const QString& keyFilename = {},
|
||||
FILE* outputDescriptor = stdout, FILE* errorDescriptor = stderr);
|
||||
|
||||
public slots:
|
||||
void markAsModified();
|
||||
void markAsClean();
|
||||
|
||||
signals:
|
||||
void filePathChanged(const QString& oldPath, const QString& newPath);
|
||||
void groupDataChanged(Group* group);
|
||||
void groupAboutToAdd(Group* group, int index);
|
||||
void groupAdded();
|
||||
@ -142,33 +134,51 @@ signals:
|
||||
void groupRemoved();
|
||||
void groupAboutToMove(Group* group, Group* toGroup, int index);
|
||||
void groupMoved();
|
||||
void nameTextChanged();
|
||||
void modified();
|
||||
void modifiedImmediate();
|
||||
void databaseModified();
|
||||
void databaseSaved();
|
||||
void databaseDiscarded();
|
||||
|
||||
private slots:
|
||||
void startModifiedTimer();
|
||||
|
||||
private:
|
||||
Entry* findEntryRecursive(const QUuid& uuid, Group* group);
|
||||
Entry* findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group);
|
||||
Group* findGroupRecursive(const QUuid& uuid, Group* group);
|
||||
struct DatabaseData
|
||||
{
|
||||
QString filePath;
|
||||
bool isReadOnly = false;
|
||||
QUuid cipher = KeePass2::CIPHER_AES256;
|
||||
CompressionAlgorithm compressionAlgorithm = CompressionGZip;
|
||||
QByteArray transformedMasterKey;
|
||||
QSharedPointer<Kdf> kdf = QSharedPointer<AesKdf>::create(true);
|
||||
QSharedPointer<const CompositeKey> key;
|
||||
bool hasKey = false;
|
||||
QByteArray masterSeed;
|
||||
QByteArray challengeResponseKey;
|
||||
QVariantMap publicCustomData;
|
||||
|
||||
DatabaseData()
|
||||
{
|
||||
kdf->randomizeSeed();
|
||||
}
|
||||
};
|
||||
|
||||
void createRecycleBin();
|
||||
QString writeDatabase(QIODevice* device);
|
||||
|
||||
bool writeDatabase(QIODevice* device, QString* error = nullptr);
|
||||
bool backupDatabase(const QString& filePath);
|
||||
|
||||
Metadata* const m_metadata;
|
||||
DatabaseData m_data;
|
||||
Group* m_rootGroup;
|
||||
QList<DeletedObject> m_deletedObjects;
|
||||
QTimer* m_timer;
|
||||
DatabaseData m_data;
|
||||
QPointer<QTimer> m_timer;
|
||||
bool m_initialized = false;
|
||||
bool m_modified = false;
|
||||
bool m_emitModified;
|
||||
|
||||
QString m_filePath;
|
||||
|
||||
QUuid m_uuid;
|
||||
static QHash<QUuid, Database*> m_uuidMap;
|
||||
static QHash<QUuid, QPointer<Database>> s_uuidMap;
|
||||
static QHash<QString, QPointer<Database>> s_filePathMap;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASE_H
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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:
|
||||
|
@ -29,77 +29,77 @@
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
bool keepDatabase)
|
||||
bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db)
|
||||
{
|
||||
Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3_1);
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if all required headers were present
|
||||
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_streamStartBytes.isEmpty()
|
||||
|| m_protectedStreamKey.isEmpty()
|
||||
|| m_db->cipher().isNull()) {
|
||||
|| db->cipher().isNull()) {
|
||||
raiseError(tr("missing database headers"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_db->setKey(key, false)) {
|
||||
if (!db->setKey(key, false)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_db->challengeMasterSeed(m_masterSeed)) {
|
||||
if (!db->challengeMasterSeed(m_masterSeed)) {
|
||||
raiseError(tr("Unable to issue challenge-response."));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(m_db->challengeResponseKey());
|
||||
hash.addData(m_db->transformedMasterKey());
|
||||
hash.addData(db->challengeResponseKey());
|
||||
hash.addData(db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
|
||||
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher());
|
||||
SymmetricCipherStream cipherStream(
|
||||
device, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
|
||||
if (!cipherStream.init(finalKey, m_encryptionIV)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
if (!cipherStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray realStart = cipherStream.read(32);
|
||||
|
||||
if (realStart != m_streamStartBytes) {
|
||||
raiseError(tr("Wrong key or database file is corrupt."));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
HashedBlockStream hashedStream(&cipherStream);
|
||||
if (!hashedStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
QIODevice* xmlDevice = nullptr;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (m_db->compressionAlgo() == Database::CompressionNone) {
|
||||
if (db->compressionAlgorithm() == Database::CompressionNone) {
|
||||
xmlDevice = &hashedStream;
|
||||
} else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
if (!ioCompressor->open(QIODevice::ReadOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
xmlDevice = ioCompressor.data();
|
||||
}
|
||||
@ -107,20 +107,17 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
||||
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20);
|
||||
if (!randomStream.init(m_protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(xmlDevice);
|
||||
|
||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3_1);
|
||||
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
|
||||
xmlReader.readDatabase(xmlDevice, db, &randomStream);
|
||||
|
||||
if (xmlReader.hasError()) {
|
||||
raiseError(xmlReader.errorString());
|
||||
if (keepDatabase) {
|
||||
return m_db.take();
|
||||
}
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(!xmlReader.headerHash().isEmpty() || m_kdbxVersion < KeePass2::FILE_VERSION_3_1);
|
||||
@ -129,15 +126,17 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
||||
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);
|
||||
if (headerHash != xmlReader.headerHash()) {
|
||||
raiseError(tr("Header doesn't match hash"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return m_db.take();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream)
|
||||
bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream, Database* db)
|
||||
{
|
||||
Q_UNUSED(db);
|
||||
|
||||
QByteArray fieldIDArray = headerStream.read(1);
|
||||
if (fieldIDArray.size() != 1) {
|
||||
raiseError(tr("Invalid header id size"));
|
||||
|
@ -29,13 +29,13 @@ class Kdbx3Reader : public KdbxReader
|
||||
Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader)
|
||||
|
||||
public:
|
||||
Database* readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
bool keepDatabase) override;
|
||||
bool readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db) override;
|
||||
|
||||
protected:
|
||||
bool readHeaderField(StoreDataStream& headerStream) override;
|
||||
bool readHeaderField(StoreDataStream& headerStream, Database* db) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KDBX3READER_H
|
||||
|
@ -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));
|
||||
|
@ -28,85 +28,85 @@
|
||||
#include "streams/QtIOCompressor"
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
|
||||
Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
bool keepDatabase)
|
||||
bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db)
|
||||
{
|
||||
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
|
||||
|
||||
m_binaryPoolInverse.clear();
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if all required headers were present
|
||||
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_db->cipher().isNull()) {
|
||||
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || db->cipher().isNull()) {
|
||||
raiseError(tr("missing database headers"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_db->setKey(key, false, false)) {
|
||||
if (!db->setKey(key, false, false)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(m_db->transformedMasterKey());
|
||||
hash.addData(db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
QByteArray headerSha256 = device->read(32);
|
||||
QByteArray headerHmac = device->read(32);
|
||||
if (headerSha256.size() != 32 || headerHmac.size() != 32) {
|
||||
raiseError(tr("Invalid header checksum size"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
if (headerSha256 != CryptoHash::hash(headerData, CryptoHash::Sha256)) {
|
||||
raiseError(tr("Header SHA256 mismatch"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, m_db->transformedMasterKey());
|
||||
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, db->transformedMasterKey());
|
||||
if (headerHmac
|
||||
!= CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
|
||||
raiseError(tr("Wrong key or database file is corrupt. (HMAC mismatch)"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
HmacBlockStream hmacStream(device, hmacKey);
|
||||
if (!hmacStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(hmacStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
|
||||
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher());
|
||||
if (cipher == SymmetricCipher::InvalidAlgorithm) {
|
||||
raiseError(tr("Unknown cipher"));
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
SymmetricCipherStream cipherStream(
|
||||
&hmacStream, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
|
||||
if (!cipherStream.init(finalKey, m_encryptionIV)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
if (!cipherStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
QIODevice* xmlDevice = nullptr;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (m_db->compressionAlgo() == Database::CompressionNone) {
|
||||
if (db->compressionAlgorithm() == Database::CompressionNone) {
|
||||
xmlDevice = &cipherStream;
|
||||
} else {
|
||||
ioCompressor.reset(new QtIOCompressor(&cipherStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
if (!ioCompressor->open(QIODevice::ReadOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
xmlDevice = ioCompressor.data();
|
||||
}
|
||||
@ -115,32 +115,29 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
||||
}
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream(m_irsAlgo);
|
||||
if (!randomStream.init(m_protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(xmlDevice);
|
||||
|
||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, binaryPool());
|
||||
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
|
||||
xmlReader.readDatabase(xmlDevice, db, &randomStream);
|
||||
|
||||
if (xmlReader.hasError()) {
|
||||
raiseError(xmlReader.errorString());
|
||||
if (keepDatabase) {
|
||||
return m_db.take();
|
||||
}
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_db.take();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
|
||||
bool Kdbx4Reader::readHeaderField(StoreDataStream& device, Database* db)
|
||||
{
|
||||
QByteArray fieldIDArray = device.read(1);
|
||||
if (fieldIDArray.size() != 1) {
|
||||
@ -197,7 +194,7 @@ bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
|
||||
raiseError(tr("Unsupported key derivation function (KDF) or invalid parameters"));
|
||||
return false;
|
||||
}
|
||||
m_db->setKdf(kdf);
|
||||
db->setKdf(kdf);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -206,7 +203,7 @@ bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
|
||||
variantBuffer.setBuffer(&fieldData);
|
||||
variantBuffer.open(QBuffer::ReadOnly);
|
||||
QVariantMap data = readVariantMap(&variantBuffer);
|
||||
m_db->setPublicCustomData(data);
|
||||
db->setPublicCustomData(data);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -30,15 +30,15 @@ class Kdbx4Reader : public KdbxReader
|
||||
Q_DECLARE_TR_FUNCTIONS(Kdbx4Reader)
|
||||
|
||||
public:
|
||||
Database* readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
bool keepDatabase) override;
|
||||
bool readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db) override;
|
||||
QHash<QByteArray, QString> binaryPoolInverse() const;
|
||||
QHash<QString, QByteArray> binaryPool() const;
|
||||
|
||||
protected:
|
||||
bool readHeaderField(StoreDataStream& headerStream) override;
|
||||
bool readHeaderField(StoreDataStream& headerStream, Database* db) override;
|
||||
|
||||
private:
|
||||
bool readInnerHeaderField(QIODevice* device);
|
||||
|
@ -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()));
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,7 +40,7 @@ public:
|
||||
virtual ~KdbxReader() = default;
|
||||
|
||||
static bool readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version);
|
||||
Database* readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase = false);
|
||||
bool readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db);
|
||||
|
||||
bool hasError() const;
|
||||
QString errorString() const;
|
||||
@ -57,22 +57,22 @@ protected:
|
||||
* @param device input device at the payload starting position
|
||||
* @param KDBX header data as bytes
|
||||
* @param key database encryption composite key
|
||||
* @param keepDatabase keep database in case of read failure
|
||||
* @return pointer to the read database, nullptr on failure
|
||||
* @param db database to read into
|
||||
* @return true on success
|
||||
*/
|
||||
virtual Database*
|
||||
readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
bool keepDatabase) = 0;
|
||||
virtual bool readDatabaseImpl(QIODevice* device,
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db) = 0;
|
||||
|
||||
/**
|
||||
* Read next header field from stream.
|
||||
*
|
||||
* @param headerStream input header stream
|
||||
* @param database to read header field for
|
||||
* @return true if there are more header fields
|
||||
*/
|
||||
virtual bool readHeaderField(StoreDataStream& headerStream) = 0;
|
||||
virtual bool readHeaderField(StoreDataStream& headerStream, Database* db) = 0;
|
||||
|
||||
virtual void setCipher(const QByteArray& data);
|
||||
virtual void setCompressionFlags(const QByteArray& data);
|
||||
@ -88,9 +88,6 @@ protected:
|
||||
|
||||
void decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const;
|
||||
|
||||
QScopedPointer<Database> m_db;
|
||||
|
||||
QPair<quint32, quint32> m_kdbxSignature;
|
||||
quint32 m_kdbxVersion = 0;
|
||||
|
||||
QByteArray m_masterSeed;
|
||||
@ -102,6 +99,9 @@ protected:
|
||||
QByteArray m_xmlData;
|
||||
|
||||
private:
|
||||
QPair<quint32, quint32> m_kdbxSignature;
|
||||
QPointer<Database> m_db;
|
||||
|
||||
bool m_saveXml = false;
|
||||
bool m_error = false;
|
||||
QString m_errorStr = "";
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
@ -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;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -32,6 +32,7 @@
|
||||
#include <QSplitter>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/EntrySearcher.h"
|
||||
#include "core/FilePath.h"
|
||||
@ -40,6 +41,7 @@
|
||||
#include "core/Metadata.h"
|
||||
#include "core/Tools.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "gui/FileDialog.h"
|
||||
#include "gui/Clipboard.h"
|
||||
#include "gui/CloneDialog.h"
|
||||
#include "gui/DatabaseOpenWidget.h"
|
||||
@ -68,131 +70,127 @@
|
||||
#include "sshagent/SSHAgent.h"
|
||||
#endif
|
||||
|
||||
DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
: QStackedWidget(parent)
|
||||
, m_db(db)
|
||||
, m_newGroup(nullptr)
|
||||
, m_newEntry(nullptr)
|
||||
, m_newParent(nullptr)
|
||||
{
|
||||
m_mainWidget = new QWidget(this);
|
||||
, m_db(std::move(db))
|
||||
|
||||
m_messageWidget = new MessageWidget(this);
|
||||
, m_mainWidget(new QWidget(this))
|
||||
, m_mainSplitter(new QSplitter(m_mainWidget))
|
||||
, m_messageWidget(new MessageWidget(this))
|
||||
, m_previewView(new EntryPreviewWidget(this))
|
||||
, m_previewSplitter(new QSplitter(m_mainWidget))
|
||||
, m_searchingLabel(new QLabel(this))
|
||||
, m_csvImportWizard(new CsvImportWizard(this))
|
||||
, m_editEntryWidget(new EditEntryWidget(this))
|
||||
, m_editGroupWidget(new EditGroupWidget(this))
|
||||
, m_historyEditEntryWidget(new EditEntryWidget(this))
|
||||
, m_databaseSettingDialog(new DatabaseSettingsDialog(this))
|
||||
, m_databaseOpenWidget(new DatabaseOpenWidget(this))
|
||||
, m_databaseOpenMergeWidget(new DatabaseOpenWidget(this))
|
||||
, m_keepass1OpenWidget(new KeePass1OpenWidget(this))
|
||||
, m_unlockDatabaseWidget(new UnlockDatabaseWidget(this))
|
||||
, m_unlockDatabaseDialog(new UnlockDatabaseDialog(this))
|
||||
, m_groupView(new GroupView(m_db.data(), m_mainSplitter))
|
||||
, m_entryView(nullptr)
|
||||
|
||||
, m_newGroup()
|
||||
, m_newEntry()
|
||||
, m_newParent()
|
||||
{
|
||||
m_messageWidget->setHidden(true);
|
||||
|
||||
auto* mainLayout = new QVBoxLayout();
|
||||
QLayout* layout = new QHBoxLayout();
|
||||
mainLayout->addWidget(m_messageWidget);
|
||||
mainLayout->addLayout(layout);
|
||||
m_mainSplitter = new QSplitter(m_mainWidget);
|
||||
auto* hbox = new QHBoxLayout();
|
||||
mainLayout->addLayout(hbox);
|
||||
hbox->addWidget(m_mainSplitter);
|
||||
m_mainWidget->setLayout(mainLayout);
|
||||
|
||||
auto* rightHandSideWidget = new QWidget(m_mainSplitter);
|
||||
auto* vbox = new QVBoxLayout();
|
||||
vbox->setMargin(0);
|
||||
vbox->addWidget(m_searchingLabel);
|
||||
vbox->addWidget(m_previewSplitter);
|
||||
rightHandSideWidget->setLayout(vbox);
|
||||
m_entryView = new EntryView(rightHandSideWidget);
|
||||
|
||||
m_mainSplitter->setChildrenCollapsible(false);
|
||||
m_previewSplitter = new QSplitter(m_mainWidget);
|
||||
m_mainSplitter->addWidget(m_groupView);
|
||||
m_mainSplitter->addWidget(rightHandSideWidget);
|
||||
m_mainSplitter->setStretchFactor(0, 30);
|
||||
m_mainSplitter->setStretchFactor(1, 70);
|
||||
|
||||
m_previewSplitter->setOrientation(Qt::Vertical);
|
||||
m_previewSplitter->setChildrenCollapsible(true);
|
||||
|
||||
QWidget* rightHandSideWidget = new QWidget(m_mainSplitter);
|
||||
|
||||
m_groupView = new GroupView(db, m_mainSplitter);
|
||||
m_groupView->setObjectName("groupView");
|
||||
m_groupView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_groupView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(emitGroupContextMenuRequested(QPoint)));
|
||||
|
||||
m_entryView = new EntryView(rightHandSideWidget);
|
||||
m_entryView->setObjectName("entryView");
|
||||
m_entryView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_entryView->displayGroup(db->rootGroup());
|
||||
m_entryView->displayGroup(m_db->rootGroup());
|
||||
connect(m_entryView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(emitEntryContextMenuRequested(QPoint)));
|
||||
|
||||
// Add a notification for when we are searching
|
||||
m_searchingLabel = new QLabel();
|
||||
m_searchingLabel->setText(tr("Searching..."));
|
||||
m_searchingLabel->setAlignment(Qt::AlignCenter);
|
||||
m_searchingLabel->setStyleSheet("color: rgb(0, 0, 0);"
|
||||
"background-color: rgb(255, 253, 160);"
|
||||
"border: 2px solid rgb(190, 190, 190);"
|
||||
"border-radius: 5px;");
|
||||
"border-radius: 2px;");
|
||||
m_searchingLabel->setVisible(false);
|
||||
|
||||
m_previewView = new EntryPreviewWidget(this);
|
||||
m_previewView->hide();
|
||||
connect(this, SIGNAL(pressedGroup(Group*)), m_previewView, SLOT(setGroup(Group*)));
|
||||
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)),
|
||||
m_previewView, SLOT(setDatabaseMode(DatabaseWidget::Mode)));
|
||||
connect(m_previewView, SIGNAL(errorOccurred(QString)), this, SLOT(showErrorMessage(QString)));
|
||||
|
||||
auto* vLayout = new QVBoxLayout(rightHandSideWidget);
|
||||
vLayout->setMargin(0);
|
||||
vLayout->addWidget(m_searchingLabel);
|
||||
vLayout->addWidget(m_previewSplitter);
|
||||
|
||||
m_previewSplitter->addWidget(m_entryView);
|
||||
m_previewSplitter->addWidget(m_previewView);
|
||||
|
||||
m_previewSplitter->setStretchFactor(0, 100);
|
||||
m_previewSplitter->setStretchFactor(1, 0);
|
||||
m_previewSplitter->setSizes({1, 1});
|
||||
|
||||
m_searchingLabel->setVisible(false);
|
||||
|
||||
rightHandSideWidget->setLayout(vLayout);
|
||||
|
||||
m_mainSplitter->addWidget(m_groupView);
|
||||
m_mainSplitter->addWidget(rightHandSideWidget);
|
||||
|
||||
m_mainSplitter->setStretchFactor(0, 30);
|
||||
m_mainSplitter->setStretchFactor(1, 70);
|
||||
|
||||
layout->addWidget(m_mainSplitter);
|
||||
m_mainWidget->setLayout(mainLayout);
|
||||
|
||||
m_editEntryWidget = new EditEntryWidget();
|
||||
m_editEntryWidget->setObjectName("editEntryWidget");
|
||||
m_historyEditEntryWidget = new EditEntryWidget();
|
||||
m_editGroupWidget = new EditGroupWidget();
|
||||
m_editGroupWidget->setObjectName("editGroupWidget");
|
||||
m_csvImportWizard = new CsvImportWizard();
|
||||
m_csvImportWizard->setObjectName("csvImportWizard");
|
||||
m_databaseSettingDialog = new DatabaseSettingsDialog();
|
||||
m_databaseSettingDialog->setObjectName("databaseSettingsDialog");
|
||||
m_databaseOpenWidget = new DatabaseOpenWidget();
|
||||
m_databaseOpenWidget->setObjectName("databaseOpenWidget");
|
||||
m_databaseOpenMergeWidget = new DatabaseOpenWidget();
|
||||
m_databaseOpenMergeWidget->setObjectName("databaseOpenMergeWidget");
|
||||
m_keepass1OpenWidget = new KeePass1OpenWidget();
|
||||
m_keepass1OpenWidget->setObjectName("keepass1OpenWidget");
|
||||
m_unlockDatabaseWidget = new UnlockDatabaseWidget();
|
||||
m_unlockDatabaseWidget->setObjectName("unlockDatabaseWidget");
|
||||
m_unlockDatabaseDialog = new UnlockDatabaseDialog();
|
||||
m_unlockDatabaseDialog->setObjectName("unlockDatabaseDialog");
|
||||
addWidget(m_mainWidget);
|
||||
addWidget(m_editEntryWidget);
|
||||
addWidget(m_editGroupWidget);
|
||||
addWidget(m_databaseSettingDialog);
|
||||
addWidget(m_historyEditEntryWidget);
|
||||
addWidget(m_databaseOpenWidget);
|
||||
addWidget(m_csvImportWizard);
|
||||
addWidget(m_databaseOpenMergeWidget);
|
||||
addWidget(m_keepass1OpenWidget);
|
||||
addWidget(m_unlockDatabaseWidget);
|
||||
|
||||
addChildWidget(m_mainWidget);
|
||||
addChildWidget(m_editEntryWidget);
|
||||
addChildWidget(m_editGroupWidget);
|
||||
addChildWidget(m_databaseSettingDialog);
|
||||
addChildWidget(m_historyEditEntryWidget);
|
||||
addChildWidget(m_databaseOpenWidget);
|
||||
addChildWidget(m_csvImportWizard);
|
||||
addChildWidget(m_databaseOpenMergeWidget);
|
||||
addChildWidget(m_keepass1OpenWidget);
|
||||
addChildWidget(m_unlockDatabaseWidget);
|
||||
|
||||
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
|
||||
connect(m_previewSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(previewSplitterSizesChanged()));
|
||||
connect(this, SIGNAL(pressedEntry(Entry*)), m_previewView, SLOT(setEntry(Entry*)));
|
||||
connect(this, SIGNAL(pressedGroup(Group*)), m_previewView, SLOT(setGroup(Group*)));
|
||||
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), m_previewView, SLOT(setDatabaseMode(DatabaseWidget::Mode)));
|
||||
connect(m_previewView, SIGNAL(errorOccurred(QString)), this, SLOT(showErrorMessage(QString)));
|
||||
connect(m_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged()));
|
||||
connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(onGroupChanged(Group*)));
|
||||
connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged()));
|
||||
connect(m_entryView,
|
||||
SIGNAL(entryActivated(Entry*,EntryModel::ModelColumn)),
|
||||
SLOT(entryActivationSignalReceived(Entry*,EntryModel::ModelColumn)));
|
||||
connect(m_entryView, SIGNAL(entrySelectionChanged()), SLOT(emitEntrySelectionChanged()));
|
||||
connect(m_entryView, SIGNAL(entryActivated(Entry*,EntryModel::ModelColumn)),
|
||||
SLOT(entryActivationSignalReceived(Entry*,EntryModel::ModelColumn)));
|
||||
connect(m_entryView, SIGNAL(entrySelectionChanged()), SIGNAL(entrySelectionChanged()));
|
||||
connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool)));
|
||||
connect(m_editEntryWidget, SIGNAL(historyEntryActivated(Entry*)), SLOT(switchToHistoryView(Entry*)));
|
||||
connect(m_historyEditEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchBackToEntryEdit()));
|
||||
connect(m_editGroupWidget, SIGNAL(editFinished(bool)), SLOT(switchToView(bool)));
|
||||
connect(m_databaseSettingDialog, SIGNAL(editFinished(bool)), SLOT(switchToView(bool)));
|
||||
connect(m_databaseOpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
|
||||
connect(m_databaseOpenMergeWidget, SIGNAL(editFinished(bool)), SLOT(mergeDatabase(bool)));
|
||||
connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
|
||||
connect(m_databaseOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
|
||||
connect(m_databaseOpenMergeWidget, SIGNAL(dialogFinished(bool)), SLOT(mergeDatabase(bool)));
|
||||
connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
|
||||
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
|
||||
connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool)));
|
||||
connect(m_unlockDatabaseWidget, SIGNAL(dialogFinished(bool)), SLOT(unlockDatabase(bool)));
|
||||
connect(m_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool)));
|
||||
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
|
||||
connect(&m_fileWatchTimer, SIGNAL(timeout()), this, SLOT(reloadDatabaseFile()));
|
||||
@ -203,7 +201,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
connect(m_groupView, SIGNAL(groupChanged(Group*)), SLOT(emitPressedGroup(Group*)));
|
||||
connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(emitEntrySelectionChanged()));
|
||||
|
||||
m_databaseModified = false;
|
||||
connectDatabaseSignals();
|
||||
|
||||
m_fileWatchTimer.setSingleShot(true);
|
||||
m_fileWatchUnblockTimer.setSingleShot(true);
|
||||
@ -225,29 +223,44 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
setCurrentWidget(m_mainWidget);
|
||||
}
|
||||
|
||||
DatabaseWidget::DatabaseWidget(const QString& filePath, QWidget* parent)
|
||||
: DatabaseWidget(QSharedPointer<Database>::create(filePath), parent)
|
||||
{
|
||||
}
|
||||
|
||||
DatabaseWidget::~DatabaseWidget()
|
||||
{
|
||||
delete m_EntrySearcher;
|
||||
}
|
||||
|
||||
QSharedPointer<Database> DatabaseWidget::database() const
|
||||
{
|
||||
return m_db;
|
||||
}
|
||||
|
||||
DatabaseWidget::Mode DatabaseWidget::currentMode() const
|
||||
{
|
||||
if (currentWidget() == nullptr) {
|
||||
return DatabaseWidget::None;
|
||||
return DatabaseWidget::Mode::None;
|
||||
} else if (currentWidget() == m_csvImportWizard) {
|
||||
return DatabaseWidget::ImportMode;
|
||||
return DatabaseWidget::Mode::ImportMode;
|
||||
} else if (currentWidget() == m_mainWidget) {
|
||||
return DatabaseWidget::ViewMode;
|
||||
return DatabaseWidget::Mode::ViewMode;
|
||||
} else if (currentWidget() == m_unlockDatabaseWidget || currentWidget() == m_databaseOpenWidget) {
|
||||
return DatabaseWidget::LockedMode;
|
||||
return DatabaseWidget::Mode::LockedMode;
|
||||
} else {
|
||||
return DatabaseWidget::EditMode;
|
||||
return DatabaseWidget::Mode::EditMode;
|
||||
}
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isInEditMode() const
|
||||
bool DatabaseWidget::isLocked() const
|
||||
{
|
||||
return currentMode() == DatabaseWidget::EditMode;
|
||||
return currentMode() == Mode::LockedMode;
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isSearchActive() const
|
||||
{
|
||||
return m_entryView->inSearchMode();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isEditWidgetModified() const
|
||||
@ -341,11 +354,6 @@ void DatabaseWidget::emitCurrentModeChanged()
|
||||
emit currentModeChanged(currentMode());
|
||||
}
|
||||
|
||||
Database* DatabaseWidget::database()
|
||||
{
|
||||
return m_db;
|
||||
}
|
||||
|
||||
void DatabaseWidget::createEntry()
|
||||
{
|
||||
Q_ASSERT(m_groupView->currentGroup());
|
||||
@ -355,7 +363,7 @@ void DatabaseWidget::createEntry()
|
||||
|
||||
m_newEntry = new Entry();
|
||||
|
||||
if (isInSearchMode()) {
|
||||
if (isSearchActive()) {
|
||||
m_newEntry->setTitle(getCurrentSearch());
|
||||
endSearch();
|
||||
}
|
||||
@ -383,13 +391,15 @@ void DatabaseWidget::setIconFromParent()
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::replaceDatabase(Database* db)
|
||||
void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
|
||||
{
|
||||
Database* oldDb = m_db;
|
||||
m_db = db;
|
||||
// TODO: instead of increasing the ref count temporarily, there should be a clean
|
||||
// break from the old database. Without this crashes occur due to the change
|
||||
// signals triggering dangling pointers.
|
||||
auto oldDb = m_db;
|
||||
m_db = std::move(db);
|
||||
connectDatabaseSignals();
|
||||
m_groupView->changeDatabase(m_db);
|
||||
emit databaseChanged(m_db, m_databaseModified);
|
||||
delete oldDb;
|
||||
}
|
||||
|
||||
void DatabaseWidget::cloneEntry()
|
||||
@ -400,7 +410,7 @@ void DatabaseWidget::cloneEntry()
|
||||
return;
|
||||
}
|
||||
|
||||
auto cloneDialog = new CloneDialog(this, m_db, currentEntry);
|
||||
auto cloneDialog = new CloneDialog(this, m_db.data(), currentEntry);
|
||||
cloneDialog->show();
|
||||
}
|
||||
|
||||
@ -660,8 +670,8 @@ void DatabaseWidget::deleteGroup()
|
||||
|
||||
auto* recycleBin = m_db->metadata()->recycleBin();
|
||||
bool inRecycleBin = recycleBin && recycleBin->findGroupByUuid(currentGroup->uuid());
|
||||
bool isRecycleBin = (currentGroup == m_db->metadata()->recycleBin());
|
||||
bool isRecycleBinSubgroup = currentGroup->findGroupByUuid(m_db->metadata()->recycleBin()->uuid());
|
||||
bool isRecycleBin = recycleBin && (currentGroup == recycleBin);
|
||||
bool isRecycleBinSubgroup = recycleBin && currentGroup->findGroupByUuid(recycleBin->uuid());
|
||||
if (inRecycleBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) {
|
||||
QMessageBox::StandardButton result = MessageBox::question(
|
||||
this,
|
||||
@ -676,40 +686,14 @@ void DatabaseWidget::deleteGroup()
|
||||
}
|
||||
}
|
||||
|
||||
int DatabaseWidget::addWidget(QWidget* w)
|
||||
int DatabaseWidget::addChildWidget(QWidget* w)
|
||||
{
|
||||
w->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
||||
|
||||
int index = QStackedWidget::addWidget(w);
|
||||
|
||||
adjustSize();
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void DatabaseWidget::setCurrentIndex(int index)
|
||||
{
|
||||
// use setCurrentWidget() instead
|
||||
// index is not reliable
|
||||
Q_UNUSED(index);
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
|
||||
void DatabaseWidget::setCurrentWidget(QWidget* widget)
|
||||
{
|
||||
if (currentWidget()) {
|
||||
currentWidget()->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
||||
}
|
||||
|
||||
QStackedWidget::setCurrentWidget(widget);
|
||||
|
||||
if (currentWidget()) {
|
||||
currentWidget()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void DatabaseWidget::csvImportFinished(bool accepted)
|
||||
{
|
||||
if (!accepted) {
|
||||
@ -788,24 +772,32 @@ void DatabaseWidget::switchToGroupEdit(Group* group, bool create)
|
||||
setCurrentWidget(m_editGroupWidget);
|
||||
}
|
||||
|
||||
void DatabaseWidget::openDatabase(bool accepted)
|
||||
void DatabaseWidget::connectDatabaseSignals()
|
||||
{
|
||||
if (accepted) {
|
||||
replaceDatabase(static_cast<DatabaseOpenWidget*>(sender())->database());
|
||||
setCurrentWidget(m_mainWidget);
|
||||
emit unlockedDatabase();
|
||||
// relayed Database events
|
||||
connect(m_db.data(), SIGNAL(filePathChanged(QString,QString)),
|
||||
this, SIGNAL(databaseFilePathChanged(QString,QString)));
|
||||
connect(m_db.data(), SIGNAL(databaseModified()), this, SIGNAL(databaseModified()));
|
||||
connect(m_db.data(), SIGNAL(databaseSaved()), this, SIGNAL(databaseSaved()));
|
||||
}
|
||||
|
||||
// We won't need those anymore and KeePass1OpenWidget closes
|
||||
// the file in its dtor.
|
||||
delete m_databaseOpenWidget;
|
||||
m_databaseOpenWidget = nullptr;
|
||||
delete m_keepass1OpenWidget;
|
||||
m_keepass1OpenWidget = nullptr;
|
||||
m_fileWatcher.addPath(m_filePath);
|
||||
void DatabaseWidget::loadDatabase(bool accepted)
|
||||
{
|
||||
auto* openWidget = qobject_cast<DatabaseOpenWidget*>(sender());
|
||||
Q_ASSERT(openWidget);
|
||||
if (!openWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (accepted) {
|
||||
replaceDatabase(openWidget->database());
|
||||
setCurrentWidget(m_mainWidget);
|
||||
m_fileWatcher.addPath(m_db->filePath());
|
||||
emit databaseUnlocked();
|
||||
} else {
|
||||
m_fileWatcher.removePath(m_filePath);
|
||||
m_fileWatcher.removePath(m_db->filePath());
|
||||
if (m_databaseOpenWidget->database()) {
|
||||
delete m_databaseOpenWidget->database();
|
||||
m_databaseOpenWidget->database().reset();
|
||||
}
|
||||
emit closeRequest();
|
||||
}
|
||||
@ -815,18 +807,18 @@ void DatabaseWidget::mergeDatabase(bool accepted)
|
||||
{
|
||||
if (accepted) {
|
||||
if (!m_db) {
|
||||
m_messageWidget->showMessage(tr("No current database."), MessageWidget::Error);
|
||||
showMessage(tr("No current database."), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Database* srcDb = static_cast<DatabaseOpenWidget*>(sender())->database();
|
||||
auto srcDb = qobject_cast<DatabaseOpenWidget*>(sender())->database();
|
||||
|
||||
if (!srcDb) {
|
||||
m_messageWidget->showMessage(tr("No source database, nothing to do."), MessageWidget::Error);
|
||||
showMessage(tr("No source database, nothing to do."), MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Merger merger(srcDb, m_db);
|
||||
Merger merger(srcDb.data(), m_db.data());
|
||||
merger.merge();
|
||||
}
|
||||
|
||||
@ -842,7 +834,7 @@ void DatabaseWidget::unlockDatabase(bool accepted)
|
||||
return;
|
||||
}
|
||||
|
||||
Database* db = nullptr;
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
if (sender() == m_unlockDatabaseDialog) {
|
||||
db = m_unlockDatabaseDialog->database();
|
||||
} else if (sender() == m_unlockDatabaseWidget) {
|
||||
@ -850,6 +842,9 @@ void DatabaseWidget::unlockDatabase(bool accepted)
|
||||
}
|
||||
|
||||
replaceDatabase(db);
|
||||
if (db->isReadOnly()) {
|
||||
showMessage(tr("File opened in read only mode."), MessageWidget::Warning, false, -1);
|
||||
}
|
||||
|
||||
restoreGroupEntryFocus(m_groupBeforeLock, m_entryBeforeLock);
|
||||
m_groupBeforeLock = QUuid();
|
||||
@ -857,10 +852,10 @@ void DatabaseWidget::unlockDatabase(bool accepted)
|
||||
|
||||
setCurrentWidget(m_mainWidget);
|
||||
m_unlockDatabaseWidget->clearForms();
|
||||
emit unlockedDatabase();
|
||||
emit databaseUnlocked();
|
||||
|
||||
if (sender() == m_unlockDatabaseDialog) {
|
||||
QList<Database*> dbList;
|
||||
QList<QSharedPointer<Database>> dbList;
|
||||
dbList.append(m_db);
|
||||
autoType()->performGlobalAutoType(dbList);
|
||||
}
|
||||
@ -946,6 +941,11 @@ void DatabaseWidget::switchToDatabaseSettings()
|
||||
setCurrentWidget(m_databaseSettingDialog);
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToOpenDatabase()
|
||||
{
|
||||
switchToOpenDatabase(m_db->filePath());
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToOpenDatabase(const QString& filePath)
|
||||
{
|
||||
updateFilePath(filePath);
|
||||
@ -957,22 +957,10 @@ void DatabaseWidget::switchToOpenDatabase(const QString& filePath)
|
||||
setCurrentWidget(m_unlockDatabaseWidget);
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToOpenDatabase(const QString& filePath, const QString& password, const QString& keyFile)
|
||||
{
|
||||
updateFilePath(filePath);
|
||||
switchToOpenDatabase(filePath);
|
||||
if (m_databaseOpenWidget) {
|
||||
m_databaseOpenWidget->enterKey(password, keyFile);
|
||||
} else if (m_unlockDatabaseWidget) {
|
||||
m_unlockDatabaseWidget->enterKey(password, keyFile);
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToCsvImport(const QString& filePath)
|
||||
{
|
||||
setCurrentWidget(m_csvImportWizard);
|
||||
m_csvImportWizard->load(filePath, m_db);
|
||||
m_csvImportWizard->load(filePath, m_db.data());
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToOpenMergeDatabase(const QString& filePath)
|
||||
@ -995,19 +983,9 @@ void DatabaseWidget::switchToImportKeepass1(const QString& filePath)
|
||||
setCurrentWidget(m_keepass1OpenWidget);
|
||||
}
|
||||
|
||||
void DatabaseWidget::databaseModified()
|
||||
{
|
||||
m_databaseModified = true;
|
||||
}
|
||||
|
||||
void DatabaseWidget::databaseSaved()
|
||||
{
|
||||
m_databaseModified = false;
|
||||
}
|
||||
|
||||
void DatabaseWidget::refreshSearch()
|
||||
{
|
||||
if (isInSearchMode()) {
|
||||
if (isSearchActive()) {
|
||||
search(m_lastSearchText);
|
||||
}
|
||||
}
|
||||
@ -1054,10 +1032,10 @@ void DatabaseWidget::setSearchLimitGroup(bool state)
|
||||
|
||||
void DatabaseWidget::onGroupChanged(Group* group)
|
||||
{
|
||||
if (isInSearchMode() && m_searchLimitGroup) {
|
||||
// Perform new search if we are limiting search to the current group
|
||||
// Intercept group changes if in search mode
|
||||
if (isSearchActive()) {
|
||||
search(m_lastSearchText);
|
||||
} else if (isInSearchMode()) {
|
||||
} else if (isSearchActive()) {
|
||||
// Otherwise cancel search
|
||||
emit clearSearch();
|
||||
} else {
|
||||
@ -1072,7 +1050,7 @@ QString DatabaseWidget::getCurrentSearch()
|
||||
|
||||
void DatabaseWidget::endSearch()
|
||||
{
|
||||
if (isInSearchMode()) {
|
||||
if (isSearchActive()) {
|
||||
emit listModeAboutToActivate();
|
||||
|
||||
// Show the normal entry view of the current group
|
||||
@ -1117,30 +1095,74 @@ void DatabaseWidget::emitPressedGroup(Group* currentGroup)
|
||||
emit pressedGroup(currentGroup);
|
||||
}
|
||||
|
||||
bool DatabaseWidget::dbHasKey() const
|
||||
{
|
||||
return m_db->hasKey();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::canDeleteCurrentGroup() const
|
||||
{
|
||||
bool isRootGroup = m_db->rootGroup() == m_groupView->currentGroup();
|
||||
return !isRootGroup;
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isInSearchMode() const
|
||||
{
|
||||
return m_entryView->inSearchMode();
|
||||
}
|
||||
|
||||
Group* DatabaseWidget::currentGroup() const
|
||||
{
|
||||
return m_groupView->currentGroup();
|
||||
}
|
||||
|
||||
void DatabaseWidget::lock()
|
||||
void DatabaseWidget::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
Q_ASSERT(currentMode() != DatabaseWidget::LockedMode);
|
||||
if (!isLocked() && !lock()) {
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void DatabaseWidget::showEvent(QShowEvent* event)
|
||||
{
|
||||
if (!m_db->isInitialized() || isLocked()) {
|
||||
switchToOpenDatabase();
|
||||
}
|
||||
|
||||
event->accept();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::lock()
|
||||
{
|
||||
if (isLocked()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
clipboard()->clearCopiedText();
|
||||
|
||||
if (currentMode() == DatabaseWidget::Mode::EditMode) {
|
||||
auto result = MessageBox::question(this, tr("Lock Database?"),
|
||||
tr("You are editing an entry. Discard changes and lock anyway?"),
|
||||
QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
|
||||
if (result == QMessageBox::Cancel) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_db->isModified()) {
|
||||
if (config()->get("AutoSaveOnExit").toBool()) {
|
||||
if (!m_db->save(nullptr, false, false)) {
|
||||
return false;
|
||||
}
|
||||
} else if (isLocked()) {
|
||||
QString msg;
|
||||
if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) {
|
||||
msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped());
|
||||
} else {
|
||||
msg = tr("Database was modified.\nSave changes?");
|
||||
}
|
||||
auto result = MessageBox::question(this, tr("Save changes?"), msg,
|
||||
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
|
||||
if (result == QMessageBox::Yes && !m_db->save(nullptr, false, false)) {
|
||||
return false;
|
||||
} else if (result == QMessageBox::Cancel) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_groupView->currentGroup()) {
|
||||
m_groupBeforeLock = m_groupView->currentGroup()->uuid();
|
||||
@ -1154,21 +1176,24 @@ void DatabaseWidget::lock()
|
||||
|
||||
endSearch();
|
||||
clearAllWidgets();
|
||||
m_unlockDatabaseWidget->load(m_filePath);
|
||||
m_unlockDatabaseWidget->load(m_db->filePath());
|
||||
setCurrentWidget(m_unlockDatabaseWidget);
|
||||
Database* newDb = new Database();
|
||||
newDb->metadata()->setName(m_db->metadata()->name());
|
||||
|
||||
auto newDb = QSharedPointer<Database>::create(m_db->filePath());
|
||||
replaceDatabase(newDb);
|
||||
emit lockedDatabase();
|
||||
|
||||
emit databaseLocked();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DatabaseWidget::updateFilePath(const QString& filePath)
|
||||
{
|
||||
if (!m_filePath.isEmpty()) {
|
||||
m_fileWatcher.removePath(m_filePath);
|
||||
if (!m_db->filePath().isEmpty()) {
|
||||
m_fileWatcher.removePath(m_db->filePath());
|
||||
}
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
#ifdef Q_OS_LINUX
|
||||
struct statfs statfsBuf;
|
||||
bool forcePolling = false;
|
||||
const auto NFS_SUPER_MAGIC = 0x6969;
|
||||
@ -1184,7 +1209,6 @@ void DatabaseWidget::updateFilePath(const QString& filePath)
|
||||
#endif
|
||||
|
||||
m_fileWatcher.addPath(filePath);
|
||||
m_filePath = filePath;
|
||||
m_db->setFilePath(filePath);
|
||||
}
|
||||
|
||||
@ -1201,7 +1225,7 @@ void DatabaseWidget::blockAutoReload(bool block)
|
||||
void DatabaseWidget::unblockAutoReload()
|
||||
{
|
||||
m_ignoreAutoReload = false;
|
||||
updateFilePath(m_filePath);
|
||||
updateFilePath(m_db->filePath());
|
||||
}
|
||||
|
||||
void DatabaseWidget::onWatchedFileChanged()
|
||||
@ -1217,86 +1241,69 @@ void DatabaseWidget::onWatchedFileChanged()
|
||||
|
||||
void DatabaseWidget::reloadDatabaseFile()
|
||||
{
|
||||
if (!m_db || currentMode() == DatabaseWidget::LockedMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentMode() == DatabaseWidget::LockedMode) {
|
||||
if (!m_db || isLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config()->get("AutoReloadOnChange").toBool()) {
|
||||
// Ask if we want to reload the db
|
||||
QMessageBox::StandardButton mb =
|
||||
MessageBox::question(this,
|
||||
tr("File has changed"),
|
||||
tr("The database file has changed. Do you want to load the changes?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
auto result = MessageBox::question(this,
|
||||
tr("File has changed"),
|
||||
tr("The database file has changed. Do you want to load the changes?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (mb == QMessageBox::No) {
|
||||
if (result == QMessageBox::No) {
|
||||
// Notify everyone the database does not match the file
|
||||
m_db->markAsModified();
|
||||
m_databaseModified = true;
|
||||
// Rewatch the database file
|
||||
m_fileWatcher.addPath(m_filePath);
|
||||
m_fileWatcher.addPath(m_db->filePath());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
KeePass2Reader reader;
|
||||
QFile file(m_filePath);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
Database* db = reader.readDatabase(&file, database()->key());
|
||||
if (db != nullptr) {
|
||||
if (m_databaseModified) {
|
||||
// Ask if we want to merge changes into new database
|
||||
QMessageBox::StandardButton mb =
|
||||
MessageBox::question(this,
|
||||
tr("Merge Request"),
|
||||
tr("The database file has changed and you have unsaved changes.\n"
|
||||
"Do you want to merge your changes?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
QString error;
|
||||
auto db = QSharedPointer<Database>::create(m_db->filePath());
|
||||
if (db->open(database()->key(), &error, true)) {
|
||||
if (m_db->isModified()) {
|
||||
// Ask if we want to merge changes into new database
|
||||
auto result = MessageBox::question(this,
|
||||
tr("Merge Request"),
|
||||
tr("The database file has changed and you have unsaved changes.\nDo you want to merge your changes?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (mb == QMessageBox::Yes) {
|
||||
// Merge the old database into the new one
|
||||
m_db->setEmitModified(false);
|
||||
Merger merger(m_db, db);
|
||||
merger.merge();
|
||||
} else {
|
||||
// Since we are accepting the new file as-is, internally mark as unmodified
|
||||
// TODO: when saving is moved out of DatabaseTabWidget, this should be replaced
|
||||
m_databaseModified = false;
|
||||
}
|
||||
if (result == QMessageBox::Yes) {
|
||||
// Merge the old database into the new one
|
||||
Merger merger(m_db.data(), db.data());
|
||||
merger.merge();
|
||||
}
|
||||
|
||||
QUuid groupBeforeReload;
|
||||
if (m_groupView && m_groupView->currentGroup()) {
|
||||
groupBeforeReload = m_groupView->currentGroup()->uuid();
|
||||
} else {
|
||||
groupBeforeReload = m_db->rootGroup()->uuid();
|
||||
}
|
||||
|
||||
QUuid entryBeforeReload;
|
||||
if (m_entryView && m_entryView->currentEntry()) {
|
||||
entryBeforeReload = m_entryView->currentEntry()->uuid();
|
||||
}
|
||||
|
||||
replaceDatabase(db);
|
||||
restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload);
|
||||
}
|
||||
|
||||
QUuid groupBeforeReload;
|
||||
if (m_groupView && m_groupView->currentGroup()) {
|
||||
groupBeforeReload = m_groupView->currentGroup()->uuid();
|
||||
} else {
|
||||
groupBeforeReload = m_db->rootGroup()->uuid();
|
||||
}
|
||||
|
||||
QUuid entryBeforeReload;
|
||||
if (m_entryView && m_entryView->currentEntry()) {
|
||||
entryBeforeReload = m_entryView->currentEntry()->uuid();
|
||||
}
|
||||
|
||||
bool isReadOnly = m_db->isReadOnly();
|
||||
replaceDatabase(db);
|
||||
m_db->setReadOnly(isReadOnly);
|
||||
restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload);
|
||||
} else {
|
||||
m_messageWidget->showMessage(
|
||||
tr("Could not open the new database file while attempting to autoreload this database.")
|
||||
.append("\n")
|
||||
.append(file.errorString()),
|
||||
showMessage(
|
||||
tr("Could not open the new database file while attempting to autoreload.\nError: %1").arg(error),
|
||||
MessageWidget::Error);
|
||||
// HACK: Directly calling the database's signal
|
||||
// Mark db as modified since existing data may differ from file or file was deleted
|
||||
m_db->markAsModified();
|
||||
}
|
||||
|
||||
// Rewatch the database file
|
||||
m_fileWatcher.addPath(m_filePath);
|
||||
m_fileWatcher.addPath(m_db->filePath());
|
||||
}
|
||||
|
||||
int DatabaseWidget::numberOfSelectedEntries() const
|
||||
@ -1319,7 +1326,7 @@ QStringList DatabaseWidget::customEntryAttributes() const
|
||||
*/
|
||||
void DatabaseWidget::restoreGroupEntryFocus(const QUuid& groupUuid, const QUuid& entryUuid)
|
||||
{
|
||||
auto group = m_db->resolveGroup(groupUuid);
|
||||
auto group = m_db->rootGroup()->findGroupByUuid(groupUuid);
|
||||
if (group) {
|
||||
m_groupView->setCurrentGroup(group);
|
||||
auto entry = group->findEntryByUuid(entryUuid);
|
||||
@ -1409,10 +1416,10 @@ EntryView* DatabaseWidget::entryView()
|
||||
return m_entryView;
|
||||
}
|
||||
|
||||
void DatabaseWidget::showUnlockDialog()
|
||||
void DatabaseWidget::prepareUnlock()
|
||||
{
|
||||
m_unlockDatabaseDialog->clearForms();
|
||||
m_unlockDatabaseDialog->setFilePath(m_filePath);
|
||||
m_unlockDatabaseDialog->setFilePath(m_db->filePath());
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
autoType()->raiseWindow();
|
||||
@ -1423,15 +1430,101 @@ void DatabaseWidget::showUnlockDialog()
|
||||
m_unlockDatabaseDialog->activateWindow();
|
||||
}
|
||||
|
||||
void DatabaseWidget::closeUnlockDialog()
|
||||
/**
|
||||
* Save the database to disk.
|
||||
*
|
||||
* This method will try to save several times in case of failure and
|
||||
* ask to disable safe saves if it is unable to save after the third attempt.
|
||||
* Set `attempt` to -1 to disable this behavior.
|
||||
*
|
||||
* @param attempt current save attempt or -1 to disable attempts
|
||||
* @return true on success
|
||||
*/
|
||||
bool DatabaseWidget::save(int attempt)
|
||||
{
|
||||
m_unlockDatabaseDialog->close();
|
||||
// Never allow saving a locked database; it causes corruption
|
||||
Q_ASSERT(!isLocked());
|
||||
// Release build interlock
|
||||
if (isLocked()) {
|
||||
// We return true since a save is not required
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_db->isReadOnly() || m_db->filePath().isEmpty()) {
|
||||
return saveAs();
|
||||
}
|
||||
|
||||
blockAutoReload(true);
|
||||
// TODO: Make this async, but lock out the database widget to prevent re-entrance
|
||||
bool useAtomicSaves = config()->get("UseAtomicSaves", true).toBool();
|
||||
QString errorMessage;
|
||||
bool ok = m_db->save(&errorMessage, useAtomicSaves, config()->get("BackupBeforeSave").toBool());
|
||||
blockAutoReload(false);
|
||||
|
||||
if (ok) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (attempt >= 0 && attempt <= 2) {
|
||||
return save(attempt + 1);
|
||||
}
|
||||
|
||||
if (attempt > 2 && useAtomicSaves) {
|
||||
// Saving failed 3 times, issue a warning and attempt to resolve
|
||||
auto choice = MessageBox::question(this,
|
||||
tr("Disable safe saves?"),
|
||||
tr("KeePassXC has failed to save the database multiple times. "
|
||||
"This is likely caused by file sync services holding a lock on "
|
||||
"the save file.\nDisable safe saves and try again?"),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::Yes);
|
||||
if (choice == QMessageBox::Yes) {
|
||||
config()->set("UseAtomicSaves", false);
|
||||
return save(attempt + 1);
|
||||
}
|
||||
}
|
||||
|
||||
showMessage(tr("Writing the database failed.\n%1").arg(errorMessage), MessageWidget::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
void DatabaseWidget::showMessage(const QString& text,
|
||||
MessageWidget::MessageType type,
|
||||
bool showClosebutton,
|
||||
int autoHideTimeout)
|
||||
/**
|
||||
* Save database under a new user-selected filename.
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool DatabaseWidget::saveAs()
|
||||
{
|
||||
while (true) {
|
||||
QString oldFilePath = m_db->filePath();
|
||||
if (!QFileInfo(oldFilePath).exists()) {
|
||||
oldFilePath = QDir::toNativeSeparators(config()->get("LastDir", QDir::homePath()).toString()
|
||||
+ "/" + tr("Passwords").append(".kdbx"));
|
||||
}
|
||||
QString newFilePath = fileDialog()->getSaveFileName(
|
||||
this, tr("Save database as"), oldFilePath,
|
||||
tr("KeePass 2 Database").append(" (*.kdbx)"), nullptr, nullptr, "kdbx");
|
||||
|
||||
if (!newFilePath.isEmpty()) {
|
||||
// Ensure we don't recurse back into this function
|
||||
m_db->setReadOnly(false);
|
||||
m_db->setFilePath(newFilePath);
|
||||
|
||||
if (!save(-1)) {
|
||||
// Failed to save, try again
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Canceled file selection
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::showMessage(const QString& text, MessageWidget::MessageType type,
|
||||
bool showClosebutton, int autoHideTimeout)
|
||||
{
|
||||
m_messageWidget->setCloseButtonVisible(showClosebutton);
|
||||
m_messageWidget->showMessage(text, type, autoHideTimeout);
|
||||
@ -1454,26 +1547,6 @@ bool DatabaseWidget::isRecycleBinSelected() const
|
||||
return m_groupView->currentGroup() && m_groupView->currentGroup() == m_db->metadata()->recycleBin();
|
||||
}
|
||||
|
||||
QString DatabaseWidget::getDatabaseName() const
|
||||
{
|
||||
return m_databaseName;
|
||||
}
|
||||
|
||||
void DatabaseWidget::setDatabaseName(const QString& databaseName)
|
||||
{
|
||||
m_databaseName = databaseName;
|
||||
}
|
||||
|
||||
QString DatabaseWidget::getDatabaseFileName() const
|
||||
{
|
||||
return m_databaseFileName;
|
||||
}
|
||||
|
||||
void DatabaseWidget::setDatabaseFileName(const QString& databaseFileName)
|
||||
{
|
||||
m_databaseFileName = databaseFileName;
|
||||
}
|
||||
|
||||
void DatabaseWidget::emptyRecycleBin()
|
||||
{
|
||||
if (!isRecycleBinSelected()) {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -44,7 +44,7 @@ void DatabaseSettingsWidgetGeneral::initialize()
|
||||
m_ui->dbDescriptionEdit->setText(meta->description());
|
||||
m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled());
|
||||
m_ui->defaultUsernameEdit->setText(meta->defaultUserName());
|
||||
m_ui->compressionCheckbox->setChecked(m_db->compressionAlgo() != Database::CompressionNone);
|
||||
m_ui->compressionCheckbox->setChecked(m_db->compressionAlgorithm() != Database::CompressionNone);
|
||||
|
||||
if (meta->historyMaxItems() > -1) {
|
||||
m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems());
|
||||
@ -75,8 +75,8 @@ void DatabaseSettingsWidgetGeneral::showEvent(QShowEvent* event)
|
||||
|
||||
bool DatabaseSettingsWidgetGeneral::save()
|
||||
{
|
||||
m_db->setCompressionAlgo(m_ui->compressionCheckbox->isChecked() ? Database::CompressionGZip
|
||||
: Database::CompressionNone);
|
||||
m_db->setCompressionAlgorithm(m_ui->compressionCheckbox->isChecked() ? Database::CompressionGZip
|
||||
: Database::CompressionNone);
|
||||
Metadata* meta = m_db->metadata();
|
||||
|
||||
meta->setName(m_ui->dbNameEdit->text());
|
||||
|
@ -68,7 +68,7 @@ DatabaseSettingsWidgetMasterKey::~DatabaseSettingsWidgetMasterKey()
|
||||
{
|
||||
}
|
||||
|
||||
void DatabaseSettingsWidgetMasterKey::load(Database* db)
|
||||
void DatabaseSettingsWidgetMasterKey::load(QSharedPointer<Database> db)
|
||||
{
|
||||
DatabaseSettingsWidget::load(db);
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -49,7 +49,7 @@ void AutoTypeAssociationsModel::setAutoTypeAssociations(AutoTypeAssociations* au
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void AutoTypeAssociationsModel::setEntry(const Entry* entry)
|
||||
void AutoTypeAssociationsModel::setEntry(Entry* entry)
|
||||
{
|
||||
m_entry = entry;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
#include "ElidedLabel.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QResizeEvent>
|
||||
|
||||
namespace
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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"));
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user