mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-08-09 06:52:50 -04:00
Merge branch 'master' into develop
This commit is contained in:
commit
31bd90a8df
99 changed files with 4428 additions and 3872 deletions
|
@ -21,11 +21,11 @@
|
|||
#include "NativeMessagingBase.h"
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
#include <sodium.h>
|
||||
#include <sodium/crypto_box.h>
|
||||
#include <sodium/randombytes.h>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
|
||||
BrowserAction::BrowserAction(BrowserService& browserService)
|
||||
: m_mutex(QMutex::Recursive)
|
||||
|
@ -88,6 +88,8 @@ QJsonObject BrowserAction::handleAction(const QJsonObject& json)
|
|||
return handleLockDatabase(json, action);
|
||||
} else if (action.compare("get-database-groups", Qt::CaseSensitive) == 0) {
|
||||
return handleGetDatabaseGroups(json, action);
|
||||
} else if (action.compare("create-new-group", Qt::CaseSensitive) == 0) {
|
||||
return handleCreateNewGroup(json, action);
|
||||
}
|
||||
|
||||
// Action was not recognized
|
||||
|
@ -407,6 +409,42 @@ QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, cons
|
|||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (command.isEmpty() || command.compare("create-new-group", Qt::CaseSensitive) != 0) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
QString group = decrypted.value("groupName").toString();
|
||||
const QJsonObject newGroup = m_browserService.createNewGroup(group);
|
||||
if (newGroup.isEmpty() || newGroup["name"].toString().isEmpty() || newGroup["uuid"].toString().isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP);
|
||||
}
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["name"] = newGroup["name"];
|
||||
message["uuid"] = newGroup["uuid"];
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const
|
||||
{
|
||||
QJsonObject response;
|
||||
|
@ -468,6 +506,8 @@ QString BrowserAction::getErrorMessage(const int errorCode) const
|
|||
return QObject::tr("No logins found");
|
||||
case ERROR_KEEPASS_NO_GROUPS_FOUND:
|
||||
return QObject::tr("No groups found");
|
||||
case ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP:
|
||||
return QObject::tr("Cannot create new group");
|
||||
default:
|
||||
return QObject::tr("Unknown error");
|
||||
}
|
||||
|
|
|
@ -46,7 +46,8 @@ class BrowserAction : public QObject
|
|||
ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
|
||||
ERROR_KEEPASS_NO_URL_PROVIDED = 14,
|
||||
ERROR_KEEPASS_NO_LOGINS_FOUND = 15,
|
||||
ERROR_KEEPASS_NO_GROUPS_FOUND = 16
|
||||
ERROR_KEEPASS_NO_GROUPS_FOUND = 16,
|
||||
ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP = 17
|
||||
};
|
||||
|
||||
public:
|
||||
|
@ -66,6 +67,7 @@ private:
|
|||
QJsonObject handleSetLogin(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleGetDatabaseGroups(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleCreateNewGroup(const QJsonObject& json, const QString& action);
|
||||
|
||||
QJsonObject buildMessage(const QString& nonce) const;
|
||||
QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);
|
||||
|
|
|
@ -32,13 +32,24 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
|
|||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
// clang-format off
|
||||
QString snapInstructions;
|
||||
#if defined(KEEPASSXC_DIST_SNAP)
|
||||
snapInstructions = "<br /><br />" +
|
||||
tr("Due to Snap sandboxing, you must run a script to enable browser integration."
|
||||
"<br />"
|
||||
"You can obtain this script from %1")
|
||||
.arg("<a href=\"https://keepassxc.org/download#linux\">https://keepassxc.org</a>");
|
||||
#endif
|
||||
|
||||
m_ui->extensionLabel->setOpenExternalLinks(true);
|
||||
m_ui->extensionLabel->setText(
|
||||
tr("KeePassXC-Browser is needed for the browser integration to work. <br />Download it for %1 and %2.")
|
||||
tr("KeePassXC-Browser is needed for the browser integration to work. <br />Download it for %1 and %2. %3")
|
||||
.arg("<a href=\"https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/\">Firefox</a>",
|
||||
"<a "
|
||||
"href=\"https://chrome.google.com/webstore/detail/keepassxc-browser/"
|
||||
"oboonakemofpalcgghocfoadofidjkkk\">Google Chrome / Chromium / Vivaldi</a>"));
|
||||
"<a href=\"https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk\">"
|
||||
"Google Chrome / Chromium / Vivaldi</a>",
|
||||
snapInstructions));
|
||||
// clang-format on
|
||||
|
||||
m_ui->scriptWarningWidget->setVisible(false);
|
||||
m_ui->scriptWarningWidget->setAutoHideTimeout(-1);
|
||||
|
@ -119,11 +130,18 @@ void BrowserOptionDialog::loadSettings()
|
|||
m_ui->supportBrowserProxy->setChecked(true);
|
||||
m_ui->supportBrowserProxy->setEnabled(false);
|
||||
#elif defined(KEEPASSXC_DIST_SNAP)
|
||||
m_ui->enableBrowserSupport->setChecked(false);
|
||||
m_ui->enableBrowserSupport->setEnabled(false);
|
||||
m_ui->browserGlobalWarningWidget->showMessage(
|
||||
tr("We're sorry, but KeePassXC-Browser is not supported for Snap releases at the moment."),
|
||||
MessageWidget::Warning);
|
||||
// Disable settings that will not work
|
||||
m_ui->supportBrowserProxy->setChecked(true);
|
||||
m_ui->supportBrowserProxy->setEnabled(false);
|
||||
m_ui->useCustomProxy->setChecked(false);
|
||||
m_ui->useCustomProxy->setEnabled(false);
|
||||
m_ui->browsersGroupBox->setVisible(false);
|
||||
m_ui->browsersGroupBox->setEnabled(false);
|
||||
m_ui->updateBinaryPath->setChecked(false);
|
||||
m_ui->updateBinaryPath->setEnabled(false);
|
||||
// Show notice to user
|
||||
m_ui->browserGlobalWarningWidget->showMessage(tr("Please see special instructions for browser extension use below"),
|
||||
MessageWidget::Warning);
|
||||
m_ui->browserGlobalWarningWidget->setCloseButtonVisible(false);
|
||||
m_ui->browserGlobalWarningWidget->setAutoHideTimeout(-1);
|
||||
#endif
|
||||
|
|
|
@ -150,7 +150,7 @@ QString BrowserService::getDatabaseRecycleBinUuid()
|
|||
return recycleBin->uuidToHex();
|
||||
}
|
||||
|
||||
QJsonArray BrowserService::addChildrenToGroup(Group* group)
|
||||
QJsonArray BrowserService::getChildrenFromGroup(Group* group)
|
||||
{
|
||||
QJsonArray groupList;
|
||||
|
||||
|
@ -166,7 +166,7 @@ QJsonArray BrowserService::addChildrenToGroup(Group* group)
|
|||
QJsonObject jsonGroup;
|
||||
jsonGroup["name"] = c->name();
|
||||
jsonGroup["uuid"] = Tools::uuidToHex(c->uuid());
|
||||
jsonGroup["children"] = addChildrenToGroup(c);
|
||||
jsonGroup["children"] = getChildrenFromGroup(c);
|
||||
groupList.push_back(jsonGroup);
|
||||
}
|
||||
return groupList;
|
||||
|
@ -187,7 +187,7 @@ QJsonObject BrowserService::getDatabaseGroups()
|
|||
QJsonObject root;
|
||||
root["name"] = rootGroup->name();
|
||||
root["uuid"] = Tools::uuidToHex(rootGroup->uuid());
|
||||
root["children"] = addChildrenToGroup(rootGroup);
|
||||
root["children"] = getChildrenFromGroup(rootGroup);
|
||||
|
||||
QJsonArray groups;
|
||||
groups.push_back(root);
|
||||
|
@ -198,6 +198,84 @@ QJsonObject BrowserService::getDatabaseGroups()
|
|||
return result;
|
||||
}
|
||||
|
||||
QJsonObject BrowserService::createNewGroup(const QString& groupName)
|
||||
{
|
||||
QJsonObject result;
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this,
|
||||
"createNewGroup",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QJsonObject, result),
|
||||
Q_ARG(QString, groupName));
|
||||
return result;
|
||||
}
|
||||
|
||||
auto db = getDatabase();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Group* rootGroup = db->rootGroup();
|
||||
if (!rootGroup) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto group = rootGroup->findGroupByPath(groupName);
|
||||
|
||||
// Group already exists
|
||||
if (group) {
|
||||
result["name"] = group->name();
|
||||
result["uuid"] = Tools::uuidToHex(group->uuid());
|
||||
return result;
|
||||
}
|
||||
|
||||
auto dialogResult = MessageBox::warning(nullptr,
|
||||
tr("KeePassXC: Create a new group"),
|
||||
tr("A request for creating a new group \"%1\" has been received.\n"
|
||||
"Do you want to create this group?\n")
|
||||
.arg(groupName),
|
||||
MessageBox::Yes | MessageBox::No);
|
||||
|
||||
if (dialogResult != MessageBox::Yes) {
|
||||
return result;
|
||||
}
|
||||
|
||||
QString name, uuid;
|
||||
Group* previousGroup = rootGroup;
|
||||
auto groups = groupName.split("/");
|
||||
|
||||
// Returns the group name based on depth
|
||||
auto getGroupName = [&](int depth) {
|
||||
QString gName;
|
||||
for (int i = 0; i < depth + 1; ++i) {
|
||||
gName.append((i == 0 ? "" : "/") + groups[i]);
|
||||
}
|
||||
return gName;
|
||||
};
|
||||
|
||||
// Create new group(s) always when the path is not found
|
||||
for (int i = 0; i < groups.length(); ++i) {
|
||||
QString gName = getGroupName(i);
|
||||
auto tempGroup = rootGroup->findGroupByPath(gName);
|
||||
if (!tempGroup) {
|
||||
Group* newGroup = new Group();
|
||||
newGroup->setName(groups[i]);
|
||||
newGroup->setUuid(QUuid::createUuid());
|
||||
newGroup->setParent(previousGroup);
|
||||
name = newGroup->name();
|
||||
uuid = Tools::uuidToHex(newGroup->uuid());
|
||||
previousGroup = newGroup;
|
||||
continue;
|
||||
}
|
||||
|
||||
previousGroup = tempGroup;
|
||||
}
|
||||
|
||||
result["name"] = name;
|
||||
result["uuid"] = uuid;
|
||||
return result;
|
||||
}
|
||||
|
||||
QString BrowserService::storeKey(const QString& key)
|
||||
{
|
||||
QString id;
|
||||
|
@ -630,7 +708,7 @@ QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QStrin
|
|||
{
|
||||
QUrl url(entryUrl);
|
||||
if (url.scheme().isEmpty()) {
|
||||
url.setScheme("http");
|
||||
url.setScheme("https");
|
||||
}
|
||||
|
||||
const QString submitUrl = url.toString(QUrl::StripTrailingSlash);
|
||||
|
@ -996,12 +1074,13 @@ bool BrowserService::checkLegacySettings()
|
|||
return false;
|
||||
}
|
||||
|
||||
auto dialogResult = MessageBox::warning(nullptr,
|
||||
tr("KeePassXC: Legacy browser integration settings detected"),
|
||||
tr("Legacy browser integration settings have been detected.\n"
|
||||
"Do you want to upgrade the settings to the latest standard?\n"
|
||||
"This is necessary to maintain compatibility with the browser plugin."),
|
||||
MessageBox::Yes | MessageBox::No);
|
||||
auto dialogResult =
|
||||
MessageBox::warning(nullptr,
|
||||
tr("KeePassXC: Legacy browser integration settings detected"),
|
||||
tr("Your KeePassXC-Browser settings need to be moved into the database settings.\n"
|
||||
"This is necessary to maintain your current browser connections.\n"
|
||||
"Would you like to migrate your existing settings now?"),
|
||||
MessageBox::Yes | MessageBox::No);
|
||||
|
||||
return dialogResult == MessageBox::Yes;
|
||||
}
|
||||
|
@ -1034,6 +1113,8 @@ void BrowserService::raiseWindow(const bool force)
|
|||
m_prevWindowState = WindowState::Minimized;
|
||||
}
|
||||
#ifdef Q_OS_MACOS
|
||||
Q_UNUSED(force);
|
||||
|
||||
if (macUtils()->isHidden()) {
|
||||
m_prevWindowState = WindowState::Hidden;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ public:
|
|||
QString getDatabaseRootUuid();
|
||||
QString getDatabaseRecycleBinUuid();
|
||||
QJsonObject getDatabaseGroups();
|
||||
QJsonObject createNewGroup(const QString& groupName);
|
||||
QString getKey(const QString& id);
|
||||
void addEntry(const QString& id,
|
||||
const QString& login,
|
||||
|
@ -121,7 +122,7 @@ private:
|
|||
QString baseDomain(const QString& url) const;
|
||||
QSharedPointer<Database> getDatabase();
|
||||
QSharedPointer<Database> selectedDatabase();
|
||||
QJsonArray addChildrenToGroup(Group* group);
|
||||
QJsonArray getChildrenFromGroup(Group* group);
|
||||
bool moveSettingsToCustomData(Entry* entry, const QString& name) const;
|
||||
int moveKeysToCustomData(Entry* entry, const QSharedPointer<Database>& db) const;
|
||||
bool checkLegacySettings();
|
||||
|
|
|
@ -186,7 +186,7 @@ void BrowserSettings::setCustomProxyLocation(const QString& location)
|
|||
|
||||
bool BrowserSettings::updateBinaryPath()
|
||||
{
|
||||
return config()->get("Browser/UpdateBinaryPath", false).toBool();
|
||||
return config()->get("Browser/UpdateBinaryPath", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setUpdateBinaryPath(bool enabled)
|
||||
|
|
|
@ -65,6 +65,7 @@ void NativeMessagingBase::newNativeMessage()
|
|||
EV_SET(ev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, nullptr);
|
||||
if (kevent(fd, ev, 1, nullptr, 0, &ts) == -1) {
|
||||
m_notifier->setEnabled(false);
|
||||
::close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -81,6 +82,7 @@ void NativeMessagingBase::newNativeMessage()
|
|||
event.data.fd = 0;
|
||||
if (epoll_ctl(fd, EPOLL_CTL_ADD, 0, &event) != 0) {
|
||||
m_notifier->setEnabled(false);
|
||||
::close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -135,7 +137,9 @@ void NativeMessagingBase::sendReply(const QString& reply)
|
|||
QString NativeMessagingBase::getLocalServerPath() const
|
||||
{
|
||||
const QString serverPath = "/kpxc_server";
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
#if defined(KEEPASSXC_DIST_SNAP)
|
||||
return QProcessEnvironment::systemEnvironment().value("SNAP_COMMON") + serverPath;
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
// Use XDG_RUNTIME_DIR instead of /tmp if it's available
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
|
||||
return path.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::TempLocation) + serverPath
|
||||
|
|
|
@ -53,7 +53,7 @@ protected slots:
|
|||
protected:
|
||||
virtual void readLength() = 0;
|
||||
virtual bool readStdIn(const quint32 length) = 0;
|
||||
void readNativeMessages();
|
||||
virtual void readNativeMessages();
|
||||
QString jsonToString(const QJsonObject& json) const;
|
||||
void sendReply(const QJsonObject& json);
|
||||
void sendReply(const QString& reply);
|
||||
|
|
|
@ -32,7 +32,7 @@ class NativeMessagingHost : public NativeMessagingBase
|
|||
|
||||
public:
|
||||
explicit NativeMessagingHost(DatabaseTabWidget* parent = nullptr, const bool enabled = false);
|
||||
~NativeMessagingHost();
|
||||
~NativeMessagingHost() override;
|
||||
int init();
|
||||
void run();
|
||||
void stop();
|
||||
|
|
|
@ -275,6 +275,7 @@ bool Database::writeDatabase(QIODevice* device, QString* error)
|
|||
return false;
|
||||
}
|
||||
|
||||
QByteArray oldTransformedKey = m_data.transformedMasterKey;
|
||||
KeePass2Writer writer;
|
||||
setEmitModified(false);
|
||||
writer.writeDatabase(device, this);
|
||||
|
@ -287,6 +288,15 @@ bool Database::writeDatabase(QIODevice* device, QString* error)
|
|||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(!m_data.transformedMasterKey.isEmpty());
|
||||
Q_ASSERT(m_data.transformedMasterKey != oldTransformedKey);
|
||||
if (m_data.transformedMasterKey.isEmpty() || m_data.transformedMasterKey == oldTransformedKey) {
|
||||
if (error) {
|
||||
*error = tr("Key not transformed. This is a bug, please report it to the developers!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
markAsClean();
|
||||
return true;
|
||||
}
|
||||
|
@ -307,16 +317,18 @@ bool Database::extract(QByteArray& xmlOutput, QString* error)
|
|||
|
||||
/**
|
||||
* Remove the old backup and replace it with a new one
|
||||
* backups are named <filename>.old.kdbx
|
||||
* backups are named <filename>.old.<extension>
|
||||
*
|
||||
* @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");
|
||||
static auto re = QRegularExpression("(\\.[^.]+)$");
|
||||
|
||||
auto match = re.match(filePath);
|
||||
auto backupFilePath = filePath;
|
||||
backupFilePath = backupFilePath.replace(re, "") + ".old" + match.captured(1);
|
||||
QFile::remove(backupFilePath);
|
||||
return QFile::copy(filePath, backupFilePath);
|
||||
}
|
||||
|
@ -512,9 +524,13 @@ void Database::setCompressionAlgorithm(Database::CompressionAlgorithm algo)
|
|||
* @param key key to set and transform or nullptr to reset the key
|
||||
* @param updateChangedTime true to update database change time
|
||||
* @param updateTransformSalt true to update the transform salt
|
||||
* @param transformKey trigger the KDF after setting the key
|
||||
* @return true on success
|
||||
*/
|
||||
bool Database::setKey(const QSharedPointer<const CompositeKey>& key, bool updateChangedTime, bool updateTransformSalt)
|
||||
bool Database::setKey(const QSharedPointer<const CompositeKey>& key,
|
||||
bool updateChangedTime,
|
||||
bool updateTransformSalt,
|
||||
bool transformKey)
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
|
||||
|
@ -532,7 +548,9 @@ bool Database::setKey(const QSharedPointer<const CompositeKey>& key, bool update
|
|||
|
||||
QByteArray oldTransformedMasterKey = m_data.transformedMasterKey;
|
||||
QByteArray transformedMasterKey;
|
||||
if (!key->transform(*m_data.kdf, transformedMasterKey)) {
|
||||
if (!transformKey) {
|
||||
transformedMasterKey = oldTransformedMasterKey;
|
||||
} else if (!key->transform(*m_data.kdf, transformedMasterKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -109,7 +109,8 @@ public:
|
|||
QSharedPointer<const CompositeKey> key() const;
|
||||
bool setKey(const QSharedPointer<const CompositeKey>& key,
|
||||
bool updateChangedTime = true,
|
||||
bool updateTransformSalt = false);
|
||||
bool updateTransformSalt = false,
|
||||
bool transformKey = true);
|
||||
QByteArray challengeResponseKey() const;
|
||||
bool challengeMasterSeed(const QByteArray& masterSeed);
|
||||
bool verifyKey(const QSharedPointer<CompositeKey>& key) const;
|
||||
|
|
|
@ -926,7 +926,7 @@ QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder,
|
|||
|
||||
Q_ASSERT(m_group);
|
||||
Q_ASSERT(m_group->database());
|
||||
const Entry* refEntry = m_group->findEntryBySearchTerm(searchText, searchInType);
|
||||
const Entry* refEntry = m_group->database()->rootGroup()->findEntryBySearchTerm(searchText, searchInType);
|
||||
|
||||
if (refEntry) {
|
||||
const QString wantedField = match.captured(EntryAttributes::WantedFieldGroupName);
|
||||
|
|
|
@ -67,6 +67,15 @@ QString EntryAttributes::value(const QString& key) const
|
|||
return m_attributes.value(key);
|
||||
}
|
||||
|
||||
QList<QString> EntryAttributes::values(const QList<QString>& keys) const
|
||||
{
|
||||
QList<QString> values;
|
||||
for (const QString& key : keys) {
|
||||
values.append(m_attributes.value(key));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
bool EntryAttributes::contains(const QString& key) const
|
||||
{
|
||||
return m_attributes.contains(key);
|
||||
|
|
|
@ -36,6 +36,7 @@ public:
|
|||
bool hasKey(const QString& key) const;
|
||||
QList<QString> customKeys() const;
|
||||
QString value(const QString& key) const;
|
||||
QList<QString> values(const QList<QString>& keys) const;
|
||||
bool contains(const QString& key) const;
|
||||
bool containsValue(const QString& value) const;
|
||||
bool isProtected(const QString& key) const;
|
||||
|
|
|
@ -28,31 +28,83 @@ EntrySearcher::EntrySearcher(bool caseSensitive)
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Search group, and its children, by parsing the provided search
|
||||
* string for search terms.
|
||||
*
|
||||
* @param searchString search terms
|
||||
* @param baseGroup group to start search from, cannot be null
|
||||
* @param forceSearch ignore group search settings
|
||||
* @return list of entries that match the search terms
|
||||
*/
|
||||
QList<Entry*> EntrySearcher::search(const QString& searchString, const Group* baseGroup, bool forceSearch)
|
||||
{
|
||||
Q_ASSERT(baseGroup);
|
||||
|
||||
parseSearchTerms(searchString);
|
||||
return repeat(baseGroup, forceSearch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeat the last search starting from the given group
|
||||
*
|
||||
* @param baseGroup group to start search from, cannot be null
|
||||
* @param forceSearch ignore group search settings
|
||||
* @return list of entries that match the search terms
|
||||
*/
|
||||
QList<Entry*> EntrySearcher::repeat(const Group* baseGroup, bool forceSearch)
|
||||
{
|
||||
Q_ASSERT(baseGroup);
|
||||
|
||||
QList<Entry*> results;
|
||||
for (const auto group : baseGroup->groupsRecursive(true)) {
|
||||
if (forceSearch || group->resolveSearchingEnabled()) {
|
||||
results.append(searchEntries(searchString, group->entries()));
|
||||
for (auto* entry : group->entries()) {
|
||||
if (searchEntryImpl(entry)) {
|
||||
results.append(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search provided entries by parsing the search string
|
||||
* for search terms.
|
||||
*
|
||||
* @param searchString search terms
|
||||
* @param entries list of entries to include in the search
|
||||
* @return list of entries that match the search terms
|
||||
*/
|
||||
QList<Entry*> EntrySearcher::searchEntries(const QString& searchString, const QList<Entry*>& entries)
|
||||
{
|
||||
parseSearchTerms(searchString);
|
||||
return repeatEntries(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeat the last search on the given entries
|
||||
*
|
||||
* @param entries list of entries to include in the search
|
||||
* @return list of entries that match the search terms
|
||||
*/
|
||||
QList<Entry*> EntrySearcher::repeatEntries(const QList<Entry*>& entries)
|
||||
{
|
||||
QList<Entry*> results;
|
||||
for (Entry* entry : entries) {
|
||||
if (searchEntryImpl(searchString, entry)) {
|
||||
for (auto* entry : entries) {
|
||||
if (searchEntryImpl(entry)) {
|
||||
results.append(entry);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next search to be case sensitive or not
|
||||
*
|
||||
* @param state
|
||||
*/
|
||||
void EntrySearcher::setCaseSensitive(bool state)
|
||||
{
|
||||
m_caseSensitive = state;
|
||||
|
@ -63,16 +115,15 @@ bool EntrySearcher::isCaseSensitive()
|
|||
return m_caseSensitive;
|
||||
}
|
||||
|
||||
bool EntrySearcher::searchEntryImpl(const QString& searchString, Entry* entry)
|
||||
bool EntrySearcher::searchEntryImpl(Entry* entry)
|
||||
{
|
||||
// Pre-load in case they are needed
|
||||
auto attributes = QStringList(entry->attributes()->keys());
|
||||
auto attributes_keys = entry->attributes()->customKeys();
|
||||
auto attributes = QStringList(attributes_keys + entry->attributes()->values(attributes_keys));
|
||||
auto attachments = QStringList(entry->attachments()->keys());
|
||||
|
||||
bool found;
|
||||
auto searchTerms = parseSearchTerms(searchString);
|
||||
|
||||
for (const auto& term : searchTerms) {
|
||||
for (const auto& term : m_searchTerms) {
|
||||
switch (term->field) {
|
||||
case Field::Title:
|
||||
found = term->regex.match(entry->resolvePlaceholder(entry->title())).hasMatch();
|
||||
|
@ -112,10 +163,9 @@ bool EntrySearcher::searchEntryImpl(const QString& searchString, Entry* entry)
|
|||
return true;
|
||||
}
|
||||
|
||||
QList<QSharedPointer<EntrySearcher::SearchTerm>> EntrySearcher::parseSearchTerms(const QString& searchString)
|
||||
void EntrySearcher::parseSearchTerms(const QString& searchString)
|
||||
{
|
||||
auto terms = QList<QSharedPointer<SearchTerm>>();
|
||||
|
||||
m_searchTerms.clear();
|
||||
auto results = m_termParser.globalMatch(searchString);
|
||||
while (results.hasNext()) {
|
||||
auto result = results.next();
|
||||
|
@ -165,8 +215,6 @@ QList<QSharedPointer<EntrySearcher::SearchTerm>> EntrySearcher::parseSearchTerms
|
|||
}
|
||||
}
|
||||
|
||||
terms.append(term);
|
||||
m_searchTerms.append(term);
|
||||
}
|
||||
|
||||
return terms;
|
||||
}
|
||||
|
|
|
@ -31,14 +31,15 @@ public:
|
|||
explicit EntrySearcher(bool caseSensitive = false);
|
||||
|
||||
QList<Entry*> search(const QString& searchString, const Group* baseGroup, bool forceSearch = false);
|
||||
QList<Entry*> repeat(const Group* baseGroup, bool forceSearch = false);
|
||||
|
||||
QList<Entry*> searchEntries(const QString& searchString, const QList<Entry*>& entries);
|
||||
QList<Entry*> repeatEntries(const QList<Entry*>& entries);
|
||||
|
||||
void setCaseSensitive(bool state);
|
||||
bool isCaseSensitive();
|
||||
|
||||
private:
|
||||
bool searchEntryImpl(const QString& searchString, Entry* entry);
|
||||
|
||||
enum class Field
|
||||
{
|
||||
Undefined,
|
||||
|
@ -59,10 +60,12 @@ private:
|
|||
bool exclude;
|
||||
};
|
||||
|
||||
QList<QSharedPointer<SearchTerm>> parseSearchTerms(const QString& searchString);
|
||||
bool searchEntryImpl(Entry* entry);
|
||||
void parseSearchTerms(const QString& searchString);
|
||||
|
||||
bool m_caseSensitive;
|
||||
QRegularExpression m_termParser;
|
||||
QList<QSharedPointer<SearchTerm>> m_searchTerms;
|
||||
|
||||
friend class TestEntrySearcher;
|
||||
};
|
||||
|
|
|
@ -123,6 +123,7 @@ BulkFileWatcher::BulkFileWatcher(QObject* parent)
|
|||
connect(&m_fileWatchUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges()));
|
||||
connect(&m_pendingSignalsTimer, SIGNAL(timeout()), this, SLOT(emitSignals()));
|
||||
m_fileWatchUnblockTimer.setSingleShot(true);
|
||||
m_pendingSignalsTimer.setSingleShot(true);
|
||||
}
|
||||
|
||||
void BulkFileWatcher::clear()
|
||||
|
|
|
@ -1,8 +1,30 @@
|
|||
/*
|
||||
* Copyright (C) 2013 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 "OSEventFilter.h"
|
||||
|
||||
#include <QByteArray>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
OSEventFilter::OSEventFilter()
|
||||
{
|
||||
|
@ -15,12 +37,18 @@ bool OSEventFilter::nativeEventFilter(const QByteArray& eventType, void* message
|
|||
#if defined(Q_OS_UNIX)
|
||||
if (eventType == QByteArrayLiteral("xcb_generic_event_t")) {
|
||||
#elif defined(Q_OS_WIN)
|
||||
if (eventType == QByteArrayLiteral("windows_generic_MSG")
|
||||
|| eventType == QByteArrayLiteral("windows_dispatcher_MSG")) {
|
||||
auto winmsg = static_cast<MSG*>(message);
|
||||
if (winmsg->message == WM_QUERYENDSESSION) {
|
||||
*result = 1;
|
||||
return true;
|
||||
} else if (winmsg->message == WM_ENDSESSION) {
|
||||
getMainWindow()->appExit();
|
||||
*result = 0;
|
||||
return true;
|
||||
} else if (eventType == QByteArrayLiteral("windows_generic_MSG")
|
||||
|| eventType == QByteArrayLiteral("windows_dispatcher_MSG")) {
|
||||
#endif
|
||||
int retCode = autoType()->callEventFilter(message);
|
||||
|
||||
return retCode == 1;
|
||||
return autoType()->callEventFilter(message) == 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
* Copyright (C) 2013 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 OSEVENTFILTER_H
|
||||
#define OSEVENTFILTER_H
|
||||
#include <QAbstractNativeEventFilter>
|
||||
|
|
|
@ -84,7 +84,8 @@ int Kdf::benchmark(int msec) const
|
|||
}
|
||||
|
||||
Kdf::BenchmarkThread::BenchmarkThread(int msec, const Kdf* kdf)
|
||||
: m_msec(msec)
|
||||
: m_rounds(1)
|
||||
, m_msec(msec)
|
||||
, m_kdf(kdf)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -19,12 +19,6 @@
|
|||
#include "BinaryStream.h"
|
||||
#include <QtEndian>
|
||||
|
||||
BinaryStream::BinaryStream(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_timeout(-1)
|
||||
{
|
||||
}
|
||||
|
||||
BinaryStream::BinaryStream(QIODevice* device)
|
||||
: QObject(device)
|
||||
, m_timeout(-1)
|
||||
|
@ -36,7 +30,10 @@ BinaryStream::BinaryStream(QByteArray* ba, QObject* parent)
|
|||
: QObject(parent)
|
||||
, m_timeout(-1)
|
||||
{
|
||||
setData(ba);
|
||||
m_buffer.reset(new QBuffer(ba));
|
||||
m_buffer->open(QIODevice::ReadWrite);
|
||||
|
||||
m_device = m_buffer.data();
|
||||
}
|
||||
|
||||
BinaryStream::~BinaryStream()
|
||||
|
@ -53,19 +50,6 @@ QIODevice* BinaryStream::device() const
|
|||
return m_device;
|
||||
}
|
||||
|
||||
void BinaryStream::setDevice(QIODevice* device)
|
||||
{
|
||||
m_device = device;
|
||||
}
|
||||
|
||||
void BinaryStream::setData(QByteArray* ba)
|
||||
{
|
||||
m_buffer.reset(new QBuffer(ba));
|
||||
m_buffer->open(QIODevice::ReadWrite);
|
||||
|
||||
m_device = m_buffer.data();
|
||||
}
|
||||
|
||||
void BinaryStream::setTimeout(int timeout)
|
||||
{
|
||||
m_timeout = timeout;
|
||||
|
|
|
@ -26,16 +26,14 @@
|
|||
class BinaryStream : QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(BinaryStream)
|
||||
public:
|
||||
BinaryStream(QObject* parent = nullptr);
|
||||
BinaryStream(QIODevice* device);
|
||||
BinaryStream(QByteArray* ba, QObject* parent = nullptr);
|
||||
~BinaryStream();
|
||||
explicit BinaryStream(QIODevice* device);
|
||||
explicit BinaryStream(QByteArray* ba, QObject* parent = nullptr);
|
||||
~BinaryStream() override;
|
||||
|
||||
const QString errorString() const;
|
||||
QIODevice* device() const;
|
||||
void setDevice(QIODevice* device);
|
||||
void setData(QByteArray* ba);
|
||||
void setTimeout(int timeout);
|
||||
|
||||
bool read(QByteArray& ba);
|
||||
|
|
|
@ -125,9 +125,8 @@ Application::Application(int& argc, char** argv)
|
|||
break;
|
||||
}
|
||||
default:
|
||||
qWarning() << QObject::tr("The lock file could not be created. Single-instance mode disabled.")
|
||||
.toUtf8()
|
||||
.constData();
|
||||
qWarning()
|
||||
<< QObject::tr("The lock file could not be created. Single-instance mode disabled.").toUtf8().constData();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -176,7 +176,8 @@ void ApplicationSettingsWidget::loadSettings()
|
|||
m_generalUi->minimizeOnCloseCheckBox->setChecked(config()->get("GUI/MinimizeOnClose").toBool());
|
||||
m_generalUi->systrayMinimizeOnStartup->setChecked(config()->get("GUI/MinimizeOnStartup").toBool());
|
||||
m_generalUi->checkForUpdatesOnStartupCheckBox->setChecked(config()->get("GUI/CheckForUpdates").toBool());
|
||||
m_generalUi->checkForUpdatesIncludeBetasCheckBox->setChecked(config()->get("GUI/CheckForUpdatesIncludeBetas").toBool());
|
||||
m_generalUi->checkForUpdatesIncludeBetasCheckBox->setChecked(
|
||||
config()->get("GUI/CheckForUpdatesIncludeBetas").toBool());
|
||||
m_generalUi->autoTypeAskCheckBox->setChecked(config()->get("security/autotypeask").toBool());
|
||||
|
||||
if (autoType()->isAvailable()) {
|
||||
|
|
|
@ -251,7 +251,11 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::databaseKey()
|
|||
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
// check if TouchID is available and enabled for unlocking the database
|
||||
if (m_ui->checkTouchID->isChecked() && TouchID::getInstance().isAvailable() && masterKey->isEmpty()) {
|
||||
if (m_ui->checkTouchID->isChecked() && TouchID::getInstance().isAvailable()
|
||||
&& m_ui->editPassword->text().isEmpty()) {
|
||||
// clear empty password from composite key
|
||||
masterKey->clear();
|
||||
|
||||
// try to get, decrypt and use PasswordKey
|
||||
QSharedPointer<QByteArray> passwordKey = TouchID::getInstance().getKey(m_filename);
|
||||
if (passwordKey != NULL) {
|
||||
|
|
|
@ -61,8 +61,6 @@
|
|||
#include "keeshare/KeeShare.h"
|
||||
#include "touchid/TouchID.h"
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <sys/vfs.h>
|
||||
#endif
|
||||
|
@ -80,6 +78,9 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
|||
, m_previewView(new EntryPreviewWidget(this))
|
||||
, m_previewSplitter(new QSplitter(m_mainWidget))
|
||||
, m_searchingLabel(new QLabel(this))
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
, m_shareLabel(new QLabel(this))
|
||||
#endif
|
||||
, m_csvImportWizard(new CsvImportWizard(this))
|
||||
, m_editEntryWidget(new EditEntryWidget(this))
|
||||
, m_editGroupWidget(new EditGroupWidget(this))
|
||||
|
@ -103,6 +104,9 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
|||
auto* vbox = new QVBoxLayout();
|
||||
vbox->setMargin(0);
|
||||
vbox->addWidget(m_searchingLabel);
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
vbox->addWidget(m_shareLabel);
|
||||
#endif
|
||||
vbox->addWidget(m_previewSplitter);
|
||||
rightHandSideWidget->setLayout(vbox);
|
||||
m_entryView = new EntryView(rightHandSideWidget);
|
||||
|
@ -134,6 +138,16 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
|||
"border-radius: 4px;");
|
||||
m_searchingLabel->setVisible(false);
|
||||
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
m_shareLabel->setText(tr("Shared group..."));
|
||||
m_shareLabel->setAlignment(Qt::AlignCenter);
|
||||
m_shareLabel->setStyleSheet("color: rgb(0, 0, 0);"
|
||||
"background-color: rgb(255, 253, 160);"
|
||||
"border: 2px solid rgb(190, 190, 190);"
|
||||
"border-radius: 4px;");
|
||||
m_shareLabel->setVisible(false);
|
||||
#endif
|
||||
|
||||
m_previewView->hide();
|
||||
m_previewSplitter->addWidget(m_entryView);
|
||||
m_previewSplitter->addWidget(m_previewView);
|
||||
|
@ -194,6 +208,12 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
// We need to reregister the database to allow exports
|
||||
// from a newly created database
|
||||
KeeShare::instance()->connectDatabase(m_db, {});
|
||||
#endif
|
||||
|
||||
switchToMainView();
|
||||
}
|
||||
|
||||
|
@ -377,6 +397,9 @@ void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
|
|||
processAutoOpen();
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
KeeShare::instance()->connectDatabase(m_db, oldDb);
|
||||
#else
|
||||
// Keep the instance active till the end of this function
|
||||
Q_UNUSED(oldDb);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -765,9 +788,9 @@ void DatabaseWidget::switchToMainView(bool previousDialogAccepted)
|
|||
|
||||
setCurrentWidget(m_mainWidget);
|
||||
|
||||
if (sender() == m_entryView) {
|
||||
if (sender() == m_entryView || sender() == m_editEntryWidget) {
|
||||
onEntryChanged(m_entryView->currentEntry());
|
||||
} else if (sender() == m_groupView) {
|
||||
} else if (sender() == m_groupView || sender() == m_editGroupWidget) {
|
||||
onGroupChanged(m_groupView->currentGroup());
|
||||
}
|
||||
}
|
||||
|
@ -1089,6 +1112,7 @@ void DatabaseWidget::search(const QString& searchtext)
|
|||
}
|
||||
|
||||
m_searchingLabel->setVisible(true);
|
||||
m_shareLabel->setVisible(false);
|
||||
|
||||
emit searchModeActivated();
|
||||
}
|
||||
|
@ -1117,6 +1141,16 @@ void DatabaseWidget::onGroupChanged(Group* group)
|
|||
}
|
||||
|
||||
m_previewView->setGroup(group);
|
||||
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
auto shareLabel = KeeShare::sharingLabel(group);
|
||||
if (!shareLabel.isEmpty()) {
|
||||
m_shareLabel->setText(shareLabel);
|
||||
m_shareLabel->setVisible(true);
|
||||
} else {
|
||||
m_shareLabel->setVisible(false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void DatabaseWidget::onDatabaseModified()
|
||||
|
@ -1140,6 +1174,7 @@ void DatabaseWidget::endSearch()
|
|||
|
||||
// Show the normal entry view of the current group
|
||||
m_entryView->displayGroup(currentGroup());
|
||||
onGroupChanged(currentGroup());
|
||||
|
||||
emit listModeActivated();
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
#include "gui/csvImport/CsvImportWizard.h"
|
||||
#include "gui/entry/EntryModel.h"
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
class DatabaseOpenWidget;
|
||||
class KeePass1OpenWidget;
|
||||
class DatabaseSettingsDialog;
|
||||
|
@ -233,6 +235,9 @@ private:
|
|||
QPointer<EntryPreviewWidget> m_previewView;
|
||||
QPointer<QSplitter> m_previewSplitter;
|
||||
QPointer<QLabel> m_searchingLabel;
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
QPointer<QLabel> m_shareLabel;
|
||||
#endif
|
||||
QPointer<CsvImportWizard> m_csvImportWizard;
|
||||
QPointer<EditEntryWidget> m_editEntryWidget;
|
||||
QPointer<EditGroupWidget> m_editGroupWidget;
|
||||
|
|
|
@ -59,6 +59,7 @@ void EditWidget::addPage(const QString& labelText, const QIcon& icon, QWidget* w
|
|||
*/
|
||||
auto* scrollArea = new QScrollArea(m_ui->stackedWidget);
|
||||
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
scrollArea->setWidget(widget);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
m_ui->stackedWidget->addWidget(scrollArea);
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "gui/MessageBox.h"
|
||||
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
#include <QHostInfo>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QtNetwork>
|
||||
#endif
|
||||
|
@ -65,7 +66,6 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent)
|
|||
connect(m_ui->deleteButton, SIGNAL(clicked()), SLOT(removeCustomIcon()));
|
||||
connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon()));
|
||||
|
||||
connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SIGNAL(widgetUpdated()));
|
||||
connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SIGNAL(widgetUpdated()));
|
||||
connect(m_ui->defaultIconsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
||||
this, SIGNAL(widgetUpdated()));
|
||||
|
@ -196,13 +196,27 @@ void EditWidgetIcons::downloadFavicon()
|
|||
m_urlsToTry.clear();
|
||||
|
||||
QString fullyQualifiedDomain = m_url.host();
|
||||
QString secondLevelDomain = getSecondLevelDomain(m_url);
|
||||
|
||||
// Attempt to simply load the favicon.ico file
|
||||
if (fullyQualifiedDomain != secondLevelDomain) {
|
||||
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico"));
|
||||
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico"));
|
||||
|
||||
// Determine if host portion of URL is an IP address by resolving it and
|
||||
// searching for a match with the returned address(es).
|
||||
bool hostIsIp = false;
|
||||
QList<QHostAddress> hostAddressess = QHostInfo::fromName(fullyQualifiedDomain).addresses();
|
||||
for (auto addr : hostAddressess) {
|
||||
if (addr.toString() == fullyQualifiedDomain) {
|
||||
hostIsIp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hostIsIp) {
|
||||
QString secondLevelDomain = getSecondLevelDomain(m_url);
|
||||
|
||||
// Attempt to simply load the favicon.ico file
|
||||
if (fullyQualifiedDomain != secondLevelDomain) {
|
||||
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico"));
|
||||
}
|
||||
}
|
||||
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico"));
|
||||
|
||||
// Try to use alternative fallback URL, if enabled
|
||||
if (config()->get("security/IconDownloadFallback", false).toBool()) {
|
||||
|
@ -210,6 +224,15 @@ void EditWidgetIcons::downloadFavicon()
|
|||
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico");
|
||||
|
||||
m_urlsToTry.append(fallbackUrl);
|
||||
|
||||
if (!hostIsIp) {
|
||||
QString secondLevelDomain = getSecondLevelDomain(m_url);
|
||||
|
||||
if (fullyQualifiedDomain != secondLevelDomain) {
|
||||
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico");
|
||||
m_urlsToTry.append(fallbackUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startFetchFavicon(m_urlsToTry.takeFirst());
|
||||
|
@ -277,7 +300,7 @@ void EditWidgetIcons::fetchFinished()
|
|||
#endif
|
||||
}
|
||||
|
||||
void EditWidgetIcons::fetchCanceled()
|
||||
void EditWidgetIcons::abortRequests()
|
||||
{
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
if (m_reply) {
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#ifndef KEEPASSX_EDITWIDGETICONS_H
|
||||
#define KEEPASSX_EDITWIDGETICONS_H
|
||||
|
||||
#include <QSet>
|
||||
#include <QUrl>
|
||||
#include <QUuid>
|
||||
#include <QWidget>
|
||||
|
@ -66,6 +65,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void setUrl(const QString& url);
|
||||
void abortRequests();
|
||||
|
||||
signals:
|
||||
void messageEditEntry(QString, MessageWidget::MessageType);
|
||||
|
@ -77,7 +77,6 @@ private slots:
|
|||
void startFetchFavicon(const QUrl& url);
|
||||
void fetchFinished();
|
||||
void fetchReadyRead();
|
||||
void fetchCanceled();
|
||||
void addCustomIconFromFile();
|
||||
bool addCustomIcon(const QImage& icon);
|
||||
void removeCustomIcon();
|
||||
|
|
|
@ -42,9 +42,9 @@
|
|||
#include "keys/PasswordKey.h"
|
||||
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
#include "updatecheck/UpdateChecker.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include "gui/UpdateCheckDialog.h"
|
||||
#include "updatecheck/UpdateChecker.h"
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
|
@ -76,7 +76,7 @@
|
|||
class BrowserPlugin : public ISettingsPage
|
||||
{
|
||||
public:
|
||||
BrowserPlugin(DatabaseTabWidget* tabWidget)
|
||||
explicit BrowserPlugin(DatabaseTabWidget* tabWidget)
|
||||
{
|
||||
m_nativeMessagingHost =
|
||||
QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget, browserSettings()->isEnabled()));
|
||||
|
@ -374,7 +374,9 @@ MainWindow::MainWindow()
|
|||
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
connect(m_ui->actionCheckForUpdates, SIGNAL(triggered()), SLOT(showUpdateCheckDialog()));
|
||||
connect(UpdateChecker::instance(), SIGNAL(updateCheckFinished(bool, QString, bool)), SLOT(hasUpdateAvailable(bool, QString, bool)));
|
||||
connect(UpdateChecker::instance(),
|
||||
SIGNAL(updateCheckFinished(bool, QString, bool)),
|
||||
SLOT(hasUpdateAvailable(bool, QString, bool)));
|
||||
QTimer::singleShot(3000, this, SLOT(showUpdateCheckStartup()));
|
||||
#else
|
||||
m_ui->actionCheckForUpdates->setVisible(false);
|
||||
|
@ -687,12 +689,13 @@ void MainWindow::showUpdateCheckStartup()
|
|||
{
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
if (!config()->get("UpdateCheckMessageShown", false).toBool()) {
|
||||
auto result = MessageBox::question(this,
|
||||
tr("Check for updates on startup?"),
|
||||
tr("Would you like KeePassXC to check for updates on startup?") + "\n\n" +
|
||||
tr("You can always check for updates manually from the application menu."),
|
||||
MessageBox::Yes | MessageBox::No,
|
||||
MessageBox::Yes);
|
||||
auto result =
|
||||
MessageBox::question(this,
|
||||
tr("Check for updates on startup?"),
|
||||
tr("Would you like KeePassXC to check for updates on startup?") + "\n\n"
|
||||
+ tr("You can always check for updates manually from the application menu."),
|
||||
MessageBox::Yes | MessageBox::No,
|
||||
MessageBox::Yes);
|
||||
|
||||
config()->set("GUI/CheckForUpdates", (result == MessageBox::Yes));
|
||||
config()->set("UpdateCheckMessageShown", true);
|
||||
|
@ -713,6 +716,10 @@ void MainWindow::hasUpdateAvailable(bool hasUpdate, const QString& version, bool
|
|||
updateCheckDialog->showUpdateCheckResponse(hasUpdate, version);
|
||||
updateCheckDialog->show();
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(hasUpdate)
|
||||
Q_UNUSED(version)
|
||||
Q_UNUSED(isManuallyRequested)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -120,6 +120,7 @@ bool SearchWidget::eventFilter(QObject* obj, QEvent* event)
|
|||
|
||||
void SearchWidget::connectSignals(SignalMultiplexer& mx)
|
||||
{
|
||||
// Connects basically only to the current DatabaseWidget, but allows to switch between instances!
|
||||
mx.connect(this, SIGNAL(search(QString)), SLOT(search(QString)));
|
||||
mx.connect(this, SIGNAL(caseSensitiveChanged(bool)), SLOT(setSearchCaseSensitive(bool)));
|
||||
mx.connect(this, SIGNAL(limitGroupChanged(bool)), SLOT(setSearchLimitGroup(bool)));
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
*/
|
||||
|
||||
#include "UpdateCheckDialog.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "ui_UpdateCheckDialog.h"
|
||||
#include "updatecheck/UpdateChecker.h"
|
||||
#include "core/FilePath.h"
|
||||
|
||||
UpdateCheckDialog::UpdateCheckDialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
|
@ -31,35 +31,34 @@ UpdateCheckDialog::UpdateCheckDialog(QWidget* parent)
|
|||
m_ui->iconLabel->setPixmap(filePath()->applicationIcon().pixmap(48));
|
||||
|
||||
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close()));
|
||||
connect(UpdateChecker::instance(), SIGNAL(updateCheckFinished(bool, QString, bool)), SLOT(showUpdateCheckResponse(bool, QString)));
|
||||
connect(UpdateChecker::instance(),
|
||||
SIGNAL(updateCheckFinished(bool, QString, bool)),
|
||||
SLOT(showUpdateCheckResponse(bool, QString)));
|
||||
}
|
||||
|
||||
void UpdateCheckDialog::showUpdateCheckResponse(bool status, const QString& version) {
|
||||
void UpdateCheckDialog::showUpdateCheckResponse(bool status, const QString& version)
|
||||
{
|
||||
m_ui->progressBar->setVisible(false);
|
||||
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Close"));
|
||||
|
||||
if (version == QString("error")) {
|
||||
setWindowTitle(tr("Update Error!"));
|
||||
|
||||
m_ui->statusLabel->setText(
|
||||
"<strong>" + tr("Update Error!") + "</strong><br><br>" +
|
||||
tr("An error occurred in retrieving update information.") + "<br>" +
|
||||
tr("Please try again later."));
|
||||
m_ui->statusLabel->setText("<strong>" + tr("Update Error!") + "</strong><br><br>"
|
||||
+ tr("An error occurred in retrieving update information.") + "<br>"
|
||||
+ tr("Please try again later."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
setWindowTitle(tr("Software Update"));
|
||||
m_ui->statusLabel->setText(
|
||||
"<strong>" + tr("A new version of KeePassXC is available!") + "</strong><br><br>" +
|
||||
tr("KeePassXC %1 is now available — you have %2.").arg(version, KEEPASSXC_VERSION) + "<br><br>" +
|
||||
"<a href='https://keepassxc.org/download/'>" +
|
||||
tr("Download it at keepassxc.org") +
|
||||
"</a>");
|
||||
m_ui->statusLabel->setText("<strong>" + tr("A new version of KeePassXC is available!") + "</strong><br><br>"
|
||||
+ tr("KeePassXC %1 is now available — you have %2.").arg(version, KEEPASSXC_VERSION)
|
||||
+ "<br><br>" + "<a href='https://keepassxc.org/download/'>"
|
||||
+ tr("Download it at keepassxc.org") + "</a>");
|
||||
} else {
|
||||
setWindowTitle(tr("You're up-to-date!"));
|
||||
m_ui->statusLabel->setText(tr(
|
||||
"KeePassXC %1 is currently the newest version available").arg(KEEPASSXC_VERSION));
|
||||
m_ui->statusLabel->setText(tr("KeePassXC %1 is currently the newest version available").arg(KEEPASSXC_VERSION));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
#ifndef KEEPASSXC_UPDATECHECKDIALOG_H
|
||||
#define KEEPASSXC_UPDATECHECKDIALOG_H
|
||||
|
||||
#include <QUrl>
|
||||
#include <QDialog>
|
||||
#include <QScopedPointer>
|
||||
#include "gui/MessageBox.h"
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Global.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include "updatecheck/UpdateChecker.h"
|
||||
#include <QDialog>
|
||||
#include <QScopedPointer>
|
||||
#include <QUrl>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
|
@ -33,7 +33,7 @@ namespace Ui
|
|||
|
||||
class UpdateCheckDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UpdateCheckDialog(QWidget* parent = nullptr);
|
||||
|
@ -46,5 +46,4 @@ private:
|
|||
QScopedPointer<Ui::UpdateCheckDialog> m_ui;
|
||||
};
|
||||
|
||||
|
||||
#endif //KEEPASSXC_UPDATECHECKDIALOG_H
|
||||
#endif // KEEPASSXC_UPDATECHECKDIALOG_H
|
||||
|
|
|
@ -81,7 +81,7 @@ void DatabaseSettingsWidgetEncryption::initialize()
|
|||
isDirty = true;
|
||||
}
|
||||
if (!m_db->key()) {
|
||||
m_db->setKey(QSharedPointer<CompositeKey>::create());
|
||||
m_db->setKey(QSharedPointer<CompositeKey>::create(), true, false, false);
|
||||
m_db->setCipher(KeePass2::CIPHER_AES256);
|
||||
isDirty = true;
|
||||
}
|
||||
|
@ -127,8 +127,7 @@ void DatabaseSettingsWidgetEncryption::setupAlgorithmComboBox()
|
|||
{
|
||||
m_ui->algorithmComboBox->clear();
|
||||
for (auto& cipher : asConst(KeePass2::CIPHERS)) {
|
||||
m_ui->algorithmComboBox->addItem(cipher.second.toUtf8(),
|
||||
cipher.first.toByteArray());
|
||||
m_ui->algorithmComboBox->addItem(cipher.second.toUtf8(), cipher.first.toByteArray());
|
||||
}
|
||||
int cipherIndex = m_ui->algorithmComboBox->findData(m_db->cipher().toByteArray());
|
||||
if (cipherIndex > -1) {
|
||||
|
@ -142,8 +141,7 @@ void DatabaseSettingsWidgetEncryption::setupKdfComboBox()
|
|||
bool block = m_ui->kdfComboBox->blockSignals(true);
|
||||
m_ui->kdfComboBox->clear();
|
||||
for (auto& kdf : asConst(KeePass2::KDFS)) {
|
||||
m_ui->kdfComboBox->addItem(kdf.second.toUtf8(),
|
||||
kdf.first.toByteArray());
|
||||
m_ui->kdfComboBox->addItem(kdf.second.toUtf8(), kdf.first.toByteArray());
|
||||
}
|
||||
m_ui->kdfComboBox->blockSignals(block);
|
||||
}
|
||||
|
|
|
@ -136,34 +136,34 @@ bool DatabaseSettingsWidgetMasterKey::save()
|
|||
|
||||
auto newKey = QSharedPointer<CompositeKey>::create();
|
||||
|
||||
QSharedPointer<Key> passwordKey;
|
||||
QSharedPointer<Key> fileKey;
|
||||
QSharedPointer<ChallengeResponseKey> ykCrKey;
|
||||
QSharedPointer<Key> oldPasswordKey;
|
||||
QSharedPointer<Key> oldFileKey;
|
||||
QSharedPointer<ChallengeResponseKey> oldChallengeResponse;
|
||||
|
||||
for (const auto& key : m_db->key()->keys()) {
|
||||
if (key->uuid() == PasswordKey::UUID) {
|
||||
passwordKey = key;
|
||||
oldPasswordKey = key;
|
||||
} else if (key->uuid() == FileKey::UUID) {
|
||||
fileKey = key;
|
||||
oldFileKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& key : m_db->key()->challengeResponseKeys()) {
|
||||
if (key->uuid() == YkChallengeResponseKey::UUID) {
|
||||
ykCrKey = key;
|
||||
oldChallengeResponse = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (!addToCompositeKey(m_passwordEditWidget, newKey, passwordKey)) {
|
||||
if (!addToCompositeKey(m_passwordEditWidget, newKey, oldPasswordKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!addToCompositeKey(m_keyFileEditWidget, newKey, fileKey)) {
|
||||
if (!addToCompositeKey(m_keyFileEditWidget, newKey, oldFileKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
if (!addToCompositeKey(m_yubiKeyEditWidget, newKey, ykCrKey)) {
|
||||
if (!addToCompositeKey(m_yubiKeyEditWidget, newKey, oldChallengeResponse)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
@ -177,7 +177,7 @@ bool DatabaseSettingsWidgetMasterKey::save()
|
|||
return false;
|
||||
}
|
||||
|
||||
if (m_passwordEditWidget->visiblePage() == KeyComponentWidget::AddNew) {
|
||||
if (m_passwordEditWidget->isEmpty()) {
|
||||
auto answer = MessageBox::warning(this,
|
||||
tr("No password set"),
|
||||
tr("WARNING! You have not set a password. Using a database without "
|
||||
|
@ -190,9 +190,13 @@ bool DatabaseSettingsWidgetMasterKey::save()
|
|||
}
|
||||
}
|
||||
|
||||
m_db->setKey(newKey);
|
||||
m_db->setKey(newKey, true, false, false);
|
||||
|
||||
emit editFinished(true);
|
||||
if (m_isDirty) {
|
||||
m_db->markAsModified();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -142,6 +142,7 @@ void EditEntryWidget::setupMain()
|
|||
connect(m_mainUi->togglePasswordGeneratorButton, SIGNAL(toggled(bool)), SLOT(togglePasswordGeneratorButton(bool)));
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
connect(m_mainUi->fetchFaviconButton, SIGNAL(clicked()), m_iconsWidget, SLOT(downloadFavicon()));
|
||||
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
|
||||
#endif
|
||||
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
|
||||
connect(m_mainUi->notesEnabled, SIGNAL(toggled(bool)), this, SLOT(toggleHideNotes(bool)));
|
||||
|
@ -193,6 +194,8 @@ void EditEntryWidget::setupAdvanced()
|
|||
void EditEntryWidget::setupIcon()
|
||||
{
|
||||
addPage(tr("Icon"), FilePath::instance()->icon("apps", "preferences-desktop-icons"), m_iconsWidget);
|
||||
connect(this, SIGNAL(accepted()), m_iconsWidget, SLOT(abortRequests()));
|
||||
connect(this, SIGNAL(rejected()), m_iconsWidget, SLOT(abortRequests()));
|
||||
}
|
||||
|
||||
void EditEntryWidget::setupAutoType()
|
||||
|
@ -764,7 +767,6 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
|
|||
iconStruct.uuid = entry->iconUuid();
|
||||
iconStruct.number = entry->iconNumber();
|
||||
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());
|
||||
if (entry->defaultAutoTypeSequence().isEmpty()) {
|
||||
|
|
|
@ -36,9 +36,9 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
void set(Group* temporaryGroup) const
|
||||
void set(Group* temporaryGroup, QSharedPointer<Database> database) const
|
||||
{
|
||||
editPage->set(widget, temporaryGroup);
|
||||
editPage->set(widget, temporaryGroup, database);
|
||||
}
|
||||
|
||||
void assign() const
|
||||
|
@ -133,7 +133,7 @@ void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<
|
|||
m_editWidgetProperties->setCustomData(m_temporaryGroup->customData());
|
||||
|
||||
for (const ExtraPage& page : asConst(m_extraPages)) {
|
||||
page.set(m_temporaryGroup.data());
|
||||
page.set(m_temporaryGroup.data(), m_db);
|
||||
}
|
||||
|
||||
setCurrentPage(0);
|
||||
|
|
|
@ -43,7 +43,7 @@ public:
|
|||
virtual QString name() = 0;
|
||||
virtual QIcon icon() = 0;
|
||||
virtual QWidget* createWidget() = 0;
|
||||
virtual void set(QWidget* widget, Group* tempoaryGroup) = 0;
|
||||
virtual void set(QWidget* widget, Group* tempoaryGroup, QSharedPointer<Database> database) = 0;
|
||||
virtual void assign(QWidget* widget) = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@ QVariant GroupModel::data(const QModelIndex& index, int role) const
|
|||
Group* group = groupFromIndex(index);
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
QString nameTemplate = tr("%1", "Template for name without annotation");
|
||||
QString nameTemplate = "%1";
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
nameTemplate = KeeShare::indicatorSuffix(group, nameTemplate);
|
||||
#endif
|
||||
|
|
|
@ -40,7 +40,10 @@ PasswordEditWidget::~PasswordEditWidget()
|
|||
|
||||
bool PasswordEditWidget::addToCompositeKey(QSharedPointer<CompositeKey> key)
|
||||
{
|
||||
key->addKey(QSharedPointer<PasswordKey>::create(m_compUi->enterPasswordEdit->text()));
|
||||
QString pw = m_compUi->enterPasswordEdit->text();
|
||||
if (!pw.isEmpty()) {
|
||||
key->addKey(QSharedPointer<PasswordKey>::create(pw));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -60,6 +63,11 @@ bool PasswordEditWidget::isPasswordVisible() const
|
|||
return m_compUi->togglePasswordButton->isChecked();
|
||||
}
|
||||
|
||||
bool PasswordEditWidget::isEmpty() const
|
||||
{
|
||||
return m_compUi->enterPasswordEdit->text().isEmpty();
|
||||
}
|
||||
|
||||
QWidget* PasswordEditWidget::componentEditWidget()
|
||||
{
|
||||
m_compEditWidget = new QWidget();
|
||||
|
@ -86,11 +94,6 @@ void PasswordEditWidget::initComponentEditWidget(QWidget* widget)
|
|||
|
||||
bool PasswordEditWidget::validate(QString& errorMessage) const
|
||||
{
|
||||
if (m_compUi->enterPasswordEdit->text().isEmpty()) {
|
||||
errorMessage = tr("Password cannot be empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_compUi->enterPasswordEdit->text() != m_compUi->repeatPasswordEdit->text()) {
|
||||
errorMessage = tr("Passwords do not match.");
|
||||
return false;
|
||||
|
|
|
@ -38,6 +38,7 @@ public:
|
|||
bool addToCompositeKey(QSharedPointer<CompositeKey> key) override;
|
||||
void setPasswordVisible(bool visible);
|
||||
bool isPasswordVisible() const;
|
||||
bool isEmpty() const;
|
||||
bool validate(QString& errorMessage) const override;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -93,7 +93,7 @@ void KeeShare::setOwn(const KeeShareSettings::Own& own)
|
|||
|
||||
bool KeeShare::isShared(const Group* group)
|
||||
{
|
||||
return group->customData()->contains(KeeShare_Reference);
|
||||
return group && group->customData()->contains(KeeShare_Reference);
|
||||
}
|
||||
|
||||
KeeShareSettings::Reference KeeShare::referenceOf(const Group* group)
|
||||
|
@ -142,6 +142,40 @@ bool KeeShare::isEnabled(const Group* group)
|
|||
return (reference.isImporting() && active.in) || (reference.isExporting() && active.out);
|
||||
}
|
||||
|
||||
const Group* KeeShare::resolveSharedGroup(const Group* group)
|
||||
{
|
||||
while (group && group != group->database()->rootGroup()) {
|
||||
if (isShared(group)) {
|
||||
return group;
|
||||
}
|
||||
group = group->parentGroup();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString KeeShare::sharingLabel(const Group* group)
|
||||
{
|
||||
auto* share = resolveSharedGroup(group);
|
||||
if (!share) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto reference = referenceOf(share);
|
||||
switch (reference.type) {
|
||||
case KeeShareSettings::Inactive:
|
||||
return tr("Disabled share %1").arg(reference.path);
|
||||
case KeeShareSettings::ImportFrom:
|
||||
return tr("Import from share %1").arg(reference.path);
|
||||
case KeeShareSettings::ExportTo:
|
||||
return tr("Export to share %1").arg(reference.path);
|
||||
case KeeShareSettings::SynchronizeWith:
|
||||
return tr("Synchronize with share %1").arg(reference.path);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QPixmap KeeShare::indicatorBadge(const Group* group, QPixmap pixmap)
|
||||
{
|
||||
if (!isShared(group)) {
|
||||
|
|
|
@ -43,6 +43,9 @@ public:
|
|||
static bool isShared(const Group* group);
|
||||
static bool isEnabled(const Group* group);
|
||||
|
||||
static const Group* resolveSharedGroup(const Group* group);
|
||||
static QString sharingLabel(const Group* group);
|
||||
|
||||
static KeeShareSettings::Own own();
|
||||
static KeeShareSettings::Active active();
|
||||
static KeeShareSettings::Foreign foreign();
|
||||
|
|
|
@ -183,7 +183,7 @@ void SettingsWidgetKeeShare::exportCertificate()
|
|||
}
|
||||
const auto filetype = tr("key.share", "Filetype for KeeShare key");
|
||||
const auto filters = QString("%1 (*." + filetype + ");;%2 (*)").arg(tr("KeeShare key file"), tr("All files"));
|
||||
QString filename = tr("%1.%2", "Template for KeeShare key file").arg(m_own.certificate.signer).arg(filetype);
|
||||
QString filename = QString("%1.%2").arg(m_own.certificate.signer).arg(filetype);
|
||||
filename = fileDialog()->getSaveFileName(
|
||||
this, tr("Select path"), defaultDirPath, filters, nullptr, QFileDialog::Options(0), filetype, filename);
|
||||
if (filename.isEmpty()) {
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>327</width>
|
||||
<height>434</height>
|
||||
<width>378</width>
|
||||
<height>508</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0">
|
||||
|
@ -82,7 +82,7 @@
|
|||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="ownCertificateSignerLabel">
|
||||
<property name="text">
|
||||
<string>Signer</string>
|
||||
<string>Signer:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "core/Entry.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "core/FileWatcher.h"
|
||||
#include "core/Global.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Merger.h"
|
||||
#include "core/Metadata.h"
|
||||
|
@ -191,7 +192,7 @@ void ShareObserver::reinitialize()
|
|||
|
||||
const auto active = KeeShare::active();
|
||||
QList<Update> updated;
|
||||
QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
|
||||
const 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) {
|
||||
|
@ -214,7 +215,9 @@ void ShareObserver::reinitialize()
|
|||
QStringList success;
|
||||
QStringList warning;
|
||||
QStringList error;
|
||||
for (const auto& update : updated) {
|
||||
QMap<QString, QStringList> imported;
|
||||
QMap<QString, QStringList> exported;
|
||||
for (const auto& update : asConst(updated)) {
|
||||
if (!update.oldReference.path.isEmpty()) {
|
||||
m_fileWatcher->removePath(update.oldReference.path);
|
||||
}
|
||||
|
@ -222,8 +225,12 @@ void ShareObserver::reinitialize()
|
|||
if (!update.newReference.path.isEmpty() && update.newReference.type != KeeShareSettings::Inactive) {
|
||||
m_fileWatcher->addPath(update.newReference.path);
|
||||
}
|
||||
if (update.newReference.isExporting()) {
|
||||
exported[update.newReference.path] << update.group->name();
|
||||
}
|
||||
|
||||
if (update.newReference.isImporting()) {
|
||||
imported[update.newReference.path] << update.group->name();
|
||||
const auto result = this->importFromReferenceContainer(update.newReference.path);
|
||||
if (!result.isValid()) {
|
||||
// tolerable result - blocked import or missing source
|
||||
|
@ -241,6 +248,16 @@ void ShareObserver::reinitialize()
|
|||
}
|
||||
}
|
||||
}
|
||||
for (auto it = imported.cbegin(); it != imported.cend(); ++it) {
|
||||
if (it.value().count() > 1) {
|
||||
warning << tr("Multiple import source path to %1 in %2").arg(it.key(), it.value().join(", "));
|
||||
}
|
||||
}
|
||||
for (auto it = exported.cbegin(); it != exported.cend(); ++it) {
|
||||
if (it.value().count() > 1) {
|
||||
error << tr("Conflicting export target path %1 in %2").arg(it.key(), it.value().join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
notifyAbout(success, warning, error);
|
||||
}
|
||||
|
@ -659,8 +676,10 @@ ShareObserver::Result ShareObserver::exportIntoReferenceSignedContainer(const Ke
|
|||
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())};
|
||||
::qWarning("Embedding signature failed: Could not open file to write (%d)", zip.getZipError());
|
||||
return {reference.path,
|
||||
Result::Error,
|
||||
tr("Could not embed signature: Could not open file to write (%1)").arg(file.getZipError())};
|
||||
}
|
||||
QTextStream stream(&file);
|
||||
KeeShareSettings::Sign sign;
|
||||
|
@ -672,8 +691,10 @@ ShareObserver::Result ShareObserver::exportIntoReferenceSignedContainer(const Ke
|
|||
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())};
|
||||
::qWarning("Embedding signature failed: Could not write file (%d)", zip.getZipError());
|
||||
return {reference.path,
|
||||
Result::Error,
|
||||
tr("Could not embed signature: Could not write file (%1)").arg(file.getZipError())};
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
@ -681,14 +702,18 @@ ShareObserver::Result ShareObserver::exportIntoReferenceSignedContainer(const Ke
|
|||
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())};
|
||||
::qWarning("Embedding database failed: Could not open file to write (%d)", zip.getZipError());
|
||||
return {reference.path,
|
||||
Result::Error,
|
||||
tr("Could not embed database: Could not open file to write (%1)").arg(file.getZipError())};
|
||||
}
|
||||
file.write(bytes);
|
||||
if (file.getZipError() != ZIP_OK) {
|
||||
::qWarning("Embedding database failed: Could not write file (%d)", zip.getZipError());
|
||||
return {reference.path,
|
||||
Result::Error,
|
||||
tr("Could not embed database: Could not write file (%1)").arg(file.getZipError())};
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
zip.close();
|
||||
|
@ -725,28 +750,55 @@ ShareObserver::Result ShareObserver::exportIntoReferenceUnsignedContainer(const
|
|||
QList<ShareObserver::Result> ShareObserver::exportIntoReferenceContainers()
|
||||
{
|
||||
QList<Result> results;
|
||||
struct Reference
|
||||
{
|
||||
KeeShareSettings::Reference config;
|
||||
const Group* group;
|
||||
};
|
||||
|
||||
QMap<QString, QList<Reference>> references;
|
||||
const auto groups = m_db->rootGroup()->groupsRecursive(true);
|
||||
for (const auto* group : groups) {
|
||||
const auto reference = KeeShare::referenceOf(group);
|
||||
if (!reference.isExporting()) {
|
||||
continue;
|
||||
}
|
||||
references[reference.path] << Reference{reference, group};
|
||||
}
|
||||
|
||||
m_fileWatcher->ignoreFileChanges(reference.path);
|
||||
QScopedPointer<Database> targetDb(exportIntoContainer(reference, group));
|
||||
QFileInfo info(reference.path);
|
||||
for (auto it = references.cbegin(); it != references.cend(); ++it) {
|
||||
if (it.value().count() != 1) {
|
||||
const auto path = it.value().first().config.path;
|
||||
QStringList groups;
|
||||
for (const auto& reference : it.value()) {
|
||||
groups << reference.group->name();
|
||||
}
|
||||
results << Result{
|
||||
path, Result::Error, tr("Conflicting export target path %1 in %2").arg(path, groups.join(", "))};
|
||||
}
|
||||
}
|
||||
if (!results.isEmpty()) {
|
||||
// We need to block export due to config
|
||||
return results;
|
||||
}
|
||||
|
||||
for (auto it = references.cbegin(); it != references.cend(); ++it) {
|
||||
const auto& reference = it.value().first();
|
||||
m_fileWatcher->ignoreFileChanges(reference.config.path);
|
||||
QScopedPointer<Database> targetDb(exportIntoContainer(reference.config, reference.group));
|
||||
QFileInfo info(reference.config.path);
|
||||
if (isOfExportType(info, KeeShare::signedContainerFileType())) {
|
||||
results << exportIntoReferenceSignedContainer(reference, targetDb.data());
|
||||
results << exportIntoReferenceSignedContainer(reference.config, targetDb.data());
|
||||
m_fileWatcher->observeFileChanges(true);
|
||||
continue;
|
||||
}
|
||||
if (isOfExportType(info, KeeShare::unsignedContainerFileType())) {
|
||||
results << exportIntoReferenceUnsignedContainer(reference, targetDb.data());
|
||||
results << exportIntoReferenceUnsignedContainer(reference.config, targetDb.data());
|
||||
m_fileWatcher->observeFileChanges(true);
|
||||
continue;
|
||||
}
|
||||
Q_ASSERT(false);
|
||||
results << Result{reference.path, Result::Error, tr("Unexpected export error occurred")};
|
||||
results << Result{reference.config.path, Result::Error, tr("Unexpected export error occurred")};
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
@ -759,6 +811,7 @@ void ShareObserver::handleDatabaseSaved()
|
|||
QStringList error;
|
||||
QStringList warning;
|
||||
QStringList success;
|
||||
|
||||
const auto results = exportIntoReferenceContainers();
|
||||
for (const Result& result : results) {
|
||||
if (!result.isValid()) {
|
||||
|
@ -787,7 +840,7 @@ ShareObserver::Result::Result(const QString& path, ShareObserver::Result::Type t
|
|||
|
||||
bool ShareObserver::Result::isValid() const
|
||||
{
|
||||
return !path.isEmpty() || !message.isEmpty() || !message.isEmpty() || !message.isEmpty();
|
||||
return !path.isEmpty() || !message.isEmpty();
|
||||
}
|
||||
|
||||
bool ShareObserver::Result::isError() const
|
||||
|
|
|
@ -42,10 +42,10 @@ QWidget* EditGroupPageKeeShare::createWidget()
|
|||
return new EditGroupWidgetKeeShare();
|
||||
}
|
||||
|
||||
void EditGroupPageKeeShare::set(QWidget* widget, Group* temporaryGroup)
|
||||
void EditGroupPageKeeShare::set(QWidget* widget, Group* temporaryGroup, QSharedPointer<Database> database)
|
||||
{
|
||||
EditGroupWidgetKeeShare* settingsWidget = reinterpret_cast<EditGroupWidgetKeeShare*>(widget);
|
||||
settingsWidget->setGroup(temporaryGroup);
|
||||
settingsWidget->setGroup(temporaryGroup, database);
|
||||
}
|
||||
|
||||
void EditGroupPageKeeShare::assign(QWidget* widget)
|
||||
|
|
|
@ -30,7 +30,7 @@ public:
|
|||
QString name() override;
|
||||
QIcon icon() override;
|
||||
QWidget* createWidget() override;
|
||||
void set(QWidget* widget, Group* temporaryGroup) override;
|
||||
void set(QWidget* widget, Group* temporaryGroup, QSharedPointer<Database> database) override;
|
||||
void assign(QWidget* widget) override;
|
||||
};
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ EditGroupWidgetKeeShare::EditGroupWidgetKeeShare(QWidget* parent)
|
|||
connect(m_ui->pathEdit, SIGNAL(editingFinished()), SLOT(selectPath()));
|
||||
connect(m_ui->pathSelectionButton, SIGNAL(pressed()), SLOT(launchPathSelectionDialog()));
|
||||
connect(m_ui->typeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(selectType()));
|
||||
connect(m_ui->clearButton, SIGNAL(clicked(bool)), SLOT(clearInputs()));
|
||||
|
||||
connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(showSharingState()));
|
||||
|
||||
|
@ -84,12 +85,13 @@ EditGroupWidgetKeeShare::~EditGroupWidgetKeeShare()
|
|||
{
|
||||
}
|
||||
|
||||
void EditGroupWidgetKeeShare::setGroup(Group* temporaryGroup)
|
||||
void EditGroupWidgetKeeShare::setGroup(Group* temporaryGroup, QSharedPointer<Database> database)
|
||||
{
|
||||
if (m_temporaryGroup) {
|
||||
m_temporaryGroup->disconnect(this);
|
||||
}
|
||||
|
||||
m_database = database;
|
||||
m_temporaryGroup = temporaryGroup;
|
||||
|
||||
if (m_temporaryGroup) {
|
||||
|
@ -127,9 +129,43 @@ void EditGroupWidgetKeeShare::showSharingState()
|
|||
.arg(supportedExtensions.join(", ")),
|
||||
MessageWidget::Warning);
|
||||
return;
|
||||
} else {
|
||||
m_ui->messageWidget->hide();
|
||||
}
|
||||
|
||||
const auto groups = m_database->rootGroup()->groupsRecursive(true);
|
||||
bool conflictExport = false;
|
||||
bool multipleImport = false;
|
||||
bool cycleImportExport = false;
|
||||
for (const auto* group : groups) {
|
||||
if (group->uuid() == m_temporaryGroup->uuid()) {
|
||||
continue;
|
||||
}
|
||||
const auto other = KeeShare::referenceOf(group);
|
||||
if (other.path != reference.path) {
|
||||
continue;
|
||||
}
|
||||
multipleImport |= other.isImporting() && reference.isImporting();
|
||||
conflictExport |= other.isExporting() && reference.isExporting();
|
||||
cycleImportExport |=
|
||||
(other.isImporting() && reference.isExporting()) || (other.isExporting() && reference.isImporting());
|
||||
}
|
||||
if (conflictExport) {
|
||||
m_ui->messageWidget->showMessage(tr("The export container %1 is already referenced.").arg(reference.path),
|
||||
MessageWidget::Error);
|
||||
return;
|
||||
}
|
||||
if (multipleImport) {
|
||||
m_ui->messageWidget->showMessage(tr("The import container %1 is already imported.").arg(reference.path),
|
||||
MessageWidget::Warning);
|
||||
return;
|
||||
}
|
||||
if (cycleImportExport) {
|
||||
m_ui->messageWidget->showMessage(
|
||||
tr("The container %1 imported and export by different groups.").arg(reference.path),
|
||||
MessageWidget::Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui->messageWidget->hide();
|
||||
}
|
||||
const auto active = KeeShare::active();
|
||||
if (!active.in && !active.out) {
|
||||
|
@ -166,6 +202,17 @@ void EditGroupWidgetKeeShare::update()
|
|||
m_ui->togglePasswordButton->setChecked(false);
|
||||
}
|
||||
|
||||
void EditGroupWidgetKeeShare::clearInputs()
|
||||
{
|
||||
if (m_temporaryGroup) {
|
||||
KeeShare::setReferenceTo(m_temporaryGroup, KeeShareSettings::Reference());
|
||||
}
|
||||
m_ui->passwordEdit->clear();
|
||||
m_ui->pathEdit->clear();
|
||||
m_ui->typeComboBox->setCurrentIndex(KeeShareSettings::Inactive);
|
||||
m_ui->passwordGenerator->setVisible(false);
|
||||
}
|
||||
|
||||
void EditGroupWidgetKeeShare::togglePasswordGeneratorButton(bool checked)
|
||||
{
|
||||
m_ui->passwordGenerator->regeneratePassword();
|
||||
|
|
|
@ -37,13 +37,14 @@ public:
|
|||
explicit EditGroupWidgetKeeShare(QWidget* parent = nullptr);
|
||||
~EditGroupWidgetKeeShare();
|
||||
|
||||
void setGroup(Group* temporaryGroup);
|
||||
void setGroup(Group* temporaryGroup, QSharedPointer<Database> database);
|
||||
|
||||
private slots:
|
||||
void showSharingState();
|
||||
|
||||
private slots:
|
||||
void update();
|
||||
void clearInputs();
|
||||
void selectType();
|
||||
void selectPassword();
|
||||
void launchPathSelectionDialog();
|
||||
|
@ -54,6 +55,7 @@ private slots:
|
|||
private:
|
||||
QScopedPointer<Ui::EditGroupWidgetKeeShare> m_ui;
|
||||
QPointer<Group> m_temporaryGroup;
|
||||
QSharedPointer<Database> m_database;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_EDITGROUPWIDGETKEESHARE_H
|
||||
|
|
|
@ -97,6 +97,13 @@
|
|||
<item row="5" column="1">
|
||||
<widget class="PasswordGeneratorWidget" name="passwordGenerator" native="true"/>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QPushButton" name="clearButton">
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
|
20
src/main.cpp
20
src/main.cpp
|
@ -67,19 +67,13 @@ int main(int argc, char** argv)
|
|||
Bootstrap::bootstrapApplication();
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(
|
||||
QObject::tr("KeePassXC - cross-platform password manager"));
|
||||
parser.setApplicationDescription(QObject::tr("KeePassXC - cross-platform password manager"));
|
||||
parser.addPositionalArgument(
|
||||
"filename",
|
||||
QObject::tr("filenames of the password databases to open (*.kdbx)"),
|
||||
"[filename(s)]");
|
||||
"filename", QObject::tr("filenames of the password databases to open (*.kdbx)"), "[filename(s)]");
|
||||
|
||||
QCommandLineOption configOption(
|
||||
"config", QObject::tr("path to a custom config file"), "config");
|
||||
QCommandLineOption keyfileOption(
|
||||
"keyfile", QObject::tr("key file of the database"), "keyfile");
|
||||
QCommandLineOption pwstdinOption("pw-stdin",
|
||||
QObject::tr("read password of the database from stdin"));
|
||||
QCommandLineOption configOption("config", QObject::tr("path to a custom config file"), "config");
|
||||
QCommandLineOption keyfileOption("keyfile", QObject::tr("key file of the database"), "keyfile");
|
||||
QCommandLineOption pwstdinOption("pw-stdin", QObject::tr("read password of the database from stdin"));
|
||||
// This is needed under Windows where clients send --parent-window parameter with Native Messaging connect method
|
||||
QCommandLineOption parentWindowOption(QStringList() << "pw"
|
||||
<< "parent-window",
|
||||
|
@ -106,9 +100,7 @@ int main(int argc, char** argv)
|
|||
if (!fileNames.isEmpty()) {
|
||||
app.sendFileNamesToRunningInstance(fileNames);
|
||||
}
|
||||
qWarning() << QObject::tr("Another instance of KeePassXC is already running.")
|
||||
.toUtf8()
|
||||
.constData();
|
||||
qWarning() << QObject::tr("Another instance of KeePassXC is already running.").toUtf8().constData();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include <QCoreApplication>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Winsock2.h>
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
|
||||
NativeMessagingHost::NativeMessagingHost()
|
||||
|
@ -36,14 +36,12 @@ NativeMessagingHost::NativeMessagingHost()
|
|||
}
|
||||
#ifdef Q_OS_WIN
|
||||
m_running.store(true);
|
||||
m_future =
|
||||
QtConcurrent::run(this, static_cast<void (NativeMessagingHost::*)()>(&NativeMessagingHost::readNativeMessages));
|
||||
m_future = QtConcurrent::run(this, &NativeMessagingHost::readNativeMessages);
|
||||
#endif
|
||||
connect(m_localSocket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
|
||||
connect(m_localSocket, SIGNAL(disconnected()), this, SLOT(deleteSocket()));
|
||||
connect(m_localSocket,
|
||||
SIGNAL(stateChanged(QLocalSocket::LocalSocketState)),
|
||||
this,
|
||||
SLOT(socketStateChanged(QLocalSocket::LocalSocketState)));
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class NativeMessagingHost : public NativeMessagingBase
|
|||
Q_OBJECT
|
||||
public:
|
||||
NativeMessagingHost();
|
||||
~NativeMessagingHost();
|
||||
~NativeMessagingHost() override;
|
||||
|
||||
public slots:
|
||||
void newLocalMessage();
|
||||
|
@ -33,12 +33,14 @@ public slots:
|
|||
void socketStateChanged(QLocalSocket::LocalSocketState socketState);
|
||||
|
||||
private:
|
||||
void readNativeMessages();
|
||||
void readLength();
|
||||
bool readStdIn(const quint32 length);
|
||||
void readNativeMessages() override;
|
||||
void readLength() override;
|
||||
bool readStdIn(const quint32 length) override;
|
||||
|
||||
private:
|
||||
QLocalSocket* m_localSocket;
|
||||
|
||||
Q_DISABLE_COPY(NativeMessagingHost)
|
||||
};
|
||||
|
||||
#endif // NATIVEMESSAGINGHOST_H
|
||||
|
|
|
@ -55,13 +55,29 @@ void catchUnixSignals(std::initializer_list<int> quitSignals)
|
|||
sigaction(sig, &sa, nullptr);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#include <windows.h>
|
||||
|
||||
BOOL WINAPI ConsoleHandler(DWORD dwType)
|
||||
{
|
||||
switch (dwType) {
|
||||
case CTRL_C_EVENT:
|
||||
case CTRL_SHUTDOWN_EVENT:
|
||||
case CTRL_LOGOFF_EVENT:
|
||||
QCoreApplication::quit();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX)
|
||||
#ifndef Q_OS_WIN
|
||||
catchUnixSignals({SIGQUIT, SIGINT, SIGTERM, SIGHUP});
|
||||
#else
|
||||
SetConsoleCtrlHandler(static_cast<PHANDLER_ROUTINE>(ConsoleHandler), TRUE);
|
||||
#endif
|
||||
NativeMessagingHost host;
|
||||
return a.exec();
|
||||
|
|
|
@ -28,6 +28,7 @@ SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice,
|
|||
, m_error(false)
|
||||
, m_isInitialized(false)
|
||||
, m_dataWritten(false)
|
||||
, m_streamCipher(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
*/
|
||||
|
||||
#include "UpdateChecker.h"
|
||||
#include "core/Config.h"
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Config.h"
|
||||
#include <QJsonObject>
|
||||
#include <QtNetwork>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QtNetwork>
|
||||
|
||||
UpdateChecker* UpdateChecker::m_instance(nullptr);
|
||||
|
||||
|
@ -28,6 +28,7 @@ UpdateChecker::UpdateChecker(QObject* parent)
|
|||
: QObject(parent)
|
||||
, m_netMgr(new QNetworkAccessManager(this))
|
||||
, m_reply(nullptr)
|
||||
, m_isManuallyRequested(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
#ifndef KEEPASSXC_UPDATECHECK_H
|
||||
#define KEEPASSXC_UPDATECHECK_H
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
@ -57,4 +57,4 @@ inline UpdateChecker* updateCheck()
|
|||
return UpdateChecker::instance();
|
||||
}
|
||||
|
||||
#endif //KEEPASSXC_UPDATECHECK_H
|
||||
#endif // KEEPASSXC_UPDATECHECK_H
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue