Support for triggering Global Auto-Type from browser extension

This commit is contained in:
varjolintu 2021-10-10 14:49:25 +03:00 committed by Jonathan White
parent be6835e42f
commit c7cdce6e33
15 changed files with 164 additions and 62 deletions

View File

@ -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<QSharedPointer<Database>>& dbList)
void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbList, const QString& search)
{
if (!m_plugin) {
return;
@ -449,6 +453,10 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& 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);

View File

@ -52,16 +52,16 @@ public:
static void createTestInstance();
public slots:
void performGlobalAutoType(const QList<QSharedPointer<Database>>& dbList);
void performGlobalAutoType(const QList<QSharedPointer<Database>>& 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:

View File

@ -102,6 +102,12 @@ void AutoTypeSelectDialog::setMatches(const QList<AutoTypeMatch>& 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) {

View File

@ -40,6 +40,7 @@ public:
~AutoTypeSelectDialog() override;
void setMatches(const QList<AutoTypeMatch>& matchList, const QList<QSharedPointer<Database>>& dbs);
void setSearchString(const QString& search);
signals:
void matchActivated(AutoTypeMatch match);

View File

@ -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;

View File

@ -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;

View File

@ -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<Database> db)
}
}
void BrowserService::requestGlobalAutoType(const QString& search)
{
emit osUtils->globalShortcutTriggered("autotype", search);
}
QList<Entry*>
BrowserService::sortEntries(QList<Entry*>& 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())) {

View File

@ -80,6 +80,7 @@ public:
const StringPairList& keyList,
const bool httpAuth = false);
void requestGlobalAutoType(const QString& search);
static void convertAttributesToCustomData(QSharedPointer<Database> db);
static const QString KEEPASSXCBROWSER_NAME;
@ -132,9 +133,11 @@ private:
Group* getDefaultEntryGroup(const QSharedPointer<Database>& 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<Database> getDatabase();
QSharedPointer<Database> selectedDatabase();

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2021 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
@ -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<QSharedPointer<Database>> 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);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2021 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
@ -80,7 +80,7 @@ public slots:
void showDatabaseSecurity();
void showDatabaseReports();
void showDatabaseSettings();
void performGlobalAutoType();
void performGlobalAutoType(const QString& search);
void performBrowserUnlock();
signals:

View File

@ -186,7 +186,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> 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<int>& 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);
}
}

View File

@ -121,6 +121,7 @@ public:
void setMainSplitterSizes(const QList<int>& sizes);
QList<int> previewSplitterSizes() const;
void setPreviewSplitterSizes(const QList<int>& 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

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2021 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
@ -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).

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2021 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
@ -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()

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2021 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
@ -37,7 +37,8 @@ private slots:
void testGetBase64FromKey();
void testIncrementNonce();
void testBaseDomain();
void testTopLevelDomain();
void testIsIpAddress();
void testSortPriority();
void testSortPriority_data();
void testSearchEntries();