Skip to content

Commit 682d6f1

Browse files
committed
refactor(urltools): UrlTools is now a namespace
UrlTools does not need to inherit from QObject or even be a class. It is a collection of regular functions.
1 parent aa0cac5 commit 682d6f1

9 files changed

Lines changed: 75 additions & 109 deletions

File tree

src/browser/BrowserService.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1542,7 +1542,7 @@ bool BrowserService::handleURL(const QString& entryUrl,
15421542
}
15431543

15441544
// Match the base domain
1545-
if (urlTools()->getBaseDomainFromUrl(siteQUrl.host()) != urlTools()->getBaseDomainFromUrl(entryQUrl.host())) {
1545+
if (UrlTools::getBaseDomainFromUrl(siteQUrl.host()) != UrlTools::getBaseDomainFromUrl(entryQUrl.host())) {
15461546
return false;
15471547
}
15481548

src/browser/PasskeyUtils.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,11 @@ bool PasskeyUtils::isRegistrableDomainSuffix(const QString& hostSuffixString, co
232232
return false;
233233
}
234234

235-
if (hostSuffix == urlTools()->getTopLevelDomainFromUrl(hostSuffix)) {
235+
if (hostSuffix == UrlTools::getTopLevelDomainFromUrl(hostSuffix)) {
236236
return false;
237237
}
238238

239-
const auto originalPublicSuffix = urlTools()->getTopLevelDomainFromUrl(originalHost);
239+
const auto originalPublicSuffix = UrlTools::getTopLevelDomainFromUrl(originalHost);
240240
if (originalPublicSuffix.isEmpty()) {
241241
return false;
242242
}
@@ -256,7 +256,7 @@ bool PasskeyUtils::isDomain(const QString& hostName) const
256256
{
257257
const auto domain = QUrl::fromUserInput(hostName).host();
258258
return !domain.isEmpty() && !domain.endsWith('.') && Tools::isAsciiString(domain)
259-
&& !urlTools()->domainHasIllegalCharacters(domain) && !urlTools()->isIpAddress(hostName);
259+
&& !UrlTools::domainHasIllegalCharacters(domain) && !UrlTools::isIpAddress(hostName);
260260
}
261261

262262
bool PasskeyUtils::isUserVerificationValid(const QString& userVerification) const

src/gui/IconDownloader.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ void IconDownloader::setUrl(const QString& entryUrl)
8484
// Determine the second-level domain, if available
8585
QString secondLevelDomain;
8686
if (!hostIsIp) {
87-
secondLevelDomain = urlTools()->getBaseDomainFromUrl(url.toString());
87+
secondLevelDomain = UrlTools::getBaseDomainFromUrl(url.toString());
8888
}
8989

9090
// Start with the "fallback" url (if enabled) to try to get the best favicon
@@ -172,7 +172,7 @@ void IconDownloader::fetchFinished()
172172
QString url = m_url;
173173

174174
bool error = (m_reply->error() != QNetworkReply::NoError);
175-
QUrl redirectTarget = urlTools()->getRedirectTarget(m_reply);
175+
QUrl redirectTarget = UrlTools::getRedirectTarget(m_reply);
176176

177177
m_reply->deleteLater();
178178
m_reply = nullptr;

src/gui/URLEdit.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ void URLEdit::updateStylesheet(const QString& url)
5050
const QString stylesheetTemplate("QLineEdit { background: %1; }");
5151
const auto resolvedUrl = m_entry ? m_entry->resolveMultiplePlaceholders(url) : url;
5252

53-
if (!urlTools()->isUrlValid(resolvedUrl)) {
53+
if (!UrlTools::isUrlValid(resolvedUrl)) {
5454
const StateColorPalette statePalette;
5555
const auto color = statePalette.color(StateColorPalette::ColorRole::Error);
5656
setStyleSheet(stylesheetTemplate.arg(color.name()));

src/gui/UrlTools.cpp

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,20 @@
2626

2727
const QString UrlTools::URL_WILDCARD = "1kpxcwc1";
2828

29-
Q_GLOBAL_STATIC(UrlTools, s_urlTools)
30-
31-
UrlTools* UrlTools::instance()
32-
{
33-
return s_urlTools;
34-
}
29+
namespace {
30+
QUrl convertVariantToUrl(const QVariant& var)
31+
{
32+
QUrl url;
33+
if (var.canConvert<QUrl>()) {
34+
url = var.toUrl();
35+
}
36+
return url;
3537

36-
QUrl UrlTools::convertVariantToUrl(const QVariant& var) const
37-
{
38-
QUrl url;
39-
if (var.canConvert<QUrl>()) {
40-
url = var.toUrl();
4138
}
42-
return url;
4339
}
4440

4541
#if defined(KPXC_FEATURE_NETWORK) || defined(KPXC_FEATURE_BROWSER)
46-
QUrl UrlTools::getRedirectTarget(QNetworkReply* reply) const
42+
QUrl UrlTools::getRedirectTarget(QNetworkReply* reply)
4743
{
4844
QVariant var = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
4945
QUrl url = convertVariantToUrl(var);
@@ -56,7 +52,7 @@ QUrl UrlTools::getRedirectTarget(QNetworkReply* reply) const
5652
* Returns the base domain, e.g. https://another.example.co.uk -> example.co.uk
5753
* Up-to-date list can be found: https://publicsuffix.org/list/public_suffix_list.dat
5854
*/
59-
QString UrlTools::getBaseDomainFromUrl(const QString& url) const
55+
QString UrlTools::getBaseDomainFromUrl(const QString& url)
6056
{
6157
auto qUrl = QUrl::fromUserInput(url);
6258

@@ -85,7 +81,7 @@ QString UrlTools::getBaseDomainFromUrl(const QString& url) const
8581
*
8682
* Returns the TLD e.g. https://another.example.co.uk -> co.uk
8783
*/
88-
QString UrlTools::getTopLevelDomainFromUrl(const QString& url) const
84+
QString UrlTools::getTopLevelDomainFromUrl(const QString& url)
8985
{
9086
auto host = QUrl::fromUserInput(url).host();
9187
if (isIpAddress(host)) {
@@ -112,7 +108,7 @@ QString UrlTools::getTopLevelDomainFromUrl(const QString& url) const
112108
return host;
113109
}
114110

115-
bool UrlTools::isIpAddress(const QString& host) const
111+
bool UrlTools::isIpAddress(const QString& host)
116112
{
117113
// Handle IPv6 host with brackets, e.g [::1]
118114
const auto hostAddress = host.startsWith('[') && host.endsWith(']') ? host.mid(1, host.length() - 2) : host;
@@ -121,35 +117,37 @@ bool UrlTools::isIpAddress(const QString& host) const
121117
}
122118
#endif
123119

124-
// Returns true if URLs are identical. Paths with "/" are removed during comparison.
125-
// URLs without scheme reverts to https.
126-
// Special handling is needed because QUrl::matches() with QUrl::StripTrailingSlash does not strip "/" paths.
127-
bool UrlTools::isUrlIdentical(const QString& first, const QString& second) const
120+
namespace
128121
{
129-
auto trimUrl = [](QString url) {
122+
QString trimUrl(QString&& url) {
130123
url = url.trimmed();
131-
if (url.endsWith("/")) {
132-
url.remove(url.length() - 1, 1);
124+
if (url.endsWith('/')) {
125+
url.chop(1); // Removes the last character
133126
}
134-
135127
return url;
136-
};
128+
}
129+
}
137130

131+
// Returns true if URLs are identical. Paths with "/" are removed during comparison.
132+
// URLs without scheme reverts to https.
133+
// Special handling is needed because QUrl::matches() with QUrl::StripTrailingSlash does not strip "/" paths.
134+
bool UrlTools::isUrlIdentical(QString first, QString second)
135+
{
138136
if (first.isEmpty() || second.isEmpty()) {
139137
return false;
140138
}
141139

142140
// Replace URL wildcards for comparison if found
143-
const auto firstUrl = trimUrl(QString(first).replace("*", UrlTools::URL_WILDCARD));
144-
const auto secondUrl = trimUrl(QString(second).replace("*", UrlTools::URL_WILDCARD));
141+
const auto&& firstUrl = trimUrl(std::move(first.replace("*", UrlTools::URL_WILDCARD)));
142+
const auto&& secondUrl = trimUrl(std::move(second.replace("*", UrlTools::URL_WILDCARD)));
145143
if (firstUrl == secondUrl) {
146144
return true;
147145
}
148146

149147
return QUrl(firstUrl).matches(QUrl(secondUrl), QUrl::StripTrailingSlash);
150148
}
151149

152-
bool UrlTools::isUrlValid(const QString& urlField, bool looseComparison) const
150+
bool UrlTools::isUrlValid(const QString& urlField, bool looseComparison)
153151
{
154152
if (urlField.isEmpty() || urlField.startsWith("cmd://", Qt::CaseInsensitive)
155153
|| urlField.startsWith("kdbx://", Qt::CaseInsensitive) || urlField.startsWith("{REF:A", Qt::CaseInsensitive)) {
@@ -211,8 +209,8 @@ bool UrlTools::isUrlValid(const QString& urlField, bool looseComparison) const
211209
return true;
212210
}
213211

214-
bool UrlTools::domainHasIllegalCharacters(const QString& domain) const
212+
bool UrlTools::domainHasIllegalCharacters(const QString& domain)
215213
{
216-
QRegularExpression re(R"([\s\^#|/:<>\?@\[\]\\])");
214+
const QRegularExpression re(R"([\s\^#|/:<>\?@\[\]\\])");
217215
return re.match(domain).hasMatch();
218216
}

src/gui/UrlTools.h

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,36 +26,19 @@
2626
#include <QNetworkReply>
2727
#endif
2828

29-
class UrlTools : public QObject
29+
namespace UrlTools
3030
{
31-
Q_OBJECT
32-
33-
public:
34-
explicit UrlTools() = default;
35-
static UrlTools* instance();
36-
3731
#if defined(KPXC_FEATURE_NETWORK) || defined(KPXC_FEATURE_BROWSER)
38-
QUrl getRedirectTarget(QNetworkReply* reply) const;
39-
QString getBaseDomainFromUrl(const QString& url) const;
40-
QString getTopLevelDomainFromUrl(const QString& url) const;
41-
bool isIpAddress(const QString& host) const;
32+
QUrl getRedirectTarget(QNetworkReply* reply);
33+
QString getBaseDomainFromUrl(const QString& url);
34+
QString getTopLevelDomainFromUrl(const QString& url);
35+
bool isIpAddress(const QString& host);
4236
#endif
43-
bool isUrlIdentical(const QString& first, const QString& second) const;
44-
bool isUrlValid(const QString& urlField, bool looseComparison = false) const;
45-
bool domainHasIllegalCharacters(const QString& domain) const;
46-
47-
static const QString URL_WILDCARD;
37+
bool isUrlIdentical(QString first, QString second);
38+
bool isUrlValid(const QString& urlField, bool looseComparison = false);
39+
bool domainHasIllegalCharacters(const QString& domain);
4840

49-
private:
50-
QUrl convertVariantToUrl(const QVariant& var) const;
51-
52-
private:
53-
Q_DISABLE_COPY(UrlTools);
54-
};
55-
56-
static inline UrlTools* urlTools()
57-
{
58-
return UrlTools::instance();
41+
extern const QString URL_WILDCARD;
5942
}
6043

6144
#endif // KEEPASSXC_URLTOOLS_H

src/gui/entry/EntryURLModel.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ QVariant EntryURLModel::data(const QModelIndex& index, int role) const
6767
}
6868

6969
const auto value = m_entryAttributes->value(key);
70-
const auto urlValid = urlTools()->isUrlValid(value, true);
70+
const auto urlValid = UrlTools::isUrlValid(value, true);
7171

7272
// Check for duplicate URLs in the attribute list. Excludes the current key/value from the comparison.
7373
auto customAttributeKeys = m_entryAttributes->customKeys().filter(EntryAttributes::AdditionalUrlAttribute);
7474
customAttributeKeys.removeOne(key);
7575

7676
const auto duplicateUrl =
77-
m_entryAttributes->values(customAttributeKeys).contains(value) || urlTools()->isUrlIdentical(value, m_entryUrl);
77+
m_entryAttributes->values(customAttributeKeys).contains(value) || UrlTools::isUrlIdentical(value, m_entryUrl);
7878
if (role == Qt::BackgroundRole && (!urlValid || duplicateUrl)) {
7979
StateColorPalette statePalette;
8080
return statePalette.color(StateColorPalette::ColorRole::Error);

tests/TestUrlTools.cpp

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,6 @@
2020

2121
QTEST_GUILESS_MAIN(TestUrlTools)
2222

23-
void TestUrlTools::initTestCase()
24-
{
25-
m_urlTools = urlTools();
26-
}
27-
28-
void TestUrlTools::init()
29-
{
30-
}
31-
3223
void TestUrlTools::testTopLevelDomain()
3324
{
3425
// Create list of URLs and expected TLD responses
@@ -48,7 +39,7 @@ void TestUrlTools::testTopLevelDomain()
4839
};
4940

5041
for (const auto& u : tldUrls) {
51-
QCOMPARE(urlTools()->getTopLevelDomainFromUrl(u.first), u.second);
42+
QCOMPARE(UrlTools::getTopLevelDomainFromUrl(u.first), u.second);
5243
}
5344

5445
// Create list of URLs and expected base URL responses
@@ -66,7 +57,7 @@ void TestUrlTools::testTopLevelDomain()
6657
};
6758

6859
for (const auto& u : baseUrls) {
69-
QCOMPARE(urlTools()->getBaseDomainFromUrl(u.first), u.second);
60+
QCOMPARE(UrlTools::getBaseDomainFromUrl(u.first), u.second);
7061
}
7162
}
7263

@@ -84,32 +75,32 @@ void TestUrlTools::testIsIpAddress()
8475
auto host10 = "::";
8576
auto host11 = "[2001:20::1]";
8677

87-
QVERIFY(!urlTools()->isIpAddress(host1));
88-
QVERIFY(urlTools()->isIpAddress(host2));
89-
QVERIFY(!urlTools()->isIpAddress(host3));
90-
QVERIFY(urlTools()->isIpAddress(host4));
91-
QVERIFY(urlTools()->isIpAddress(host5));
92-
QVERIFY(urlTools()->isIpAddress(host6));
93-
QVERIFY(urlTools()->isIpAddress(host7));
94-
QVERIFY(!urlTools()->isIpAddress(host8));
95-
QVERIFY(urlTools()->isIpAddress(host9));
96-
QVERIFY(urlTools()->isIpAddress(host10));
97-
QVERIFY(urlTools()->isIpAddress(host11));
78+
QVERIFY(!UrlTools::isIpAddress(host1));
79+
QVERIFY(UrlTools::isIpAddress(host2));
80+
QVERIFY(!UrlTools::isIpAddress(host3));
81+
QVERIFY(UrlTools::isIpAddress(host4));
82+
QVERIFY(UrlTools::isIpAddress(host5));
83+
QVERIFY(UrlTools::isIpAddress(host6));
84+
QVERIFY(UrlTools::isIpAddress(host7));
85+
QVERIFY(!UrlTools::isIpAddress(host8));
86+
QVERIFY(UrlTools::isIpAddress(host9));
87+
QVERIFY(UrlTools::isIpAddress(host10));
88+
QVERIFY(UrlTools::isIpAddress(host11));
9889
}
9990

10091
void TestUrlTools::testIsUrlIdentical()
10192
{
102-
QVERIFY(urlTools()->isUrlIdentical("https://example.com", "https://example.com"));
103-
QVERIFY(urlTools()->isUrlIdentical("https://example.com", " https://example.com "));
104-
QVERIFY(!urlTools()->isUrlIdentical("https://example.com", "https://example2.com"));
105-
QVERIFY(!urlTools()->isUrlIdentical("https://example.com/", "https://example.com/#login"));
106-
QVERIFY(urlTools()->isUrlIdentical("https://example.com", "https://example.com/"));
107-
QVERIFY(urlTools()->isUrlIdentical("https://example.com/", "https://example.com"));
108-
QVERIFY(urlTools()->isUrlIdentical("https://example.com/ ", " https://example.com"));
109-
QVERIFY(!urlTools()->isUrlIdentical("https://example.com/", " example.com"));
110-
QVERIFY(urlTools()->isUrlIdentical("https://example.com/path/to/nowhere", "https://example.com/path/to/nowhere/"));
111-
QVERIFY(!urlTools()->isUrlIdentical("https://example.com/", "://example.com/"));
112-
QVERIFY(urlTools()->isUrlIdentical("ftp://127.0.0.1/", "ftp://127.0.0.1"));
93+
QVERIFY(UrlTools::isUrlIdentical("https://example.com", "https://example.com"));
94+
QVERIFY(UrlTools::isUrlIdentical("https://example.com", " https://example.com "));
95+
QVERIFY(!UrlTools::isUrlIdentical("https://example.com", "https://example2.com"));
96+
QVERIFY(!UrlTools::isUrlIdentical("https://example.com/", "https://example.com/#login"));
97+
QVERIFY(UrlTools::isUrlIdentical("https://example.com", "https://example.com/"));
98+
QVERIFY(UrlTools::isUrlIdentical("https://example.com/", "https://example.com"));
99+
QVERIFY(UrlTools::isUrlIdentical("https://example.com/ ", " https://example.com"));
100+
QVERIFY(!UrlTools::isUrlIdentical("https://example.com/", " example.com"));
101+
QVERIFY(UrlTools::isUrlIdentical("https://example.com/path/to/nowhere", "https://example.com/path/to/nowhere/"));
102+
QVERIFY(!UrlTools::isUrlIdentical("https://example.com/", "://example.com/"));
103+
QVERIFY(UrlTools::isUrlIdentical("ftp://127.0.0.1/", "ftp://127.0.0.1"));
113104
}
114105

115106
void TestUrlTools::testIsUrlValid()
@@ -130,7 +121,7 @@ void TestUrlTools::testIsUrlValid()
130121
QHashIterator<QString, bool> i(urls);
131122
while (i.hasNext()) {
132123
i.next();
133-
QCOMPARE(urlTools()->isUrlValid(i.key()), i.value());
124+
QCOMPARE(UrlTools::isUrlValid(i.key()), i.value());
134125
}
135126
}
136127

@@ -165,13 +156,13 @@ void TestUrlTools::testIsUrlValidWithLooseComparison()
165156
QHashIterator<QString, bool> i(urls);
166157
while (i.hasNext()) {
167158
i.next();
168-
QCOMPARE(urlTools()->isUrlValid(i.key(), true), i.value());
159+
QCOMPARE(UrlTools::isUrlValid(i.key(), true), i.value());
169160
}
170161
}
171162

172163
void TestUrlTools::testDomainHasIllegalCharacters()
173164
{
174-
QVERIFY(!urlTools()->domainHasIllegalCharacters("example.com"));
175-
QVERIFY(urlTools()->domainHasIllegalCharacters("domain has spaces.com"));
176-
QVERIFY(urlTools()->domainHasIllegalCharacters("example#|.com"));
165+
QVERIFY(!UrlTools::domainHasIllegalCharacters("example.com"));
166+
QVERIFY(UrlTools::domainHasIllegalCharacters("domain has spaces.com"));
167+
QVERIFY(UrlTools::domainHasIllegalCharacters("example#|.com"));
177168
}

tests/TestUrlTools.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,11 @@ class TestUrlTools : public QObject
2727
Q_OBJECT
2828

2929
private slots:
30-
void initTestCase();
31-
void init();
32-
3330
void testTopLevelDomain();
3431
void testIsIpAddress();
3532
void testIsUrlIdentical();
3633
void testIsUrlValid();
3734
void testIsUrlValidWithLooseComparison();
3835
void testDomainHasIllegalCharacters();
39-
40-
private:
41-
QPointer<UrlTools> m_urlTools;
4236
};
4337
#endif // KEEPASSXC_TESTURLTOOLS_H

0 commit comments

Comments
 (0)