mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-03-30 09:58:15 -04:00
Add support for URL wildcards and exact URL (#9835)
* Add support for URL wildcards with Additional URL feature * Only check TLD if wildcard is used * Avoid using network function in no-feature build --------- Co-authored-by: varjolintu <sami.vanttinen@ahmala.org> Co-authored-by: Jonathan White <support@dmapps.us>
This commit is contained in:
parent
51e8c042af
commit
9ba6ada266
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||||
* Copyright (C) 2013 Francois Ferrand
|
* Copyright (C) 2013 Francois Ferrand
|
||||||
*
|
*
|
||||||
@ -51,6 +51,7 @@
|
|||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
#include <QProgressDialog>
|
#include <QProgressDialog>
|
||||||
|
#include <QStringView>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
||||||
@ -1375,9 +1376,15 @@ bool BrowserService::shouldIncludeEntry(Entry* entry,
|
|||||||
return url.endsWith("by-path/" + entry->path());
|
return url.endsWith("by-path/" + entry->path());
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto allEntryUrls = entry->getAllUrls();
|
// Handle the entry URL
|
||||||
for (const auto& entryUrl : allEntryUrls) {
|
if (handleURL(entry->resolveUrl(), url, submitUrl, omitWwwSubdomain)) {
|
||||||
if (handleURL(entryUrl, url, submitUrl, omitWwwSubdomain)) {
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle additional URLs
|
||||||
|
const auto additionalUrls = entry->getAdditionalUrls();
|
||||||
|
for (const auto& additionalUrl : additionalUrls) {
|
||||||
|
if (handleURL(additionalUrl, url, submitUrl, omitWwwSubdomain, true)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1465,17 +1472,35 @@ QJsonObject BrowserService::getPasskeyError(int errorCode) const
|
|||||||
bool BrowserService::handleURL(const QString& entryUrl,
|
bool BrowserService::handleURL(const QString& entryUrl,
|
||||||
const QString& siteUrl,
|
const QString& siteUrl,
|
||||||
const QString& formUrl,
|
const QString& formUrl,
|
||||||
const bool omitWwwSubdomain)
|
const bool omitWwwSubdomain,
|
||||||
|
const bool allowWildcards)
|
||||||
{
|
{
|
||||||
if (entryUrl.isEmpty()) {
|
if (entryUrl.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isWildcardUrl = false;
|
||||||
|
auto tempUrl = entryUrl;
|
||||||
|
|
||||||
|
// Allows matching with exact URL and wildcards
|
||||||
|
if (allowWildcards) {
|
||||||
|
// Exact match where URL is wrapped inside " characters
|
||||||
|
if (entryUrl.startsWith("\"") && entryUrl.endsWith("\"")) {
|
||||||
|
return QStringView{entryUrl}.mid(1, entryUrl.length() - 2) == siteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace wildcards
|
||||||
|
isWildcardUrl = entryUrl.contains("*");
|
||||||
|
if (isWildcardUrl) {
|
||||||
|
tempUrl = tempUrl.replace("*", UrlTools::URL_WILDCARD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QUrl entryQUrl;
|
QUrl entryQUrl;
|
||||||
if (entryUrl.contains("://")) {
|
if (entryUrl.contains("://")) {
|
||||||
entryQUrl = entryUrl;
|
entryQUrl = tempUrl;
|
||||||
} else {
|
} else {
|
||||||
entryQUrl = QUrl::fromUserInput(entryUrl);
|
entryQUrl = QUrl::fromUserInput(tempUrl);
|
||||||
|
|
||||||
if (browserSettings()->matchUrlScheme()) {
|
if (browserSettings()->matchUrlScheme()) {
|
||||||
entryQUrl.setScheme("https");
|
entryQUrl.setScheme("https");
|
||||||
@ -1515,6 +1540,11 @@ bool BrowserService::handleURL(const QString& entryUrl,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use wildcard matching instead
|
||||||
|
if (isWildcardUrl) {
|
||||||
|
return handleURLWithWildcards(entryQUrl, siteUrl);
|
||||||
|
}
|
||||||
|
|
||||||
// Match the base domain
|
// Match the base domain
|
||||||
if (urlTools()->getBaseDomainFromUrl(siteQUrl.host()) != urlTools()->getBaseDomainFromUrl(entryQUrl.host())) {
|
if (urlTools()->getBaseDomainFromUrl(siteQUrl.host()) != urlTools()->getBaseDomainFromUrl(entryQUrl.host())) {
|
||||||
return false;
|
return false;
|
||||||
@ -1528,6 +1558,46 @@ bool BrowserService::handleURL(const QString& entryUrl,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BrowserService::handleURLWithWildcards(const QUrl& entryQUrl, const QString& siteUrl)
|
||||||
|
{
|
||||||
|
auto matchWithRegex = [&](QString firstPart, const QString& secondPart, bool hostnameUsed = false) {
|
||||||
|
if (firstPart == secondPart) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's no wildcard with hostname, just compare directly
|
||||||
|
if (hostnameUsed && !firstPart.contains(UrlTools::URL_WILDCARD) && firstPart != secondPart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape illegal characters
|
||||||
|
auto re = firstPart.replace(QRegularExpression(R"(([!\^\$\+\-\(\)@<>]))"), "\\\\1");
|
||||||
|
|
||||||
|
if (hostnameUsed) {
|
||||||
|
// Replace all host parts with wildcards
|
||||||
|
re = re.replace(QString("%1.").arg(UrlTools::URL_WILDCARD), "(.*?)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append a + to the end of regex to match all paths after the last asterisk
|
||||||
|
if (re.endsWith(UrlTools::URL_WILDCARD)) {
|
||||||
|
re.append("+");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace any remaining wildcards for paths
|
||||||
|
re = re.replace(UrlTools::URL_WILDCARD, "(.*?)");
|
||||||
|
return QRegularExpression(re).match(secondPart).hasMatch();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Match hostname and path
|
||||||
|
QUrl siteQUrl = siteUrl;
|
||||||
|
if (!matchWithRegex(entryQUrl.host(), siteQUrl.host(), true)
|
||||||
|
|| !matchWithRegex(entryQUrl.path(), siteQUrl.path())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
QSharedPointer<Database> BrowserService::getDatabase(const QUuid& rootGroupUuid)
|
QSharedPointer<Database> BrowserService::getDatabase(const QUuid& rootGroupUuid)
|
||||||
{
|
{
|
||||||
if (!rootGroupUuid.isNull()) {
|
if (!rootGroupUuid.isNull()) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||||
* Copyright (C) 2013 Francois Ferrand
|
* Copyright (C) 2013 Francois Ferrand
|
||||||
*
|
*
|
||||||
@ -132,6 +132,7 @@ public:
|
|||||||
static const QString OPTION_ONLY_HTTP_AUTH;
|
static const QString OPTION_ONLY_HTTP_AUTH;
|
||||||
static const QString OPTION_NOT_HTTP_AUTH;
|
static const QString OPTION_NOT_HTTP_AUTH;
|
||||||
static const QString OPTION_OMIT_WWW;
|
static const QString OPTION_OMIT_WWW;
|
||||||
|
static const QString ADDITIONAL_URL;
|
||||||
static const QString OPTION_RESTRICT_KEY;
|
static const QString OPTION_RESTRICT_KEY;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@ -199,7 +200,9 @@ private:
|
|||||||
bool handleURL(const QString& entryUrl,
|
bool handleURL(const QString& entryUrl,
|
||||||
const QString& siteUrl,
|
const QString& siteUrl,
|
||||||
const QString& formUrl,
|
const QString& formUrl,
|
||||||
const bool omitWwwSubdomain = false);
|
const bool omitWwwSubdomain = false,
|
||||||
|
const bool allowWildcards = false);
|
||||||
|
bool handleURLWithWildcards(const QUrl& entryQUrl, const QString& siteUrl);
|
||||||
QString getDatabaseRootUuid();
|
QString getDatabaseRootUuid();
|
||||||
QString getDatabaseRecycleBinUuid();
|
QString getDatabaseRecycleBinUuid();
|
||||||
void hideWindow() const;
|
void hideWindow() const;
|
||||||
|
@ -381,16 +381,32 @@ QString Entry::url() const
|
|||||||
return m_attributes->value(EntryAttributes::URLKey);
|
return m_attributes->value(EntryAttributes::URLKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Entry::resolveUrl() const
|
||||||
|
{
|
||||||
|
const auto entryUrl = url();
|
||||||
|
if (entryUrl.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return EntryAttributes::matchReference(entryUrl).hasMatch() ? resolveMultiplePlaceholders(entryUrl) : entryUrl;
|
||||||
|
}
|
||||||
|
|
||||||
QStringList Entry::getAllUrls() const
|
QStringList Entry::getAllUrls() const
|
||||||
{
|
{
|
||||||
QStringList urlList;
|
QStringList urlList;
|
||||||
auto entryUrl = url();
|
|
||||||
|
|
||||||
|
const auto entryUrl = resolveUrl();
|
||||||
if (!entryUrl.isEmpty()) {
|
if (!entryUrl.isEmpty()) {
|
||||||
urlList << (EntryAttributes::matchReference(entryUrl).hasMatch() ? resolveMultiplePlaceholders(entryUrl)
|
urlList << entryUrl;
|
||||||
: entryUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return urlList << getAdditionalUrls();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList Entry::getAdditionalUrls() const
|
||||||
|
{
|
||||||
|
QStringList urlList;
|
||||||
|
|
||||||
for (const auto& key : m_attributes->keys()) {
|
for (const auto& key : m_attributes->keys()) {
|
||||||
if (key.startsWith(EntryAttributes::AdditionalUrlAttribute)
|
if (key.startsWith(EntryAttributes::AdditionalUrlAttribute)
|
||||||
|| key == QString("%1_RELYING_PARTY").arg(EntryAttributes::PasskeyAttribute)) {
|
|| key == QString("%1_RELYING_PARTY").arg(EntryAttributes::PasskeyAttribute)) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -100,7 +100,9 @@ public:
|
|||||||
const AutoTypeAssociations* autoTypeAssociations() const;
|
const AutoTypeAssociations* autoTypeAssociations() const;
|
||||||
QString title() const;
|
QString title() const;
|
||||||
QString url() const;
|
QString url() const;
|
||||||
|
QString resolveUrl() const;
|
||||||
QStringList getAllUrls() const;
|
QStringList getAllUrls() const;
|
||||||
|
QStringList getAdditionalUrls() const;
|
||||||
QString webUrl() const;
|
QString webUrl() const;
|
||||||
QString displayUrl() const;
|
QString displayUrl() const;
|
||||||
QString username() const;
|
QString username() const;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -24,6 +24,8 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
const QString UrlTools::URL_WILDCARD = "1kpxcwc1";
|
||||||
|
|
||||||
Q_GLOBAL_STATIC(UrlTools, s_urlTools)
|
Q_GLOBAL_STATIC(UrlTools, s_urlTools)
|
||||||
|
|
||||||
UrlTools* UrlTools::instance()
|
UrlTools* UrlTools::instance()
|
||||||
@ -137,8 +139,9 @@ bool UrlTools::isUrlIdentical(const QString& first, const QString& second) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto firstUrl = trimUrl(first);
|
// Replace URL wildcards for comparison if found
|
||||||
const auto secondUrl = trimUrl(second);
|
const auto firstUrl = trimUrl(QString(first).replace("*", UrlTools::URL_WILDCARD));
|
||||||
|
const auto secondUrl = trimUrl(QString(second).replace("*", UrlTools::URL_WILDCARD));
|
||||||
if (firstUrl == secondUrl) {
|
if (firstUrl == secondUrl) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -146,27 +149,61 @@ bool UrlTools::isUrlIdentical(const QString& first, const QString& second) const
|
|||||||
return QUrl(firstUrl).matches(QUrl(secondUrl), QUrl::StripTrailingSlash);
|
return QUrl(firstUrl).matches(QUrl(secondUrl), QUrl::StripTrailingSlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UrlTools::isUrlValid(const QString& urlField) const
|
bool UrlTools::isUrlValid(const QString& urlField, bool looseComparison) const
|
||||||
{
|
{
|
||||||
if (urlField.isEmpty() || urlField.startsWith("cmd://", Qt::CaseInsensitive)
|
if (urlField.isEmpty() || urlField.startsWith("cmd://", Qt::CaseInsensitive)
|
||||||
|| urlField.startsWith("kdbx://", Qt::CaseInsensitive) || urlField.startsWith("{REF:A", Qt::CaseInsensitive)) {
|
|| urlField.startsWith("kdbx://", Qt::CaseInsensitive) || urlField.startsWith("{REF:A", Qt::CaseInsensitive)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl url;
|
auto url = urlField;
|
||||||
if (urlField.contains("://")) {
|
|
||||||
url = urlField;
|
// Loose comparison that allows wildcards and exact URL inside " characters
|
||||||
} else {
|
if (looseComparison) {
|
||||||
url = QUrl::fromUserInput(urlField);
|
// Exact URL
|
||||||
|
if (url.startsWith("\"") && url.endsWith("\"")) {
|
||||||
|
// Do not allow exact URL with wildcards, or empty exact URL
|
||||||
|
if (url.contains("*") || url.length() == 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the URL inside ""
|
||||||
|
url.remove(0, 1);
|
||||||
|
url.remove(url.length() - 1, 1);
|
||||||
|
} else {
|
||||||
|
// Do not allow URL with just wildcards, or double wildcards, or no separator (.)
|
||||||
|
if (url.length() == url.count("*") || url.contains("**") || url.contains("*.*") || !url.contains(".")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
url.replace("*", UrlTools::URL_WILDCARD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.scheme() != "file" && url.host().isEmpty()) {
|
QUrl qUrl;
|
||||||
|
if (urlField.contains("://")) {
|
||||||
|
qUrl = url;
|
||||||
|
} else {
|
||||||
|
qUrl = QUrl::fromUserInput(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qUrl.scheme() != "file" && qUrl.host().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(WITH_XC_NETWORKING) || defined(WITH_XC_BROWSER)
|
||||||
|
// Prevent TLD wildcards
|
||||||
|
if (looseComparison && url.contains(UrlTools::URL_WILDCARD)) {
|
||||||
|
const auto tld = getTopLevelDomainFromUrl(url);
|
||||||
|
if (qUrl.host() == QString("%1.%2").arg(UrlTools::URL_WILDCARD, tld)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Check for illegal characters. Adds also the wildcard * to the list
|
// Check for illegal characters. Adds also the wildcard * to the list
|
||||||
QRegularExpression re("[<>\\^`{|}\\*]");
|
QRegularExpression re("[<>\\^`{|}\\*]");
|
||||||
auto match = re.match(urlField);
|
auto match = re.match(url);
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -41,9 +41,11 @@ public:
|
|||||||
bool isIpAddress(const QString& host) const;
|
bool isIpAddress(const QString& host) const;
|
||||||
#endif
|
#endif
|
||||||
bool isUrlIdentical(const QString& first, const QString& second) const;
|
bool isUrlIdentical(const QString& first, const QString& second) const;
|
||||||
bool isUrlValid(const QString& urlField) const;
|
bool isUrlValid(const QString& urlField, bool looseComparison = false) const;
|
||||||
bool domainHasIllegalCharacters(const QString& domain) const;
|
bool domainHasIllegalCharacters(const QString& domain) const;
|
||||||
|
|
||||||
|
static const QString URL_WILDCARD;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUrl convertVariantToUrl(const QVariant& var) const;
|
QUrl convertVariantToUrl(const QVariant& var) const;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -67,7 +67,7 @@ QVariant EntryURLModel::data(const QModelIndex& index, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto value = m_entryAttributes->value(key);
|
const auto value = m_entryAttributes->value(key);
|
||||||
const auto urlValid = urlTools()->isUrlValid(value);
|
const auto urlValid = urlTools()->isUrlValid(value, true);
|
||||||
|
|
||||||
// Check for duplicate URLs in the attribute list. Excludes the current key/value from the comparison.
|
// Check for duplicate URLs in the attribute list. Excludes the current key/value from the comparison.
|
||||||
auto customAttributeKeys = m_entryAttributes->customKeys().filter(EntryAttributes::AdditionalUrlAttribute);
|
auto customAttributeKeys = m_entryAttributes->customKeys().filter(EntryAttributes::AdditionalUrlAttribute);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -222,7 +222,7 @@ void TestBrowser::testSearchEntries()
|
|||||||
QCOMPARE(result[4]->url(), QString("http://github.com"));
|
QCOMPARE(result[4]->url(), QString("http://github.com"));
|
||||||
QCOMPARE(result[5]->url(), QString("http://github.com/login"));
|
QCOMPARE(result[5]->url(), QString("http://github.com/login"));
|
||||||
|
|
||||||
// With matching there should be only 3 results + 4 without a scheme
|
// With matching there should be only 4 results + 4 without a scheme
|
||||||
browserSettings()->setMatchUrlScheme(true);
|
browserSettings()->setMatchUrlScheme(true);
|
||||||
result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
|
result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
|
||||||
QCOMPARE(result.length(), 7);
|
QCOMPARE(result.length(), 7);
|
||||||
@ -396,6 +396,121 @@ void TestBrowser::testSearchEntriesWithAdditionalURLs()
|
|||||||
QCOMPARE(additionalResult[0]->url(), QString("https://github.com/"));
|
QCOMPARE(additionalResult[0]->url(), QString("https://github.com/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestBrowser::testSearchEntriesWithWildcardURLs()
|
||||||
|
{
|
||||||
|
auto db = QSharedPointer<Database>::create();
|
||||||
|
auto* root = db->rootGroup();
|
||||||
|
|
||||||
|
QStringList urls = {
|
||||||
|
"https://github.com/login_page/*",
|
||||||
|
"https://github.com/*/second",
|
||||||
|
"https://github.com/*",
|
||||||
|
"http://github.com/*",
|
||||||
|
"github.com/*", // Defaults to https
|
||||||
|
"https://*.github.com/*",
|
||||||
|
"https://subdomain.*.github.com/*/second",
|
||||||
|
"https://*.sub.github.com/*",
|
||||||
|
"https://********", // Invalid wildcard URL
|
||||||
|
"https://subdomain.yes.github.com/*",
|
||||||
|
"https://example.com:8448/*",
|
||||||
|
"https://example.com/*/*",
|
||||||
|
"https://example.com/$/*",
|
||||||
|
"https://127.128.129.*:8448/",
|
||||||
|
"https://127.128.*/",
|
||||||
|
"https://127.160.*.2/login",
|
||||||
|
"http://[2001:db8:85a3:8d3:1319:8a2e:370:*]/",
|
||||||
|
"https://[2001:db8:85a3:8d3:*]:443/",
|
||||||
|
"fe80::1ff:fe23:4567:890a",
|
||||||
|
"2001-db8-85a3-8d3-1319-8a2e-370-7348.ipv6-literal.net",
|
||||||
|
"\"https://thisisatest.com/login.php\"" // Exact URL
|
||||||
|
};
|
||||||
|
|
||||||
|
createEntries(urls, root, true);
|
||||||
|
browserSettings()->setMatchUrlScheme(false);
|
||||||
|
|
||||||
|
// Return first Additional URL
|
||||||
|
auto firstUrl = [&](Entry* entry) { return entry->attributes()->value(EntryAttributes::AdditionalUrlAttribute); };
|
||||||
|
|
||||||
|
auto result = m_browserService->searchEntries(
|
||||||
|
db, "https://github.com/login_page/second", "https://github.com/login_page/second");
|
||||||
|
QCOMPARE(result.length(), 6);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("https://github.com/login_page/*"));
|
||||||
|
QCOMPARE(firstUrl(result[1]), QString("https://github.com/*/second"));
|
||||||
|
QCOMPARE(firstUrl(result[2]), QString("https://github.com/*"));
|
||||||
|
QCOMPARE(firstUrl(result[3]), QString("http://github.com/*"));
|
||||||
|
QCOMPARE(firstUrl(result[4]), QString("github.com/*"));
|
||||||
|
QCOMPARE(firstUrl(result[5]), QString("https://*.github.com/*"));
|
||||||
|
|
||||||
|
result = m_browserService->searchEntries(
|
||||||
|
db, "https://subdomain.sub.github.com/login_page/second", "https://subdomain.sub.github.com/login_page/second");
|
||||||
|
QCOMPARE(result.length(), 3);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("https://*.github.com/*"));
|
||||||
|
QCOMPARE(firstUrl(result[1]), QString("https://subdomain.*.github.com/*/second"));
|
||||||
|
QCOMPARE(firstUrl(result[2]), QString("https://*.sub.github.com/*"));
|
||||||
|
|
||||||
|
result = m_browserService->searchEntries(
|
||||||
|
db, "https://subdomain.sub.github.com/other_page", "https://subdomain.sub.github.com/other_page");
|
||||||
|
QCOMPARE(result.length(), 2);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("https://*.github.com/*"));
|
||||||
|
QCOMPARE(firstUrl(result[1]), QString("https://*.sub.github.com/*"));
|
||||||
|
|
||||||
|
result = m_browserService->searchEntries(
|
||||||
|
db, "https://subdomain.yes.github.com/other_page/second", "https://subdomain.yes.github.com/other_page/second");
|
||||||
|
QCOMPARE(result.length(), 3);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("https://*.github.com/*"));
|
||||||
|
QCOMPARE(firstUrl(result[1]), QString("https://subdomain.*.github.com/*/second"));
|
||||||
|
QCOMPARE(firstUrl(result[2]), QString("https://subdomain.yes.github.com/*"));
|
||||||
|
|
||||||
|
result = m_browserService->searchEntries(
|
||||||
|
db, "https://example.com:8448/login/page", "https://example.com:8448/login/page");
|
||||||
|
QCOMPARE(result.length(), 2);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("https://example.com:8448/*"));
|
||||||
|
QCOMPARE(firstUrl(result[1]), QString("https://example.com/*/*"));
|
||||||
|
|
||||||
|
result = m_browserService->searchEntries(
|
||||||
|
db, "https://example.com:8449/login/page", "https://example.com:8449/login/page");
|
||||||
|
QCOMPARE(result.length(), 1);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("https://example.com/*/*"));
|
||||||
|
|
||||||
|
result =
|
||||||
|
m_browserService->searchEntries(db, "https://example.com/$/login_page", "https://example.com/$/login_page");
|
||||||
|
QCOMPARE(result.length(), 2);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("https://example.com/*/*"));
|
||||||
|
QCOMPARE(firstUrl(result[1]), QString("https://example.com/$/*"));
|
||||||
|
|
||||||
|
result = m_browserService->searchEntries(db, "https://127.128.129.130:8448/", "https://127.128.129.130:8448/");
|
||||||
|
QCOMPARE(result.length(), 2);
|
||||||
|
|
||||||
|
result = m_browserService->searchEntries(db, "https://127.128.129.130/", "https://127.128.129.130/");
|
||||||
|
QCOMPARE(result.length(), 1);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("https://127.128.*/"));
|
||||||
|
|
||||||
|
result = m_browserService->searchEntries(db, "https://127.1.129.130/", "https://127.1.129.130/");
|
||||||
|
QCOMPARE(result.length(), 0);
|
||||||
|
|
||||||
|
result = m_browserService->searchEntries(db, "https://127.160.8.2/login", "https://127.160.8.2/login");
|
||||||
|
QCOMPARE(result.length(), 1);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("https://127.160.*.2/login"));
|
||||||
|
|
||||||
|
// Exact URL
|
||||||
|
result =
|
||||||
|
m_browserService->searchEntries(db, "https://thisisatest.com/login.php", "https://thisisatest.com/login.php");
|
||||||
|
QCOMPARE(result.length(), 1);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("\"https://thisisatest.com/login.php\""));
|
||||||
|
|
||||||
|
// With scheme matching enabled
|
||||||
|
browserSettings()->setMatchUrlScheme(true);
|
||||||
|
result = m_browserService->searchEntries(
|
||||||
|
db, "https://github.com/login_page/second", "https://github.com/login_page/second");
|
||||||
|
|
||||||
|
QCOMPARE(result.length(), 5);
|
||||||
|
QCOMPARE(firstUrl(result[0]), QString("https://github.com/login_page/*"));
|
||||||
|
QCOMPARE(firstUrl(result[1]), QString("https://github.com/*/second"));
|
||||||
|
QCOMPARE(firstUrl(result[2]), QString("https://github.com/*"));
|
||||||
|
QCOMPARE(firstUrl(result[3]), QString("github.com/*")); // Defaults to https
|
||||||
|
QCOMPARE(firstUrl(result[4]), QString("https://*.github.com/*"));
|
||||||
|
}
|
||||||
|
|
||||||
void TestBrowser::testInvalidEntries()
|
void TestBrowser::testInvalidEntries()
|
||||||
{
|
{
|
||||||
auto db = QSharedPointer<Database>::create();
|
auto db = QSharedPointer<Database>::create();
|
||||||
@ -516,14 +631,18 @@ void TestBrowser::testSubdomainsAndPaths()
|
|||||||
QCOMPARE(result.length(), 1);
|
QCOMPARE(result.length(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const
|
QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root, bool additionalUrl) const
|
||||||
{
|
{
|
||||||
QList<Entry*> entries;
|
QList<Entry*> entries;
|
||||||
for (int i = 0; i < urls.length(); ++i) {
|
for (int i = 0; i < urls.length(); ++i) {
|
||||||
auto entry = new Entry();
|
auto entry = new Entry();
|
||||||
entry->setGroup(root);
|
entry->setGroup(root);
|
||||||
entry->beginUpdate();
|
entry->beginUpdate();
|
||||||
entry->setUrl(urls[i]);
|
if (additionalUrl) {
|
||||||
|
entry->attributes()->set(EntryAttributes::AdditionalUrlAttribute, urls[i]);
|
||||||
|
} else {
|
||||||
|
entry->setUrl(urls[i]);
|
||||||
|
}
|
||||||
entry->setUsername(QString("User %1").arg(i));
|
entry->setUsername(QString("User %1").arg(i));
|
||||||
entry->setUuid(QUuid::createUuid());
|
entry->setUuid(QUuid::createUuid());
|
||||||
entry->setTitle(QString("Name_%1").arg(entry->uuidToHex()));
|
entry->setTitle(QString("Name_%1").arg(entry->uuidToHex()));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -45,6 +45,7 @@ private slots:
|
|||||||
void testSearchEntriesByReference();
|
void testSearchEntriesByReference();
|
||||||
void testSearchEntriesWithPort();
|
void testSearchEntriesWithPort();
|
||||||
void testSearchEntriesWithAdditionalURLs();
|
void testSearchEntriesWithAdditionalURLs();
|
||||||
|
void testSearchEntriesWithWildcardURLs();
|
||||||
void testInvalidEntries();
|
void testInvalidEntries();
|
||||||
void testSubdomainsAndPaths();
|
void testSubdomainsAndPaths();
|
||||||
void testBestMatchingCredentials();
|
void testBestMatchingCredentials();
|
||||||
@ -52,7 +53,7 @@ private slots:
|
|||||||
void testRestrictBrowserKey();
|
void testRestrictBrowserKey();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<Entry*> createEntries(QStringList& urls, Group* root) const;
|
QList<Entry*> createEntries(QStringList& urls, Group* root, bool additionalUrl = false) const;
|
||||||
void compareEntriesByPath(QSharedPointer<Database> db, QList<Entry*> entries, QString path);
|
void compareEntriesByPath(QSharedPointer<Database> db, QList<Entry*> entries, QString path);
|
||||||
|
|
||||||
QScopedPointer<BrowserAction> m_browserAction;
|
QScopedPointer<BrowserAction> m_browserAction;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -136,6 +136,36 @@ void TestUrlTools::testIsUrlValid()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestUrlTools::testIsUrlValidWithLooseComparison()
|
||||||
|
{
|
||||||
|
QHash<QString, bool> urls;
|
||||||
|
urls[""] = true;
|
||||||
|
urls["\"https://github.com/login\""] = true;
|
||||||
|
urls["https://*.github.com/"] = true;
|
||||||
|
urls["*.github.com"] = true;
|
||||||
|
urls["https://*.com"] = false;
|
||||||
|
urls["https://*.computer.com"] = true; // TLD in domain (com) should not affect
|
||||||
|
urls["\"\""] = false;
|
||||||
|
urls["\"*.example.com\""] = false;
|
||||||
|
urls["http://*"] = false;
|
||||||
|
urls["*"] = false;
|
||||||
|
urls["****"] = false;
|
||||||
|
urls["*.co.jp"] = false;
|
||||||
|
urls["*.com"] = false;
|
||||||
|
urls["*.computer.com"] = true;
|
||||||
|
urls["*.computer.com/*com"] = true; // TLD in path should not affect this
|
||||||
|
urls["*com"] = false;
|
||||||
|
urls["*.com/"] = false;
|
||||||
|
urls["*.com/*"] = false;
|
||||||
|
urls["**.com/**"] = false;
|
||||||
|
|
||||||
|
QHashIterator<QString, bool> i(urls);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
QCOMPARE(urlTools()->isUrlValid(i.key(), true), i.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TestUrlTools::testDomainHasIllegalCharacters()
|
void TestUrlTools::testDomainHasIllegalCharacters()
|
||||||
{
|
{
|
||||||
QVERIFY(!urlTools()->domainHasIllegalCharacters("example.com"));
|
QVERIFY(!urlTools()->domainHasIllegalCharacters("example.com"));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -34,6 +34,7 @@ private slots:
|
|||||||
void testIsIpAddress();
|
void testIsIpAddress();
|
||||||
void testIsUrlIdentical();
|
void testIsUrlIdentical();
|
||||||
void testIsUrlValid();
|
void testIsUrlValid();
|
||||||
|
void testIsUrlValidWithLooseComparison();
|
||||||
void testDomainHasIllegalCharacters();
|
void testDomainHasIllegalCharacters();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user