diff options
-rw-r--r-- | src/core/FileWatcher.cpp | 127 | ||||
-rw-r--r-- | src/core/FileWatcher.h | 19 | ||||
-rw-r--r-- | src/keeshare/ShareObserver.cpp | 1210 | ||||
-rw-r--r-- | src/keeshare/ShareObserver.h | 2 |
4 files changed, 719 insertions, 639 deletions
diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp index 4873adbf4..d57d7de52 100644 --- a/src/core/FileWatcher.cpp +++ b/src/core/FileWatcher.cpp @@ -37,8 +37,7 @@ DelayingFileWatcher::DelayingFileWatcher(QObject* parent) { connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged())); connect(&m_fileUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges())); - connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), SIGNAL(fileChanged())); - + connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), this, SIGNAL(fileChanged())); m_fileChangeDelayTimer.setSingleShot(true); m_fileUnblockTimer.setSingleShot(true); } @@ -122,6 +121,7 @@ BulkFileWatcher::BulkFileWatcher(QObject* parent) connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(handleFileChanged(QString))); connect(&m_fileWatcher, SIGNAL(directoryChanged(QString)), SLOT(handleDirectoryChanged(QString))); connect(&m_fileWatchUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges())); + connect(&m_pendingSignalsTimer, SIGNAL(timeout()), this, SLOT(emitSignals())); m_fileWatchUnblockTimer.setSingleShot(true); } @@ -132,7 +132,7 @@ void BulkFileWatcher::clear() m_fileWatcher.removePath(info.absoluteFilePath()); m_fileWatcher.removePath(info.absolutePath()); } - m_filePaths.clear(); + m_watchedPaths.clear(); m_watchedFilesInDirectory.clear(); m_ignoreFilesChangess.clear(); } @@ -140,70 +140,127 @@ void BulkFileWatcher::clear() void BulkFileWatcher::removePath(const QString& path) { const QFileInfo info(path); - m_fileWatcher.removePath(info.absoluteFilePath()); - m_fileWatcher.removePath(info.absolutePath()); - m_filePaths.remove(info.absoluteFilePath()); - m_filePaths.remove(info.absolutePath()); - m_watchedFilesInDirectory[info.absolutePath()].remove(info.absoluteFilePath()); + const QString filePath = info.absoluteFilePath(); + const QString directoryPath = info.absolutePath(); + m_watchedFilesInDirectory[directoryPath].remove(filePath); + m_fileWatcher.removePath(filePath); + m_watchedPaths.remove(filePath); + if (m_watchedFilesInDirectory[directoryPath].isEmpty()) { + m_fileWatcher.removePath(directoryPath); + m_watchedPaths.remove(directoryPath); + m_watchedFilesInDirectory.remove(directoryPath); + } } void BulkFileWatcher::addPath(const QString& path) { const QFileInfo info(path); - m_fileWatcher.addPath(info.absoluteFilePath()); - m_fileWatcher.addPath(info.absolutePath()); - m_filePaths.insert(info.absoluteFilePath()); - m_filePaths.insert(info.absolutePath()); - m_watchedFilesInDirectory[info.absolutePath()][info.absoluteFilePath()] = info.exists(); -} - -void BulkFileWatcher::restart(const QString& path) -{ - const QFileInfo info(path); - Q_ASSERT(m_filePaths.contains(info.absoluteFilePath())); - Q_ASSERT(m_filePaths.contains(info.absolutePath())); - m_fileWatcher.addPath(info.absoluteFilePath()); - m_fileWatcher.addPath(info.absolutePath()); + const QString filePath = info.absoluteFilePath(); + const QString directoryPath = info.absolutePath(); + if (!m_watchedPaths.value(filePath)) { + const bool fileSuccess = m_fileWatcher.addPath(filePath); + m_watchedPaths[filePath] = fileSuccess; + } + if (!m_watchedPaths.value(directoryPath)) { + const bool directorySuccess = m_fileWatcher.addPath(directoryPath); + m_watchedPaths[directoryPath] = directorySuccess; + } + m_watchedFilesInDirectory[directoryPath][filePath] = info.exists(); } void BulkFileWatcher::handleFileChanged(const QString& path) { + const QFileInfo info(path); + const QString filePath = info.absoluteFilePath(); + const QString directoryPath = info.absolutePath(); + const QMap<QString, bool>& watchedFiles = m_watchedFilesInDirectory[directoryPath]; + const bool created = !watchedFiles[filePath] && info.exists(); + const bool deleted = watchedFiles[filePath] && !info.exists(); + const bool changed = !created && !deleted; addPath(path); - const QFileInfo info(path); if (m_ignoreFilesChangess[info.canonicalFilePath()] > Clock::currentDateTimeUtc()) { // changes are blocked return; } - - emit fileChanged(path); + if (created) { + qDebug("File created %s", qPrintable(path)); + scheduleSignal(Created, filePath); + } + if (changed) { + qDebug("File changed %s", qPrintable(path)); + scheduleSignal(Updated, filePath); + } + if (deleted) { + qDebug("File removed %s", qPrintable(path)); + scheduleSignal(Removed, filePath); + } } void BulkFileWatcher::handleDirectoryChanged(const QString& path) { qDebug("Directory changed %s", qPrintable(path)); const QFileInfo directoryInfo(path); - const QMap<QString, bool>& watchedFiles = m_watchedFilesInDirectory[directoryInfo.absoluteFilePath()]; + const QString directoryPath = directoryInfo.absoluteFilePath(); + QMap<QString, bool>& watchedFiles = m_watchedFilesInDirectory[directoryPath]; for (const QString& filename : watchedFiles.keys()) { const QFileInfo fileInfo(filename); - const bool existed = watchedFiles[fileInfo.absoluteFilePath()]; + const QString filePath = fileInfo.absoluteFilePath(); + const bool existed = watchedFiles[filePath]; if (!fileInfo.exists() && existed) { - qDebug("Remove watch file %s", qPrintable(fileInfo.absoluteFilePath())); - m_fileWatcher.removePath(fileInfo.absolutePath()); - emit fileRemoved(fileInfo.absoluteFilePath()); + qDebug("Remove watch file %s", qPrintable(filePath)); + m_fileWatcher.removePath(filePath); + m_watchedPaths.remove(filePath); + watchedFiles.remove(filePath); + scheduleSignal(Removed, filePath); } if (!existed && fileInfo.exists()) { - qDebug("Add watch file %s", qPrintable(fileInfo.absoluteFilePath())); - m_fileWatcher.addPath(fileInfo.absolutePath()); - emit fileCreated(fileInfo.absoluteFilePath()); + qDebug("Add watch file %s", qPrintable(filePath)); + if (!m_watchedPaths.value(filePath)) { + const bool success = m_fileWatcher.addPath(filePath); + m_watchedPaths[filePath] = success; + watchedFiles[filePath] = fileInfo.exists(); + } + scheduleSignal(Created, filePath); } if (existed && fileInfo.exists()) { + // this case is handled using qDebug("Refresh watch file %s", qPrintable(fileInfo.absoluteFilePath())); m_fileWatcher.removePath(fileInfo.absolutePath()); m_fileWatcher.addPath(fileInfo.absolutePath()); - emit fileChanged(fileInfo.absoluteFilePath()); + scheduleSignal(Updated, filePath); + } + m_watchedFilesInDirectory[directoryPath][filePath] = fileInfo.exists(); + } +} + +void BulkFileWatcher::emitSignals() +{ + QMap<QString, QList<Signal>> queued; + m_pendingSignals.swap(queued); + for (const auto& path : queued.keys()){ + const auto &signal = queued[path]; + if (signal.last() == Removed) { + emit fileRemoved(path); + continue; + } + if (signal.first() == Created){ + emit fileCreated(path); + continue; } - m_watchedFilesInDirectory[fileInfo.absolutePath()][fileInfo.absoluteFilePath()] = fileInfo.exists(); + emit fileChanged(path); + } +} + +void BulkFileWatcher::scheduleSignal(Signal signal, const QString &path) +{ + // we need to collect signals since the file watcher API may send multiple signals for a "single" change + // therefore we wait until the event loop finished before starting to import any changes + const QString filePath = QFileInfo(path).absoluteFilePath(); + m_pendingSignals[filePath] << signal; + + if (!m_pendingSignalsTimer.isActive()) { + m_pendingSignalsTimer.start(); } } diff --git a/src/core/FileWatcher.h b/src/core/FileWatcher.h index de7dbb1c2..1ef46785c 100644 --- a/src/core/FileWatcher.h +++ b/src/core/FileWatcher.h @@ -57,6 +57,12 @@ private: class BulkFileWatcher : public QObject { Q_OBJECT + + enum Signal { + Created, + Updated, + Removed + }; public: explicit BulkFileWatcher(QObject* parent = nullptr); @@ -65,7 +71,6 @@ public: void removePath(const QString& path); void addPath(const QString& path); - void restart(const QString& path); void ignoreFileChanges(const QString& path); @@ -80,13 +85,21 @@ public slots: private slots: void handleFileChanged(const QString& path); void handleDirectoryChanged(const QString& path); + void emitSignals(); + +private: + void scheduleSignal(Signal event, const QString &path); private: - QSet<QString> m_filePaths; + QMap<QString, bool> m_watchedPaths; QMap<QString, QDateTime> m_ignoreFilesChangess; QFileSystemWatcher m_fileWatcher; QMap<QString, QMap<QString, bool>> m_watchedFilesInDirectory; - QTimer m_fileWatchUnblockTimer; // needed for Import/Export-References + // needed for Import/Export-References to prevent update after self-write + QTimer m_fileWatchUnblockTimer; + // needed to tolerate multiple signals for same event + QTimer m_pendingSignalsTimer; + QMap<QString, QList<Signal>> m_pendingSignals; }; #endif // KEEPASSXC_FILEWATCHER_H diff --git a/src/keeshare/ShareObserver.cpp b/src/keeshare/ShareObserver.cpp index bb87c1c75..036413e6b 100644 --- a/src/keeshare/ShareObserver.cpp +++ b/src/keeshare/ShareObserver.cpp @@ -56,116 +56,111 @@ static const QString KeeShare_Container("container.share.kdbx"); enum Trust { - Invalid, - Own, - UntrustedForever, - UntrustedOnce, - TrustedOnce, - TrustedForever, + Invalid, + Own, + UntrustedForever, + UntrustedOnce, + TrustedOnce, + TrustedForever, }; bool isOfExportType(const QFileInfo &fileInfo, const QString type) { - return fileInfo.fileName().endsWith(type, Qt::CaseInsensitive); + return fileInfo.fileName().endsWith(type, Qt::CaseInsensitive); } QPair<Trust, KeeShareSettings::Certificate> check(QByteArray& data, - const KeeShareSettings::Reference& reference, - const KeeShareSettings::Certificate& ownCertificate, - const QList<KeeShareSettings::ScopedCertificate>& knownCertificates, - const KeeShareSettings::Sign& sign) + const KeeShareSettings::Reference& reference, + const KeeShareSettings::Certificate& ownCertificate, + const QList<KeeShareSettings::ScopedCertificate>& knownCertificates, + const KeeShareSettings::Sign& sign) { - KeeShareSettings::Certificate certificate; - if (!sign.signature.isEmpty()) { - certificate = sign.certificate; - auto key = sign.certificate.sshKey(); - key.openKey(QString()); - const auto signer = Signature(); - if (!signer.verify(data, sign.signature, key)) { - qCritical("Invalid signature for sharing container %s.", qPrintable(reference.path)); - return {Invalid, KeeShareSettings::Certificate()}; - } - - if (ownCertificate.key == sign.certificate.key) { - return {Own, ownCertificate }; - } - } - enum Scope { Invalid, Global, Local }; - Scope scope = Invalid; - KeeShareSettings::Trust trusted = KeeShareSettings::Trust::Ask; - for (const auto& scopedCertificate : knownCertificates) { - if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) { - // Global scope is overwritten by local scope - scope = Global; - trusted = scopedCertificate.trust; - } - if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) { - scope = Local; - trusted = scopedCertificate.trust; - break; - } - } - if (scope != Invalid && trusted != KeeShareSettings::Trust::Ask){ - // we introduce now scopes if there is a global - return {trusted == KeeShareSettings::Trust::Trusted ? TrustedForever : UntrustedForever, certificate}; - } - - QMessageBox warning; - if (sign.signature.isEmpty()){ - warning.setIcon(QMessageBox::Warning); - warning.setWindowTitle(ShareObserver::tr("Import from container without signature")); - warning.setText(ShareObserver::tr("We cannot verify the source of the shared container because it is not signed. Do you really want to import from %1?") - .arg(reference.path)); - } - else { - warning.setIcon(QMessageBox::Question); - warning.setWindowTitle(ShareObserver::tr("Import from container with certificate")); - warning.setText(ShareObserver::tr("Do you want to trust %1 with the fingerprint of %2 from %3") - .arg(certificate.signer, certificate.fingerprint(), reference.path)); - } - auto untrustedOnce = warning.addButton(ShareObserver::tr("Not this time"), QMessageBox::ButtonRole::NoRole); - auto untrustedForever = warning.addButton(ShareObserver::tr("Never"), QMessageBox::ButtonRole::NoRole); - auto trustedForever = warning.addButton(ShareObserver::tr("Always"), QMessageBox::ButtonRole::YesRole); - auto trustedOnce = warning.addButton(ShareObserver::tr("Just this time"), QMessageBox::ButtonRole::YesRole); - warning.setDefaultButton(untrustedOnce); - warning.exec(); - if (warning.clickedButton() == trustedForever){ - return {TrustedForever, certificate }; - } - if (warning.clickedButton() == trustedOnce){ - return {TrustedOnce, certificate}; - } - if (warning.clickedButton() == untrustedOnce){ - return {UntrustedOnce, certificate }; - } - if (warning.clickedButton() == untrustedForever){ - return {UntrustedForever, certificate }; - } - return {UntrustedOnce, certificate }; + KeeShareSettings::Certificate certificate; + if (!sign.signature.isEmpty()) { + certificate = sign.certificate; + auto key = sign.certificate.sshKey(); + key.openKey(QString()); + const auto signer = Signature(); + if (!signer.verify(data, sign.signature, key)) { + qCritical("Invalid signature for sharing container %s.", qPrintable(reference.path)); + return {Invalid, KeeShareSettings::Certificate()}; + } + + if (ownCertificate.key == sign.certificate.key) { + return {Own, ownCertificate }; + } + } + enum Scope { Invalid, Global, Local }; + Scope scope = Invalid; + KeeShareSettings::Trust trusted = KeeShareSettings::Trust::Ask; + for (const auto& scopedCertificate : knownCertificates) { + if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) { + // Global scope is overwritten by local scope + scope = Global; + trusted = scopedCertificate.trust; + } + if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) { + scope = Local; + trusted = scopedCertificate.trust; + break; + } + } + if (scope != Invalid && trusted != KeeShareSettings::Trust::Ask){ + // we introduce now scopes if there is a global + return {trusted == KeeShareSettings::Trust::Trusted ? TrustedForever : UntrustedForever, certificate}; + } + + QMessageBox warning; + if (sign.signature.isEmpty()){ + warning.setIcon(QMessageBox::Warning); + warning.setWindowTitle(ShareObserver::tr("Import from container without signature")); + warning.setText(ShareObserver::tr("We cannot verify the source of the shared container because it is not signed. Do you really want to import from %1?") + .arg(reference.path)); + } + else { + warning.setIcon(QMessageBox::Question); + warning.setWindowTitle(ShareObserver::tr("Import from container with certificate")); + warning.setText(ShareObserver::tr("Do you want to trust %1 with the fingerprint of %2 from %3") + .arg(certificate.signer, certificate.fingerprint(), reference.path)); + } + auto untrustedOnce = warning.addButton(ShareObserver::tr("Not this time"), QMessageBox::ButtonRole::NoRole); + auto untrustedForever = warning.addButton(ShareObserver::tr("Never"), QMessageBox::ButtonRole::NoRole); + auto trustedForever = warning.addButton(ShareObserver::tr("Always"), QMessageBox::ButtonRole::YesRole); + auto trustedOnce = warning.addButton(ShareObserver::tr("Just this time"), QMessageBox::ButtonRole::YesRole); + warning.setDefaultButton(untrustedOnce); + warning.exec(); + if (warning.clickedButton() == trustedForever){ + return {TrustedForever, certificate }; + } + if (warning.clickedButton() == trustedOnce){ + return {TrustedOnce, certificate}; + } + if (warning.clickedButton() == untrustedOnce){ + return {UntrustedOnce, certificate }; + } + if (warning.clickedButton() == untrustedForever){ + return {UntrustedForever, certificate }; + } + return {UntrustedOnce, certificate }; } } // End Namespace ShareObserver::ShareObserver(QSharedPointer<Database> db, QObject* parent) - : QObject(parent) - , m_db(std::move(db)) - , m_fileWatcher(new BulkFileWatcher(this)) + : QObject(parent) + , m_db(std::move(db)) + , m_fileWatcher(new BulkFileWatcher(this)) { - connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(handleDatabaseChanged())); + connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(handleDatabaseChanged())); - connect(m_db.data(), SIGNAL(databaseModified()), SLOT(handleDatabaseChanged())); - connect(m_db.data(), SIGNAL(databaseSaved()), SLOT(handleDatabaseSaved())); + connect(m_db.data(), SIGNAL(databaseModified()), SLOT(handleDatabaseChanged())); + connect(m_db.data(), SIGNAL(databaseSaved()), SLOT(handleDatabaseSaved())); - connect(m_fileWatcher, SIGNAL(fileCreated(QString)), SLOT(handleFileUpdated(QString))); - connect(m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(handleFileUpdated(QString))); - connect(m_fileWatcher, SIGNAL(fileRemoved(QString)), SLOT(handleFileUpdated(QString))); + connect(m_fileWatcher, SIGNAL(fileCreated(QString)), SLOT(handleFileCreated(QString))); + connect(m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(handleFileUpdated(QString))); + connect(m_fileWatcher, SIGNAL(fileRemoved(QString)), SLOT(handleFileDeleted(QString))); - const auto active = KeeShare::active(); - if (!active.in && !active.out) { - deinitialize(); - } else { - reinitialize(); - } + handleDatabaseChanged(); } ShareObserver::~ShareObserver() @@ -174,604 +169,617 @@ ShareObserver::~ShareObserver() void ShareObserver::deinitialize() { - m_fileWatcher->clear(); - m_groupToReference.clear(); - m_referenceToGroup.clear(); + m_fileWatcher->clear(); + m_groupToReference.clear(); + m_referenceToGroup.clear(); } void ShareObserver::reinitialize() { - struct Update - { - Group* group; - KeeShareSettings::Reference oldReference; - KeeShareSettings::Reference newReference; - }; - - const auto active = KeeShare::active(); - QList<Update> updated; - QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true); - for (Group* group : groups) { - Update couple{group, m_groupToReference.value(group), KeeShare::referenceOf(group)}; - if (couple.oldReference == couple.newReference) { - continue; - } - - m_groupToReference.remove(couple.group); - m_referenceToGroup.remove(couple.oldReference); - m_shareToGroup.remove(couple.oldReference.path); - if (couple.newReference.isValid() && ((active.in && couple.newReference.isImporting()) - || (active.out && couple.newReference.isExporting()))) { - m_groupToReference[couple.group] = couple.newReference; - m_referenceToGroup[couple.newReference] = couple.group; - m_shareToGroup[couple.newReference.path] = couple.group; - } - updated << couple; - } - - QStringList success; - QStringList warning; - QStringList error; - for (const auto& update : updated) { - if (!update.oldReference.path.isEmpty()) { - m_fileWatcher->removePath(update.oldReference.path); - } - - if (!update.newReference.path.isEmpty() && update.newReference.type != KeeShareSettings::Inactive) { - m_fileWatcher->addPath(update.newReference.path); - } - - if (update.newReference.isImporting()) { - const auto result = this->importFromReferenceContainer(update.newReference.path); - if (!result.isValid()) { - // tolerable result - blocked import or missing source - continue; - } - - if (result.isError()) { - error << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message); - } else if (result.isWarning()) { - warning << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message); - } else if (result.isInfo()) { - success << tr("Import from %1 successful (%2)").arg(result.path).arg(result.message); - } else { - success << tr("Imported from %1").arg(result.path); - } - } - } - - notifyAbout(success, warning, error); + struct Update + { + Group* group; + KeeShareSettings::Reference oldReference; + KeeShareSettings::Reference newReference; + }; + + const auto active = KeeShare::active(); + QList<Update> updated; + QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true); + for (Group* group : groups) { + Update couple{group, m_groupToReference.value(group), KeeShare::referenceOf(group)}; + if (couple.oldReference == couple.newReference) { + continue; + } + + m_groupToReference.remove(couple.group); + m_referenceToGroup.remove(couple.oldReference); + m_shareToGroup.remove(couple.oldReference.path); + if (couple.newReference.isValid() && ((active.in && couple.newReference.isImporting()) + || (active.out && couple.newReference.isExporting()))) { + m_groupToReference[couple.group] = couple.newReference; + m_referenceToGroup[couple.newReference] = couple.group; + m_shareToGroup[couple.newReference.path] = couple.group; + } + updated << couple; + } + + QStringList success; + QStringList warning; + QStringList error; + for (const auto& update : updated) { + if (!update.oldReference.path.isEmpty()) { + m_fileWatcher->removePath(update.oldReference.path); + } + + if (!update.newReference.path.isEmpty() && update.newReference.type != KeeShareSettings::Inactive) { + m_fileWatcher->addPath(update.newReference.path); + } + + if (update.newReference.isImporting()) { + const auto result = this->importFromReferenceContainer(update.newReference.path); + if (!result.isValid()) { + // tolerable result - blocked import or missing source + continue; + } + + if (result.isError()) { + error << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message); + } else if (result.isWarning()) { + warning << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message); + } else if (result.isInfo()) { + success << tr("Import from %1 successful (%2)").arg(result.path).arg(result.message); + } else { + success << tr("Imported from %1").arg(result.path); + } + } + } + + notifyAbout(success, warning, error); } void ShareObserver::notifyAbout(const QStringList& success, const QStringList& warning, const QStringList& error) { - if (error.isEmpty() && warning.isEmpty() && success.isEmpty()) { - return; - } - - MessageWidget::MessageType type = MessageWidget::Positive; - if (!warning.isEmpty()) { - type = MessageWidget::Warning; - } - if (!error.isEmpty()) { - type = MessageWidget::Error; - } - emit sharingMessage((success + warning + error).join("\n"), type); + if (error.isEmpty() && warning.isEmpty() && success.isEmpty()) { + return; + } + + MessageWidget::MessageType type = MessageWidget::Positive; + if (!warning.isEmpty()) { + type = MessageWidget::Warning; + } + if (!error.isEmpty()) { + type = MessageWidget::Error; + } + emit sharingMessage((success + warning + error).join("\n"), type); } void ShareObserver::handleDatabaseChanged() { - if (!m_db) { - Q_ASSERT(m_db); - return; - } - const auto active = KeeShare::active(); - if (!active.out && !active.in) { - deinitialize(); - } else { - reinitialize(); - } + if (!m_db) { + Q_ASSERT(m_db); + return; + } + const auto active = KeeShare::active(); + if (!active.out && !active.in) { + deinitialize(); + } else { + reinitialize(); + } +} + +void ShareObserver::handleFileCreated(const QString& path) +{ + // there is currently no difference in handling an added share or updating from one + this->handleFileUpdated(path); +} + +void ShareObserver::handleFileDeleted(const QString& path) +{ + Q_UNUSED(path); + // There is nothing we can or should do for now, ignore deletion } void ShareObserver::handleFileUpdated(const QString& path) { - const Result result = this->importFromReferenceContainer(path); - if (!result.isValid()) { - return; - } - QStringList success; - QStringList warning; - QStringList error; - if (result.isError()) { - error << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message); - } else if (result.isWarning()) { - warning << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message); - } else if (result.isInfo()) { - success << tr("Import from %1 successful (%2)").arg(result.path).arg(result.message); - } else { - success << tr("Imported from %1").arg(result.path); - } - notifyAbout(success, warning, error); + const Result result = this->importFromReferenceContainer(path); + if (!result.isValid()) { + return; + } + QStringList success; + QStringList warning; + QStringList error; + if (result.isError()) { + error << tr("Import from %1 failed (%2)").arg(result.path, result.message); + } else if (result.isWarning()) { + warning << tr("Import from %1 failed (%2)").arg(result.path, result.message); + } else if (result.isInfo()) { + success << tr("Import from %1 successful (%2)").arg(result.path, result.message); + } else { + success << tr("Imported from %1").arg(result.path); + } + notifyAbout(success, warning, error); } ShareObserver::Result ShareObserver::importSingedContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup) { #if !defined(WITH_XC_KEESHARE_SECURE) - Q_UNUSED(targetGroup); - return { reference.path, Result::Warning, tr("Signed share container are not supported - import prevented") }; + Q_UNUSED(targetGroup); + return { reference.path, Result::Warning, tr("Signed share container are not supported - import prevented") }; #else - QuaZip zip(reference.path); - if (!zip.open(QuaZip::mdUnzip)) { - qCritical("Unable to open file %s.", qPrintable(reference.path)); - return {reference.path, Result::Error, tr("File is not readable")}; - } - const auto expected = QSet<QString>() << KeeShare_Signature << KeeShare_Container; - const auto files = zip.getFileInfoList(); - QSet<QString> actual; - for (const auto& file : files) { - actual << file.name; - } - if (expected != actual) { - qCritical("Invalid sharing container %s.", qPrintable(reference.path)); - return {reference.path, Result::Error, tr("Invalid sharing container")}; - } - - zip.setCurrentFile(KeeShare_Signature); - QuaZipFile signatureFile(&zip); - signatureFile.open(QuaZipFile::ReadOnly); - QTextStream stream(&signatureFile); - - const auto sign = KeeShareSettings::Sign::deserialize(stream.readAll()); - signatureFile.close(); - - zip.setCurrentFile(KeeShare_Container); - QuaZipFile databaseFile(&zip); - databaseFile.open(QuaZipFile::ReadOnly); - auto payload = databaseFile.readAll(); - databaseFile.close(); - QBuffer buffer(&payload); - buffer.open(QIODevice::ReadOnly); - - KeePass2Reader reader; - auto key = QSharedPointer<CompositeKey>::create(); - key->addKey(QSharedPointer<PasswordKey>::create(reference.password)); - auto sourceDb = QSharedPointer<Database>::create(); - if (!reader.readDatabase(&buffer, key, sourceDb.data())) { - qCritical("Error while parsing the database: %s", qPrintable(reader.errorString())); - return {reference.path, Result::Error, reader.errorString()}; - } - - auto foreign = KeeShare::foreign(); - auto own = KeeShare::own(); - auto trust = check(payload, reference, own.certificate, foreign.certificates, sign); - switch (trust.first) { - case Invalid: - qWarning("Prevent untrusted import"); - return {reference.path, Result::Error, tr("Untrusted import prevented")}; - - case UntrustedForever: - case TrustedForever: { - bool found = false; - const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted; - for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) { - if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) { - scopedCertificate.certificate.signer = trust.second.signer; - scopedCertificate.path = reference.path; - scopedCertificate.trust = trusted; - found = true; - } - } - if (!found) { - foreign.certificates << KeeShareSettings::ScopedCertificate{ reference.path, trust.second, trusted}; - // we need to update with the new signer - KeeShare::setForeign(foreign); - } - if (trust.first == TrustedForever) { - qDebug("Synchronize %s %s with %s", - qPrintable(reference.path), - qPrintable(targetGroup->name()), - qPrintable(sourceDb->rootGroup()->name())); - Merger merger(sourceDb->rootGroup(), targetGroup); - merger.setForcedMergeMode(Group::Synchronize); - const bool changed = merger.merge(); - if (changed) { - return {reference.path, Result::Success, tr("Successful signed import")}; - } - } - // Silent ignore of untrusted import or unchanging import - return {}; - } - case TrustedOnce: - case Own: { - qDebug("Synchronize %s %s with %s", - qPrintable(reference.path), - qPrintable(targetGroup->name()), - qPrintable(sourceDb->rootGroup()->name())); - Merger merger(sourceDb->rootGroup(), targetGroup); - merger.setForcedMergeMode(Group::Synchronize); - const bool changed = merger.merge(); - if (changed) { - return {reference.path, Result::Success, tr("Successful signed import")}; - } - return {}; - } - default: - Q_ASSERT(false); - return {reference.path, Result::Error, tr("Unexpected error")}; - } + QuaZip zip(reference.path); + if (!zip.open(QuaZip::mdUnzip)) { + qCritical("Unable to open file %s.", qPrintable(reference.path)); + return {reference.path, Result::Error, tr("File is not readable")}; + } + const auto expected = QSet<QString>() << KeeShare_Signature << KeeShare_Container; + const auto files = zip.getFileInfoList(); + QSet<QString> actual; + for (const auto& file : files) { + actual << file.name; + } + if (expected != actual) { + qCritical("Invalid sharing container %s.", qPrintable(reference.path)); + return {reference.path, Result::Error, tr("Invalid sharing container")}; + } + + zip.setCurrentFile(KeeShare_Signature); + QuaZipFile signatureFile(&zip); + signatureFile.open(QuaZipFile::ReadOnly); + QTextStream stream(&signatureFile); + + const auto sign = KeeShareSettings::Sign::deserialize(stream.readAll()); + signatureFile.close(); + + zip.setCurrentFile(KeeShare_Container); + QuaZipFile databaseFile(&zip); + databaseFile.open(QuaZipFile::ReadOnly); + auto payload = databaseFile.readAll(); + databaseFile.close(); + QBuffer buffer(&payload); + buffer.open(QIODevice::ReadOnly); + + KeePass2Reader reader; + auto key = QSharedPointer<CompositeKey>::create(); + key->addKey(QSharedPointer<PasswordKey>::create(reference.password)); + auto sourceDb = QSharedPointer<Database>::create(); + if (!reader.readDatabase(&buffer, key, sourceDb.data())) { + qCritical("Error while parsing the database: %s", qPrintable(reader.errorString())); + return {reference.path, Result::Error, reader.errorString()}; + } + + auto foreign = KeeShare::foreign(); + auto own = KeeShare::own(); + auto trust = check(payload, reference, own.certificate, foreign.certificates, sign); + switch (trust.first) { + case Invalid: + qWarning("Prevent untrusted import"); + return {reference.path, Result::Error, tr("Untrusted import prevented")}; + + case UntrustedForever: + case TrustedForever: { + bool found = false; + const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted; + for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) { + if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) { + scopedCertificate.certificate.signer = trust.second.signer; + scopedCertificate.path = reference.path; + scopedCertificate.trust = trusted; + found = true; + } + } + if (!found) { + foreign.certificates << KeeShareSettings::ScopedCertificate{ reference.path, trust.second, trusted}; + // we need to update with the new signer + KeeShare::setForeign(foreign); + } + if (trust.first == TrustedForever) { + qDebug("Synchronize %s %s with %s", + qPrintable(reference.path), + qPrintable(targetGroup->name()), + qPrintable(sourceDb->rootGroup()->name())); + Merger merger(sourceDb->rootGroup(), targetGroup); + merger.setForcedMergeMode(Group::Synchronize); + const bool changed = merger.merge(); + if (changed) { + return {reference.path, Result::Success, tr("Successful signed import")}; + } + } + // Silent ignore of untrusted import or unchanging import + return {}; + } + case TrustedOnce: + case Own: { + qDebug("Synchronize %s %s with %s", + qPrintable(reference.path), + qPrintable(targetGroup->name()), + qPrintable(sourceDb->rootGroup()->name())); + Merger merger(sourceDb->rootGroup(), targetGroup); + merger.setForcedMergeMode(Group::Synchronize); + const bool changed = merger.merge(); + if (changed) { + return {reference.path, Result::Success, tr("Successful signed import")}; + } + return {}; + } + default: + Q_ASSERT(false); + return {reference.path, Result::Error, tr("Unexpected error")}; + } #endif } ShareObserver::Result ShareObserver::importUnsignedContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup) { #if !defined(WITH_XC_KEESHARE_INSECURE) - Q_UNUSED(targetGroup); - return {reference.path, Result::Warning, tr("Unsigned share container are not supported - import prevented")}; + Q_UNUSED(targetGroup); + return {reference.path, Result::Warning, tr("Unsigned share container are not supported - import prevented")}; #else - QFile file(reference.path); - if (!file.open(QIODevice::ReadOnly)){ - qCritical("Unable to open file %s.", qPrintable(reference.path)); - return {reference.path, Result::Error, tr("File is not readable")}; - } - auto payload = file.readAll(); - file.close(); - QBuffer buffer(&payload); - buffer.open(QIODevice::ReadOnly); - - KeePass2Reader reader; - auto key = QSharedPointer<CompositeKey>::create(); - key->addKey(QSharedPointer<PasswordKey>::create(reference.password)); - auto sourceDb = QSharedPointer<Database>::create(); - if (!reader.readDatabase(&buffer, key, sourceDb.data())) { - qCritical("Error while parsing the database: %s", qPrintable(reader.errorString())); - return {reference.path, Result::Error, reader.errorString()}; - } - - auto foreign = KeeShare::foreign(); - const auto own = KeeShare::own(); - const auto sign = KeeShareSettings::Sign(); // invalid sign - auto trust = check(payload, reference, own.certificate, foreign.certificates, sign); - switch(trust.first) { - case UntrustedForever: - case TrustedForever: { - bool found = false; - const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted; - for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) { - if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) { - scopedCertificate.certificate.signer = trust.second.signer; - scopedCertificate.path = reference.path; - scopedCertificate.trust = trusted; - found = true; - } - } - if (!found) { - foreign.certificates << KeeShareSettings::ScopedCertificate{ reference.path, trust.second, trusted}; - // we need to update with the new signer - KeeShare::setForeign(foreign); - } - if (trust.first == TrustedForever) { - qDebug("Synchronize %s %s with %s", - qPrintable(reference.path), - qPrintable(targetGroup->name()), - qPrintable(sourceDb->rootGroup()->name())); - Merger merger(sourceDb->rootGroup(), targetGroup); - merger.setForcedMergeMode(Group::Synchronize); - const bool changed = merger.merge(); - if (changed) { - return {reference.path, Result::Success, tr("Successful signed import")}; - } - } - return {}; - } - - case TrustedOnce: { - qDebug("Synchronize %s %s with %s", - qPrintable(reference.path), - qPrintable(targetGroup->name()), - qPrintable(sourceDb->rootGroup()->name())); - Merger merger(sourceDb->rootGroup(), targetGroup); - merger.setForcedMergeMode(Group::Synchronize); - const bool changed = merger.merge(); - if (changed) { - return {reference.path, Result::Success, tr("Successful unsigned import")}; - } - return {}; - } - default: - qWarning("Prevent untrusted import"); - return {reference.path, Result::Warning, tr("Untrusted import prevented")}; - } + QFile file(reference.path); + if (!file.open(QIODevice::ReadOnly)){ + qCritical("Unable to open file %s.", qPrintable(reference.path)); + return {reference.path, Result::Error, tr("File is not readable")}; + } + auto payload = file.readAll(); + file.close(); + QBuffer buffer(&payload); + buffer.open(QIODevice::ReadOnly); + + KeePass2Reader reader; + auto key = QSharedPointer<CompositeKey>::create(); + key->addKey(QSharedPointer<PasswordKey>::create(reference.password)); + auto sourceDb = QSharedPointer<Database>::create(); + if (!reader.readDatabase(&buffer, key, sourceDb.data())) { + qCritical("Error while parsing the database: %s", qPrintable(reader.errorString())); + return {reference.path, Result::Error, reader.errorString()}; + } + + auto foreign = KeeShare::foreign(); + const auto own = KeeShare::own(); + const auto sign = KeeShareSettings::Sign(); // invalid sign + auto trust = check(payload, reference, own.certificate, foreign.certificates, sign); + switch(trust.first) { + case UntrustedForever: + case TrustedForever: { + bool found = false; + const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted; + for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) { + if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) { + scopedCertificate.certificate.signer = trust.second.signer; + scopedCertificate.path = reference.path; + scopedCertificate.trust = trusted; + found = true; + } + } + if (!found) { + foreign.certificates << KeeShareSettings::ScopedCertificate{ reference.path, trust.second, trusted}; + // we need to update with the new signer + KeeShare::setForeign(foreign); + } + if (trust.first == TrustedForever) { + qDebug("Synchronize %s %s with %s", + qPrintable(reference.path), + qPrintable(targetGroup->name()), + qPrintable(sourceDb->rootGroup()->name())); + Merger merger(sourceDb->rootGroup(), targetGroup); + merger.setForcedMergeMode(Group::Synchronize); + const bool changed = merger.merge(); + if (changed) { + return {reference.path, Result::Success, tr("Successful signed import")}; + } + } + return {}; + } + + case TrustedOnce: { + qDebug("Synchronize %s %s with %s", + qPrintable(reference.path), + qPrintable(targetGroup->name()), + qPrintable(sourceDb->rootGroup()->name())); + Merger merger(sourceDb->rootGroup(), targetGroup); + merger.setForcedMergeMode(Group::Synchronize); + const bool changed = merger.merge(); + if (changed) { + return {reference.path, Result::Success, tr("Successful unsigned import")}; + } + return {}; + } + default: + qWarning("Prevent untrusted import"); + return {reference.path, Result::Warning, tr("Untrusted import prevented")}; + } #endif } ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup) { - const QFileInfo info(reference.path); - if (!info.exists()) { - qCritical("File %s does not exist.", qPrintable(info.absoluteFilePath())); - return {reference.path, Result::Warning, tr("File does not exist")}; - } - - if (isOfExportType(info, KeeShare::signedContainerFileType())) { - return importSingedContainerInto(reference, targetGroup); - } - if (isOfExportType(info, KeeShare::unsignedContainerFileType())) { - return importUnsignedContainerInto(reference, targetGroup); - } - return {reference.path, Result::Error, tr("Unknown share container type")}; + const QFileInfo info(reference.path); + if (!info.exists()) { + qCritical("File %s does not exist.", qPrintable(info.absoluteFilePath())); + return {reference.path, Result::Warning, tr("File does not exist")}; + } + + if (isOfExportType(info, KeeShare::signedContainerFileType())) { + return importSingedContainerInto(reference, targetGroup); + } + if (isOfExportType(info, KeeShare::unsignedContainerFileType())) { + return importUnsignedContainerInto(reference, targetGroup); + } + return {reference.path, Result::Error, tr("Unknown share container type")}; } ShareObserver::Result ShareObserver::importFromReferenceContainer(const QString& path) { - if (!KeeShare::active().in) { - return {}; - } - auto shareGroup = m_shareToGroup.value(path); - if (!shareGroup) { - qWarning("Source for %s does not exist", qPrintable(path)); - Q_ASSERT(shareGroup); - return {}; - } - const auto reference = KeeShare::referenceOf(shareGroup); - if (reference.type == KeeShareSettings::Inactive) { - qDebug("Ignore change of inactive reference %s", qPrintable(reference.path)); - return {}; - } - if (reference.type == KeeShareSettings::ExportTo) { - qDebug("Ignore change of export reference %s", qPrintable(reference.path)); - return {}; - } - Q_ASSERT(shareGroup->database() == m_db); - Q_ASSERT(shareGroup == m_db->rootGroup()->findGroupByUuid(shareGroup->uuid())); - return importContainerInto(reference, shareGroup); + if (!KeeShare::active().in) { + return {}; + } + auto shareGroup = m_shareToGroup.value(path); + if (!shareGroup) { + qWarning("Source for %s does not exist", qPrintable(path)); + Q_ASSERT(shareGroup); + return {}; + } + const auto reference = KeeShare::referenceOf(shareGroup); + if (reference.type == KeeShareSettings::Inactive) { + // changes of inactive references are ignored + return {}; + } + if (reference.type == KeeShareSettings::ExportTo) { + // changes of export only references are ignored + return {}; + } + + Q_ASSERT(shareGroup->database() == m_db); + Q_ASSERT(shareGroup == m_db->rootGroup()->findGroupByUuid(shareGroup->uuid())); + return importContainerInto(reference, shareGroup); } void ShareObserver::resolveReferenceAttributes(Entry* targetEntry, const Database* sourceDb) { - for (const auto& attribute : EntryAttributes::DefaultAttributes) { - const auto standardValue = targetEntry->attributes()->value(attribute); - const auto type = targetEntry->placeholderType(standardValue); - if (type != Entry::PlaceholderType::Reference) { - // No reference to resolve - continue; - } - const auto* referencedTargetEntry = targetEntry->resolveReference(standardValue); - if (referencedTargetEntry) { - // References is within scope, no resolving needed - continue; - } - // We could do more sophisticated **** trying to point the reference to the next in-scope reference - // but those cases with high propability constructed examples and very rare in real usage - const auto* sourceReference = sourceDb->rootGroup()->findEntryByUuid(targetEntry->uuid()); - const auto resolvedValue = sourceReference->resolveMultiplePlaceholders(standardValue); - targetEntry->setUpdateTimeinfo(false); - targetEntry->attributes()->set(attribute, resolvedValue, targetEntry->attributes()->isProtected(attribute)); - targetEntry->setUpdateTimeinfo(true); - } + for (const auto& attribute : EntryAttributes::DefaultAttributes) { + const auto standardValue = targetEntry->attributes()->value(attribute); + const auto type = targetEntry->placeholderType(standardValue); + if (type != Entry::PlaceholderType::Reference) { + // No reference to resolve + continue; + } + const auto* referencedTargetEntry = targetEntry->resolveReference(standardValue); + if (referencedTargetEntry) { + // References is within scope, no resolving needed + continue; + } + // We could do more sophisticated **** trying to point the reference to the next in-scope reference + // but those cases with high propability constructed examples and very rare in real usage + const auto* sourceReference = sourceDb->rootGroup()->findEntryByUuid(targetEntry->uuid()); + const auto resolvedValue = sourceReference->resolveMultiplePlaceholders(standardValue); + targetEntry->setUpdateTimeinfo(false); + targetEntry->attributes()->set(attribute, resolvedValue, targetEntry->attributes()->isProtected(attribute)); + targetEntry->setUpdateTimeinfo(true); + } } Database* ShareObserver::exportIntoContainer(const KeeShareSettings::Reference& reference, const Group* sourceRoot) { - const auto* sourceDb = sourceRoot->database(); - auto* targetDb = new Database(); - targetDb->metadata()->setRecycleBinEnabled(false); - auto key = QSharedPointer<CompositeKey>::create(); - key->addKey(QSharedPointer<PasswordKey>::create(reference.password)); - - // Copy the source root as the root of the export database, memory manage the old root node - auto* targetRoot = sourceRoot->clone(Entry::CloneNoFlags, Group::CloneNoFlags); - const bool updateTimeinfo = targetRoot->canUpdateTimeinfo(); - targetRoot->setUpdateTimeinfo(false); - KeeShare::setReferenceTo(targetRoot, KeeShareSettings::Reference()); - targetRoot->setUpdateTimeinfo(updateTimeinfo); - const auto sourceEntries = sourceRoot->entriesRecursive(false); - for (const Entry* sourceEntry : sourceEntries) { - auto* targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory); - const bool updateTimeinfo = targetEntry->canUpdateTimeinfo(); - targetEntry->setUpdateTimeinfo(false); - targetEntry->setGroup(targetRoot); - targetEntry->setUpdateTimeinfo(updateTimeinfo); - const auto iconUuid = targetEntry->iconUuid(); - if (!iconUuid.isNull()) { - targetDb->metadata()->addCustomIcon(iconUuid, sourceEntry->icon()); - } - } - - targetDb->setKey(key); - auto* obsoleteRoot = targetDb->rootGroup(); - targetDb->setRootGroup(targetRoot); - delete obsoleteRoot; - - targetDb->metadata()->setName(sourceRoot->name()); - - // Push all deletions of the source database to the target - // simple moving out of a share group will not trigger a deletion in the - // target - a more elaborate mechanism may need the use of another custom - // attribute to share unshared entries from the target db - for (const auto& object : sourceDb->deletedObjects()) { - targetDb->addDeletedObject(object); - } - for (auto* targetEntry : targetRoot->entriesRecursive(false)) { - if (targetEntry->hasReferences()) { - resolveReferenceAttributes(targetEntry, sourceDb); - } - } - return targetDb; + const auto* sourceDb = sourceRoot->database(); + auto* targetDb = new Database(); + targetDb->metadata()->setRecycleBinEnabled(false); + auto key = QSharedPointer<CompositeKey>::create(); + key->addKey(QSharedPointer<PasswordKey>::create(reference.password)); + + // Copy the source root as the root of the export database, memory manage the old root node + auto* targetRoot = sourceRoot->clone(Entry::CloneNoFlags, Group::CloneNoFlags); + const bool updateTimeinfo = targetRoot->canUpdateTimeinfo(); + targetRoot->setUpdateTimeinfo(false); + KeeShare::setReferenceTo(targetRoot, KeeShareSettings::Reference()); + targetRoot->setUpdateTimeinfo(updateTimeinfo); + const auto sourceEntries = sourceRoot->entriesRecursive(false); + for (const Entry* sourceEntry : sourceEntries) { + auto* targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory); + const bool updateTimeinfo = targetEntry->canUpdateTimeinfo(); + targetEntry->setUpdateTimeinfo(false); + targetEntry->setGroup(targetRoot); + targetEntry->setUpdateTimeinfo(updateTimeinfo); + const auto iconUuid = targetEntry->iconUuid(); + if (!iconUuid.isNull()) { + targetDb->metadata()->addCustomIcon(iconUuid, sourceEntry->icon()); + } + } + + targetDb->setKey(key); + auto* obsoleteRoot = targetDb->rootGroup(); + targetDb->setRootGroup(targetRoot); + delete obsoleteRoot; + + targetDb->metadata()->setName(sourceRoot->name()); + + // Push all deletions of the source database to the target + // simple moving out of a share group will not trigger a deletion in the + // target - a more elaborate mechanism may need the use of another custom + // attribute to share unshared entries from the target db + for (const auto& object : sourceDb->deletedObjects()) { + targetDb->addDeletedObject(object); + } + for (auto* targetEntry : targetRoot->entriesRecursive(false)) { + if (targetEntry->hasReferences()) { + resolveReferenceAttributes(targetEntry, sourceDb); + } + } + return targetDb; } QSharedPointer<Database> ShareObserver::database() { - return m_db; + return m_db; } ShareObserver::Result ShareObserver::exportIntoReferenceSignedContainer(const KeeShareSettings::Reference &reference, Database *targetDb) { #if !defined(WITH_XC_KEESHARE_SECURE) - Q_UNUSED(targetDb); - return {reference.path, Result::Warning, tr("Overwriting signed share container is not supported - export prevented")}; + Q_UNUSED(targetDb); + return {reference.path, Result::Warning, tr("Overwriting signed share container is not supported - export prevented")}; #else - QByteArray bytes; - { - QBuffer buffer(&bytes); - buffer.open(QIODevice::WriteOnly); - KeePass2Writer writer; - writer.writeDatabase(&buffer, targetDb); - if (writer.hasError()) { - qWarning("Serializing export dabase failed: %s.", writer.errorString().toLatin1().data()); - return {reference.path, Result::Error, writer.errorString()}; - } - } - const auto own = KeeShare::own(); - QuaZip zip(reference.path); - zip.setFileNameCodec("UTF-8"); - const bool zipOpened = zip.open(QuaZip::mdCreate); - if (!zipOpened) { - ::qWarning("Opening export file failed: %d", zip.getZipError()); - return {reference.path, Result::Error, tr("Could not write export container (%1)").arg(zip.getZipError())}; - } - { - QuaZipFile file(&zip); - const auto signatureOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Signature)); - if (!signatureOpened) { - ::qWarning("Embedding signature failed: %d", zip.getZipError()); - return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())}; - } - QTextStream stream(&file); - KeeShareSettings::Sign sign; - auto sshKey = own.key.sshKey(); - sshKey.openKey(QString()); - const Signature signer; - sign.signature = signer.create(bytes, sshKey); - sign.certificate = own.certificate; - stream << KeeShareSettings::Sign::serialize(sign); - stream.flush(); - if (file.getZipError() != ZIP_OK) { - ::qWarning("Embedding signature failed: %d", zip.getZipError()); - return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())}; - } - file.close(); - } - { - QuaZipFile file(&zip); - const auto dbOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Container)); - if (!dbOpened) { - ::qWarning("Embedding database failed: %d", zip.getZipError()); - return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())}; - } - if (file.getZipError() != ZIP_OK) { - ::qWarning("Embedding database failed: %d", zip.getZipError()); - return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())}; - } - file.write(bytes); - file.close(); - } - zip.close(); - return {reference.path}; + QByteArray bytes; + { + QBuffer buffer(&bytes); + buffer.open(QIODevice::WriteOnly); + KeePass2Writer writer; + writer.writeDatabase(&buffer, targetDb); + if (writer.hasError()) { + qWarning("Serializing export dabase failed: %s.", writer.errorString().toLatin1().data()); + return {reference.path, Result::Error, writer.errorString()}; + } + } + const auto own = KeeShare::own(); + QuaZip zip(reference.path); + zip.setFileNameCodec("UTF-8"); + const bool zipOpened = zip.open(QuaZip::mdCreate); + if (!zipOpened) { + ::qWarning("Opening export file failed: %d", zip.getZipError()); + return {reference.path, Result::Error, tr("Could not write export container (%1)").arg(zip.getZipError())}; + } + { + QuaZipFile file(&zip); + const auto signatureOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Signature)); + if (!signatureOpened) { + ::qWarning("Embedding signature failed: %d", zip.getZipError()); + return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())}; + } + QTextStream stream(&file); + KeeShareSettings::Sign sign; + auto sshKey = own.key.sshKey(); + sshKey.openKey(QString()); + const Signature signer; + sign.signature = signer.create(bytes, sshKey); + sign.certificate = own.certificate; + stream << KeeShareSettings::Sign::serialize(sign); + stream.flush(); + if (file.getZipError() != ZIP_OK) { + ::qWarning("Embedding signature failed: %d", zip.getZipError()); + return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())}; + } + file.close(); + } + { + QuaZipFile file(&zip); + const auto dbOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Container)); + if (!dbOpened) { + ::qWarning("Embedding database failed: %d", zip.getZipError()); + return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())}; + } + if (file.getZipError() != ZIP_OK) { + ::qWarning("Embedding database failed: %d", zip.getZipError()); + return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())}; + } + file.write(bytes); + file.close(); + } + zip.close(); + return {reference.path}; #endif } ShareObserver::Result ShareObserver::exportIntoReferenceUnsignedContainer(const KeeShareSettings::Reference &reference, Database *targetDb) { #if !defined(WITH_XC_KEESHARE_INSECURE) - Q_UNUSED(targetDb); - return {reference.path, Result::Warning, tr("Overwriting unsigned share container is not supported - export prevented")}; + Q_UNUSED(targetDb); + return {reference.path, Result::Warning, tr("Overwriting unsigned share container is not supported - export prevented")}; #else - QFile file(reference.path); - const bool fileOpened = file.open(QIODevice::WriteOnly); - if (!fileOpened) { - ::qWarning("Opening export file failed"); - return {reference.path, Result::Error, tr("Could not write export container")}; - } - KeePass2Writer writer; - writer.writeDatabase(&file, targetDb); - if (writer.hasError()) { - qWarning("Exporting dabase failed: %s.", writer.errorString().toLatin1().data()); - return {reference.path, Result::Error, writer.errorString()}; - } - file.close(); + QFile file(reference.path); + const bool fileOpened = file.open(QIODevice::WriteOnly); + if (!fileOpened) { + ::qWarning("Opening export file failed"); + return {reference.path, Result::Error, tr("Could not write export container")}; + } + KeePass2Writer writer; + writer.writeDatabase(&file, targetDb); + if (writer.hasError()) { + qWarning("Exporting dabase failed: %s.", writer.errorString().toLatin1().data()); + return {reference.path, Result::Error, writer.errorString()}; + } + file.close(); #endif - return {reference.path}; + return {reference.path}; } QList<ShareObserver::Result> ShareObserver::exportIntoReferenceContainers() { - QList<Result> results; - const auto groups = m_db->rootGroup()->groupsRecursive(true); - for (const auto* group : groups) { - const auto reference = KeeShare::referenceOf(group); - if (!reference.isExporting()) { - continue; - } - - m_fileWatcher->ignoreFileChanges(reference.path); - QScopedPointer<Database> targetDb(exportIntoContainer(reference, group)); - QFileInfo info(reference.path); - if (isOfExportType(info, KeeShare::signedContainerFileType())) { - results << exportIntoReferenceSignedContainer(reference, targetDb.data()); - m_fileWatcher->observeFileChanges(true); - continue; - } - if (isOfExportType(info, KeeShare::unsignedContainerFileType())) { - results << exportIntoReferenceUnsignedContainer(reference, targetDb.data()); - m_fileWatcher->observeFileChanges(true); - continue; - } - Q_ASSERT(false); - results << Result{reference.path, Result::Error, tr("Unexpected export error occurred")}; - } - return results; + QList<Result> results; + const auto groups = m_db->rootGroup()->groupsRecursive(true); + for (const auto* group : groups) { + const auto reference = KeeShare::referenceOf(group); + if (!reference.isExporting()) { + continue; + } + + m_fileWatcher->ignoreFileChanges(reference.path); + QScopedPointer<Database> targetDb(exportIntoContainer(reference, group)); + QFileInfo info(reference.path); + if (isOfExportType(info, KeeShare::signedContainerFileType())) { + results << exportIntoReferenceSignedContainer(reference, targetDb.data()); + m_fileWatcher->observeFileChanges(true); + continue; + } + if (isOfExportType(info, KeeShare::unsignedContainerFileType())) { + results << exportIntoReferenceUnsignedContainer(reference, targetDb.data()); + m_fileWatcher->observeFileChanges(true); + continue; + } + Q_ASSERT(false); + results << Result{reference.path, Result::Error, tr("Unexpected export error occurred")}; + } + return results; } void ShareObserver::handleDatabaseSaved() { - if (!KeeShare::active().out) { - return; - } - QStringList error; - QStringList warning; - QStringList success; - const auto results = exportIntoReferenceContainers(); - for (const Result& result : results) { - if (!result.isValid()) { - Q_ASSERT(result.isValid()); - continue; - } - if (result.isError()) { - error << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message); - } else if (result.isWarning()) { - warning << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message); - } else if (result.isInfo()) { - success << tr("Export to %1 successful (%2)").arg(result.path).arg(result.message); - } else { - success << tr("Export to %1").arg(result.path); - } - } - notifyAbout(success, warning, error); + if (!KeeShare::active().out) { + return; + } + QStringList error; + QStringList warning; + QStringList success; + const auto results = exportIntoReferenceContainers(); + for (const Result& result : results) { + if (!result.isValid()) { + Q_ASSERT(result.isValid()); + continue; + } + if (result.isError()) { + error << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message); + } else if (result.isWarning()) { + warning << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message); + } else if (result.isInfo()) { + success << tr("Export to %1 successful (%2)").arg(result.path).arg(result.message); + } else { + success << tr("Export to %1").arg(result.path); + } + } + notifyAbout(success, warning, error); } ShareObserver::Result::Result(const QString& path, ShareObserver::Result::Type type, const QString& message) - : path(path) - , type(type) - , message(message) + : path(path) + , type(type) + , message(message) { } bool ShareObserver::Result::isValid() const { - return !path.isEmpty() || !message.isEmpty() || !message.isEmpty() || !message.isEmpty(); + return !path.isEmpty() || !message.isEmpty() || !message.isEmpty() || !message.isEmpty(); } bool ShareObserver::Result::isError() const { - return !message.isEmpty() && type == Error; + return !message.isEmpty() && type == Error; } bool ShareObserver::Result::isInfo() const { - return !message.isEmpty() && type == Info; + return !message.isEmpty() && type == Info; } bool ShareObserver::Result::isWarning() const { - return !message.isEmpty() && type == Warning; + return !message.isEmpty() && type == Warning; } diff --git a/src/keeshare/ShareObserver.h b/src/keeshare/ShareObserver.h index 26f010813..d67a1f7ef 100644 --- a/src/keeshare/ShareObserver.h +++ b/src/keeshare/ShareObserver.h @@ -49,7 +49,9 @@ signals: private slots: void handleDatabaseChanged(); void handleDatabaseSaved(); + void handleFileCreated(const QString& path); void handleFileUpdated(const QString& path); + void handleFileDeleted(const QString& path); private: struct Result |