mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-03-13 01:26:37 -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
3b2f54daff
commit
3af68b1d3f
@ -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) 2013 Francois Ferrand
|
||||
*
|
||||
@ -50,6 +50,7 @@
|
||||
#include <QListWidget>
|
||||
#include <QLocalSocket>
|
||||
#include <QProgressDialog>
|
||||
#include <QStringView>
|
||||
#include <QUrl>
|
||||
|
||||
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
||||
@ -1427,9 +1428,15 @@ bool BrowserService::shouldIncludeEntry(Entry* entry,
|
||||
return url.endsWith("by-path/" + entry->path());
|
||||
}
|
||||
|
||||
const auto allEntryUrls = entry->getAllUrls();
|
||||
for (const auto& entryUrl : allEntryUrls) {
|
||||
if (handleURL(entryUrl, url, submitUrl, omitWwwSubdomain)) {
|
||||
// Handle the entry URL
|
||||
if (handleURL(entry->resolveUrl(), 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;
|
||||
}
|
||||
}
|
||||
@ -1517,17 +1524,35 @@ QJsonObject BrowserService::getPasskeyError(int errorCode) const
|
||||
bool BrowserService::handleURL(const QString& entryUrl,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl,
|
||||
const bool omitWwwSubdomain)
|
||||
const bool omitWwwSubdomain,
|
||||
const bool allowWildcards)
|
||||
{
|
||||
if (entryUrl.isEmpty()) {
|
||||
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;
|
||||
if (entryUrl.contains("://")) {
|
||||
entryQUrl = entryUrl;
|
||||
entryQUrl = tempUrl;
|
||||
} else {
|
||||
entryQUrl = QUrl::fromUserInput(entryUrl);
|
||||
entryQUrl = QUrl::fromUserInput(tempUrl);
|
||||
|
||||
if (browserSettings()->matchUrlScheme()) {
|
||||
entryQUrl.setScheme("https");
|
||||
@ -1567,6 +1592,11 @@ bool BrowserService::handleURL(const QString& entryUrl,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use wildcard matching instead
|
||||
if (isWildcardUrl) {
|
||||
return handleURLWithWildcards(entryQUrl, siteUrl);
|
||||
}
|
||||
|
||||
// Match the base domain
|
||||
if (urlTools()->getBaseDomainFromUrl(siteQUrl.host()) != urlTools()->getBaseDomainFromUrl(entryQUrl.host())) {
|
||||
return false;
|
||||
@ -1580,6 +1610,46 @@ bool BrowserService::handleURL(const QString& entryUrl,
|
||||
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)
|
||||
{
|
||||
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) 2013 Francois Ferrand
|
||||
*
|
||||
@ -196,7 +196,9 @@ private:
|
||||
bool handleURL(const QString& entryUrl,
|
||||
const QString& siteUrl,
|
||||
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 getDatabaseRecycleBinUuid();
|
||||
bool checkLegacySettings(QSharedPointer<Database> db);
|
||||
|
@ -380,16 +380,32 @@ QString Entry::url() const
|
||||
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 urlList;
|
||||
auto entryUrl = url();
|
||||
|
||||
const auto entryUrl = resolveUrl();
|
||||
if (!entryUrl.isEmpty()) {
|
||||
urlList << (EntryAttributes::matchReference(entryUrl).hasMatch() ? resolveMultiplePlaceholders(entryUrl)
|
||||
: entryUrl);
|
||||
urlList << entryUrl;
|
||||
}
|
||||
|
||||
return urlList << getAdditionalUrls();
|
||||
}
|
||||
|
||||
QStringList Entry::getAdditionalUrls() const
|
||||
{
|
||||
QStringList urlList;
|
||||
|
||||
for (const auto& key : m_attributes->keys()) {
|
||||
if (key.startsWith(EntryAttributes::AdditionalUrlAttribute)
|
||||
|| 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>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -100,7 +100,9 @@ public:
|
||||
const AutoTypeAssociations* autoTypeAssociations() const;
|
||||
QString title() const;
|
||||
QString url() const;
|
||||
QString resolveUrl() const;
|
||||
QStringList getAllUrls() const;
|
||||
QStringList getAdditionalUrls() const;
|
||||
QString webUrl() const;
|
||||
QString displayUrl() 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -24,6 +24,8 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QUrl>
|
||||
|
||||
const QString UrlTools::URL_WILDCARD = "1kpxcwc1";
|
||||
|
||||
Q_GLOBAL_STATIC(UrlTools, s_urlTools)
|
||||
|
||||
UrlTools* UrlTools::instance()
|
||||
@ -137,8 +139,9 @@ bool UrlTools::isUrlIdentical(const QString& first, const QString& second) const
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto firstUrl = trimUrl(first);
|
||||
const auto secondUrl = trimUrl(second);
|
||||
// Replace URL wildcards for comparison if found
|
||||
const auto firstUrl = trimUrl(QString(first).replace("*", UrlTools::URL_WILDCARD));
|
||||
const auto secondUrl = trimUrl(QString(second).replace("*", UrlTools::URL_WILDCARD));
|
||||
if (firstUrl == secondUrl) {
|
||||
return true;
|
||||
}
|
||||
@ -146,27 +149,61 @@ bool UrlTools::isUrlIdentical(const QString& first, const QString& second) const
|
||||
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)
|
||||
|| urlField.startsWith("kdbx://", Qt::CaseInsensitive) || urlField.startsWith("{REF:A", Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QUrl url;
|
||||
if (urlField.contains("://")) {
|
||||
url = urlField;
|
||||
} else {
|
||||
url = QUrl::fromUserInput(urlField);
|
||||
auto url = urlField;
|
||||
|
||||
// Loose comparison that allows wildcards and exact URL inside " characters
|
||||
if (looseComparison) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
#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
|
||||
QRegularExpression re("[<>\\^`{|}\\*]");
|
||||
auto match = re.match(urlField);
|
||||
auto match = re.match(url);
|
||||
if (match.hasMatch()) {
|
||||
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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -39,9 +39,11 @@ public:
|
||||
bool isIpAddress(const QString& host) const;
|
||||
#endif
|
||||
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;
|
||||
|
||||
static const QString URL_WILDCARD;
|
||||
|
||||
private:
|
||||
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>
|
||||
*
|
||||
* 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 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.
|
||||
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
|
||||
* 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[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);
|
||||
result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
|
||||
QCOMPARE(result.length(), 7);
|
||||
@ -396,6 +396,121 @@ void TestBrowser::testSearchEntriesWithAdditionalURLs()
|
||||
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()
|
||||
{
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
@ -516,14 +631,18 @@ void TestBrowser::testSubdomainsAndPaths()
|
||||
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;
|
||||
for (int i = 0; i < urls.length(); ++i) {
|
||||
auto entry = new Entry();
|
||||
entry->setGroup(root);
|
||||
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->setUuid(QUuid::createUuid());
|
||||
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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -45,13 +45,14 @@ private slots:
|
||||
void testSearchEntriesByReference();
|
||||
void testSearchEntriesWithPort();
|
||||
void testSearchEntriesWithAdditionalURLs();
|
||||
void testSearchEntriesWithWildcardURLs();
|
||||
void testInvalidEntries();
|
||||
void testSubdomainsAndPaths();
|
||||
void testBestMatchingCredentials();
|
||||
void testBestMatchingWithAdditionalURLs();
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
* 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()
|
||||
{
|
||||
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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -34,6 +34,7 @@ private slots:
|
||||
void testIsIpAddress();
|
||||
void testIsUrlIdentical();
|
||||
void testIsUrlValid();
|
||||
void testIsUrlValidWithLooseComparison();
|
||||
void testDomainHasIllegalCharacters();
|
||||
|
||||
private:
|
||||
|
Loading…
x
Reference in New Issue
Block a user