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
This commit is contained in:
committed by
Jonathan White
parent
c1e9f45df9
commit
eca9c658f4
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
#include "Clock.h"
|
||||
|
||||
QSharedPointer<Clock> Clock::m_instance = QSharedPointer<Clock>();
|
||||
QSharedPointer<Clock> Clock::m_instance;
|
||||
|
||||
QDateTime Clock::currentDateTimeUtc()
|
||||
{
|
||||
@@ -92,7 +92,7 @@ QDateTime Clock::currentDateTimeImpl() const
|
||||
|
||||
void Clock::resetInstance()
|
||||
{
|
||||
m_instance.clear();
|
||||
m_instance.reset();
|
||||
}
|
||||
|
||||
void Clock::setInstance(Clock* clock)
|
||||
|
||||
@@ -48,7 +48,16 @@ QString Config::getFileName()
|
||||
|
||||
void Config::set(const QString& key, const QVariant& value)
|
||||
{
|
||||
if (m_settings->contains(key) && m_settings->value(key) == value) {
|
||||
return;
|
||||
}
|
||||
const bool surpressSignal = !m_settings->contains(key) && m_defaults.value(key) == value;
|
||||
|
||||
m_settings->setValue(key, value);
|
||||
|
||||
if (!surpressSignal) {
|
||||
emit changed(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,6 +43,9 @@ public:
|
||||
static void createConfigFromFile(const QString& file);
|
||||
static void createTempFileInstance();
|
||||
|
||||
signals:
|
||||
void changed(const QString& key);
|
||||
|
||||
private:
|
||||
Config(const QString& fileName, QObject* parent);
|
||||
explicit Config(QObject* parent);
|
||||
|
||||
@@ -111,7 +111,7 @@ void Database::setFilePath(const QString& filePath)
|
||||
m_filePath = filePath;
|
||||
}
|
||||
|
||||
Entry* Database::resolveEntry(const QUuid& uuid)
|
||||
Entry* Database::resolveEntry(const QUuid& uuid) const
|
||||
{
|
||||
return findEntryRecursive(uuid, m_rootGroup);
|
||||
}
|
||||
@@ -121,7 +121,7 @@ Entry* Database::resolveEntry(const QString& text, EntryReferenceType referenceT
|
||||
return findEntryRecursive(text, referenceType, m_rootGroup);
|
||||
}
|
||||
|
||||
Entry* Database::findEntryRecursive(const QUuid& uuid, Group* group)
|
||||
Entry* Database::findEntryRecursive(const QUuid& uuid, Group* group) const
|
||||
{
|
||||
const QList<Entry*> entryList = group->entries();
|
||||
for (Entry* entry : entryList) {
|
||||
@@ -289,8 +289,11 @@ QByteArray Database::challengeResponseKey() const
|
||||
|
||||
bool Database::challengeMasterSeed(const QByteArray& masterSeed)
|
||||
{
|
||||
m_data.masterSeed = masterSeed;
|
||||
return m_data.key->challenge(masterSeed, m_data.challengeResponseKey);
|
||||
if (m_data.key) {
|
||||
m_data.masterSeed = masterSeed;
|
||||
return m_data.key->challenge(masterSeed, m_data.challengeResponseKey);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Database::setCipher(const QUuid& cipher)
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "crypto/kdf/Kdf.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
|
||||
@@ -88,7 +89,7 @@ public:
|
||||
const Metadata* metadata() const;
|
||||
QString filePath() const;
|
||||
void setFilePath(const QString& filePath);
|
||||
Entry* resolveEntry(const QUuid& uuid);
|
||||
Entry* resolveEntry(const QUuid& uuid) const;
|
||||
Entry* resolveEntry(const QString& text, EntryReferenceType referenceType);
|
||||
Group* resolveGroup(const QUuid& uuid);
|
||||
QList<DeletedObject> deletedObjects();
|
||||
@@ -149,7 +150,7 @@ private slots:
|
||||
void startModifiedTimer();
|
||||
|
||||
private:
|
||||
Entry* findEntryRecursive(const QUuid& uuid, Group* group);
|
||||
Entry* findEntryRecursive(const QUuid& uuid, Group* group) const;
|
||||
Entry* findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group);
|
||||
Group* findGroupRecursive(const QUuid& uuid, Group* group);
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
DatabaseIcons* DatabaseIcons::m_instance(nullptr);
|
||||
const int DatabaseIcons::IconCount(69);
|
||||
const int DatabaseIcons::ExpiredIconIndex(45);
|
||||
const int DatabaseIcons::SharedIconIndex(1);
|
||||
const int DatabaseIcons::UnsharedIconIndex(45);
|
||||
|
||||
// clang-format off
|
||||
const char* const DatabaseIcons::m_indexToName[] = {
|
||||
|
||||
@@ -33,6 +33,8 @@ public:
|
||||
|
||||
static const int IconCount;
|
||||
static const int ExpiredIconIndex;
|
||||
static const int SharedIconIndex;
|
||||
static const int UnsharedIconIndex;
|
||||
|
||||
private:
|
||||
DatabaseIcons();
|
||||
|
||||
@@ -955,6 +955,20 @@ QString Entry::maskPasswordPlaceholders(const QString& str) const
|
||||
return result;
|
||||
}
|
||||
|
||||
Entry* Entry::resolveReference(const QString& str) const
|
||||
{
|
||||
QRegularExpressionMatch match = EntryAttributes::matchReference(str);
|
||||
if (!match.hasMatch()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QString searchIn = match.captured(EntryAttributes::SearchInGroupName);
|
||||
const QString searchText = match.captured(EntryAttributes::SearchTextGroupName);
|
||||
|
||||
const EntryReferenceType searchInType = Entry::referenceType(searchIn);
|
||||
return m_group->database()->resolveEntry(searchText, searchInType);
|
||||
}
|
||||
|
||||
QString Entry::resolveMultiplePlaceholders(const QString& str) const
|
||||
{
|
||||
return resolveMultiplePlaceholdersRecursive(str, ResolveMaximumDepth);
|
||||
|
||||
@@ -195,6 +195,7 @@ public:
|
||||
Entry* clone(CloneFlags flags) const;
|
||||
void copyDataFrom(const Entry* other);
|
||||
QString maskPasswordPlaceholders(const QString& str) const;
|
||||
Entry* resolveReference(const QString& str) const;
|
||||
QString resolveMultiplePlaceholders(const QString& str) const;
|
||||
QString resolvePlaceholder(const QString& str) const;
|
||||
QString resolveUrlPlaceholder(const QString& str, PlaceholderType placeholderType) const;
|
||||
|
||||
238
src/core/FileWatcher.cpp
Normal file
238
src/core/FileWatcher.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "FileWatcher.h"
|
||||
|
||||
#include "core/Clock.h"
|
||||
#include <QFileInfo>
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <sys/vfs.h>
|
||||
#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<QString, bool>& 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<int>(current.msecsTo(m_ignoreFilesChangess[key]));
|
||||
}
|
||||
}
|
||||
if (timeout > 0 && !m_fileWatchUnblockTimer.isActive()) {
|
||||
m_fileWatchUnblockTimer.start(timeout);
|
||||
}
|
||||
}
|
||||
92
src/core/FileWatcher.h
Normal file
92
src/core/FileWatcher.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_FILEWATCHER_H
|
||||
#define KEEPASSXC_FILEWATCHER_H
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QSet>
|
||||
#include <QTimer>
|
||||
#include <QVariant>
|
||||
|
||||
class DelayingFileWatcher : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DelayingFileWatcher(QObject* parent = nullptr);
|
||||
|
||||
void blockAutoReload(bool block);
|
||||
void start(const QString& path);
|
||||
|
||||
void restart();
|
||||
void stop();
|
||||
void ignoreFileChanges();
|
||||
|
||||
signals:
|
||||
void fileChanged();
|
||||
|
||||
public slots:
|
||||
void observeFileChanges(bool delayed = false);
|
||||
|
||||
private slots:
|
||||
void onWatchedFileChanged();
|
||||
|
||||
private:
|
||||
QString m_filePath;
|
||||
QFileSystemWatcher m_fileWatcher;
|
||||
QTimer m_fileChangeDelayTimer;
|
||||
QTimer m_fileUnblockTimer;
|
||||
bool m_ignoreFileChange;
|
||||
};
|
||||
|
||||
class BulkFileWatcher : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BulkFileWatcher(QObject* parent = nullptr);
|
||||
|
||||
void clear();
|
||||
|
||||
void removePath(const QString& path);
|
||||
void addPath(const QString& path);
|
||||
|
||||
void restart(const QString& path);
|
||||
|
||||
void ignoreFileChanges(const QString& path);
|
||||
|
||||
signals:
|
||||
void fileCreated(QString);
|
||||
void fileChanged(QString);
|
||||
void fileRemoved(QString);
|
||||
|
||||
public slots:
|
||||
void observeFileChanges(bool delayed = false);
|
||||
|
||||
private slots:
|
||||
void handleFileChanged(const QString& path);
|
||||
void handleDirectoryChanged(const QString& path);
|
||||
|
||||
private:
|
||||
QSet<QString> m_filePaths;
|
||||
QMap<QString, QDateTime> m_ignoreFilesChangess;
|
||||
QFileSystemWatcher m_fileWatcher;
|
||||
QMap<QString, QMap<QString, bool>> m_watchedFilesInDirectory;
|
||||
QTimer m_fileWatchUnblockTimer; // needed for Import/Export-References
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_FILEWATCHER_H
|
||||
@@ -124,8 +124,7 @@ bool TimeInfo::equals(const TimeInfo& other, CompareItemOptions options) const
|
||||
if (::compare(m_creationTime, other.m_creationTime, options) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (::compare(!options.testFlag(CompareItemIgnoreStatistics), m_lastAccessTime, other.m_lastAccessTime, options)
|
||||
!= 0) {
|
||||
if (::compare(!options.testFlag(CompareItemIgnoreStatistics), m_lastAccessTime, other.m_lastAccessTime, options) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (::compare(m_expires, m_expiryTime, other.m_expires, other.expiryTime(), options) != 0) {
|
||||
@@ -134,8 +133,7 @@ bool TimeInfo::equals(const TimeInfo& other, CompareItemOptions options) const
|
||||
if (::compare(!options.testFlag(CompareItemIgnoreStatistics), m_usageCount, other.m_usageCount, options) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (::compare(!options.testFlag(CompareItemIgnoreLocation), m_locationChanged, other.m_locationChanged, options)
|
||||
!= 0) {
|
||||
if (::compare(!options.testFlag(CompareItemIgnoreLocation), m_locationChanged, other.m_locationChanged, options) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -346,4 +346,31 @@ namespace Tools
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
|
||||
Buffer::Buffer()
|
||||
: raw(nullptr)
|
||||
, size(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Buffer::~Buffer()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void Buffer::clear()
|
||||
{
|
||||
if(size > 0){
|
||||
free(raw);
|
||||
}
|
||||
raw = nullptr; size = 0;
|
||||
}
|
||||
|
||||
QByteArray Buffer::content() const
|
||||
{
|
||||
return QByteArray(reinterpret_cast<char*>(raw), size );
|
||||
}
|
||||
|
||||
|
||||
} // namespace Tools
|
||||
|
||||
@@ -56,6 +56,33 @@ namespace Tools
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Key, typename Value, void deleter(Value)> struct Map
|
||||
{
|
||||
QMap<Key, Value> values;
|
||||
Value& operator[](const Key index)
|
||||
{
|
||||
return values[index];
|
||||
}
|
||||
|
||||
~Map()
|
||||
{
|
||||
for (Value m : values) {
|
||||
deleter(m);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Buffer
|
||||
{
|
||||
unsigned char* raw;
|
||||
size_t size;
|
||||
|
||||
Buffer();
|
||||
~Buffer();
|
||||
|
||||
void clear();
|
||||
QByteArray content() const;
|
||||
};
|
||||
} // namespace Tools
|
||||
|
||||
#endif // KEEPASSX_TOOLS_H
|
||||
|
||||
Reference in New Issue
Block a user