Browser: Add a new group setting for omitting WWW subdomain when matching URLs

This commit is contained in:
varjolintu 2022-06-05 10:37:00 +03:00 committed by Jonathan White
parent a2aac7066c
commit 6cb6f1f007
6 changed files with 130 additions and 68 deletions

View File

@ -3088,6 +3088,14 @@ Would you like to correct it?</source>
<source>Do not use HTTP Auth toggle for this and sub groups</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Omit WWW subdomain from matching:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Omit WWW subdomain from matching toggle for this and sub groups</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EditGroupWidgetKeeShare</name>

View File

@ -56,6 +56,7 @@ const QString BrowserService::OPTION_SKIP_AUTO_SUBMIT = QStringLiteral("BrowserS
const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEntry");
const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnlyHttpAuth");
const QString BrowserService::OPTION_NOT_HTTP_AUTH = QStringLiteral("BrowserNotHttpAuth");
const QString BrowserService::OPTION_OMIT_WWW = QStringLiteral("BrowserOmitWww");
// Multiple URL's
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
@ -426,8 +427,8 @@ QString BrowserService::getKey(const QString& id)
}
QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
const QString& siteUrlStr,
const QString& formUrlStr,
const QString& siteUrl,
const QString& formUrl,
const QString& realm,
const StringPairList& keyList,
const bool httpAuth)
@ -435,13 +436,13 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
Q_UNUSED(dbid);
const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess();
const bool ignoreHttpAuth = browserSettings()->httpAuthPermission();
const QString siteHost = QUrl(siteUrlStr).host();
const QString formHost = QUrl(formUrlStr).host();
const QString siteHost = QUrl(siteUrl).host();
const QString formHost = QUrl(formUrl).host();
// Check entries for authorization
QList<Entry*> pwEntriesToConfirm;
QList<Entry*> pwEntries;
for (auto* entry : searchEntries(siteUrlStr, formUrlStr, keyList)) {
for (auto* entry : searchEntries(siteUrl, formUrl, keyList)) {
auto entryCustomData = entry->customData();
if (!httpAuth
@ -484,7 +485,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
// Confirm entries
QList<Entry*> selectedEntriesToConfirm =
confirmEntries(pwEntriesToConfirm, siteUrlStr, siteHost, formHost, realm, httpAuth);
confirmEntries(pwEntriesToConfirm, siteUrl, siteHost, formHost, realm, httpAuth);
if (!selectedEntriesToConfirm.isEmpty()) {
pwEntries.append(selectedEntriesToConfirm);
}
@ -499,7 +500,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
}
// Sort results
pwEntries = sortEntries(pwEntries, siteUrlStr, formUrlStr);
pwEntries = sortEntries(pwEntries, siteUrl, formUrl);
// Fill the list
QJsonArray result;
@ -513,8 +514,8 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
void BrowserService::addEntry(const QString& dbid,
const QString& login,
const QString& password,
const QString& siteUrlStr,
const QString& formUrlStr,
const QString& siteUrl,
const QString& formUrl,
const QString& realm,
const QString& group,
const QString& groupUuid,
@ -530,8 +531,8 @@ void BrowserService::addEntry(const QString& dbid,
auto* entry = new Entry();
entry->setUuid(QUuid::createUuid());
entry->setTitle(QUrl(siteUrlStr).host());
entry->setUrl(siteUrlStr);
entry->setTitle(QUrl(siteUrl).host());
entry->setUrl(siteUrl);
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
entry->setUsername(login);
entry->setPassword(password);
@ -550,8 +551,8 @@ void BrowserService::addEntry(const QString& dbid,
entry->setGroup(getDefaultEntryGroup(db));
}
const QString host = QUrl(siteUrlStr).host();
const QString submitHost = QUrl(formUrlStr).host();
const QString host = QUrl(siteUrl).host();
const QString submitHost = QUrl(formUrl).host();
BrowserEntryConfig config;
config.allow(host);
@ -572,8 +573,8 @@ bool BrowserService::updateEntry(const QString& dbid,
const QString& uuid,
const QString& login,
const QString& password,
const QString& siteUrlStr,
const QString& formUrlStr)
const QString& siteUrl,
const QString& formUrl)
{
// TODO: select database based on this key id
Q_UNUSED(dbid);
@ -585,7 +586,7 @@ bool BrowserService::updateEntry(const QString& dbid,
Entry* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
if (!entry) {
// If entry is not found for update, add a new one to the selected database
addEntry(dbid, login, password, siteUrlStr, formUrlStr, "", "", "", db);
addEntry(dbid, login, password, siteUrl, formUrl, "", "", "", db);
return true;
}
@ -614,7 +615,7 @@ bool BrowserService::updateEntry(const QString& dbid,
dialogResult = MessageBox::question(
nullptr,
tr("KeePassXC: Update Entry"),
tr("Do you want to update the information in %1 - %2?").arg(QUrl(siteUrlStr).host(), username),
tr("Do you want to update the information in %1 - %2?").arg(QUrl(siteUrl).host(), username),
MessageBox::Save | MessageBox::Cancel,
MessageBox::Cancel,
MessageBox::Raise);
@ -663,7 +664,7 @@ bool BrowserService::deleteEntry(const QString& uuid)
}
QList<Entry*>
BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString& siteUrlStr, const QString& formUrlStr)
BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString& siteUrl, const QString& formUrl)
{
QList<Entry*> entries;
auto* rootGroup = db->rootGroup();
@ -677,6 +678,9 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
continue;
}
const auto omitWwwSubdomain =
group->resolveCustomDataTriState(BrowserService::OPTION_OMIT_WWW) == Group::Enable;
for (auto* entry : group->entries()) {
if (entry->isRecycled()
|| (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY)
@ -684,16 +688,7 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
continue;
}
// Search for additional URL's starting with KP2A_URL
for (const auto& key : entry->attributes()->keys()) {
if (key.startsWith(ADDITIONAL_URL) && handleURL(entry->attributes()->value(key), siteUrlStr, formUrlStr)
&& !entries.contains(entry)) {
entries.append(entry);
continue;
}
}
if (!handleEntry(entry, siteUrlStr, formUrlStr)) {
if (!shouldIncludeEntry(entry, siteUrl, formUrl, omitWwwSubdomain)) {
continue;
}
@ -708,7 +703,7 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
}
QList<Entry*>
BrowserService::searchEntries(const QString& siteUrlStr, const QString& formUrlStr, const StringPairList& keyList)
BrowserService::searchEntries(const QString& siteUrl, const QString& formUrl, const StringPairList& keyList)
{
// Check if database is connected with KeePassXC-Browser
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
@ -738,11 +733,11 @@ BrowserService::searchEntries(const QString& siteUrlStr, const QString& formUrlS
}
// Search entries matching the hostname
QString hostname = QUrl(siteUrlStr).host();
QString hostname = QUrl(siteUrl).host();
QList<Entry*> entries;
do {
for (const auto& db : databases) {
entries << searchEntries(db, siteUrlStr, formUrlStr);
entries << searchEntries(db, siteUrl, formUrl);
}
} while (entries.isEmpty() && removeFirstDomain(hostname));
@ -853,9 +848,9 @@ BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& siteUrlStr,
}
QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
const QString& siteUrlStr,
const QString& siteUrl,
const QString& siteHost,
const QString& formUrlStr,
const QString& formUrl,
const QString& realm,
const bool httpAuth)
{
@ -874,8 +869,8 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
BrowserEntryConfig config;
config.load(entry);
config.deny(siteHost);
if (!formUrlStr.isEmpty() && siteHost != formUrlStr) {
config.deny(formUrlStr);
if (!formUrl.isEmpty() && siteHost != formUrl) {
config.deny(formUrl);
}
if (!realm.isEmpty()) {
config.setRealm(realm);
@ -883,7 +878,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
config.save(entry);
});
accessControlDialog.setItems(pwEntriesToConfirm, siteUrlStr, httpAuth);
accessControlDialog.setItems(pwEntriesToConfirm, siteUrl, httpAuth);
QList<Entry*> allowedEntries;
if (accessControlDialog.exec() == QDialog::Accepted) {
@ -894,8 +889,8 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
BrowserEntryConfig config;
config.load(entry);
config.allow(siteHost);
if (!formUrlStr.isEmpty() && siteHost != formUrlStr) {
config.allow(formUrlStr);
if (!formUrl.isEmpty() && siteHost != formUrl) {
config.allow(formUrl);
}
if (!realm.isEmpty()) {
config.setRealm(realm);
@ -1006,14 +1001,14 @@ Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& sele
// Returns the maximum sort priority given a set of match urls and the
// extension provided site and form url.
int BrowserService::sortPriority(const QStringList& urls, const QString& siteUrlStr, const QString& formUrlStr)
int BrowserService::sortPriority(const QStringList& urls, const QString& siteUrl, const QString& formUrl)
{
QList<int> priorityList;
// NOTE: QUrl::matches is utterly broken in Qt < 5.11, so we work around that
// by removing parts of the url that we don't match and direct matching others
const auto stdOpts = QUrl::RemoveFragment | QUrl::RemoveUserInfo;
const auto siteUrl = QUrl(siteUrlStr).adjusted(stdOpts);
const auto formUrl = QUrl(formUrlStr).adjusted(stdOpts);
const auto adjustedSiteUrl = QUrl(siteUrl).adjusted(stdOpts);
const auto adjustedFormUrl = QUrl(formUrl).adjusted(stdOpts);
auto getPriority = [&](const QString& givenUrl) {
auto url = QUrl::fromUserInput(givenUrl).adjusted(stdOpts);
@ -1031,38 +1026,38 @@ int BrowserService::sortPriority(const QStringList& urls, const QString& siteUrl
// Reject invalid urls and hosts, except 'localhost', and scheme mismatch
if (!url.isValid() || (!url.host().contains(".") && url.host() != "localhost")
|| url.scheme() != siteUrl.scheme()) {
|| url.scheme() != adjustedSiteUrl.scheme()) {
return 0;
}
// Exact match with site url or form url
if (url.matches(siteUrl, QUrl::None) || url.matches(formUrl, QUrl::None)) {
if (url.matches(adjustedSiteUrl, QUrl::None) || url.matches(adjustedFormUrl, QUrl::None)) {
return 100;
}
// Exact match without the query string
if (url.matches(siteUrl, QUrl::RemoveQuery) || url.matches(formUrl, QUrl::RemoveQuery)) {
if (url.matches(adjustedSiteUrl, QUrl::RemoveQuery) || url.matches(adjustedFormUrl, QUrl::RemoveQuery)) {
return 90;
}
// Parent directory match
if (url.isParentOf(siteUrl) || url.isParentOf(formUrl)) {
if (url.isParentOf(adjustedSiteUrl) || url.isParentOf(adjustedFormUrl)) {
return 85;
}
// Match without path (ie, FQDN match), form url prioritizes lower than site url
if (url.host() == siteUrl.host()) {
if (url.host() == adjustedSiteUrl.host()) {
return 80;
}
if (url.host() == formUrl.host()) {
if (url.host() == adjustedFormUrl.host()) {
return 70;
}
// Site/form url ends with given url (subdomain mismatch)
if (siteUrl.host().endsWith(url.host())) {
if (adjustedSiteUrl.host().endsWith(url.host())) {
return 60;
}
if (formUrl.host().endsWith(url.host())) {
if (adjustedFormUrl.host().endsWith(url.host())) {
return 50;
}
@ -1108,7 +1103,10 @@ bool BrowserService::removeFirstDomain(QString& hostname)
/* Test if a search URL matches a custom entry. If the URL has the schema "keepassxc", some special checks will be made.
* Otherwise, this simply delegates to handleURL(). */
bool BrowserService::handleEntry(Entry* entry, const QString& url, const QString& submitUrl)
bool BrowserService::shouldIncludeEntry(Entry* entry,
const QString& url,
const QString& submitUrl,
const bool omitWwwSubdomain)
{
// Use this special scheme to find entries by UUID
if (url.startsWith("keepassxc://by-uuid/")) {
@ -1116,10 +1114,21 @@ bool BrowserService::handleEntry(Entry* entry, const QString& url, const QString
} else if (url.startsWith("keepassxc://by-path/")) {
return url.endsWith("by-path/" + entry->path());
}
return handleURL(entry->url(), url, submitUrl);
const auto allEntryUrls = entry->getAllUrls();
for (const auto& entryUrl : allEntryUrls) {
if (handleURL(entryUrl, url, submitUrl, omitWwwSubdomain)) {
return true;
}
}
return false;
}
bool BrowserService::handleURL(const QString& entryUrl, const QString& siteUrlStr, const QString& formUrlStr)
bool BrowserService::handleURL(const QString& entryUrl,
const QString& siteUrl,
const QString& formUrl,
const bool omitWwwSubdomain)
{
if (entryUrl.isEmpty()) {
return false;
@ -1136,9 +1145,14 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& siteUrlSt
}
}
// Remove WWW subdomain from matching if group setting is enabled
if (omitWwwSubdomain && entryQUrl.host().startsWith("www.")) {
entryQUrl.setHost(entryQUrl.host().remove("www."));
}
// Make a direct compare if a local file is used
if (siteUrlStr.startsWith("file://")) {
return entryUrl == formUrlStr;
if (siteUrl.startsWith("file://")) {
return entryUrl == formUrl;
}
// URL host validation fails
@ -1147,7 +1161,7 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& siteUrlSt
}
// Match port, if used
QUrl siteQUrl(siteUrlStr);
QUrl siteQUrl(siteUrl);
if (entryQUrl.port() > 0 && entryQUrl.port() != siteQUrl.port()) {
return false;
}

View File

@ -99,6 +99,7 @@ public:
static const QString OPTION_HIDE_ENTRY;
static const QString OPTION_ONLY_HTTP_AUTH;
static const QString OPTION_NOT_HTTP_AUTH;
static const QString OPTION_OMIT_WWW;
static const QString ADDITIONAL_URL;
signals:
@ -128,26 +129,29 @@ private:
Hidden
};
QList<Entry*>
searchEntries(const QSharedPointer<Database>& db, const QString& siteUrlStr, const QString& formUrlStr);
QList<Entry*> searchEntries(const QString& siteUrlStr, const QString& formUrlStr, const StringPairList& keyList);
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& siteUrlStr, const QString& formUrlStr);
QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& siteUrl, const QString& formUrl);
QList<Entry*> searchEntries(const QString& siteUrl, const QString& formUrl, const StringPairList& keyList);
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& siteUrl, const QString& formUrl);
QList<Entry*> confirmEntries(QList<Entry*>& pwEntriesToConfirm,
const QString& siteUrlStr,
const QString& siteUrl,
const QString& siteHost,
const QString& formUrlStr,
const QString& formUrl,
const QString& realm,
const bool httpAuth);
QJsonObject prepareEntry(const Entry* entry);
QJsonArray getChildrenFromGroup(Group* group);
Access checkAccess(const Entry* entry, const QString& siteHost, const QString& formHost, const QString& realm);
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
int sortPriority(const QStringList& urls, const QString& siteUrlStr, const QString& formUrlStr);
int sortPriority(const QStringList& urls, const QString& siteUrl, const QString& formUrl);
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);
bool
shouldIncludeEntry(Entry* entry, const QString& url, const QString& submitUrl, const bool omitWwwSubdomain = false);
bool handleURL(const QString& entryUrl,
const QString& siteUrl,
const QString& formUrl,
const bool omitWwwSubdomain = false);
QString getTopLevelDomainFromUrl(const QString& url) const;
QString baseDomain(const QString& hostname) const;
QSharedPointer<Database> getDatabase();

View File

@ -219,6 +219,7 @@ void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<
auto inheritSkipSubmit = false;
auto inheritOnlyHttp = false;
auto inheritNoHttp = false;
auto inheritOmitWww = false;
auto parent = group->parentGroup();
if (parent) {
@ -226,12 +227,14 @@ void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<
inheritSkipSubmit = parent->resolveCustomDataTriState(BrowserService::OPTION_SKIP_AUTO_SUBMIT);
inheritOnlyHttp = parent->resolveCustomDataTriState(BrowserService::OPTION_ONLY_HTTP_AUTH);
inheritNoHttp = parent->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH);
inheritOmitWww = parent->resolveCustomDataTriState(BrowserService::OPTION_OMIT_WWW);
}
addTriStateItems(m_browserUi->browserIntegrationHideEntriesComboBox, inheritHideEntries);
addTriStateItems(m_browserUi->browserIntegrationSkipAutoSubmitComboBox, inheritSkipSubmit);
addTriStateItems(m_browserUi->browserIntegrationOnlyHttpAuthComboBox, inheritOnlyHttp);
addTriStateItems(m_browserUi->browserIntegrationNotHttpAuthComboBox, inheritNoHttp);
addTriStateItems(m_browserUi->browserIntegrationOmitWwwCombobox, inheritOmitWww);
m_browserUi->browserIntegrationHideEntriesComboBox->setCurrentIndex(
indexFromTriState(group->resolveCustomDataTriState(BrowserService::OPTION_HIDE_ENTRY, false)));
@ -241,6 +244,8 @@ void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<
indexFromTriState(group->resolveCustomDataTriState(BrowserService::OPTION_ONLY_HTTP_AUTH, false)));
m_browserUi->browserIntegrationNotHttpAuthComboBox->setCurrentIndex(
indexFromTriState(group->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH, false)));
m_browserUi->browserIntegrationOmitWwwCombobox->setCurrentIndex(
indexFromTriState(group->resolveCustomDataTriState(BrowserService::OPTION_OMIT_WWW, false)));
}
#endif
@ -309,6 +314,9 @@ void EditGroupWidget::apply()
m_temporaryGroup->setCustomDataTriState(
BrowserService::OPTION_NOT_HTTP_AUTH,
triStateFromIndex(m_browserUi->browserIntegrationNotHttpAuthComboBox->currentIndex()));
m_temporaryGroup->setCustomDataTriState(
BrowserService::OPTION_OMIT_WWW,
triStateFromIndex(m_browserUi->browserIntegrationOmitWwwCombobox->currentIndex()));
}
#endif

View File

@ -145,7 +145,24 @@
   </property>
   </widget>
   </item>
   <item row="4" column="0">
<item row="4" column="0">
   <widget class="QLabel" name="browserIntegrationOmitWwwLabel">
   <property name="text">
   <string>Omit WWW subdomain from matching:</string>
   </property>
   <property name="alignment">
   <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
   </property>
   </widget>
   </item>
   <item row="4" column="1">
   <widget class="QComboBox" name="browserIntegrationOmitWwwCombobox">
   <property name="accessibleName">
   <string>Omit WWW subdomain from matching toggle for this and sub groups</string>
   </property>
   </widget>
   </item>
   <item row="5" column="0">
   <spacer name="verticalSpacer_1">
   <property name="orientation">
   <enum>Qt::Vertical</enum>
@ -168,6 +185,7 @@
<tabstop>browserIntegrationSkipAutoSubmitComboBox</tabstop>
<tabstop>browserIntegrationOnlyHttpAuthComboBox</tabstop>
<tabstop>browserIntegrationNotHttpAuthComboBox</tabstop>
<tabstop>browserIntegrationOmitWwwCombobox</tabstop>
</tabstops>
<resources/>
<connections/>

View File

@ -281,7 +281,7 @@ void TestBrowser::compareEntriesByPath(QSharedPointer<Database> db, QList<Entry*
for (Entry* entry : entries) {
QString testUrl = "keepassxc://by-path/" + path + entry->title();
/* Look for an entry with that path. First using handleEntry, then through the search */
QCOMPARE(m_browserService->handleEntry(entry, testUrl, ""), true);
QCOMPARE(m_browserService->shouldIncludeEntry(entry, testUrl, ""), true);
auto result = m_browserService->searchEntries(db, testUrl, "");
QCOMPARE(result.length(), 1);
QCOMPARE(result[0], entry);
@ -311,7 +311,7 @@ void TestBrowser::testSearchEntriesByUUID()
for (Entry* entry : entries) {
QString testUrl = "keepassxc://by-uuid/" + entry->uuidToHex();
/* Look for an entry with that UUID. First using handleEntry, then through the search */
QCOMPARE(m_browserService->handleEntry(entry, testUrl, ""), true);
QCOMPARE(m_browserService->shouldIncludeEntry(entry, testUrl, ""), true);
auto result = m_browserService->searchEntries(db, testUrl, "");
QCOMPARE(result.length(), 1);
QCOMPARE(result[0], entry);
@ -329,7 +329,7 @@ void TestBrowser::testSearchEntriesByUUID()
QString testUrl = "keepassxc://by-uuid/" + uuid;
for (Entry* entry : entries) {
QCOMPARE(m_browserService->handleEntry(entry, testUrl, ""), false);
QCOMPARE(m_browserService->shouldIncludeEntry(entry, testUrl, ""), false);
}
auto result = m_browserService->searchEntries(db, testUrl, "");
@ -439,6 +439,16 @@ void TestBrowser::testSubdomainsAndPaths()
QCOMPARE(result[2]->url(), QString("http://www.github.com"));
QCOMPARE(result[3]->url(), QString("www.github.com/"));
// With www subdomain omitted
root->setCustomDataTriState(BrowserService::OPTION_OMIT_WWW, Group::Enable);
result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
root->setCustomDataTriState(BrowserService::OPTION_OMIT_WWW, Group::Inherit);
QCOMPARE(result.length(), 4);
QCOMPARE(result[0]->url(), QString("https://www.github.com/login/page.xml"));
QCOMPARE(result[1]->url(), QString("https://github.com"));
QCOMPARE(result[2]->url(), QString("http://www.github.com"));
QCOMPARE(result[3]->url(), QString("www.github.com/"));
// With scheme matching there should be only 1 result
browserSettings()->setMatchUrlScheme(true);
result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");