From eca9c658f4d0a8e956d49ce2e9eea81704e1de9b Mon Sep 17 00:00:00 2001 From: Christian Kieschnick Date: Mon, 1 Oct 2018 10:26:24 -0400 Subject: Add sharing of groups between databases * Add source folder keeshare for sharing with corresponding define WITH_XC_KEESHARE * Move common crypto parts to src/crypto/ssh * Extended OpenSSHKey * Move filewatching to own file (currently in two related classes DelayedFileWatcher and BulkFileWatcher) * Small improvements for style and code in several classes * Sharing is secured using RSA-Keys which are generated on demand * Publisher signs the container using their private key * Client can verify the signed container and choose to decline an import, import only once or trust the publisher and automatically import all data of this source henceforth * Integration of settings into Group-Settings, Database-Settings and Application-Settings * Introduced dependency QuaZip as dependency to allow combined export of key container and the (custom format) certificate --- src/core/FileWatcher.cpp | 238 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 src/core/FileWatcher.cpp (limited to 'src/core/FileWatcher.cpp') diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp new file mode 100644 index 000000000..ac44174bd --- /dev/null +++ b/src/core/FileWatcher.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2011 Felix Geyer + * Copyright (C) 2017 KeePassXC Team + * + * 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 + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "FileWatcher.h" + +#include "core/Clock.h" +#include + +#ifdef Q_OS_LINUX +#include +#endif + +namespace +{ + const int FileChangeDelay = 500; + const int TimerResolution = 100; +} + +DelayingFileWatcher::DelayingFileWatcher(QObject* parent) + : QObject(parent) + , m_ignoreFileChange(false) +{ + connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged())); + connect(&m_fileUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges())); + connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), SIGNAL(fileChanged())); + + m_fileChangeDelayTimer.setSingleShot(true); + m_fileUnblockTimer.setSingleShot(true); +} + +void DelayingFileWatcher::restart() +{ + m_fileWatcher.addPath(m_filePath); +} + +void DelayingFileWatcher::stop() +{ + m_fileWatcher.removePath(m_filePath); +} + +void DelayingFileWatcher::start(const QString& filePath) +{ + if (!m_filePath.isEmpty()) { + m_fileWatcher.removePath(m_filePath); + } + +#if defined(Q_OS_LINUX) + struct statfs statfsBuf; + bool forcePolling = false; + const auto NFS_SUPER_MAGIC = 0x6969; + + if (!statfs(filePath.toLocal8Bit().constData(), &statfsBuf)) { + forcePolling = (statfsBuf.f_type == NFS_SUPER_MAGIC); + } else { + // if we can't get the fs type let's fall back to polling + forcePolling = true; + } + auto objectName = forcePolling ? QLatin1String("_qt_autotest_force_engine_poller") : QLatin1String(""); + m_fileWatcher.setObjectName(objectName); +#endif + + m_fileWatcher.addPath(filePath); + + if (!filePath.isEmpty()) { + m_filePath = filePath; + } +} + +void DelayingFileWatcher::ignoreFileChanges() +{ + m_ignoreFileChange = true; + m_fileChangeDelayTimer.stop(); +} + +void DelayingFileWatcher::observeFileChanges(bool delayed) +{ + int timeout = 0; + if (delayed) { + timeout = FileChangeDelay; + } else { + m_ignoreFileChange = false; + start(m_filePath); + } + if (timeout > 0 && !m_fileUnblockTimer.isActive()) { + m_fileUnblockTimer.start(timeout); + } +} + +void DelayingFileWatcher::onWatchedFileChanged() +{ + if (m_ignoreFileChange) { + // the client forcefully silenced us + return; + } + if (m_fileChangeDelayTimer.isActive()) { + // we are waiting to fire the delayed fileChanged event, so nothing + // to do here + return; + } + + m_fileChangeDelayTimer.start(FileChangeDelay); +} + +BulkFileWatcher::BulkFileWatcher(QObject* parent) + : 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())); + m_fileWatchUnblockTimer.setSingleShot(true); +} + +void BulkFileWatcher::clear() +{ + for (const QString& path : m_fileWatcher.files() + m_fileWatcher.directories()) { + const QFileInfo info(path); + m_fileWatcher.removePath(info.absoluteFilePath()); + m_fileWatcher.removePath(info.absolutePath()); + } + m_filePaths.clear(); + m_watchedFilesInDirectory.clear(); + m_ignoreFilesChangess.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()); +} + +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()); +} + +void BulkFileWatcher::handleFileChanged(const QString& path) +{ + addPath(path); + + const QFileInfo info(path); + if (m_ignoreFilesChangess[info.canonicalFilePath()] > Clock::currentDateTimeUtc()) { + // changes are blocked + return; + } + + emit fileChanged(path); +} + +void BulkFileWatcher::handleDirectoryChanged(const QString& path) +{ + qDebug("Directory changed %s", qPrintable(path)); + const QFileInfo directory(path); + const QMap& watchedFiles = m_watchedFilesInDirectory[directory.absolutePath()]; + for (const QString& file : watchedFiles.keys()) { + const QFileInfo info(file); + const bool existed = watchedFiles[info.absoluteFilePath()]; + if (!info.exists() && existed) { + qDebug("Remove watch file %s", qPrintable(info.absoluteFilePath())); + m_fileWatcher.removePath(info.absolutePath()); + emit fileRemoved(info.absoluteFilePath()); + } + if (!existed && info.exists()) { + qDebug("Add watch file %s", qPrintable(info.absoluteFilePath())); + m_fileWatcher.addPath(info.absolutePath()); + emit fileCreated(info.absoluteFilePath()); + } + if (existed && info.exists()) { + qDebug("Refresh watch file %s", qPrintable(info.absoluteFilePath())); + m_fileWatcher.removePath(info.absolutePath()); + m_fileWatcher.addPath(info.absolutePath()); + emit fileChanged(info.absoluteFilePath()); + } + m_watchedFilesInDirectory[info.absolutePath()][info.absoluteFilePath()] = info.exists(); + } +} + +void BulkFileWatcher::ignoreFileChanges(const QString& path) +{ + const QFileInfo info(path); + m_ignoreFilesChangess[info.canonicalFilePath()] = Clock::currentDateTimeUtc().addMSecs(FileChangeDelay); +} + +void BulkFileWatcher::observeFileChanges(bool delayed) +{ + int timeout = 0; + if (delayed) { + timeout = TimerResolution; + } else { + const QDateTime current = Clock::currentDateTimeUtc(); + for (const QString& key : m_ignoreFilesChangess.keys()) { + if (m_ignoreFilesChangess[key] < current) { + // We assume that there was no concurrent change of the database + // during our block - so no need to reimport + qDebug("Remove block from %s", qPrintable(key)); + m_ignoreFilesChangess.remove(key); + continue; + } + qDebug("Keep block from %s", qPrintable(key)); + timeout = static_cast(current.msecsTo(m_ignoreFilesChangess[key])); + } + } + if (timeout > 0 && !m_fileWatchUnblockTimer.isActive()) { + m_fileWatchUnblockTimer.start(timeout); + } +} -- cgit v1.2.3