1 /*
2 * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 or (at your option)
7 * version 3 of the License.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "TestBrowser.h"
19
20 #include "TestGlobal.h"
21 #include "browser/BrowserSettings.h"
22 #include "core/Tools.h"
23 #include "crypto/Crypto.h"
24 #include "sodium/crypto_box.h"
25
26 #include <QString>
27
28 QTEST_GUILESS_MAIN(TestBrowser)
29
30 const QString PUBLICKEY = "UIIPObeoya1G8g1M5omgyoPR/j1mR1HlYHu0wHCgMhA=";
31 const QString SECRETKEY = "B8ei4ZjQJkWzZU2SK/tBsrYRwp+6ztEMf5GFQV+i0yI=";
32 const QString SERVERPUBLICKEY = "lKnbLhrVCOqzEjuNoUz1xj9EZlz8xeO4miZBvLrUPVQ=";
33 const QString SERVERSECRETKEY = "tbPQcghxfOgbmsnEqG2qMIj1W2+nh+lOJcNsHncaz1Q=";
34 const QString NONCE = "zBKdvTjL5bgWaKMCTut/8soM/uoMrFoZ";
35 const QString CLIENTID = "testClient";
36
initTestCase()37 void TestBrowser::initTestCase()
38 {
39 QVERIFY(Crypto::init());
40 m_browserService = browserService();
41 browserSettings()->setBestMatchOnly(false);
42 }
43
init()44 void TestBrowser::init()
45 {
46 m_browserAction.reset(new BrowserAction());
47 }
48
49 /**
50 * Tests for BrowserAction
51 */
52
testChangePublicKeys()53 void TestBrowser::testChangePublicKeys()
54 {
55 QJsonObject json;
56 json["action"] = "change-public-keys";
57 json["publicKey"] = PUBLICKEY;
58 json["nonce"] = NONCE;
59
60 auto response = m_browserAction->processClientMessage(json);
61 QCOMPARE(response["action"].toString(), QString("change-public-keys"));
62 QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
63 QCOMPARE(response["success"].toString(), TRUE_STR);
64 }
65
testEncryptMessage()66 void TestBrowser::testEncryptMessage()
67 {
68 QJsonObject message;
69 message["action"] = "test-action";
70
71 m_browserAction->m_publicKey = SERVERPUBLICKEY;
72 m_browserAction->m_secretKey = SERVERSECRETKEY;
73 m_browserAction->m_clientPublicKey = PUBLICKEY;
74 auto encrypted = m_browserAction->encryptMessage(message, NONCE);
75
76 QCOMPARE(encrypted, QString("+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP"));
77 }
78
testDecryptMessage()79 void TestBrowser::testDecryptMessage()
80 {
81 QString message = "+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP";
82 m_browserAction->m_publicKey = SERVERPUBLICKEY;
83 m_browserAction->m_secretKey = SERVERSECRETKEY;
84 m_browserAction->m_clientPublicKey = PUBLICKEY;
85 auto decrypted = m_browserAction->decryptMessage(message, NONCE);
86
87 QCOMPARE(decrypted["action"].toString(), QString("test-action"));
88 }
89
testGetBase64FromKey()90 void TestBrowser::testGetBase64FromKey()
91 {
92 unsigned char pk[crypto_box_PUBLICKEYBYTES];
93
94 for (unsigned int i = 0; i < crypto_box_PUBLICKEYBYTES; ++i) {
95 pk[i] = i;
96 }
97
98 auto response = m_browserAction->getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES);
99 QCOMPARE(response, QString("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="));
100 }
101
testIncrementNonce()102 void TestBrowser::testIncrementNonce()
103 {
104 auto result = m_browserAction->incrementNonce(NONCE);
105 QCOMPARE(result, QString("zRKdvTjL5bgWaKMCTut/8soM/uoMrFoZ"));
106 }
107
108 /**
109 * Tests for BrowserService
110 */
testBaseDomain()111 void TestBrowser::testBaseDomain()
112 {
113 QString url1 = "https://another.example.co.uk";
114 QString url2 = "https://www.example.com";
115 QString url3 = "http://test.net";
116 QString url4 = "http://so.many.subdomains.co.jp";
117
118 QString res1 = m_browserService->baseDomain(url1);
119 QString res2 = m_browserService->baseDomain(url2);
120 QString res3 = m_browserService->baseDomain(url3);
121 QString res4 = m_browserService->baseDomain(url4);
122
123 QCOMPARE(res1, QString("example.co.uk"));
124 QCOMPARE(res2, QString("example.com"));
125 QCOMPARE(res3, QString("test.net"));
126 QCOMPARE(res4, QString("subdomains.co.jp"));
127 }
128
testSortPriority()129 void TestBrowser::testSortPriority()
130 {
131 QFETCH(QString, entryUrl);
132 QFETCH(QString, siteUrl);
133 QFETCH(QString, formUrl);
134 QFETCH(int, expectedScore);
135
136 QScopedPointer<Entry> entry(new Entry());
137 entry->setUrl(entryUrl);
138
139 QCOMPARE(m_browserService->sortPriority(m_browserService->getEntryURLs(entry.data()), siteUrl, formUrl),
140 expectedScore);
141 }
142
testSortPriority_data()143 void TestBrowser::testSortPriority_data()
144 {
145 const QString siteUrl = "https://github.com/login";
146 const QString formUrl = "https://github.com/session";
147
148 QTest::addColumn<QString>("entryUrl");
149 QTest::addColumn<QString>("siteUrl");
150 QTest::addColumn<QString>("formUrl");
151 QTest::addColumn<int>("expectedScore");
152
153 QTest::newRow("Exact Match") << siteUrl << siteUrl << siteUrl << 100;
154 QTest::newRow("Exact Match (site)") << siteUrl << siteUrl << formUrl << 100;
155 QTest::newRow("Exact Match (form)") << siteUrl << "https://github.net" << siteUrl << 100;
156 QTest::newRow("Exact Match No Trailing Slash") << "https://github.com"
157 << "https://github.com/" << formUrl << 100;
158 QTest::newRow("Exact Match No Scheme") << "github.com/login" << siteUrl << formUrl << 100;
159 QTest::newRow("Exact Match with Query") << "https://github.com/login?test=test#fragment"
160 << "https://github.com/login?test=test" << formUrl << 100;
161
162 QTest::newRow("Site Query Mismatch") << siteUrl << siteUrl + "?test=test" << formUrl << 90;
163
164 QTest::newRow("Path Mismatch (site)") << "https://github.com/" << siteUrl << formUrl << 80;
165 QTest::newRow("Path Mismatch (site) No Scheme") << "github.com" << siteUrl << formUrl << 80;
166 QTest::newRow("Path Mismatch (form)") << "https://github.com/"
167 << "https://github.net" << formUrl << 70;
168
169 QTest::newRow("Subdomain Mismatch (site)") << siteUrl << "https://sub.github.com/"
170 << "https://github.net/" << 60;
171 QTest::newRow("Subdomain Mismatch (form)") << siteUrl << "https://github.net/"
172 << "https://sub.github.com/" << 50;
173
174 QTest::newRow("Scheme Mismatch") << "http://github.com" << siteUrl << formUrl << 0;
175 QTest::newRow("Scheme Mismatch w/path") << "http://github.com/login" << siteUrl << formUrl << 0;
176 QTest::newRow("Invalid URL") << "http://github" << siteUrl << formUrl << 0;
177 }
178
testSearchEntries()179 void TestBrowser::testSearchEntries()
180 {
181 auto db = QSharedPointer<Database>::create();
182 auto* root = db->rootGroup();
183
184 QStringList urls = {"https://github.com/login_page",
185 "https://github.com/login",
186 "https://github.com/",
187 "github.com/login",
188 "http://github.com",
189 "http://github.com/login",
190 "github.com",
191 "github.com/login",
192 "https://github", // Invalid URL
193 "github.com"};
194
195 createEntries(urls, root);
196
197 browserSettings()->setMatchUrlScheme(false);
198 auto result =
199 m_browserService->searchEntries(db, "https://github.com", "https://github.com/session"); // db, url, submitUrl
200
201 QCOMPARE(result.length(), 9);
202 QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
203 QCOMPARE(result[1]->url(), QString("https://github.com/login"));
204 QCOMPARE(result[2]->url(), QString("https://github.com/"));
205 QCOMPARE(result[3]->url(), QString("github.com/login"));
206 QCOMPARE(result[4]->url(), QString("http://github.com"));
207 QCOMPARE(result[5]->url(), QString("http://github.com/login"));
208
209 // With matching there should be only 3 results + 4 without a scheme
210 browserSettings()->setMatchUrlScheme(true);
211 result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
212 QCOMPARE(result.length(), 7);
213 QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
214 QCOMPARE(result[1]->url(), QString("https://github.com/login"));
215 QCOMPARE(result[2]->url(), QString("https://github.com/"));
216 QCOMPARE(result[3]->url(), QString("github.com/login"));
217 }
218
testSearchEntriesWithPort()219 void TestBrowser::testSearchEntriesWithPort()
220 {
221 auto db = QSharedPointer<Database>::create();
222 auto* root = db->rootGroup();
223
224 QStringList urls = {"http://127.0.0.1:443", "http://127.0.0.1:80"};
225
226 createEntries(urls, root);
227
228 auto result = m_browserService->searchEntries(db, "http://127.0.0.1:443", "http://127.0.0.1");
229 QCOMPARE(result.length(), 1);
230 QCOMPARE(result[0]->url(), QString("http://127.0.0.1:443"));
231 }
232
testSearchEntriesWithAdditionalURLs()233 void TestBrowser::testSearchEntriesWithAdditionalURLs()
234 {
235 auto db = QSharedPointer<Database>::create();
236 auto* root = db->rootGroup();
237
238 QStringList urls = {"https://github.com/", "https://www.example.com", "http://domain.com"};
239
240 auto entries = createEntries(urls, root);
241
242 // Add an additional URL to the first entry
243 entries.first()->attributes()->set(BrowserService::ADDITIONAL_URL, "https://keepassxc.org");
244
245 auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
246 QCOMPARE(result.length(), 1);
247 QCOMPARE(result[0]->url(), QString("https://github.com/"));
248
249 // Search the additional URL. It should return the same entry
250 auto additionalResult = m_browserService->searchEntries(db, "https://keepassxc.org", "https://keepassxc.org");
251 QCOMPARE(additionalResult.length(), 1);
252 QCOMPARE(additionalResult[0]->url(), QString("https://github.com/"));
253 }
254
testInvalidEntries()255 void TestBrowser::testInvalidEntries()
256 {
257 auto db = QSharedPointer<Database>::create();
258 auto* root = db->rootGroup();
259 const QString url("https://github.com");
260 const QString submitUrl("https://github.com/session");
261
262 QStringList urls = {
263 "https://github.com/login",
264 "https:///github.com/", // Extra '/'
265 "http://github.com/**//*",
266 "http://*.github.com/login",
267 "//github.com", // fromUserInput() corrects this one.
268 "github.com/{}<>",
269 "http:/example.com",
270 };
271
272 createEntries(urls, root);
273
274 browserSettings()->setMatchUrlScheme(true);
275 auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
276 QCOMPARE(result.length(), 2);
277 QCOMPARE(result[0]->url(), QString("https://github.com/login"));
278 QCOMPARE(result[1]->url(), QString("//github.com"));
279
280 // Test the URL's directly
281 QCOMPARE(m_browserService->handleURL(urls[0], url, submitUrl), true);
282 QCOMPARE(m_browserService->handleURL(urls[1], url, submitUrl), false);
283 QCOMPARE(m_browserService->handleURL(urls[2], url, submitUrl), false);
284 QCOMPARE(m_browserService->handleURL(urls[3], url, submitUrl), false);
285 QCOMPARE(m_browserService->handleURL(urls[4], url, submitUrl), true);
286 QCOMPARE(m_browserService->handleURL(urls[5], url, submitUrl), false);
287 }
288
testSubdomainsAndPaths()289 void TestBrowser::testSubdomainsAndPaths()
290 {
291 auto db = QSharedPointer<Database>::create();
292 auto* root = db->rootGroup();
293
294 QStringList urls = {
295 "https://www.github.com/login/page.xml",
296 "https://login.github.com/",
297 "https://github.com",
298 "http://www.github.com",
299 "http://login.github.com/pathtonowhere",
300 ".github.com", // Invalid URL
301 "www.github.com/",
302 "https://github", // Invalid URL
303 "https://hub.com" // Should not return
304 };
305
306 createEntries(urls, root);
307
308 browserSettings()->setMatchUrlScheme(false);
309 auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
310 QCOMPARE(result.length(), 1);
311 QCOMPARE(result[0]->url(), QString("https://github.com"));
312
313 // With www subdomain
314 result = m_browserService->searchEntries(db, "https://www.github.com", "https://www.github.com/session");
315 QCOMPARE(result.length(), 4);
316 QCOMPARE(result[0]->url(), QString("https://www.github.com/login/page.xml"));
317 QCOMPARE(result[1]->url(), QString("https://github.com")); // Accepts any subdomain
318 QCOMPARE(result[2]->url(), QString("http://www.github.com"));
319 QCOMPARE(result[3]->url(), QString("www.github.com/"));
320
321 // With scheme matching there should be only 1 result
322 browserSettings()->setMatchUrlScheme(true);
323 result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
324 QCOMPARE(result.length(), 1);
325 QCOMPARE(result[0]->url(), QString("https://github.com"));
326
327 // Test site with subdomain in the site URL
328 QStringList entryURLs = {
329 "https://accounts.example.com",
330 "https://accounts.example.com/path",
331 "https://subdomain.example.com/",
332 "https://another.accounts.example.com/",
333 "https://another.subdomain.example.com/",
334 "https://example.com/",
335 "https://example" // Invalid URL
336 };
337
338 createEntries(entryURLs, root);
339
340 result = m_browserService->searchEntries(db, "https://accounts.example.com/", "https://accounts.example.com/");
341 QCOMPARE(result.length(), 3);
342 QCOMPARE(result[0]->url(), QString("https://accounts.example.com"));
343 QCOMPARE(result[1]->url(), QString("https://accounts.example.com/path"));
344 QCOMPARE(result[2]->url(), QString("https://example.com/")); // Accepts any subdomain
345
346 result = m_browserService->searchEntries(
347 db, "https://another.accounts.example.com/", "https://another.accounts.example.com/");
348 QCOMPARE(result.length(), 4);
349 QCOMPARE(result[0]->url(),
350 QString("https://accounts.example.com")); // Accepts any subdomain under accounts.example.com
351 QCOMPARE(result[1]->url(), QString("https://accounts.example.com/path"));
352 QCOMPARE(result[2]->url(), QString("https://another.accounts.example.com/"));
353 QCOMPARE(result[3]->url(), QString("https://example.com/")); // Accepts one or more subdomains
354
355 // Test local files. It should be a direct match.
356 QStringList localFiles = {"file:///Users/testUser/tests/test.html"};
357
358 createEntries(localFiles, root);
359
360 // With local files, url is always set to the file scheme + ://. Submit URL holds the actual URL.
361 result = m_browserService->searchEntries(db, "file://", "file:///Users/testUser/tests/test.html");
362 QCOMPARE(result.length(), 1);
363 }
364
testSortEntries()365 void TestBrowser::testSortEntries()
366 {
367 auto db = QSharedPointer<Database>::create();
368 auto* root = db->rootGroup();
369
370 QStringList urls = {"https://github.com/login_page",
371 "https://github.com/login",
372 "https://github.com/",
373 "github.com/login",
374 "http://github.com",
375 "http://github.com/login",
376 "github.com",
377 "github.com/login?test=test",
378 "https://github", // Invalid URL
379 "github.com"};
380
381 auto entries = createEntries(urls, root);
382
383 browserSettings()->setBestMatchOnly(false);
384 browserSettings()->setSortByUsername(true);
385 auto result = m_browserService->sortEntries(entries, "https://github.com/login", "https://github.com/session");
386 QCOMPARE(result.size(), 10);
387 QCOMPARE(result[0]->username(), QString("User 1"));
388 QCOMPARE(result[0]->url(), urls[1]);
389 QCOMPARE(result[1]->username(), QString("User 3"));
390 QCOMPARE(result[1]->url(), urls[3]);
391 QCOMPARE(result[2]->username(), QString("User 7"));
392 QCOMPARE(result[2]->url(), urls[7]);
393 QCOMPARE(result[3]->username(), QString("User 0"));
394 QCOMPARE(result[3]->url(), urls[0]);
395
396 // Test with a perfect match. That should be first in the list.
397 result = m_browserService->sortEntries(entries, "https://github.com/login_page", "https://github.com/session");
398 QCOMPARE(result.size(), 10);
399 QCOMPARE(result[0]->username(), QString("User 0"));
400 QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
401 QCOMPARE(result[1]->username(), QString("User 1"));
402 QCOMPARE(result[1]->url(), QString("https://github.com/login"));
403 }
404
createEntries(QStringList & urls,Group * root) const405 QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const
406 {
407 QList<Entry*> entries;
408 for (int i = 0; i < urls.length(); ++i) {
409 auto entry = new Entry();
410 entry->setGroup(root);
411 entry->beginUpdate();
412 entry->setUrl(urls[i]);
413 entry->setUsername(QString("User %1").arg(i));
414 entry->endUpdate();
415 entries.push_back(entry);
416 }
417
418 return entries;
419 }
testValidURLs()420 void TestBrowser::testValidURLs()
421 {
422 QHash<QString, bool> urls;
423 urls["https://github.com/login"] = true;
424 urls["https:///github.com/"] = false;
425 urls["http://github.com/**//*"] = false;
426 urls["http://*.github.com/login"] = false;
427 urls["//github.com"] = true;
428 urls["github.com/{}<>"] = false;
429 urls["http:/example.com"] = false;
430 urls["cmd://C:/Toolchains/msys2/usr/bin/mintty \"ssh jon@192.168.0.1:22\""] = true;
431 urls["file:///Users/testUser/Code/test.html"] = true;
432 urls["{REF:A@I:46C9B1FFBD4ABC4BBB260C6190BAD20C} "] = true;
433
434 QHashIterator<QString, bool> i(urls);
435 while (i.hasNext()) {
436 i.next();
437 QCOMPARE(Tools::checkUrlValid(i.key()), i.value());
438 }
439 }
440
testBestMatchingCredentials()441 void TestBrowser::testBestMatchingCredentials()
442 {
443 auto db = QSharedPointer<Database>::create();
444 auto* root = db->rootGroup();
445
446 // Test with simple URL entries
447 QStringList urls = {"https://github.com/loginpage", "https://github.com/justsomepage", "https://github.com/"};
448
449 auto entries = createEntries(urls, root);
450
451 browserSettings()->setBestMatchOnly(true);
452
453 QString siteUrl = "https://github.com/loginpage";
454 auto result = m_browserService->searchEntries(db, siteUrl, siteUrl);
455 auto sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
456 QCOMPARE(sorted.size(), 1);
457 QCOMPARE(sorted[0]->url(), siteUrl);
458
459 siteUrl = "https://github.com/justsomepage";
460 result = m_browserService->searchEntries(db, siteUrl, siteUrl);
461 sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
462 QCOMPARE(sorted.size(), 1);
463 QCOMPARE(sorted[0]->url(), siteUrl);
464
465 siteUrl = "https://github.com/";
466 result = m_browserService->searchEntries(db, siteUrl, siteUrl);
467 sorted = m_browserService->sortEntries(entries, siteUrl, siteUrl);
468 QCOMPARE(sorted.size(), 1);
469 QCOMPARE(sorted[0]->url(), siteUrl);
470
471 // Without best-matching the URL with the path should be returned first
472 browserSettings()->setBestMatchOnly(false);
473 siteUrl = "https://github.com/loginpage";
474 result = m_browserService->searchEntries(db, siteUrl, siteUrl);
475 sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
476 QCOMPARE(sorted.size(), 3);
477 QCOMPARE(sorted[0]->url(), siteUrl);
478
479 // Test with subdomains
480 QStringList subdomainsUrls = {"https://sub.github.com/loginpage",
481 "https://sub.github.com/justsomepage",
482 "https://bus.github.com/justsomepage",
483 "https://subdomain.example.com/",
484 "https://subdomain.example.com",
485 "https://example.com"};
486
487 entries = createEntries(subdomainsUrls, root);
488
489 browserSettings()->setBestMatchOnly(true);
490 siteUrl = "https://sub.github.com/justsomepage";
491 result = m_browserService->searchEntries(db, siteUrl, siteUrl);
492 sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
493 QCOMPARE(sorted.size(), 1);
494 QCOMPARE(sorted[0]->url(), siteUrl);
495
496 siteUrl = "https://github.com/justsomepage";
497 result = m_browserService->searchEntries(db, siteUrl, siteUrl);
498 sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
499 QCOMPARE(sorted.size(), 1);
500 QCOMPARE(sorted[0]->url(), siteUrl);
501
502 siteUrl = "https://sub.github.com/justsomepage?wehavesomeextra=here";
503 result = m_browserService->searchEntries(db, siteUrl, siteUrl);
504 sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
505 QCOMPARE(sorted.size(), 1);
506 QCOMPARE(sorted[0]->url(), QString("https://sub.github.com/justsomepage"));
507
508 // The matching should not care if there's a / path or not.
509 siteUrl = "https://subdomain.example.com/";
510 result = m_browserService->searchEntries(db, siteUrl, siteUrl);
511 sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
512 QCOMPARE(sorted.size(), 2);
513 QCOMPARE(sorted[0]->url(), QString("https://subdomain.example.com/"));
514 QCOMPARE(sorted[1]->url(), QString("https://subdomain.example.com"));
515
516 // Entries with https://example.com should be still returned even if the site URL has a subdomain. Those have the
517 // best match.
518 db = QSharedPointer<Database>::create();
519 root = db->rootGroup();
520 QStringList domainUrls = {"https://example.com", "https://example.com", "https://other.example.com"};
521 entries = createEntries(domainUrls, root);
522 siteUrl = "https://subdomain.example.com";
523 result = m_browserService->searchEntries(db, siteUrl, siteUrl);
524 sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
525
526 QCOMPARE(sorted.size(), 2);
527 QCOMPARE(sorted[0]->url(), QString("https://example.com"));
528 QCOMPARE(sorted[1]->url(), QString("https://example.com"));
529
530 // https://github.com/keepassxreboot/keepassxc/issues/4754
531 db = QSharedPointer<Database>::create();
532 root = db->rootGroup();
533 QStringList fooUrls = {"https://example.com/foo", "https://example.com/bar"};
534 entries = createEntries(fooUrls, root);
535
536 for (const auto& url : fooUrls) {
537 result = m_browserService->searchEntries(db, url, url);
538 sorted = m_browserService->sortEntries(result, url, url);
539 QCOMPARE(sorted.size(), 1);
540 QCOMPARE(sorted[0]->url(), QString(url));
541 }
542
543 // https://github.com/keepassxreboot/keepassxc/issues/4734
544 db = QSharedPointer<Database>::create();
545 root = db->rootGroup();
546 QStringList testUrls = {"http://some.domain.tld/somePath", "http://some.domain.tld/otherPath"};
547 entries = createEntries(testUrls, root);
548
549 for (const auto& url : testUrls) {
550 result = m_browserService->searchEntries(db, url, url);
551 sorted = m_browserService->sortEntries(result, url, url);
552 QCOMPARE(sorted.size(), 1);
553 QCOMPARE(sorted[0]->url(), QString(url));
554 }
555 }
556
testBestMatchingWithAdditionalURLs()557 void TestBrowser::testBestMatchingWithAdditionalURLs()
558 {
559 auto db = QSharedPointer<Database>::create();
560 auto* root = db->rootGroup();
561
562 QStringList urls = {"https://github.com/loginpage", "https://test.github.com/", "https://github.com/"};
563
564 auto entries = createEntries(urls, root);
565 browserSettings()->setBestMatchOnly(true);
566
567 // Add an additional URL to the first entry
568 entries.first()->attributes()->set(BrowserService::ADDITIONAL_URL, "https://test.github.com/anotherpage");
569
570 // The first entry should be triggered
571 auto result = m_browserService->searchEntries(
572 db, "https://test.github.com/anotherpage", "https://test.github.com/anotherpage");
573 auto sorted = m_browserService->sortEntries(
574 result, "https://test.github.com/anotherpage", "https://test.github.com/anotherpage");
575 QCOMPARE(sorted.length(), 1);
576 QCOMPARE(sorted[0]->url(), urls[0]);
577 }
578