From c7cdce6e33533c889e057a8e8f95b618f3d1ee41 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Sun, 10 Oct 2021 14:49:25 +0300 Subject: [PATCH] Support for triggering Global Auto-Type from browser extension --- src/autotype/AutoType.cpp | 24 ++++++--- src/autotype/AutoType.h | 6 +-- src/autotype/AutoTypeSelectDialog.cpp | 6 +++ src/autotype/AutoTypeSelectDialog.h | 1 + src/browser/BrowserAction.cpp | 78 +++++++++++++++++++-------- src/browser/BrowserAction.h | 3 ++ src/browser/BrowserService.cpp | 24 ++++++--- src/browser/BrowserService.h | 3 ++ src/gui/DatabaseTabWidget.cpp | 14 ++--- src/gui/DatabaseTabWidget.h | 4 +- src/gui/DatabaseWidget.cpp | 9 +++- src/gui/DatabaseWidget.h | 6 ++- src/gui/osutils/OSUtilsBase.h | 4 +- tests/TestBrowser.cpp | 39 +++++++++++--- tests/TestBrowser.h | 5 +- 15 files changed, 164 insertions(+), 62 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 0ee96faea..d12663650 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -164,11 +164,14 @@ void AutoType::loadPlugin(const QString& pluginPath) if (m_plugin) { if (m_plugin->isAvailable()) { m_executor = m_plugin->createExecutor(); - connect(osUtils, &OSUtilsBase::globalShortcutTriggered, this, [this](const QString& name) { - if (name == "autotype") { - startGlobalAutoType(); - } - }); + connect(osUtils, + &OSUtilsBase::globalShortcutTriggered, + this, + [this](const QString& name, const QString& initialSearch) { + if (name == "autotype") { + startGlobalAutoType(initialSearch); + } + }); } else { unloadPlugin(); } @@ -359,7 +362,7 @@ void AutoType::performAutoTypeWithSequence(const Entry* entry, const QString& se executeAutoTypeActions(entry, hideWindow, sequence); } -void AutoType::startGlobalAutoType() +void AutoType::startGlobalAutoType(const QString& search) { // Never Auto-Type into KeePassXC itself if (qApp->focusWindow()) { @@ -382,6 +385,7 @@ void AutoType::startGlobalAutoType() tr("KeePassXC requires the Accessibility and Screen Recorder permission in order to perform global " "Auto-Type. Screen Recording is necessary to use the window title to find entries. If you " "already granted permission, you may have to restart KeePassXC.")); + qDebug() << "Oh noes macOS."; return; } } @@ -397,14 +401,14 @@ void AutoType::startGlobalAutoType() } #endif - emit globalAutoTypeTriggered(); + emit globalAutoTypeTriggered(search); } /** * Global Autotype entry-point function * Perform global Auto-Type on the active window */ -void AutoType::performGlobalAutoType(const QList>& dbList) +void AutoType::performGlobalAutoType(const QList>& dbList, const QString& search) { if (!m_plugin) { return; @@ -449,6 +453,10 @@ void AutoType::performGlobalAutoType(const QList>& dbLi auto* selectDialog = new AutoTypeSelectDialog(); selectDialog->setMatches(matchList, dbList); + if (!search.isEmpty()) { + selectDialog->setSearchString(search); + } + connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject); connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](const AutoTypeMatch& match) { executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal); diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index c91b08ad4..2ede0bfa5 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -52,16 +52,16 @@ public: static void createTestInstance(); public slots: - void performGlobalAutoType(const QList>& dbList); + void performGlobalAutoType(const QList>& dbList, const QString& search = {}); void raiseWindow(); signals: - void globalAutoTypeTriggered(); + void globalAutoTypeTriggered(const QString& search); void autotypePerformed(); void autotypeRejected(); private slots: - void startGlobalAutoType(); + void startGlobalAutoType(const QString& search); void unloadPlugin(); private: diff --git a/src/autotype/AutoTypeSelectDialog.cpp b/src/autotype/AutoTypeSelectDialog.cpp index 752d23e3d..6b32306ca 100644 --- a/src/autotype/AutoTypeSelectDialog.cpp +++ b/src/autotype/AutoTypeSelectDialog.cpp @@ -102,6 +102,12 @@ void AutoTypeSelectDialog::setMatches(const QList& matches, const m_ui->searchCheckBox->setChecked(m_matches.isEmpty()); } +void AutoTypeSelectDialog::setSearchString(const QString& search) +{ + m_ui->search->setText(search); + m_ui->searchCheckBox->setChecked(true); +} + void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match) { if (match.first) { diff --git a/src/autotype/AutoTypeSelectDialog.h b/src/autotype/AutoTypeSelectDialog.h index 6c601dc0e..a8428ec89 100644 --- a/src/autotype/AutoTypeSelectDialog.h +++ b/src/autotype/AutoTypeSelectDialog.h @@ -40,6 +40,7 @@ public: ~AutoTypeSelectDialog() override; void setMatches(const QList& matchList, const QList>& dbs); + void setSearchString(const QString& search); signals: void matchActivated(AutoTypeMatch match); diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index 91b023999..f66c29aae 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -57,6 +57,8 @@ namespace }; } +const int BrowserAction::MaxUrlLength = 256; + QJsonObject BrowserAction::processClientMessage(const QJsonObject& json) { if (json.isEmpty()) { @@ -65,7 +67,7 @@ QJsonObject BrowserAction::processClientMessage(const QJsonObject& json) bool triggerUnlock = false; const QString trigger = json.value("triggerUnlock").toString(); - if (!trigger.isEmpty() && trigger.compare(TRUE_STR, Qt::CaseSensitive) == 0) { + if (!trigger.isEmpty() && trigger.compare(TRUE_STR) == 0) { triggerUnlock = true; } @@ -74,7 +76,8 @@ QJsonObject BrowserAction::processClientMessage(const QJsonObject& json) return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION); } - if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !browserService()->isDatabaseOpened()) { + if (action.compare("change-public-keys") != 0 && action.compare("request-autotype") != 0 + && !browserService()->isDatabaseOpened()) { if (m_clientPublicKey.isEmpty()) { return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED); } else if (!browserService()->openDatabase(triggerUnlock)) { @@ -92,30 +95,32 @@ QJsonObject BrowserAction::handleAction(const QJsonObject& json) { QString action = json.value("action").toString(); - if (action.compare("change-public-keys", Qt::CaseSensitive) == 0) { + if (action.compare("change-public-keys") == 0) { return handleChangePublicKeys(json, action); - } else if (action.compare("get-databasehash", Qt::CaseSensitive) == 0) { + } else if (action.compare("get-databasehash") == 0) { return handleGetDatabaseHash(json, action); - } else if (action.compare("associate", Qt::CaseSensitive) == 0) { + } else if (action.compare("associate") == 0) { return handleAssociate(json, action); - } else if (action.compare("test-associate", Qt::CaseSensitive) == 0) { + } else if (action.compare("test-associate") == 0) { return handleTestAssociate(json, action); - } else if (action.compare("get-logins", Qt::CaseSensitive) == 0) { + } else if (action.compare("get-logins") == 0) { return handleGetLogins(json, action); - } else if (action.compare("generate-password", Qt::CaseSensitive) == 0) { + } else if (action.compare("generate-password") == 0) { return handleGeneratePassword(json, action); - } else if (action.compare("set-login", Qt::CaseSensitive) == 0) { + } else if (action.compare("set-login") == 0) { return handleSetLogin(json, action); - } else if (action.compare("lock-database", Qt::CaseSensitive) == 0) { + } else if (action.compare("lock-database") == 0) { return handleLockDatabase(json, action); - } else if (action.compare("get-database-groups", Qt::CaseSensitive) == 0) { + } else if (action.compare("get-database-groups") == 0) { return handleGetDatabaseGroups(json, action); - } else if (action.compare("create-new-group", Qt::CaseSensitive) == 0) { + } else if (action.compare("create-new-group") == 0) { return handleCreateNewGroup(json, action); - } else if (action.compare("get-totp", Qt::CaseSensitive) == 0) { + } else if (action.compare("get-totp") == 0) { return handleGetTotp(json, action); - } else if (action.compare("delete-entry", Qt::CaseSensitive) == 0) { + } else if (action.compare("delete-entry") == 0) { return handleDeleteEntry(json, action); + } else if (action.compare("request-autotype") == 0) { + return handleGlobalAutoType(json, action); } // Action was not recognized @@ -169,7 +174,7 @@ QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const } QString command = decrypted.value("action").toString(); - if (!command.isEmpty() && command.compare("get-databasehash", Qt::CaseSensitive) == 0) { + if (!command.isEmpty() && command.compare("get-databasehash") == 0) { const QString newNonce = incrementNonce(nonce); QJsonObject message = buildMessage(newNonce); @@ -206,7 +211,7 @@ QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QStrin return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED); } - if (key.compare(m_clientPublicKey, Qt::CaseSensitive) == 0) { + if (key.compare(m_clientPublicKey) == 0) { // Check for identification key. If it's not found, ensure backwards compatibility and use the current public // key const QString idKey = decrypted.value("idKey").toString(); @@ -245,7 +250,7 @@ QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QS } const QString key = browserService()->getKey(id); - if (key.isEmpty() || key.compare(responseKey, Qt::CaseSensitive) != 0) { + if (key.isEmpty() || key.compare(responseKey) != 0) { return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED); } @@ -290,7 +295,7 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin const QString id = decrypted.value("id").toString(); const QString formUrl = decrypted.value("submitUrl").toString(); const QString auth = decrypted.value("httpAuth").toString(); - const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0; + const bool httpAuth = auth.compare(TRUE_STR) == 0; const QJsonArray users = browserService()->findMatchingEntries(id, siteUrl, formUrl, "", keyList, httpAuth); if (users.isEmpty()) { @@ -398,7 +403,7 @@ QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QSt } QString command = decrypted.value("action").toString(); - if (!command.isEmpty() && command.compare("lock-database", Qt::CaseSensitive) == 0) { + if (!command.isEmpty() && command.compare("lock-database") == 0) { browserService()->lockDatabase(); const QString newNonce = incrementNonce(nonce); @@ -426,7 +431,7 @@ QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, cons } QString command = decrypted.value("action").toString(); - if (command.isEmpty() || command.compare("get-database-groups", Qt::CaseSensitive) != 0) { + if (command.isEmpty() || command.compare("get-database-groups") != 0) { return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION); } @@ -459,7 +464,7 @@ QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const Q } QString command = decrypted.value("action").toString(); - if (command.isEmpty() || command.compare("create-new-group", Qt::CaseSensitive) != 0) { + if (command.isEmpty() || command.compare("create-new-group") != 0) { return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION); } @@ -493,7 +498,7 @@ QJsonObject BrowserAction::handleGetTotp(const QJsonObject& json, const QString& } QString command = decrypted.value("action").toString(); - if (command.isEmpty() || command.compare("get-totp", Qt::CaseSensitive) != 0) { + if (command.isEmpty() || command.compare("get-totp") != 0) { return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION); } @@ -527,7 +532,7 @@ QJsonObject BrowserAction::handleDeleteEntry(const QJsonObject& json, const QStr } QString command = decrypted.value("action").toString(); - if (command.isEmpty() || command.compare("delete-entry", Qt::CaseSensitive) != 0) { + if (command.isEmpty() || command.compare("delete-entry") != 0) { return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION); } @@ -545,6 +550,33 @@ QJsonObject BrowserAction::handleDeleteEntry(const QJsonObject& json, const QStr return buildResponse(action, message, newNonce); } +QJsonObject BrowserAction::handleGlobalAutoType(const QJsonObject& json, const QString& action) +{ + const QString nonce = json.value("nonce").toString(); + const QString encrypted = json.value("message").toString(); + 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("request-autotype") != 0) { + return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION); + } + + const auto topLevelDomain = decrypted.value("search").toString(); + if (topLevelDomain.length() > BrowserAction::MaxUrlLength) { + return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED); + } + + browserService()->requestGlobalAutoType(topLevelDomain); + + const QString newNonce = incrementNonce(nonce); + QJsonObject message = buildMessage(newNonce); + return buildResponse(action, message, newNonce); +} + QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const { QJsonObject response; diff --git a/src/browser/BrowserAction.h b/src/browser/BrowserAction.h index cfe90e7ec..67ddad7cd 100644 --- a/src/browser/BrowserAction.h +++ b/src/browser/BrowserAction.h @@ -44,6 +44,7 @@ private: QJsonObject handleCreateNewGroup(const QJsonObject& json, const QString& action); QJsonObject handleGetTotp(const QJsonObject& json, const QString& action); QJsonObject handleDeleteEntry(const QJsonObject& json, const QString& action); + QJsonObject handleGlobalAutoType(const QJsonObject& json, const QString& action); QJsonObject buildMessage(const QString& nonce) const; QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce); @@ -63,6 +64,8 @@ private: QString incrementNonce(const QString& nonce); private: + static const int MaxUrlLength; + QString m_clientPublicKey; QString m_publicKey; QString m_secretKey; diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index f89f45d0f..8581544b9 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -18,7 +18,6 @@ */ #include "BrowserService.h" - #include "BrowserAccessControlDialog.h" #include "BrowserAction.h" #include "BrowserEntryConfig.h" @@ -28,6 +27,7 @@ #include "core/Tools.h" #include "gui/MainWindow.h" #include "gui/MessageBox.h" +#include "gui/osutils/OSUtils.h" #ifdef Q_OS_MACOS #include "gui/osutils/macutils/MacUtils.h" #endif @@ -761,6 +761,11 @@ void BrowserService::convertAttributesToCustomData(QSharedPointer db) } } +void BrowserService::requestGlobalAutoType(const QString& search) +{ + emit osUtils->globalShortcutTriggered("autotype", search); +} + QList BrowserService::sortEntries(QList& pwEntries, const QString& siteUrlStr, const QString& formUrlStr) { @@ -1012,6 +1017,12 @@ bool BrowserService::schemeFound(const QString& url) return !address.scheme().isEmpty(); } +bool BrowserService::isIpAddress(const QString& host) const +{ + QHostAddress address(host); + return address.protocol() == QAbstractSocket::IPv4Protocol || address.protocol() == QAbstractSocket::IPv6Protocol; +} + bool BrowserService::removeFirstDomain(QString& hostname) { int pos = hostname.indexOf("."); @@ -1088,7 +1099,7 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& siteUrlSt } // Match the base domain - if (baseDomain(siteQUrl.host()) != baseDomain(entryQUrl.host())) { + if (getTopLevelDomainFromUrl(siteQUrl.host()) != getTopLevelDomainFromUrl(entryQUrl.host())) { return false; } @@ -1105,15 +1116,14 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& siteUrlSt * * Returns the base domain, e.g. https://another.example.co.uk -> example.co.uk */ -QString BrowserService::baseDomain(const QString& hostname) const +QString BrowserService::getTopLevelDomainFromUrl(const QString& url) const { - QUrl qurl = QUrl::fromUserInput(hostname); + QUrl qurl = QUrl::fromUserInput(url); QString host = qurl.host(); // If the hostname is an IP address, return it directly - QHostAddress hostAddress(hostname); - if (!hostAddress.isNull()) { - return hostname; + if (isIpAddress(host)) { + return host; } if (host.isEmpty() || !host.contains(qurl.topLevelDomain())) { diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index e24ad98e9..150b147f5 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -80,6 +80,7 @@ public: const StringPairList& keyList, const bool httpAuth = false); + void requestGlobalAutoType(const QString& search); static void convertAttributesToCustomData(QSharedPointer db); static const QString KEEPASSXCBROWSER_NAME; @@ -132,9 +133,11 @@ private: Group* getDefaultEntryGroup(const QSharedPointer& selectedDb = {}); int sortPriority(const QStringList& urls, const QString& siteUrlStr, const QString& formUrlStr); bool schemeFound(const QString& url); + bool isIpAddress(const QString& host) const; bool removeFirstDomain(QString& hostname); bool handleEntry(Entry* entry, const QString& url, const QString& submitUrl); bool handleURL(const QString& entryUrl, const QString& siteUrlStr, const QString& formUrlStr); + QString getTopLevelDomainFromUrl(const QString& url) const; QString baseDomain(const QString& hostname) const; QSharedPointer getDatabase(); QSharedPointer selectedDatabase(); diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 5b7067292..9aa58f556 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 KeePassXC Team + * Copyright (C) 2021 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,7 +47,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent) connect(this, SIGNAL(currentChanged(int)), SLOT(emitActiveDatabaseChanged())); connect(this, SIGNAL(activeDatabaseChanged(DatabaseWidget*)), m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*))); - connect(autoType(), SIGNAL(globalAutoTypeTriggered()), SLOT(performGlobalAutoType())); + connect(autoType(), SIGNAL(globalAutoTypeTriggered(const QString&)), SLOT(performGlobalAutoType(const QString&))); connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase())); connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase())); connect(m_databaseOpenDialog.data(), &DatabaseOpenDialog::dialogFinished, @@ -790,28 +790,30 @@ void DatabaseTabWidget::emitDatabaseLockChanged() } } -void DatabaseTabWidget::performGlobalAutoType() +void DatabaseTabWidget::performGlobalAutoType(const QString& search) { auto currentDbWidget = currentDatabaseWidget(); if (!currentDbWidget) { - // no open databases, nothing to do + // No open databases, nothing to do return; } else if (currentDbWidget->isLocked()) { // Current database tab is locked, match behavior of browser unlock - prompt with // the unlock dialog even if there are additional unlocked open database tabs. + currentDbWidget->setSearchStringForAutoType(search); unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::AutoType); } else { - // current database is unlocked, use it for AutoType along with any other unlocked databases + // Current database is unlocked, use it for AutoType along with any other unlocked databases QList> unlockedDatabases; for (int i = 0, c = count(); i < c; ++i) { auto* dbWidget = databaseWidgetFromIndex(i); if (!dbWidget->isLocked()) { + dbWidget->setSearchStringForAutoType(search); unlockedDatabases.append(dbWidget->database()); } } Q_ASSERT(!unlockedDatabases.isEmpty()); - autoType()->performGlobalAutoType(unlockedDatabases); + autoType()->performGlobalAutoType(unlockedDatabases, search); } } diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 5afed77bd..4e539339b 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 KeePassXC Team + * Copyright (C) 2021 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -80,7 +80,7 @@ public slots: void showDatabaseSecurity(); void showDatabaseReports(); void showDatabaseSettings(); - void performGlobalAutoType(); + void performGlobalAutoType(const QString& search); void performBrowserUnlock(); signals: diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 50a379fb3..267b556f6 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -186,7 +186,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer db, QWidget* parent) connect(m_opVaultOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool))); connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool))); connect(this, SIGNAL(currentChanged(int)), SLOT(emitCurrentModeChanged())); - connect(this, SIGNAL(requestGlobalAutoType()), parent, SLOT(performGlobalAutoType())); + connect(this, SIGNAL(requestGlobalAutoType(const QString&)), parent, SLOT(performGlobalAutoType(const QString&))); // clang-format on connectDatabaseSignals(); @@ -309,6 +309,11 @@ void DatabaseWidget::setPreviewSplitterSizes(const QList& sizes) m_previewSplitter->setSizes(sizes); } +void DatabaseWidget::setSearchStringForAutoType(const QString& search) +{ + m_searchStringForAutoType = search; +} + /** * Get current view state of entry view */ @@ -1118,7 +1123,7 @@ void DatabaseWidget::unlockDatabase(bool accepted) // Rather than starting AutoType directly for this database, signal the parent DatabaseTabWidget to // restart AutoType now that this database is unlocked, so that other open+unlocked databases // can be included in the search. - emit requestGlobalAutoType(); + emit requestGlobalAutoType(m_searchStringForAutoType); } } diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index b0abdc29c..3909dfd77 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -121,6 +121,7 @@ public: void setMainSplitterSizes(const QList& sizes); QList previewSplitterSizes() const; void setPreviewSplitterSizes(const QList& sizes); + void setSearchStringForAutoType(const QString& search); signals: // relayed Database signals @@ -151,7 +152,7 @@ signals: void previewSplitterSizesChanged(); void entryViewStateChanged(); void clearSearch(); - void requestGlobalAutoType(); + void requestGlobalAutoType(const QString& search); public slots: bool lock(); @@ -297,6 +298,9 @@ private: // Autoreload bool m_blockAutoSave; + + // Auto-Type related + QString m_searchStringForAutoType; }; #endif // KEEPASSX_DATABASEWIDGET_H diff --git a/src/gui/osutils/OSUtilsBase.h b/src/gui/osutils/OSUtilsBase.h index 6e96a6a5c..080a53413 100644 --- a/src/gui/osutils/OSUtilsBase.h +++ b/src/gui/osutils/OSUtilsBase.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 KeePassXC Team + * Copyright (C) 2021 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -68,7 +68,7 @@ public: virtual bool setPreventScreenCapture(QWindow* window, bool allow) const; signals: - void globalShortcutTriggered(const QString& name); + void globalShortcutTriggered(const QString& name, const QString& search = {}); /** * Indicates platform UI theme change (light mode to dark mode). diff --git a/tests/TestBrowser.cpp b/tests/TestBrowser.cpp index 8229ac259..71d5c8636 100644 --- a/tests/TestBrowser.cpp +++ b/tests/TestBrowser.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 KeePassXC Team + * Copyright (C) 2021 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -112,22 +112,49 @@ void TestBrowser::testIncrementNonce() /** * Tests for BrowserService */ -void TestBrowser::testBaseDomain() +void TestBrowser::testTopLevelDomain() { QString url1 = "https://another.example.co.uk"; QString url2 = "https://www.example.com"; QString url3 = "http://test.net"; QString url4 = "http://so.many.subdomains.co.jp"; + QString url5 = "https://192.168.0.1"; + QString url6 = "https://192.168.0.1:8000"; - QString res1 = m_browserService->baseDomain(url1); - QString res2 = m_browserService->baseDomain(url2); - QString res3 = m_browserService->baseDomain(url3); - QString res4 = m_browserService->baseDomain(url4); + QString res1 = m_browserService->getTopLevelDomainFromUrl(url1); + QString res2 = m_browserService->getTopLevelDomainFromUrl(url2); + QString res3 = m_browserService->getTopLevelDomainFromUrl(url3); + QString res4 = m_browserService->getTopLevelDomainFromUrl(url4); + QString res5 = m_browserService->getTopLevelDomainFromUrl(url5); + QString res6 = m_browserService->getTopLevelDomainFromUrl(url6); QCOMPARE(res1, QString("example.co.uk")); QCOMPARE(res2, QString("example.com")); QCOMPARE(res3, QString("test.net")); QCOMPARE(res4, QString("subdomains.co.jp")); + QCOMPARE(res5, QString("192.168.0.1")); + QCOMPARE(res6, QString("192.168.0.1")); +} + +void TestBrowser::testIsIpAddress() +{ + auto host1 = "example.com"; // Not valid + auto host2 = "192.168.0.1"; + auto host3 = "278.21.2.0"; // Not valid + auto host4 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; + auto host5 = "2001:db8:0:1:1:1:1:1"; + auto host6 = "fe80::1ff:fe23:4567:890a"; + auto host7 = "2001:20::1"; + auto host8 = "2001:0db8:85y3:0000:0000:8a2e:0370:7334"; // Not valid + + QVERIFY(!m_browserService->isIpAddress(host1)); + QVERIFY(m_browserService->isIpAddress(host2)); + QVERIFY(!m_browserService->isIpAddress(host3)); + QVERIFY(m_browserService->isIpAddress(host4)); + QVERIFY(m_browserService->isIpAddress(host5)); + QVERIFY(m_browserService->isIpAddress(host6)); + QVERIFY(m_browserService->isIpAddress(host7)); + QVERIFY(!m_browserService->isIpAddress(host8)); } void TestBrowser::testSortPriority() diff --git a/tests/TestBrowser.h b/tests/TestBrowser.h index 8a699cf6d..c4bfb0471 100644 --- a/tests/TestBrowser.h +++ b/tests/TestBrowser.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 KeePassXC Team + * Copyright (C) 2021 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,7 +37,8 @@ private slots: void testGetBase64FromKey(); void testIncrementNonce(); - void testBaseDomain(); + void testTopLevelDomain(); + void testIsIpAddress(); void testSortPriority(); void testSortPriority_data(); void testSearchEntries();