mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Improve duplicate URL warning (#9635)
Co-authored-by: varjolintu <sami.vanttinen@protonmail.com>
This commit is contained in:
parent
eee25a1c35
commit
139153d9a3
@ -544,6 +544,33 @@ bool BrowserService::isPasswordGeneratorRequested() const
|
|||||||
return m_passwordGeneratorRequested;
|
return m_passwordGeneratorRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if URLs are identical. Paths with "/" are removed during comparison.
|
||||||
|
// URLs without scheme reverts to https.
|
||||||
|
// Special handling is needed because QUrl::matches() with QUrl::StripTrailingSlash does not strip "/" paths.
|
||||||
|
bool BrowserService::isUrlIdentical(const QString& first, const QString& second) const
|
||||||
|
{
|
||||||
|
auto trimUrl = [](QString url) {
|
||||||
|
url = url.trimmed();
|
||||||
|
if (url.endsWith("/")) {
|
||||||
|
url.remove(url.length() - 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (first.isEmpty() || second.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto firstUrl = trimUrl(first);
|
||||||
|
const auto secondUrl = trimUrl(second);
|
||||||
|
if (firstUrl == secondUrl) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QUrl(firstUrl).matches(QUrl(secondUrl), QUrl::StripTrailingSlash);
|
||||||
|
}
|
||||||
|
|
||||||
QString BrowserService::storeKey(const QString& key)
|
QString BrowserService::storeKey(const QString& key)
|
||||||
{
|
{
|
||||||
auto db = getDatabase();
|
auto db = getDatabase();
|
||||||
|
@ -82,6 +82,7 @@ public:
|
|||||||
QString getCurrentTotp(const QString& uuid);
|
QString getCurrentTotp(const QString& uuid);
|
||||||
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
|
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
|
||||||
bool isPasswordGeneratorRequested() const;
|
bool isPasswordGeneratorRequested() const;
|
||||||
|
bool isUrlIdentical(const QString& first, const QString& second) const;
|
||||||
|
|
||||||
void addEntry(const EntryParameters& entryParameters,
|
void addEntry(const EntryParameters& entryParameters,
|
||||||
const QString& group,
|
const QString& group,
|
||||||
|
@ -160,6 +160,9 @@ void EditEntryWidget::setupMain()
|
|||||||
connect(m_mainUi->fetchFaviconButton, SIGNAL(clicked()), m_iconsWidget, SLOT(downloadFavicon()));
|
connect(m_mainUi->fetchFaviconButton, SIGNAL(clicked()), m_iconsWidget, SLOT(downloadFavicon()));
|
||||||
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
|
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
|
||||||
m_mainUi->urlEdit->enableVerifyMode();
|
m_mainUi->urlEdit->enableVerifyMode();
|
||||||
|
#endif
|
||||||
|
#ifdef WITH_XC_BROWSER
|
||||||
|
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(entryURLEdited(const QString&)));
|
||||||
#endif
|
#endif
|
||||||
connect(m_mainUi->expireCheck, &QCheckBox::toggled, [&](bool enabled) {
|
connect(m_mainUi->expireCheck, &QCheckBox::toggled, [&](bool enabled) {
|
||||||
m_mainUi->expireDatePicker->setEnabled(enabled);
|
m_mainUi->expireDatePicker->setEnabled(enabled);
|
||||||
@ -398,6 +401,11 @@ void EditEntryWidget::updateCurrentURL()
|
|||||||
m_browserUi->removeURLButton->setEnabled(false);
|
m_browserUi->removeURLButton->setEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EditEntryWidget::entryURLEdited(const QString& url)
|
||||||
|
{
|
||||||
|
m_additionalURLsDataModel->setEntryUrl(url);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void EditEntryWidget::setupProperties()
|
void EditEntryWidget::setupProperties()
|
||||||
|
@ -132,6 +132,7 @@ private slots:
|
|||||||
void removeCurrentURL();
|
void removeCurrentURL();
|
||||||
void editCurrentURL();
|
void editCurrentURL();
|
||||||
void updateCurrentURL();
|
void updateCurrentURL();
|
||||||
|
void entryURLEdited(const QString& url);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -54,7 +54,7 @@ void EntryURLModel::setEntryAttributes(EntryAttributes* entryAttributes)
|
|||||||
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
#include <QDebug>
|
|
||||||
QVariant EntryURLModel::data(const QModelIndex& index, int role) const
|
QVariant EntryURLModel::data(const QModelIndex& index, int role) const
|
||||||
{
|
{
|
||||||
if (!index.isValid()) {
|
if (!index.isValid()) {
|
||||||
@ -70,10 +70,11 @@ QVariant EntryURLModel::data(const QModelIndex& index, int role) const
|
|||||||
const auto urlValid = Tools::checkUrlValid(value);
|
const auto urlValid = Tools::checkUrlValid(value);
|
||||||
|
|
||||||
// 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();
|
auto customAttributeKeys = m_entryAttributes->customKeys().filter(BrowserService::ADDITIONAL_URL);
|
||||||
customAttributeKeys.removeOne(key);
|
customAttributeKeys.removeOne(key);
|
||||||
const auto duplicateUrl = m_entryAttributes->values(customAttributeKeys).contains(value) || value == m_entryUrl;
|
|
||||||
|
|
||||||
|
const auto duplicateUrl = m_entryAttributes->values(customAttributeKeys).contains(value)
|
||||||
|
|| browserService()->isUrlIdentical(value, m_entryUrl);
|
||||||
if (role == Qt::BackgroundRole && (!urlValid || duplicateUrl)) {
|
if (role == Qt::BackgroundRole && (!urlValid || duplicateUrl)) {
|
||||||
StateColorPalette statePalette;
|
StateColorPalette statePalette;
|
||||||
return statePalette.color(StateColorPalette::ColorRole::Error);
|
return statePalette.color(StateColorPalette::ColorRole::Error);
|
||||||
|
@ -741,3 +741,19 @@ void TestBrowser::testBestMatchingWithAdditionalURLs()
|
|||||||
QCOMPARE(sorted.length(), 1);
|
QCOMPARE(sorted.length(), 1);
|
||||||
QCOMPARE(sorted[0]->url(), urls[0]);
|
QCOMPARE(sorted[0]->url(), urls[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestBrowser::testIsUrlIdentical()
|
||||||
|
{
|
||||||
|
QVERIFY(browserService()->isUrlIdentical("https://example.com", "https://example.com"));
|
||||||
|
QVERIFY(browserService()->isUrlIdentical("https://example.com", " https://example.com "));
|
||||||
|
QVERIFY(!browserService()->isUrlIdentical("https://example.com", "https://example2.com"));
|
||||||
|
QVERIFY(!browserService()->isUrlIdentical("https://example.com/", "https://example.com/#login"));
|
||||||
|
QVERIFY(browserService()->isUrlIdentical("https://example.com", "https://example.com/"));
|
||||||
|
QVERIFY(browserService()->isUrlIdentical("https://example.com/", "https://example.com"));
|
||||||
|
QVERIFY(browserService()->isUrlIdentical("https://example.com/ ", " https://example.com"));
|
||||||
|
QVERIFY(!browserService()->isUrlIdentical("https://example.com/", " example.com"));
|
||||||
|
QVERIFY(browserService()->isUrlIdentical("https://example.com/path/to/nowhere",
|
||||||
|
"https://example.com/path/to/nowhere/"));
|
||||||
|
QVERIFY(!browserService()->isUrlIdentical("https://example.com/", "://example.com/"));
|
||||||
|
QVERIFY(browserService()->isUrlIdentical("ftp://127.0.0.1/", "ftp://127.0.0.1"));
|
||||||
|
}
|
||||||
|
@ -52,6 +52,7 @@ private slots:
|
|||||||
void testValidURLs();
|
void testValidURLs();
|
||||||
void testBestMatchingCredentials();
|
void testBestMatchingCredentials();
|
||||||
void testBestMatchingWithAdditionalURLs();
|
void testBestMatchingWithAdditionalURLs();
|
||||||
|
void testIsUrlIdentical();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<Entry*> createEntries(QStringList& urls, Group* root) const;
|
QList<Entry*> createEntries(QStringList& urls, Group* root) const;
|
||||||
|
Loading…
Reference in New Issue
Block a user