mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-27 08:19:47 -05:00
Merge branch 'release/2.6.2' into develop
This commit is contained in:
commit
e1c2537084
@ -455,6 +455,8 @@ p{font-family: "Noto Sans",sans-serif !important}
|
|||||||
blockquote{color:var(--quotecolor) !important}
|
blockquote{color:var(--quotecolor) !important}
|
||||||
.quoteblock{color:var(--textcolor)}
|
.quoteblock{color:var(--textcolor)}
|
||||||
code{color:var(--textcoloralt);background-color: var(--sidebarbackground) !important}
|
code{color:var(--textcoloralt);background-color: var(--sidebarbackground) !important}
|
||||||
|
pre,pre>code{line-height:1.25; color:var(--textcoloralt);}
|
||||||
|
.keyseq{color:var(--textcoloralt);}
|
||||||
|
|
||||||
|
|
||||||
/* Table styles */
|
/* Table styles */
|
||||||
|
@ -51,6 +51,8 @@ image::linux_store.png[]
|
|||||||
|
|
||||||
The Snap and Flatpak options are sandboxed applications (more secure). The Native option is installed with the operating system files. Read more about the limitations of these options here: https://keepassxc.org/docs/#faq-appsnap-yubikey[KeePassXC Snap FAQ]
|
The Snap and Flatpak options are sandboxed applications (more secure). The Native option is installed with the operating system files. Read more about the limitations of these options here: https://keepassxc.org/docs/#faq-appsnap-yubikey[KeePassXC Snap FAQ]
|
||||||
|
|
||||||
|
NOTE: KeePassXC stores a configuration file in `~/.cache` to remember window position, recent files, and other local settings. If you mount this folder to a tmpdisk you will lose settings after reboot.
|
||||||
|
|
||||||
=== macOS
|
=== macOS
|
||||||
To install the KeePassXC app on macOS, double click on the downloaded DMG file and use the click and drag option as shown:
|
To install the KeePassXC app on macOS, double click on the downloaded DMG file and use the click and drag option as shown:
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ include::.sharedheader[]
|
|||||||
:imagesdir: ../images
|
:imagesdir: ../images
|
||||||
|
|
||||||
// tag::content[]
|
// tag::content[]
|
||||||
|
NOTE: On macOS please substitute `Ctrl` with `Cmd` (aka `⌘`).
|
||||||
|
|
||||||
[grid=rows, frame=none, width=75%]
|
[grid=rows, frame=none, width=75%]
|
||||||
|===
|
|===
|
||||||
|Action | Keyboard Shortcut
|
|Action | Keyboard Shortcut
|
||||||
@ -31,6 +33,7 @@ include::.sharedheader[]
|
|||||||
|Hide Window | Ctrl + Shift + M
|
|Hide Window | Ctrl + Shift + M
|
||||||
|Select Next Database Tab | Ctrl + Tab ; Ctrl + PageDn
|
|Select Next Database Tab | Ctrl + Tab ; Ctrl + PageDn
|
||||||
|Select Previous Database Tab | Ctrl + Shift + Tab ; Ctrl + PageUp
|
|Select Previous Database Tab | Ctrl + Shift + Tab ; Ctrl + PageUp
|
||||||
|
|Select the nth database | Ctrl + n, where n is the number of the database tab
|
||||||
|Toggle Passwords Hidden | Ctrl + Shift + C
|
|Toggle Passwords Hidden | Ctrl + Shift + C
|
||||||
|Toggle Usernames Hidden | Ctrl + Shift + B
|
|Toggle Usernames Hidden | Ctrl + Shift + B
|
||||||
|Focus Groups (edit if focused) | F1
|
|Focus Groups (edit if focused) | F1
|
||||||
|
@ -48,4 +48,41 @@ image::compact_mode_comparison.png[]
|
|||||||
|
|
||||||
=== Keyboard Shortcuts
|
=== Keyboard Shortcuts
|
||||||
include::KeyboardShortcuts.adoc[tag=content, leveloffset=+1]
|
include::KeyboardShortcuts.adoc[tag=content, leveloffset=+1]
|
||||||
|
|
||||||
|
// tag::advanced[]
|
||||||
|
=== Command-Line Options
|
||||||
|
You can use the following command line options to tailor the application to your preferences:
|
||||||
|
|
||||||
|
----
|
||||||
|
Usage: keepassxc.exe [options] [filename(s)]
|
||||||
|
KeePassXC - cross-platform password manager
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-?, -h, --help Displays help on commandline options.
|
||||||
|
--help-all Displays help including Qt specific options.
|
||||||
|
-v, --version Displays version information.
|
||||||
|
--config <config> path to a custom config file
|
||||||
|
--localconfig <localconfig> path to a custom local config file
|
||||||
|
--keyfile <keyfile> key file of the database
|
||||||
|
--pw-stdin read password of the database from stdin
|
||||||
|
--debug-info Displays debugging information.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
filename(s) filenames of the password databases to open (*.kdbx)
|
||||||
|
----
|
||||||
|
|
||||||
|
Additionally, the following environment variables may be useful when running the application:
|
||||||
|
|
||||||
|
[grid=rows, frame=none, width=75%]
|
||||||
|
|===
|
||||||
|
|Env Var | Description
|
||||||
|
|
||||||
|
|KPXC_CONFIG | Override default path to roaming configuration file
|
||||||
|
|KPXC_CONFIG_LOCAL | Override default path to local configuration file
|
||||||
|
|SSH_AUTH_SOCKET | Path of the unix file socket that the agent uses for communication with other processes (SSH Agent)
|
||||||
|
|QT_SCALE_FACTOR [numeric] | Defines a global scale factor for the whole application, including point-sized fonts.
|
||||||
|
|QT_SCREEN_SCALE_FACTORS [list] | Specifies scale factors for each screen. See https://doc.qt.io/qt-5/highdpi.html#high-dpi-support-in-qt
|
||||||
|
|QT_SCALE_FACTOR_ROUNDING_POLICY | Control device pixel ratio rounding to the nearest integer. See https://doc.qt.io/qt-5/highdpi.html#high-dpi-support-in-qt
|
||||||
|
|===
|
||||||
|
// end::advanced[]
|
||||||
// end::content[]
|
// end::content[]
|
||||||
|
@ -267,8 +267,8 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
|
|||||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString url = decrypted.value("url").toString();
|
const QString siteUrl = decrypted.value("url").toString();
|
||||||
if (url.isEmpty()) {
|
if (siteUrl.isEmpty()) {
|
||||||
return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
|
return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,10 +281,10 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
|
|||||||
}
|
}
|
||||||
|
|
||||||
const QString id = decrypted.value("id").toString();
|
const QString id = decrypted.value("id").toString();
|
||||||
const QString submit = decrypted.value("submitUrl").toString();
|
const QString formUrl = decrypted.value("submitUrl").toString();
|
||||||
const QString auth = decrypted.value("httpAuth").toString();
|
const QString auth = decrypted.value("httpAuth").toString();
|
||||||
const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0 ? true : false;
|
const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0 ? true : false;
|
||||||
const QJsonArray users = browserService()->findMatchingEntries(id, url, submit, "", keyList, httpAuth);
|
const QJsonArray users = browserService()->findMatchingEntries(id, siteUrl, formUrl, "", keyList, httpAuth);
|
||||||
|
|
||||||
if (users.isEmpty()) {
|
if (users.isEmpty()) {
|
||||||
return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND);
|
return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND);
|
||||||
|
@ -371,8 +371,8 @@ QString BrowserService::getKey(const QString& id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
||||||
const QString& url,
|
const QString& siteUrlStr,
|
||||||
const QString& submitUrl,
|
const QString& formUrlStr,
|
||||||
const QString& realm,
|
const QString& realm,
|
||||||
const StringPairList& keyList,
|
const StringPairList& keyList,
|
||||||
const bool httpAuth)
|
const bool httpAuth)
|
||||||
@ -380,13 +380,13 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
|||||||
Q_UNUSED(dbid);
|
Q_UNUSED(dbid);
|
||||||
const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess();
|
const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess();
|
||||||
const bool ignoreHttpAuth = browserSettings()->httpAuthPermission();
|
const bool ignoreHttpAuth = browserSettings()->httpAuthPermission();
|
||||||
const QString host = QUrl(url).host();
|
const QString siteHost = QUrl(siteUrlStr).host();
|
||||||
const QString submitHost = QUrl(submitUrl).host();
|
const QString formHost = QUrl(formUrlStr).host();
|
||||||
|
|
||||||
// Check entries for authorization
|
// Check entries for authorization
|
||||||
QList<Entry*> pwEntriesToConfirm;
|
QList<Entry*> pwEntriesToConfirm;
|
||||||
QList<Entry*> pwEntries;
|
QList<Entry*> pwEntries;
|
||||||
for (auto* entry : searchEntries(url, submitUrl, keyList)) {
|
for (auto* entry : searchEntries(siteUrlStr, formUrlStr, keyList)) {
|
||||||
if (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY)
|
if (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY)
|
||||||
&& entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == TRUE_STR) {
|
&& entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == TRUE_STR) {
|
||||||
continue;
|
continue;
|
||||||
@ -403,7 +403,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (checkAccess(entry, host, submitHost, realm)) {
|
switch (checkAccess(entry, siteHost, formHost, realm)) {
|
||||||
case Denied:
|
case Denied:
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -422,7 +422,8 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Confirm entries
|
// Confirm entries
|
||||||
QList<Entry*> selectedEntriesToConfirm = confirmEntries(pwEntriesToConfirm, url, host, submitHost, realm, httpAuth);
|
QList<Entry*> selectedEntriesToConfirm =
|
||||||
|
confirmEntries(pwEntriesToConfirm, siteUrlStr, siteHost, formHost, realm, httpAuth);
|
||||||
if (!selectedEntriesToConfirm.isEmpty()) {
|
if (!selectedEntriesToConfirm.isEmpty()) {
|
||||||
pwEntries.append(selectedEntriesToConfirm);
|
pwEntries.append(selectedEntriesToConfirm);
|
||||||
}
|
}
|
||||||
@ -437,7 +438,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort results
|
// Sort results
|
||||||
pwEntries = sortEntries(pwEntries, host, submitUrl, url);
|
pwEntries = sortEntries(pwEntries, siteUrlStr, formUrlStr);
|
||||||
|
|
||||||
// Fill the list
|
// Fill the list
|
||||||
QJsonArray result;
|
QJsonArray result;
|
||||||
@ -451,8 +452,8 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
|||||||
void BrowserService::addEntry(const QString& dbid,
|
void BrowserService::addEntry(const QString& dbid,
|
||||||
const QString& login,
|
const QString& login,
|
||||||
const QString& password,
|
const QString& password,
|
||||||
const QString& url,
|
const QString& siteUrlStr,
|
||||||
const QString& submitUrl,
|
const QString& formUrlStr,
|
||||||
const QString& realm,
|
const QString& realm,
|
||||||
const QString& group,
|
const QString& group,
|
||||||
const QString& groupUuid,
|
const QString& groupUuid,
|
||||||
@ -467,8 +468,8 @@ void BrowserService::addEntry(const QString& dbid,
|
|||||||
|
|
||||||
auto* entry = new Entry();
|
auto* entry = new Entry();
|
||||||
entry->setUuid(QUuid::createUuid());
|
entry->setUuid(QUuid::createUuid());
|
||||||
entry->setTitle(QUrl(url).host());
|
entry->setTitle(QUrl(siteUrlStr).host());
|
||||||
entry->setUrl(url);
|
entry->setUrl(siteUrlStr);
|
||||||
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
||||||
entry->setUsername(login);
|
entry->setUsername(login);
|
||||||
entry->setPassword(password);
|
entry->setPassword(password);
|
||||||
@ -487,8 +488,8 @@ void BrowserService::addEntry(const QString& dbid,
|
|||||||
entry->setGroup(getDefaultEntryGroup(db));
|
entry->setGroup(getDefaultEntryGroup(db));
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString host = QUrl(url).host();
|
const QString host = QUrl(siteUrlStr).host();
|
||||||
const QString submitHost = QUrl(submitUrl).host();
|
const QString submitHost = QUrl(formUrlStr).host();
|
||||||
BrowserEntryConfig config;
|
BrowserEntryConfig config;
|
||||||
config.allow(host);
|
config.allow(host);
|
||||||
|
|
||||||
@ -505,8 +506,8 @@ bool BrowserService::updateEntry(const QString& dbid,
|
|||||||
const QString& uuid,
|
const QString& uuid,
|
||||||
const QString& login,
|
const QString& login,
|
||||||
const QString& password,
|
const QString& password,
|
||||||
const QString& url,
|
const QString& siteUrlStr,
|
||||||
const QString& submitUrl)
|
const QString& formUrlStr)
|
||||||
{
|
{
|
||||||
// TODO: select database based on this key id
|
// TODO: select database based on this key id
|
||||||
Q_UNUSED(dbid);
|
Q_UNUSED(dbid);
|
||||||
@ -518,7 +519,7 @@ bool BrowserService::updateEntry(const QString& dbid,
|
|||||||
Entry* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
|
Entry* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
// If entry is not found for update, add a new one to the selected database
|
// If entry is not found for update, add a new one to the selected database
|
||||||
addEntry(dbid, login, password, url, submitUrl, "", "", "", db);
|
addEntry(dbid, login, password, siteUrlStr, formUrlStr, "", "", "", db);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,7 +548,7 @@ bool BrowserService::updateEntry(const QString& dbid,
|
|||||||
dialogResult = MessageBox::question(
|
dialogResult = MessageBox::question(
|
||||||
nullptr,
|
nullptr,
|
||||||
tr("KeePassXC: Update Entry"),
|
tr("KeePassXC: Update Entry"),
|
||||||
tr("Do you want to update the information in %1 - %2?").arg(QUrl(url).host(), username),
|
tr("Do you want to update the information in %1 - %2?").arg(QUrl(siteUrlStr).host(), username),
|
||||||
MessageBox::Save | MessageBox::Cancel,
|
MessageBox::Save | MessageBox::Cancel,
|
||||||
MessageBox::Cancel,
|
MessageBox::Cancel,
|
||||||
MessageBox::Raise);
|
MessageBox::Raise);
|
||||||
@ -570,7 +571,7 @@ bool BrowserService::updateEntry(const QString& dbid,
|
|||||||
}
|
}
|
||||||
|
|
||||||
QList<Entry*>
|
QList<Entry*>
|
||||||
BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString& url, const QString& submitUrl)
|
BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString& siteUrlStr, const QString& formUrlStr)
|
||||||
{
|
{
|
||||||
QList<Entry*> entries;
|
QList<Entry*> entries;
|
||||||
auto* rootGroup = db->rootGroup();
|
auto* rootGroup = db->rootGroup();
|
||||||
@ -590,25 +591,29 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
|
|||||||
|
|
||||||
// Search for additional URL's starting with KP2A_URL
|
// Search for additional URL's starting with KP2A_URL
|
||||||
for (const auto& key : entry->attributes()->keys()) {
|
for (const auto& key : entry->attributes()->keys()) {
|
||||||
if (key.startsWith(ADDITIONAL_URL) && handleURL(entry->attributes()->value(key), url, submitUrl)
|
if (key.startsWith(ADDITIONAL_URL) && handleURL(entry->attributes()->value(key), siteUrlStr, formUrlStr)
|
||||||
&& !entries.contains(entry)) {
|
&& !entries.contains(entry)) {
|
||||||
entries.append(entry);
|
entries.append(entry);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!handleEntry(entry, url, submitUrl)) {
|
if (!handleEntry(entry, siteUrlStr, formUrlStr)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional URL check may have already inserted the entry to the list
|
||||||
|
if (!entries.contains(entry)) {
|
||||||
entries.append(entry);
|
entries.append(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<Entry*> BrowserService::searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList)
|
QList<Entry*>
|
||||||
|
BrowserService::searchEntries(const QString& siteUrlStr, const QString& formUrlStr, const StringPairList& keyList)
|
||||||
{
|
{
|
||||||
// Check if database is connected with KeePassXC-Browser
|
// Check if database is connected with KeePassXC-Browser
|
||||||
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
|
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
|
||||||
@ -638,11 +643,11 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const QString& s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search entries matching the hostname
|
// Search entries matching the hostname
|
||||||
QString hostname = QUrl(url).host();
|
QString hostname = QUrl(siteUrlStr).host();
|
||||||
QList<Entry*> entries;
|
QList<Entry*> entries;
|
||||||
do {
|
do {
|
||||||
for (const auto& db : databases) {
|
for (const auto& db : databases) {
|
||||||
entries << searchEntries(db, url, submitUrl);
|
entries << searchEntries(db, siteUrlStr, formUrlStr);
|
||||||
}
|
}
|
||||||
} while (entries.isEmpty() && removeFirstDomain(hostname));
|
} while (entries.isEmpty() && removeFirstDomain(hostname));
|
||||||
|
|
||||||
@ -722,57 +727,40 @@ void BrowserService::convertAttributesToCustomData(QSharedPointer<Database> db)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries,
|
QList<Entry*>
|
||||||
const QString& host,
|
BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& siteUrlStr, const QString& formUrlStr)
|
||||||
const QString& entryUrl,
|
|
||||||
const QString& fullUrl)
|
|
||||||
{
|
{
|
||||||
QUrl url(entryUrl);
|
|
||||||
if (url.scheme().isEmpty()) {
|
|
||||||
url.setScheme("https");
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString submitUrl = url.toString(QUrl::StripTrailingSlash);
|
|
||||||
const QString baseSubmitUrl =
|
|
||||||
url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
|
||||||
|
|
||||||
// Build map of prioritized entries
|
// Build map of prioritized entries
|
||||||
QMultiMap<int, Entry*> priorities;
|
QMultiMap<int, Entry*> priorities;
|
||||||
for (auto* entry : pwEntries) {
|
for (auto* entry : pwEntries) {
|
||||||
priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl, fullUrl), entry);
|
priorities.insert(sortPriority(getEntryURLs(entry), siteUrlStr, formUrlStr), entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto keys = priorities.uniqueKeys();
|
||||||
|
std::sort(keys.begin(), keys.end(), [](int l, int r) { return l > r; });
|
||||||
|
|
||||||
QList<Entry*> results;
|
QList<Entry*> results;
|
||||||
QString field = browserSettings()->sortByTitle() ? "Title" : "UserName";
|
auto sortField = browserSettings()->sortByTitle() ? EntryAttributes::TitleKey : EntryAttributes::UserNameKey;
|
||||||
for (int i = 100; i >= 0; i -= 5) {
|
for (auto key : keys) {
|
||||||
if (priorities.count(i) > 0) {
|
|
||||||
// Sort same priority entries by Title or UserName
|
// Sort same priority entries by Title or UserName
|
||||||
auto entries = priorities.values(i);
|
auto entries = priorities.values(key);
|
||||||
std::sort(entries.begin(), entries.end(), [&field](Entry* left, Entry* right) {
|
std::sort(entries.begin(), entries.end(), [&sortField](Entry* left, Entry* right) {
|
||||||
return (QString::localeAwareCompare(left->attributes()->value(field), right->attributes()->value(field))
|
return QString::localeAwareCompare(left->attribute(sortField), right->attribute(sortField));
|
||||||
< 0)
|
|
||||||
|| ((QString::localeAwareCompare(left->attributes()->value(field),
|
|
||||||
right->attributes()->value(field))
|
|
||||||
== 0)
|
|
||||||
&& (QString::localeAwareCompare(left->attributes()->value("UserName"),
|
|
||||||
right->attributes()->value("UserName"))
|
|
||||||
< 0));
|
|
||||||
});
|
});
|
||||||
results << entries;
|
results << entries;
|
||||||
if (browserSettings()->bestMatchOnly() && !pwEntries.isEmpty()) {
|
if (browserSettings()->bestMatchOnly() && !results.isEmpty()) {
|
||||||
// Early out once we find the highest batch of matches
|
// Early out once we find the highest batch of matches
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||||
const QString& url,
|
const QString& siteUrlStr,
|
||||||
const QString& host,
|
const QString& siteHost,
|
||||||
const QString& submitHost,
|
const QString& formUrlStr,
|
||||||
const QString& realm,
|
const QString& realm,
|
||||||
const bool httpAuth)
|
const bool httpAuth)
|
||||||
{
|
{
|
||||||
@ -790,9 +778,9 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
|||||||
auto entry = pwEntriesToConfirm[item->row()];
|
auto entry = pwEntriesToConfirm[item->row()];
|
||||||
BrowserEntryConfig config;
|
BrowserEntryConfig config;
|
||||||
config.load(entry);
|
config.load(entry);
|
||||||
config.deny(host);
|
config.deny(siteHost);
|
||||||
if (!submitHost.isEmpty() && host != submitHost) {
|
if (!formUrlStr.isEmpty() && siteHost != formUrlStr) {
|
||||||
config.deny(submitHost);
|
config.deny(formUrlStr);
|
||||||
}
|
}
|
||||||
if (!realm.isEmpty()) {
|
if (!realm.isEmpty()) {
|
||||||
config.setRealm(realm);
|
config.setRealm(realm);
|
||||||
@ -800,7 +788,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
|||||||
config.save(entry);
|
config.save(entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
accessControlDialog.setItems(pwEntriesToConfirm, url, httpAuth);
|
accessControlDialog.setItems(pwEntriesToConfirm, siteUrlStr, httpAuth);
|
||||||
|
|
||||||
QList<Entry*> allowedEntries;
|
QList<Entry*> allowedEntries;
|
||||||
if (accessControlDialog.exec() == QDialog::Accepted) {
|
if (accessControlDialog.exec() == QDialog::Accepted) {
|
||||||
@ -810,9 +798,9 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
|||||||
if (accessControlDialog.remember()) {
|
if (accessControlDialog.remember()) {
|
||||||
BrowserEntryConfig config;
|
BrowserEntryConfig config;
|
||||||
config.load(entry);
|
config.load(entry);
|
||||||
config.allow(host);
|
config.allow(siteHost);
|
||||||
if (!submitHost.isEmpty() && host != submitHost) {
|
if (!formUrlStr.isEmpty() && siteHost != formUrlStr) {
|
||||||
config.allow(submitHost);
|
config.allow(formUrlStr);
|
||||||
}
|
}
|
||||||
if (!realm.isEmpty()) {
|
if (!realm.isEmpty()) {
|
||||||
config.setRealm(realm);
|
config.setRealm(realm);
|
||||||
@ -871,7 +859,7 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
|||||||
}
|
}
|
||||||
|
|
||||||
BrowserService::Access
|
BrowserService::Access
|
||||||
BrowserService::checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm)
|
BrowserService::checkAccess(const Entry* entry, const QString& siteHost, const QString& formHost, const QString& realm)
|
||||||
{
|
{
|
||||||
if (entry->isExpired()) {
|
if (entry->isExpired()) {
|
||||||
return browserSettings()->allowExpiredCredentials() ? Allowed : Denied;
|
return browserSettings()->allowExpiredCredentials() ? Allowed : Denied;
|
||||||
@ -881,10 +869,10 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri
|
|||||||
if (!config.load(entry)) {
|
if (!config.load(entry)) {
|
||||||
return Unknown;
|
return Unknown;
|
||||||
}
|
}
|
||||||
if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) {
|
if ((config.isAllowed(siteHost)) && (formHost.isEmpty() || config.isAllowed(formHost))) {
|
||||||
return Allowed;
|
return Allowed;
|
||||||
}
|
}
|
||||||
if ((config.isDenied(host)) || (!submitHost.isEmpty() && config.isDenied(submitHost))) {
|
if ((config.isDenied(siteHost)) || (!formHost.isEmpty() && config.isDenied(formHost))) {
|
||||||
return Denied;
|
return Denied;
|
||||||
}
|
}
|
||||||
if (!realm.isEmpty() && config.realm() != realm) {
|
if (!realm.isEmpty() && config.realm() != realm) {
|
||||||
@ -919,66 +907,72 @@ Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& sele
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
int BrowserService::sortPriority(const Entry* entry,
|
// Returns the maximum sort priority given a set of match urls and the
|
||||||
const QString& host,
|
// extension provided site and form url.
|
||||||
const QString& submitUrl,
|
int BrowserService::sortPriority(const QStringList& urls, const QString& siteUrlStr, const QString& formUrlStr)
|
||||||
const QString& baseSubmitUrl,
|
|
||||||
const QString& fullUrl) const
|
|
||||||
{
|
{
|
||||||
QUrl url(entry->url());
|
QList<int> priorityList;
|
||||||
if (url.scheme().isEmpty()) {
|
// NOTE: QUrl::matches is utterly broken in Qt < 5.11, so we work around that
|
||||||
|
// by removing parts of the url that we don't match and direct matching others
|
||||||
|
const auto stdOpts = QUrl::RemoveFragment | QUrl::RemoveUserInfo;
|
||||||
|
const auto siteUrl = QUrl(siteUrlStr).adjusted(stdOpts);
|
||||||
|
const auto formUrl = QUrl(formUrlStr).adjusted(stdOpts);
|
||||||
|
|
||||||
|
auto getPriority = [&](const QString& givenUrl) {
|
||||||
|
auto url = QUrl::fromUserInput(givenUrl).adjusted(stdOpts);
|
||||||
|
|
||||||
|
// Default to https scheme if undefined
|
||||||
|
if (url.scheme().isEmpty() || !givenUrl.contains("://")) {
|
||||||
url.setScheme("https");
|
url.setScheme("https");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the empty path to the URL if it's missing
|
// Add the empty path to the URL if it's missing.
|
||||||
|
// URL's from the extension always have a path set, entry URL's can be without.
|
||||||
if (url.path().isEmpty() && !url.hasFragment() && !url.hasQuery()) {
|
if (url.path().isEmpty() && !url.hasFragment() && !url.hasQuery()) {
|
||||||
url.setPath("/");
|
url.setPath("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString entryURL = url.toString(QUrl::StripTrailingSlash);
|
// Reject invalid urls and hosts, except 'localhost', and scheme mismatch
|
||||||
const QString baseEntryURL =
|
if (!url.isValid() || (!url.host().contains(".") && url.host() != "localhost")
|
||||||
url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
|| url.scheme() != siteUrl.scheme()) {
|
||||||
|
|
||||||
if (!url.host().contains(".") && url.host() != "localhost") {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (fullUrl == entryURL) {
|
|
||||||
|
// Exact match with site url or form url
|
||||||
|
if (url.matches(siteUrl, QUrl::None) || url.matches(formUrl, QUrl::None)) {
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
if (submitUrl == entryURL) {
|
|
||||||
return 95;
|
// Exact match without the query string
|
||||||
}
|
if (url.matches(siteUrl, QUrl::RemoveQuery) || url.matches(formUrl, QUrl::RemoveQuery)) {
|
||||||
if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL) {
|
|
||||||
return 90;
|
return 90;
|
||||||
}
|
}
|
||||||
if (submitUrl.startsWith(baseEntryURL) && entryURL != host && baseSubmitUrl != baseEntryURL) {
|
|
||||||
|
// Match without path (ie, FQDN match), form url prioritizes lower than site url
|
||||||
|
if (url.host() == siteUrl.host()) {
|
||||||
return 80;
|
return 80;
|
||||||
}
|
}
|
||||||
if (entryURL == host) {
|
if (url.host() == formUrl.host()) {
|
||||||
return 70;
|
return 70;
|
||||||
}
|
}
|
||||||
if (entryURL == baseSubmitUrl) {
|
|
||||||
|
// Site/form url ends with given url (subdomain mismatch)
|
||||||
|
if (siteUrl.host().endsWith(url.host())) {
|
||||||
return 60;
|
return 60;
|
||||||
}
|
}
|
||||||
if (entryURL.startsWith(submitUrl)) {
|
if (formUrl.host().endsWith(url.host())) {
|
||||||
return 50;
|
return 50;
|
||||||
}
|
}
|
||||||
if (entryURL.startsWith(baseSubmitUrl) && baseSubmitUrl != host) {
|
|
||||||
return 40;
|
// No valid match found
|
||||||
}
|
|
||||||
if (submitUrl.startsWith(entryURL)) {
|
|
||||||
return 30;
|
|
||||||
}
|
|
||||||
if (submitUrl.startsWith(baseEntryURL)) {
|
|
||||||
return 20;
|
|
||||||
}
|
|
||||||
if (entryURL.startsWith(host)) {
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
if (host.startsWith(entryURL)) {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& entryUrl : urls) {
|
||||||
|
priorityList << getPriority(entryUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *std::max_element(priorityList.begin(), priorityList.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BrowserService::schemeFound(const QString& url)
|
bool BrowserService::schemeFound(const QString& url)
|
||||||
@ -1015,7 +1009,7 @@ bool BrowserService::handleEntry(Entry* entry, const QString& url, const QString
|
|||||||
return handleURL(entry->url(), url, submitUrl);
|
return handleURL(entry->url(), url, submitUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BrowserService::handleURL(const QString& entryUrl, const QString& url, const QString& submitUrl)
|
bool BrowserService::handleURL(const QString& entryUrl, const QString& siteUrlStr, const QString& formUrlStr)
|
||||||
{
|
{
|
||||||
if (entryUrl.isEmpty()) {
|
if (entryUrl.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@ -1033,8 +1027,8 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& url, cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make a direct compare if a local file is used
|
// Make a direct compare if a local file is used
|
||||||
if (url.startsWith("file://")) {
|
if (siteUrlStr.startsWith("file://")) {
|
||||||
return entryUrl == submitUrl;
|
return entryUrl == formUrlStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL host validation fails
|
// URL host validation fails
|
||||||
@ -1043,7 +1037,7 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& url, cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Match port, if used
|
// Match port, if used
|
||||||
QUrl siteQUrl(url);
|
QUrl siteQUrl(siteUrlStr);
|
||||||
if (entryQUrl.port() > 0 && entryQUrl.port() != siteQUrl.port()) {
|
if (entryQUrl.port() > 0 && entryQUrl.port() != siteQUrl.port()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1067,19 +1061,9 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& url, cons
|
|||||||
|
|
||||||
// Match the subdomains with the limited wildcard
|
// Match the subdomains with the limited wildcard
|
||||||
if (siteQUrl.host().endsWith(entryQUrl.host())) {
|
if (siteQUrl.host().endsWith(entryQUrl.host())) {
|
||||||
if (!browserSettings()->bestMatchOnly()) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match the exact subdomain and path, or start of the path when entry's path is longer than plain "/"
|
|
||||||
if (siteQUrl.host() == entryQUrl.host()) {
|
|
||||||
if (siteQUrl.path() == entryQUrl.path()
|
|
||||||
|| (entryQUrl.path().size() > 1 && siteQUrl.path().startsWith(entryQUrl.path()))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1223,6 +1207,21 @@ bool BrowserService::checkLegacySettings(QSharedPointer<Database> db)
|
|||||||
return dialogResult == MessageBox::Yes;
|
return dialogResult == MessageBox::Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList BrowserService::getEntryURLs(const Entry* entry)
|
||||||
|
{
|
||||||
|
QStringList urlList;
|
||||||
|
urlList << entry->url();
|
||||||
|
|
||||||
|
// Handle additional URL's
|
||||||
|
for (const auto& key : entry->attributes()->keys()) {
|
||||||
|
if (key.startsWith(ADDITIONAL_URL)) {
|
||||||
|
urlList << entry->attributes()->value(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlList;
|
||||||
|
}
|
||||||
|
|
||||||
void BrowserService::hideWindow() const
|
void BrowserService::hideWindow() const
|
||||||
{
|
{
|
||||||
if (m_prevWindowState == WindowState::Minimized) {
|
if (m_prevWindowState == WindowState::Minimized) {
|
||||||
|
@ -63,8 +63,8 @@ public:
|
|||||||
void addEntry(const QString& dbid,
|
void addEntry(const QString& dbid,
|
||||||
const QString& login,
|
const QString& login,
|
||||||
const QString& password,
|
const QString& password,
|
||||||
const QString& url,
|
const QString& siteUrlStr,
|
||||||
const QString& submitUrl,
|
const QString& formUrlStr,
|
||||||
const QString& realm,
|
const QString& realm,
|
||||||
const QString& group,
|
const QString& group,
|
||||||
const QString& groupUuid,
|
const QString& groupUuid,
|
||||||
@ -73,12 +73,12 @@ public:
|
|||||||
const QString& uuid,
|
const QString& uuid,
|
||||||
const QString& login,
|
const QString& login,
|
||||||
const QString& password,
|
const QString& password,
|
||||||
const QString& url,
|
const QString& siteUrlStr,
|
||||||
const QString& submitUrl);
|
const QString& formUrlStr);
|
||||||
|
|
||||||
QJsonArray findMatchingEntries(const QString& dbid,
|
QJsonArray findMatchingEntries(const QString& dbid,
|
||||||
const QString& url,
|
const QString& siteUrlStr,
|
||||||
const QString& submitUrl,
|
const QString& formUrlStr,
|
||||||
const QString& realm,
|
const QString& realm,
|
||||||
const StringPairList& keyList,
|
const StringPairList& keyList,
|
||||||
const bool httpAuth = false);
|
const bool httpAuth = false);
|
||||||
@ -118,36 +118,32 @@ private:
|
|||||||
Hidden
|
Hidden
|
||||||
};
|
};
|
||||||
|
|
||||||
QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& url, const QString& submitUrl);
|
|
||||||
QList<Entry*> searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList);
|
|
||||||
QList<Entry*>
|
QList<Entry*>
|
||||||
sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl, const QString& fullUrl);
|
searchEntries(const QSharedPointer<Database>& db, const QString& siteUrlStr, const QString& formUrlStr);
|
||||||
|
QList<Entry*> searchEntries(const QString& siteUrlStr, const QString& formUrlStr, const StringPairList& keyList);
|
||||||
|
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& siteUrlStr, const QString& formUrlStr);
|
||||||
QList<Entry*> confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
QList<Entry*> confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||||
const QString& url,
|
const QString& siteUrlStr,
|
||||||
const QString& host,
|
const QString& siteHost,
|
||||||
const QString& submitUrl,
|
const QString& formUrlStr,
|
||||||
const QString& realm,
|
const QString& realm,
|
||||||
const bool httpAuth);
|
const bool httpAuth);
|
||||||
QJsonObject prepareEntry(const Entry* entry);
|
QJsonObject prepareEntry(const Entry* entry);
|
||||||
QJsonArray getChildrenFromGroup(Group* group);
|
QJsonArray getChildrenFromGroup(Group* group);
|
||||||
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
|
Access checkAccess(const Entry* entry, const QString& siteHost, const QString& formHost, const QString& realm);
|
||||||
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
|
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
|
||||||
int sortPriority(const Entry* entry,
|
int sortPriority(const QStringList& urls, const QString& siteUrlStr, const QString& formUrlStr);
|
||||||
const QString& host,
|
|
||||||
const QString& submitUrl,
|
|
||||||
const QString& baseSubmitUrl,
|
|
||||||
const QString& fullUrl) const;
|
|
||||||
bool schemeFound(const QString& url);
|
bool schemeFound(const QString& url);
|
||||||
bool removeFirstDomain(QString& hostname);
|
bool removeFirstDomain(QString& hostname);
|
||||||
bool handleEntry(Entry* entry, const QString& url, const QString& submitUrl);
|
bool handleEntry(Entry* entry, const QString& url, const QString& submitUrl);
|
||||||
bool handleURL(const QString& entryUrl, const QString& url, const QString& submitUrl);
|
bool handleURL(const QString& entryUrl, const QString& siteUrlStr, const QString& formUrlStr);
|
||||||
QString baseDomain(const QString& hostname) const;
|
QString baseDomain(const QString& hostname) const;
|
||||||
QSharedPointer<Database> getDatabase();
|
QSharedPointer<Database> getDatabase();
|
||||||
QSharedPointer<Database> selectedDatabase();
|
QSharedPointer<Database> selectedDatabase();
|
||||||
QString getDatabaseRootUuid();
|
QString getDatabaseRootUuid();
|
||||||
QString getDatabaseRecycleBinUuid();
|
QString getDatabaseRecycleBinUuid();
|
||||||
|
|
||||||
bool checkLegacySettings(QSharedPointer<Database> db);
|
bool checkLegacySettings(QSharedPointer<Database> db);
|
||||||
|
QStringList getEntryURLs(const Entry* entry);
|
||||||
|
|
||||||
void hideWindow() const;
|
void hideWindow() const;
|
||||||
void raiseWindow(const bool force = false);
|
void raiseWindow(const bool force = false);
|
||||||
|
@ -158,7 +158,7 @@ void enterInteractiveMode(const QStringList& arguments)
|
|||||||
|
|
||||||
auto cmd = Commands::getCommand(args[0]);
|
auto cmd = Commands::getCommand(args[0]);
|
||||||
if (!cmd) {
|
if (!cmd) {
|
||||||
err << QObject::tr("Unknown command %1").arg(args[0]) << "\n";
|
err << QObject::tr("Unknown command %1").arg(args[0]) << endl;
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd->name == "quit" || cmd->name == "exit") {
|
} else if (cmd->name == "quit" || cmd->name == "exit") {
|
||||||
break;
|
break;
|
||||||
@ -167,6 +167,7 @@ void enterInteractiveMode(const QStringList& arguments)
|
|||||||
cmd->currentDatabase = currentDatabase;
|
cmd->currentDatabase = currentDatabase;
|
||||||
cmd->execute(args);
|
cmd->execute(args);
|
||||||
currentDatabase = cmd->currentDatabase;
|
currentDatabase = cmd->currentDatabase;
|
||||||
|
cmd->currentDatabase.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentDatabase) {
|
if (currentDatabase) {
|
||||||
@ -246,6 +247,10 @@ int main(int argc, char** argv)
|
|||||||
arguments.removeFirst();
|
arguments.removeFirst();
|
||||||
int exitCode = command->execute(arguments);
|
int exitCode = command->execute(arguments);
|
||||||
|
|
||||||
|
if (command->currentDatabase) {
|
||||||
|
command->currentDatabase.reset();
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(WITH_ASAN) && defined(WITH_LSAN)
|
#if defined(WITH_ASAN) && defined(WITH_LSAN)
|
||||||
// do leak check here to prevent massive tail of end-of-process leak errors from third-party libraries
|
// do leak check here to prevent massive tail of end-of-process leak errors from third-party libraries
|
||||||
__lsan_do_leak_check();
|
__lsan_do_leak_check();
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QProcessEnvironment>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
@ -419,49 +420,17 @@ void Config::migrate()
|
|||||||
sync();
|
sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
Config::Config(const QString& fileName, QObject* parent)
|
Config::Config(const QString& configFileName, const QString& localConfigFileName, QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
init(fileName);
|
init(configFileName, localConfigFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Config::Config(QObject* parent)
|
Config::Config(QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
// Check if we are running in portable mode, if so store the config files local to the app
|
auto configFiles = defaultConfigFiles();
|
||||||
auto portablePath = QCoreApplication::applicationDirPath().append("/%1");
|
init(configFiles.first, configFiles.second);
|
||||||
if (QFile::exists(portablePath.arg(".portable"))) {
|
|
||||||
init(portablePath.arg("config/keepassxc.ini"), portablePath.arg("config/keepassxc_local.ini"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString configPath;
|
|
||||||
QString localConfigPath;
|
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
|
||||||
configPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
|
||||||
localConfigPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
|
||||||
#elif defined(Q_OS_MACOS)
|
|
||||||
configPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
|
||||||
localConfigPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
|
||||||
#else
|
|
||||||
// On case-sensitive Operating Systems, force use of lowercase app directories
|
|
||||||
configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/keepassxc";
|
|
||||||
localConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/keepassxc";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
configPath += "/keepassxc";
|
|
||||||
localConfigPath += "/keepassxc";
|
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
|
||||||
configPath += "_debug";
|
|
||||||
localConfigPath += "_debug";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
configPath += ".ini";
|
|
||||||
localConfigPath += ".ini";
|
|
||||||
|
|
||||||
init(QDir::toNativeSeparators(configPath), QDir::toNativeSeparators(localConfigPath));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Config::~Config()
|
Config::~Config()
|
||||||
@ -489,6 +458,45 @@ void Config::init(const QString& configFileName, const QString& localConfigFileN
|
|||||||
connect(qApp, &QCoreApplication::aboutToQuit, this, &Config::sync);
|
connect(qApp, &QCoreApplication::aboutToQuit, this, &Config::sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPair<QString, QString> Config::defaultConfigFiles()
|
||||||
|
{
|
||||||
|
// Check if we are running in portable mode, if so store the config files local to the app
|
||||||
|
auto portablePath = QCoreApplication::applicationDirPath().append("/%1");
|
||||||
|
if (QFile::exists(portablePath.arg(".portable"))) {
|
||||||
|
return {portablePath.arg("config/keepassxc.ini"), portablePath.arg("config/keepassxc_local.ini")};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString configPath;
|
||||||
|
QString localConfigPath;
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
configPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
localConfigPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
configPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
localConfigPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
||||||
|
#else
|
||||||
|
// On case-sensitive Operating Systems, force use of lowercase app directories
|
||||||
|
configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/keepassxc";
|
||||||
|
localConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/keepassxc";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString suffix;
|
||||||
|
#ifdef QT_DEBUG
|
||||||
|
suffix = "_debug";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
configPath += QString("/keepassxc%1.ini").arg(suffix);
|
||||||
|
localConfigPath += QString("/keepassxc%1.ini").arg(suffix);
|
||||||
|
|
||||||
|
// Allow overriding the default location with env vars
|
||||||
|
const auto& env = QProcessEnvironment::systemEnvironment();
|
||||||
|
configPath = env.value("KPXC_CONFIG", configPath);
|
||||||
|
localConfigPath = env.value("KPXC_CONFIG_LOCAL", localConfigPath);
|
||||||
|
|
||||||
|
return {QDir::toNativeSeparators(configPath), QDir::toNativeSeparators(localConfigPath)};
|
||||||
|
}
|
||||||
|
|
||||||
Config* Config::instance()
|
Config* Config::instance()
|
||||||
{
|
{
|
||||||
if (!m_instance) {
|
if (!m_instance) {
|
||||||
@ -498,12 +506,16 @@ Config* Config::instance()
|
|||||||
return m_instance;
|
return m_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::createConfigFromFile(const QString& file)
|
void Config::createConfigFromFile(const QString& configFileName, const QString& localConfigFileName)
|
||||||
{
|
{
|
||||||
if (m_instance) {
|
if (m_instance) {
|
||||||
delete m_instance;
|
delete m_instance;
|
||||||
}
|
}
|
||||||
m_instance = new Config(file, qApp);
|
|
||||||
|
auto defaultFiles = defaultConfigFiles();
|
||||||
|
m_instance = new Config(configFileName.isEmpty() ? defaultFiles.first : configFileName,
|
||||||
|
localConfigFileName.isEmpty() ? defaultFiles.second : localConfigFileName,
|
||||||
|
qApp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::createTempFileInstance()
|
void Config::createTempFileInstance()
|
||||||
@ -515,7 +527,7 @@ void Config::createTempFileInstance()
|
|||||||
bool openResult = tmpFile->open();
|
bool openResult = tmpFile->open();
|
||||||
Q_ASSERT(openResult);
|
Q_ASSERT(openResult);
|
||||||
Q_UNUSED(openResult);
|
Q_UNUSED(openResult);
|
||||||
m_instance = new Config(tmpFile->fileName(), qApp);
|
m_instance = new Config(tmpFile->fileName(), "", qApp);
|
||||||
tmpFile->setParent(m_instance);
|
tmpFile->setParent(m_instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,17 +199,18 @@ public:
|
|||||||
void resetToDefaults();
|
void resetToDefaults();
|
||||||
|
|
||||||
static Config* instance();
|
static Config* instance();
|
||||||
static void createConfigFromFile(const QString& file);
|
static void createConfigFromFile(const QString& configFileName, const QString& localConfigFileName = {});
|
||||||
static void createTempFileInstance();
|
static void createTempFileInstance();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void changed(ConfigKey key);
|
void changed(ConfigKey key);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Config(const QString& fileName, QObject* parent = nullptr);
|
Config(const QString& configFileName, const QString& localConfigFileName, QObject* parent);
|
||||||
explicit Config(QObject* parent);
|
explicit Config(QObject* parent);
|
||||||
void init(const QString& configFileName, const QString& localConfigFileName = "");
|
void init(const QString& configFileName, const QString& localConfigFileName);
|
||||||
void migrate();
|
void migrate();
|
||||||
|
static QPair<QString, QString> defaultConfigFiles();
|
||||||
|
|
||||||
static QPointer<Config> m_instance;
|
static QPointer<Config> m_instance;
|
||||||
|
|
||||||
|
@ -263,7 +263,8 @@ namespace Tools
|
|||||||
|
|
||||||
bool checkUrlValid(const QString& urlField)
|
bool checkUrlValid(const QString& urlField)
|
||||||
{
|
{
|
||||||
if (urlField.isEmpty() || urlField.startsWith("cmd://", Qt::CaseInsensitive)) {
|
if (urlField.isEmpty() || urlField.startsWith("cmd://", Qt::CaseInsensitive)
|
||||||
|
|| urlField.startsWith("{REF:A", Qt::CaseInsensitive)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,11 +25,7 @@ DatabaseOpenDialog::DatabaseOpenDialog(QWidget* parent)
|
|||||||
, m_view(new DatabaseOpenWidget(this))
|
, m_view(new DatabaseOpenWidget(this))
|
||||||
{
|
{
|
||||||
setWindowTitle(tr("Unlock Database - KeePassXC"));
|
setWindowTitle(tr("Unlock Database - KeePassXC"));
|
||||||
#ifdef Q_OS_MACOS
|
setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint);
|
||||||
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
|
||||||
#else
|
|
||||||
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint | Qt::ForeignWindow);
|
|
||||||
#endif
|
|
||||||
connect(m_view, SIGNAL(dialogFinished(bool)), this, SLOT(complete(bool)));
|
connect(m_view, SIGNAL(dialogFinished(bool)), this, SLOT(complete(bool)));
|
||||||
auto* layout = new QVBoxLayout();
|
auto* layout = new QVBoxLayout();
|
||||||
layout->setMargin(0);
|
layout->setMargin(0);
|
||||||
|
@ -275,7 +275,7 @@ void DatabaseTabWidget::importKeePass1Database()
|
|||||||
|
|
||||||
void DatabaseTabWidget::importOpVaultDatabase()
|
void DatabaseTabWidget::importOpVaultDatabase()
|
||||||
{
|
{
|
||||||
#ifdef Q_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
QString fileName = fileDialog()->getOpenFileName(this, tr("Open OPVault"), {}, "OPVault (*.opvault)");
|
QString fileName = fileDialog()->getOpenFileName(this, tr("Open OPVault"), {}, "OPVault (*.opvault)");
|
||||||
#else
|
#else
|
||||||
QString fileName = fileDialog()->getExistingDirectory(this, tr("Open OPVault"));
|
QString fileName = fileDialog()->getExistingDirectory(this, tr("Open OPVault"));
|
||||||
|
@ -2061,7 +2061,7 @@ void DatabaseWidget::processAutoOpen()
|
|||||||
// negated using '!'
|
// negated using '!'
|
||||||
auto ifDevice = entry->attribute("IfDevice");
|
auto ifDevice = entry->attribute("IfDevice");
|
||||||
if (!ifDevice.isEmpty()) {
|
if (!ifDevice.isEmpty()) {
|
||||||
bool loadDb = true;
|
bool loadDb = false;
|
||||||
auto hostName = QHostInfo::localHostName();
|
auto hostName = QHostInfo::localHostName();
|
||||||
for (auto& device : ifDevice.split(",")) {
|
for (auto& device : ifDevice.split(",")) {
|
||||||
device = device.trimmed();
|
device = device.trimmed();
|
||||||
@ -2070,12 +2070,13 @@ void DatabaseWidget::processAutoOpen()
|
|||||||
// Machine name matched an exclusion, don't load this database
|
// Machine name matched an exclusion, don't load this database
|
||||||
loadDb = false;
|
loadDb = false;
|
||||||
break;
|
break;
|
||||||
|
} else {
|
||||||
|
// Not matching an exclusion allows loading on all machines
|
||||||
|
loadDb = true;
|
||||||
}
|
}
|
||||||
} else if (device.compare(hostName, Qt::CaseInsensitive) == 0) {
|
} else if (device.compare(hostName, Qt::CaseInsensitive) == 0) {
|
||||||
|
// Explicitly named for loading
|
||||||
loadDb = true;
|
loadDb = true;
|
||||||
} else {
|
|
||||||
// Don't load the database if there are devices not starting with '!'
|
|
||||||
loadDb = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!loadDb) {
|
if (!loadDb) {
|
||||||
|
@ -59,12 +59,18 @@ void EditWidget::addPage(const QString& labelText, const QIcon& icon, QWidget* w
|
|||||||
* from automatic resizing and it now should be able to fit into a user's monitor even if the monitor is only 768
|
* from automatic resizing and it now should be able to fit into a user's monitor even if the monitor is only 768
|
||||||
* pixels high.
|
* pixels high.
|
||||||
*/
|
*/
|
||||||
|
if (widget->inherits("QScrollArea")) {
|
||||||
|
m_ui->stackedWidget->addWidget(widget);
|
||||||
|
} else {
|
||||||
auto* scrollArea = new QScrollArea(m_ui->stackedWidget);
|
auto* scrollArea = new QScrollArea(m_ui->stackedWidget);
|
||||||
scrollArea->setFrameShape(QFrame::NoFrame);
|
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||||
|
scrollArea->setFrameShadow(QFrame::Plain);
|
||||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
scrollArea->setWidget(widget);
|
scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
|
||||||
scrollArea->setWidgetResizable(true);
|
scrollArea->setWidgetResizable(true);
|
||||||
|
scrollArea->setWidget(widget);
|
||||||
m_ui->stackedWidget->addWidget(scrollArea);
|
m_ui->stackedWidget->addWidget(scrollArea);
|
||||||
|
}
|
||||||
m_ui->categoryList->addCategory(labelText, icon);
|
m_ui->categoryList->addCategory(labelText, icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,6 @@ EntryPreviewWidget::EntryPreviewWidget(QWidget* parent)
|
|||||||
// Entry
|
// Entry
|
||||||
m_ui->entryTotpButton->setIcon(resources()->icon("chronometer"));
|
m_ui->entryTotpButton->setIcon(resources()->icon("chronometer"));
|
||||||
m_ui->entryCloseButton->setIcon(resources()->icon("dialog-close"));
|
m_ui->entryCloseButton->setIcon(resources()->icon("dialog-close"));
|
||||||
m_ui->entryPasswordLabel->setFont(Font::fixedFont());
|
|
||||||
m_ui->togglePasswordButton->setIcon(resources()->onOffIcon("password-show"));
|
m_ui->togglePasswordButton->setIcon(resources()->onOffIcon("password-show"));
|
||||||
m_ui->toggleEntryNotesButton->setIcon(resources()->onOffIcon("password-show"));
|
m_ui->toggleEntryNotesButton->setIcon(resources()->onOffIcon("password-show"));
|
||||||
m_ui->toggleGroupNotesButton->setIcon(resources()->onOffIcon("password-show"));
|
m_ui->toggleGroupNotesButton->setIcon(resources()->onOffIcon("password-show"));
|
||||||
@ -194,6 +193,7 @@ void EntryPreviewWidget::setPasswordVisible(bool state)
|
|||||||
if (state) {
|
if (state) {
|
||||||
m_ui->entryPasswordLabel->setText(password);
|
m_ui->entryPasswordLabel->setText(password);
|
||||||
m_ui->entryPasswordLabel->setCursorPosition(0);
|
m_ui->entryPasswordLabel->setCursorPosition(0);
|
||||||
|
m_ui->entryPasswordLabel->setFont(Font::fixedFont());
|
||||||
} else if (password.isEmpty() && !config()->get(Config::Security_PasswordEmptyPlaceholder).toBool()) {
|
} else if (password.isEmpty() && !config()->get(Config::Security_PasswordEmptyPlaceholder).toBool()) {
|
||||||
m_ui->entryPasswordLabel->setText("");
|
m_ui->entryPasswordLabel->setText("");
|
||||||
} else {
|
} else {
|
||||||
|
@ -1274,6 +1274,8 @@ bool MainWindow::saveLastDatabases()
|
|||||||
void MainWindow::updateTrayIcon()
|
void MainWindow::updateTrayIcon()
|
||||||
{
|
{
|
||||||
if (isTrayIconEnabled()) {
|
if (isTrayIconEnabled()) {
|
||||||
|
QApplication::setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
if (!m_trayIcon) {
|
if (!m_trayIcon) {
|
||||||
m_trayIcon = new QSystemTrayIcon(this);
|
m_trayIcon = new QSystemTrayIcon(this);
|
||||||
auto* menu = new QMenu(this);
|
auto* menu = new QMenu(this);
|
||||||
@ -1312,6 +1314,8 @@ void MainWindow::updateTrayIcon()
|
|||||||
m_trayIcon->setIcon(resources()->trayIconLocked());
|
m_trayIcon->setIcon(resources()->trayIconLocked());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
QApplication::setQuitOnLastWindowClosed(true);
|
||||||
|
|
||||||
if (m_trayIcon) {
|
if (m_trayIcon) {
|
||||||
m_trayIcon->hide();
|
m_trayIcon->hide();
|
||||||
delete m_trayIcon;
|
delete m_trayIcon;
|
||||||
|
@ -170,6 +170,7 @@ void PasswordGeneratorWidget::loadSettings()
|
|||||||
// Set advanced mode
|
// Set advanced mode
|
||||||
m_ui->buttonAdvancedMode->setChecked(advanced);
|
m_ui->buttonAdvancedMode->setChecked(advanced);
|
||||||
setAdvancedMode(advanced);
|
setAdvancedMode(advanced);
|
||||||
|
updateGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PasswordGeneratorWidget::saveSettings()
|
void PasswordGeneratorWidget::saveSettings()
|
||||||
|
@ -76,7 +76,7 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
|
|||||||
, m_historyUi(new Ui::EditEntryWidgetHistory())
|
, m_historyUi(new Ui::EditEntryWidgetHistory())
|
||||||
, m_browserUi(new Ui::EditEntryWidgetBrowser())
|
, m_browserUi(new Ui::EditEntryWidgetBrowser())
|
||||||
, m_customData(new CustomData())
|
, m_customData(new CustomData())
|
||||||
, m_mainWidget(new QWidget())
|
, m_mainWidget(new QScrollArea())
|
||||||
, m_advancedWidget(new QWidget())
|
, m_advancedWidget(new QWidget())
|
||||||
, m_iconsWidget(new EditWidgetIcons())
|
, m_iconsWidget(new EditWidgetIcons())
|
||||||
, m_autoTypeWidget(new QWidget())
|
, m_autoTypeWidget(new QWidget())
|
||||||
@ -178,6 +178,9 @@ void EditEntryWidget::setupMain()
|
|||||||
|
|
||||||
m_mainUi->expirePresets->setMenu(createPresetsMenu());
|
m_mainUi->expirePresets->setMenu(createPresetsMenu());
|
||||||
connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*)));
|
connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*)));
|
||||||
|
|
||||||
|
// HACK: Align username text with other line edits. Qt does not let you do this with an application stylesheet.
|
||||||
|
m_mainUi->usernameComboBox->lineEdit()->setStyleSheet("padding-left: 8px;");
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditEntryWidget::setupAdvanced()
|
void EditEntryWidget::setupAdvanced()
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include <QModelIndex>
|
#include <QModelIndex>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
|
#include <QScrollArea>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "config-keepassx.h"
|
#include "config-keepassx.h"
|
||||||
@ -174,7 +175,7 @@ private:
|
|||||||
const QScopedPointer<Ui::EditEntryWidgetBrowser> m_browserUi;
|
const QScopedPointer<Ui::EditEntryWidgetBrowser> m_browserUi;
|
||||||
const QScopedPointer<CustomData> m_customData;
|
const QScopedPointer<CustomData> m_customData;
|
||||||
|
|
||||||
QWidget* const m_mainWidget;
|
QScrollArea* const m_mainWidget;
|
||||||
QWidget* const m_advancedWidget;
|
QWidget* const m_advancedWidget;
|
||||||
EditWidgetIcons* const m_iconsWidget;
|
EditWidgetIcons* const m_iconsWidget;
|
||||||
QWidget* const m_autoTypeWidget;
|
QWidget* const m_autoTypeWidget;
|
||||||
|
@ -1,13 +1,40 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>EditEntryWidgetMain</class>
|
<class>EditEntryWidgetMain</class>
|
||||||
<widget class="QWidget" name="EditEntryWidgetMain">
|
<widget class="QScrollArea" name="EditEntryWidgetMain">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>496</width>
|
<width>539</width>
|
||||||
<height>420</height>
|
<height>523</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Edit Entry</string>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Plain</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="container">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>539</width>
|
||||||
|
<height>523</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
@ -247,6 +274,7 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>PasswordEdit</class>
|
<class>PasswordEdit</class>
|
||||||
|
@ -109,21 +109,12 @@ EntryView::EntryView(QWidget* parent)
|
|||||||
header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
|
||||||
connect(header(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(showHeaderMenu(QPoint)));
|
connect(header(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(showHeaderMenu(QPoint)));
|
||||||
// clang-format off
|
connect(header(), SIGNAL(sectionCountChanged(int, int)), SIGNAL(viewStateChanged()));
|
||||||
connect(header(), SIGNAL(sectionCountChanged(int,int)), SIGNAL(viewStateChanged()));
|
connect(header(), SIGNAL(sectionMoved(int, int, int)), SIGNAL(viewStateChanged()));
|
||||||
// clang-format on
|
connect(header(), SIGNAL(sectionResized(int, int, int)), SIGNAL(viewStateChanged()));
|
||||||
|
connect(header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), SLOT(sortIndicatorChanged(int, Qt::SortOrder)));
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
connect(header(), SIGNAL(sectionMoved(int,int,int)), SIGNAL(viewStateChanged()));
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
connect(header(), SIGNAL(sectionResized(int,int,int)), SIGNAL(viewStateChanged()));
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), SLOT(sortIndicatorChanged(int,Qt::SortOrder)));
|
|
||||||
// clang-format on
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntryView::contextMenuShortcutPressed()
|
void EntryView::contextMenuShortcutPressed()
|
||||||
@ -358,6 +349,8 @@ QByteArray EntryView::viewState() const
|
|||||||
*/
|
*/
|
||||||
bool EntryView::setViewState(const QByteArray& state)
|
bool EntryView::setViewState(const QByteArray& state)
|
||||||
{
|
{
|
||||||
|
// Reset to unsorted first (https://bugreports.qt.io/browse/QTBUG-86694)
|
||||||
|
header()->setSortIndicator(-1, Qt::AscendingOrder);
|
||||||
bool status = header()->restoreState(state);
|
bool status = header()->restoreState(state);
|
||||||
resetFixedColumns();
|
resetFixedColumns();
|
||||||
m_columnsNeedRelayout = state.isEmpty();
|
m_columnsNeedRelayout = state.isEmpty();
|
||||||
@ -379,8 +372,7 @@ void EntryView::showHeaderMenu(const QPoint& position)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int columnIndex = action->data().toInt();
|
int columnIndex = action->data().toInt();
|
||||||
bool hidden = header()->isSectionHidden(columnIndex) || (header()->sectionSize(columnIndex) == 0);
|
action->setChecked(!isColumnHidden(columnIndex));
|
||||||
action->setChecked(!hidden);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_headerMenu->popup(mapToGlobal(position));
|
m_headerMenu->popup(mapToGlobal(position));
|
||||||
@ -408,6 +400,7 @@ void EntryView::toggleColumnVisibility(QAction* action)
|
|||||||
if (header()->sectionSize(columnIndex) == 0) {
|
if (header()->sectionSize(columnIndex) == 0) {
|
||||||
header()->resizeSection(columnIndex, header()->defaultSectionSize());
|
header()->resizeSection(columnIndex, header()->defaultSectionSize());
|
||||||
}
|
}
|
||||||
|
resetFixedColumns();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((header()->count() - header()->hiddenSectionCount()) > 1) {
|
if ((header()->count() - header()->hiddenSectionCount()) > 1) {
|
||||||
@ -460,11 +453,15 @@ void EntryView::fitColumnsToContents()
|
|||||||
*/
|
*/
|
||||||
void EntryView::resetFixedColumns()
|
void EntryView::resetFixedColumns()
|
||||||
{
|
{
|
||||||
|
if (!isColumnHidden(EntryModel::Paperclip)) {
|
||||||
header()->setSectionResizeMode(EntryModel::Paperclip, QHeaderView::Fixed);
|
header()->setSectionResizeMode(EntryModel::Paperclip, QHeaderView::Fixed);
|
||||||
header()->resizeSection(EntryModel::Paperclip, header()->minimumSectionSize());
|
header()->resizeSection(EntryModel::Paperclip, header()->minimumSectionSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isColumnHidden(EntryModel::Totp)) {
|
||||||
header()->setSectionResizeMode(EntryModel::Totp, QHeaderView::Fixed);
|
header()->setSectionResizeMode(EntryModel::Totp, QHeaderView::Fixed);
|
||||||
header()->resizeSection(EntryModel::Totp, header()->minimumSectionSize());
|
header()->resizeSection(EntryModel::Totp, header()->minimumSectionSize());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -533,3 +530,8 @@ void EntryView::showEvent(QShowEvent* event)
|
|||||||
m_columnsNeedRelayout = false;
|
m_columnsNeedRelayout = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EntryView::isColumnHidden(int logicalIndex)
|
||||||
|
{
|
||||||
|
return header()->isSectionHidden(logicalIndex) || header()->sectionSize(logicalIndex) == 0;
|
||||||
|
}
|
||||||
|
@ -80,6 +80,7 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void resetFixedColumns();
|
void resetFixedColumns();
|
||||||
|
bool isColumnHidden(int logicalIndex);
|
||||||
|
|
||||||
EntryModel* const m_model;
|
EntryModel* const m_model;
|
||||||
SortFilterHideProxyModel* const m_sortModel;
|
SortFilterHideProxyModel* const m_sortModel;
|
||||||
|
@ -62,7 +62,7 @@ private:
|
|||||||
EditGroupWidget::EditGroupWidget(QWidget* parent)
|
EditGroupWidget::EditGroupWidget(QWidget* parent)
|
||||||
: EditWidget(parent)
|
: EditWidget(parent)
|
||||||
, m_mainUi(new Ui::EditGroupWidgetMain())
|
, m_mainUi(new Ui::EditGroupWidgetMain())
|
||||||
, m_editGroupWidgetMain(new QWidget())
|
, m_editGroupWidgetMain(new QScrollArea())
|
||||||
, m_editGroupWidgetIcons(new EditWidgetIcons())
|
, m_editGroupWidgetIcons(new EditWidgetIcons())
|
||||||
, m_editWidgetProperties(new EditWidgetProperties())
|
, m_editWidgetProperties(new EditWidgetProperties())
|
||||||
, m_group(nullptr)
|
, m_group(nullptr)
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
|
#include <QScrollArea>
|
||||||
|
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
#include "gui/EditWidget.h"
|
#include "gui/EditWidget.h"
|
||||||
@ -78,7 +79,7 @@ private:
|
|||||||
|
|
||||||
const QScopedPointer<Ui::EditGroupWidgetMain> m_mainUi;
|
const QScopedPointer<Ui::EditGroupWidgetMain> m_mainUi;
|
||||||
|
|
||||||
QPointer<QWidget> m_editGroupWidgetMain;
|
QPointer<QScrollArea> m_editGroupWidgetMain;
|
||||||
QPointer<EditWidgetIcons> m_editGroupWidgetIcons;
|
QPointer<EditWidgetIcons> m_editGroupWidgetIcons;
|
||||||
QPointer<EditWidgetProperties> m_editWidgetProperties;
|
QPointer<EditWidgetProperties> m_editWidgetProperties;
|
||||||
|
|
||||||
|
@ -1,13 +1,40 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>EditGroupWidgetMain</class>
|
<class>EditGroupWidgetMain</class>
|
||||||
<widget class="QWidget" name="EditGroupWidgetMain">
|
<widget class="QScrollArea" name="EditGroupWidgetMain">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>410</width>
|
<width>539</width>
|
||||||
<height>430</height>
|
<height>523</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Edit Group</string>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Plain</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="container">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>539</width>
|
||||||
|
<height>523</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0,0,0,0,1" rowminimumheight="0,0,0,0,0,0,0,0,0,1">
|
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0,0,0,0,1" rowminimumheight="0,0,0,0,0,0,0,0,0,1">
|
||||||
@ -211,6 +238,7 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>editName</tabstop>
|
<tabstop>editName</tabstop>
|
||||||
<tabstop>editNotes</tabstop>
|
<tabstop>editNotes</tabstop>
|
||||||
|
@ -76,6 +76,27 @@ namespace
|
|||||||
QList<QSharedPointer<Item>> m_items;
|
QList<QSharedPointer<Item>> m_items;
|
||||||
bool m_anyKnownBad = false;
|
bool m_anyKnownBad = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ReportSortProxyModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ReportSortProxyModel(QObject* parent)
|
||||||
|
: QSortFilterProxyModel(parent){};
|
||||||
|
~ReportSortProxyModel() override = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override
|
||||||
|
{
|
||||||
|
// Check if the display data is a number, convert and compare if so
|
||||||
|
bool ok = false;
|
||||||
|
int leftInt = sourceModel()->data(left).toString().toInt(&ok);
|
||||||
|
if (ok) {
|
||||||
|
return leftInt < sourceModel()->data(right).toString().toInt();
|
||||||
|
}
|
||||||
|
// Otherwise use default sorting
|
||||||
|
return QSortFilterProxyModel::lessThan(left, right);
|
||||||
|
}
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Health::Health(QSharedPointer<Database> db)
|
Health::Health(QSharedPointer<Database> db)
|
||||||
@ -121,11 +142,12 @@ ReportsWidgetHealthcheck::ReportsWidgetHealthcheck(QWidget* parent)
|
|||||||
, m_ui(new Ui::ReportsWidgetHealthcheck())
|
, m_ui(new Ui::ReportsWidgetHealthcheck())
|
||||||
, m_errorIcon(Resources::instance()->icon("dialog-error"))
|
, m_errorIcon(Resources::instance()->icon("dialog-error"))
|
||||||
, m_referencesModel(new QStandardItemModel(this))
|
, m_referencesModel(new QStandardItemModel(this))
|
||||||
, m_modelProxy(new QSortFilterProxyModel(this))
|
, m_modelProxy(new ReportSortProxyModel(this))
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
m_modelProxy->setSourceModel(m_referencesModel.data());
|
m_modelProxy->setSourceModel(m_referencesModel.data());
|
||||||
|
m_modelProxy->setSortLocaleAware(true);
|
||||||
m_ui->healthcheckTableView->setModel(m_modelProxy.data());
|
m_ui->healthcheckTableView->setModel(m_modelProxy.data());
|
||||||
m_ui->healthcheckTableView->setSelectionMode(QAbstractItemView::NoSelection);
|
m_ui->healthcheckTableView->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
m_ui->healthcheckTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
m_ui->healthcheckTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||||
@ -256,6 +278,7 @@ void ReportsWidgetHealthcheck::calculateHealth()
|
|||||||
} else {
|
} else {
|
||||||
m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("") << tr("Title") << tr("Path") << tr("Score")
|
m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("") << tr("Title") << tr("Path") << tr("Score")
|
||||||
<< tr("Reason"));
|
<< tr("Reason"));
|
||||||
|
m_ui->healthcheckTableView->sortByColumn(0, Qt::AscendingOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui->healthcheckTableView->resizeRowsToContents();
|
m_ui->healthcheckTableView->resizeRowsToContents();
|
||||||
|
@ -45,17 +45,38 @@ namespace
|
|||||||
return entry->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
|
return entry->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
|
||||||
&& entry->customData()->value(PasswordHealth::OPTION_KNOWN_BAD) == TRUE_STR;
|
&& entry->customData()->value(PasswordHealth::OPTION_KNOWN_BAD) == TRUE_STR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ReportSortProxyModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ReportSortProxyModel(QObject* parent)
|
||||||
|
: QSortFilterProxyModel(parent){};
|
||||||
|
~ReportSortProxyModel() override = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override
|
||||||
|
{
|
||||||
|
// Sort count column by user data
|
||||||
|
if (left.column() == 2) {
|
||||||
|
return sourceModel()->data(left, Qt::UserRole).toInt()
|
||||||
|
< sourceModel()->data(right, Qt::UserRole).toInt();
|
||||||
|
}
|
||||||
|
// Otherwise use default sorting
|
||||||
|
return QSortFilterProxyModel::lessThan(left, right);
|
||||||
|
}
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ReportsWidgetHibp::ReportsWidgetHibp(QWidget* parent)
|
ReportsWidgetHibp::ReportsWidgetHibp(QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_ui(new Ui::ReportsWidgetHibp())
|
, m_ui(new Ui::ReportsWidgetHibp())
|
||||||
, m_referencesModel(new QStandardItemModel(this))
|
, m_referencesModel(new QStandardItemModel(this))
|
||||||
, m_modelProxy(new QSortFilterProxyModel(this))
|
, m_modelProxy(new ReportSortProxyModel(this))
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
m_modelProxy->setSourceModel(m_referencesModel.data());
|
m_modelProxy->setSourceModel(m_referencesModel.data());
|
||||||
|
m_modelProxy->setSortLocaleAware(true);
|
||||||
m_ui->hibpTableView->setModel(m_modelProxy.data());
|
m_ui->hibpTableView->setModel(m_modelProxy.data());
|
||||||
m_ui->hibpTableView->setSelectionMode(QAbstractItemView::NoSelection);
|
m_ui->hibpTableView->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
m_ui->hibpTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
m_ui->hibpTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||||
@ -167,6 +188,7 @@ void ReportsWidgetHibp::makeHibpTable()
|
|||||||
}
|
}
|
||||||
|
|
||||||
row[2]->setForeground(red);
|
row[2]->setForeground(red);
|
||||||
|
row[2]->setData(count, Qt::UserRole);
|
||||||
m_referencesModel->appendRow(row);
|
m_referencesModel->appendRow(row);
|
||||||
|
|
||||||
// Store entry pointer per table row (used in double click handler)
|
// Store entry pointer per table row (used in double click handler)
|
||||||
@ -198,6 +220,7 @@ void ReportsWidgetHibp::makeHibpTable()
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_ui->hibpTableView->resizeRowsToContents();
|
m_ui->hibpTableView->resizeRowsToContents();
|
||||||
|
m_ui->hibpTableView->sortByColumn(2, Qt::DescendingOrder);
|
||||||
|
|
||||||
m_ui->stackedWidget->setCurrentIndex(1);
|
m_ui->stackedWidget->setCurrentIndex(1);
|
||||||
}
|
}
|
||||||
|
@ -64,3 +64,8 @@ DatabaseWidget #SearchBanner, DatabaseWidget #KeeShareBanner {
|
|||||||
border: 1px solid palette(dark);
|
border: 1px solid palette(dark);
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPlainTextEdit, QTextEdit {
|
||||||
|
background-color: palette(base);
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
53
src/main.cpp
53
src/main.cpp
@ -56,51 +56,47 @@ int main(int argc, char** argv)
|
|||||||
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Application app(argc, argv);
|
|
||||||
Application::setApplicationName("KeePassXC");
|
|
||||||
Application::setApplicationVersion(KEEPASSXC_VERSION);
|
|
||||||
app.setProperty("KPXC_QUALIFIED_APPNAME", "org.keepassxc.KeePassXC");
|
|
||||||
app.applyTheme();
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
|
|
||||||
QGuiApplication::setDesktopFileName(app.property("KPXC_QUALIFIED_APPNAME").toString() + QStringLiteral(".desktop"));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// don't set organizationName as that changes the return value of
|
|
||||||
// QStandardPaths::writableLocation(QDesktopServices::DataLocation)
|
|
||||||
Bootstrap::bootstrapApplication();
|
|
||||||
|
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.setApplicationDescription(QObject::tr("KeePassXC - cross-platform password manager"));
|
parser.setApplicationDescription(QObject::tr("KeePassXC - cross-platform password manager"));
|
||||||
parser.addPositionalArgument(
|
parser.addPositionalArgument(
|
||||||
"filename", QObject::tr("filenames of the password databases to open (*.kdbx)"), "[filename(s)]");
|
"filename(s)", QObject::tr("filenames of the password databases to open (*.kdbx)"), "[filename(s)]");
|
||||||
|
|
||||||
QCommandLineOption configOption("config", QObject::tr("path to a custom config file"), "config");
|
QCommandLineOption configOption("config", QObject::tr("path to a custom config file"), "config");
|
||||||
|
QCommandLineOption localConfigOption(
|
||||||
|
"localconfig", QObject::tr("path to a custom local config file"), "localconfig");
|
||||||
QCommandLineOption keyfileOption("keyfile", QObject::tr("key file of the database"), "keyfile");
|
QCommandLineOption keyfileOption("keyfile", QObject::tr("key file of the database"), "keyfile");
|
||||||
QCommandLineOption pwstdinOption("pw-stdin", QObject::tr("read password of the database from stdin"));
|
QCommandLineOption pwstdinOption("pw-stdin", QObject::tr("read password of the database from stdin"));
|
||||||
// This is needed under Windows where clients send --parent-window parameter with Native Messaging connect method
|
|
||||||
QCommandLineOption parentWindowOption(QStringList() << "pw"
|
|
||||||
<< "parent-window",
|
|
||||||
QObject::tr("Parent window handle"),
|
|
||||||
"handle");
|
|
||||||
|
|
||||||
QCommandLineOption helpOption = parser.addHelpOption();
|
QCommandLineOption helpOption = parser.addHelpOption();
|
||||||
QCommandLineOption versionOption = parser.addVersionOption();
|
QCommandLineOption versionOption = parser.addVersionOption();
|
||||||
QCommandLineOption debugInfoOption(QStringList() << "debug-info", QObject::tr("Displays debugging information."));
|
QCommandLineOption debugInfoOption(QStringList() << "debug-info", QObject::tr("Displays debugging information."));
|
||||||
parser.addOption(configOption);
|
parser.addOption(configOption);
|
||||||
|
parser.addOption(localConfigOption);
|
||||||
parser.addOption(keyfileOption);
|
parser.addOption(keyfileOption);
|
||||||
parser.addOption(pwstdinOption);
|
parser.addOption(pwstdinOption);
|
||||||
parser.addOption(parentWindowOption);
|
|
||||||
parser.addOption(debugInfoOption);
|
parser.addOption(debugInfoOption);
|
||||||
|
|
||||||
|
Application app(argc, argv);
|
||||||
|
// don't set organizationName as that changes the return value of
|
||||||
|
// QStandardPaths::writableLocation(QDesktopServices::DataLocation)
|
||||||
|
Application::setApplicationName("KeePassXC");
|
||||||
|
Application::setApplicationVersion(KEEPASSXC_VERSION);
|
||||||
|
app.setProperty("KPXC_QUALIFIED_APPNAME", "org.keepassxc.KeePassXC");
|
||||||
|
|
||||||
parser.process(app);
|
parser.process(app);
|
||||||
|
|
||||||
// Don't try and do anything with the application if we're only showing the help / version
|
// Exit early if we're only showing the help / version
|
||||||
if (parser.isSet(versionOption) || parser.isSet(helpOption)) {
|
if (parser.isSet(versionOption) || parser.isSet(helpOption)) {
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QStringList fileNames = parser.positionalArguments();
|
// Process config file options early
|
||||||
|
if (parser.isSet(configOption) || parser.isSet(localConfigOption)) {
|
||||||
|
Config::createConfigFromFile(parser.value(configOption), parser.value(localConfigOption));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process single instance and early exit if already running
|
||||||
|
const QStringList fileNames = parser.positionalArguments();
|
||||||
if (app.isAlreadyRunning()) {
|
if (app.isAlreadyRunning()) {
|
||||||
if (!fileNames.isEmpty()) {
|
if (!fileNames.isEmpty()) {
|
||||||
app.sendFileNamesToRunningInstance(fileNames);
|
app.sendFileNamesToRunningInstance(fileNames);
|
||||||
@ -109,7 +105,14 @@ int main(int argc, char** argv)
|
|||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
QApplication::setQuitOnLastWindowClosed(false);
|
// Apply the configured theme before creating any GUI elements
|
||||||
|
app.applyTheme();
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
|
||||||
|
QGuiApplication::setDesktopFileName(app.property("KPXC_QUALIFIED_APPNAME").toString() + QStringLiteral(".desktop"));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Bootstrap::bootstrapApplication();
|
||||||
|
|
||||||
if (!Crypto::init()) {
|
if (!Crypto::init()) {
|
||||||
QString error = QObject::tr("Fatal error while testing the cryptographic functions.");
|
QString error = QObject::tr("Fatal error while testing the cryptographic functions.");
|
||||||
@ -128,10 +131,6 @@ int main(int argc, char** argv)
|
|||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parser.isSet(configOption)) {
|
|
||||||
Config::createConfigFromFile(parser.value(configOption));
|
|
||||||
}
|
|
||||||
|
|
||||||
MainWindow mainWindow;
|
MainWindow mainWindow;
|
||||||
QObject::connect(&app, SIGNAL(anotherInstanceStarted()), &mainWindow, SLOT(bringToFront()));
|
QObject::connect(&app, SIGNAL(anotherInstanceStarted()), &mainWindow, SLOT(bringToFront()));
|
||||||
QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront()));
|
QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront()));
|
||||||
|
@ -128,59 +128,52 @@ void TestBrowser::testBaseDomain()
|
|||||||
|
|
||||||
void TestBrowser::testSortPriority()
|
void TestBrowser::testSortPriority()
|
||||||
{
|
{
|
||||||
QString host = "github.com";
|
QFETCH(QString, entryUrl);
|
||||||
QString submitUrl = "https://github.com/session";
|
QFETCH(QString, siteUrl);
|
||||||
QString baseSubmitUrl = "https://github.com";
|
QFETCH(QString, formUrl);
|
||||||
QString fullUrl = "https://github.com/login";
|
QFETCH(int, expectedScore);
|
||||||
|
|
||||||
QScopedPointer<Entry> entry1(new Entry());
|
QScopedPointer<Entry> entry(new Entry());
|
||||||
QScopedPointer<Entry> entry2(new Entry());
|
entry->setUrl(entryUrl);
|
||||||
QScopedPointer<Entry> entry3(new Entry());
|
|
||||||
QScopedPointer<Entry> entry4(new Entry());
|
|
||||||
QScopedPointer<Entry> entry5(new Entry());
|
|
||||||
QScopedPointer<Entry> entry6(new Entry());
|
|
||||||
QScopedPointer<Entry> entry7(new Entry());
|
|
||||||
QScopedPointer<Entry> entry8(new Entry());
|
|
||||||
QScopedPointer<Entry> entry9(new Entry());
|
|
||||||
QScopedPointer<Entry> entry10(new Entry());
|
|
||||||
QScopedPointer<Entry> entry11(new Entry());
|
|
||||||
|
|
||||||
entry1->setUrl("https://github.com/login");
|
QCOMPARE(m_browserService->sortPriority(m_browserService->getEntryURLs(entry.data()), siteUrl, formUrl),
|
||||||
entry2->setUrl("https://github.com/login");
|
expectedScore);
|
||||||
entry3->setUrl("https://github.com/");
|
}
|
||||||
entry4->setUrl("github.com/login");
|
|
||||||
entry5->setUrl("http://github.com");
|
|
||||||
entry6->setUrl("http://github.com/login");
|
|
||||||
entry7->setUrl("github.com");
|
|
||||||
entry8->setUrl("github.com/login");
|
|
||||||
entry9->setUrl("https://github"); // Invalid URL
|
|
||||||
entry10->setUrl("github.com");
|
|
||||||
entry11->setUrl("https://github.com/login"); // Exact match
|
|
||||||
|
|
||||||
// The extension uses the submitUrl as default for comparison
|
void TestBrowser::testSortPriority_data()
|
||||||
auto res1 = m_browserService->sortPriority(entry1.data(), host, "https://github.com/login", baseSubmitUrl, fullUrl);
|
{
|
||||||
auto res2 = m_browserService->sortPriority(entry2.data(), host, submitUrl, baseSubmitUrl, baseSubmitUrl);
|
const QString siteUrl = "https://github.com/login";
|
||||||
auto res3 = m_browserService->sortPriority(entry3.data(), host, submitUrl, baseSubmitUrl, fullUrl);
|
const QString formUrl = "https://github.com/session";
|
||||||
auto res4 = m_browserService->sortPriority(entry4.data(), host, submitUrl, baseSubmitUrl, fullUrl);
|
|
||||||
auto res5 = m_browserService->sortPriority(entry5.data(), host, submitUrl, baseSubmitUrl, fullUrl);
|
|
||||||
auto res6 = m_browserService->sortPriority(entry6.data(), host, submitUrl, baseSubmitUrl, fullUrl);
|
|
||||||
auto res7 = m_browserService->sortPriority(entry7.data(), host, submitUrl, baseSubmitUrl, fullUrl);
|
|
||||||
auto res8 = m_browserService->sortPriority(entry8.data(), host, submitUrl, baseSubmitUrl, fullUrl);
|
|
||||||
auto res9 = m_browserService->sortPriority(entry9.data(), host, submitUrl, baseSubmitUrl, fullUrl);
|
|
||||||
auto res10 = m_browserService->sortPriority(entry10.data(), host, submitUrl, baseSubmitUrl, fullUrl);
|
|
||||||
auto res11 = m_browserService->sortPriority(entry11.data(), host, submitUrl, baseSubmitUrl, fullUrl);
|
|
||||||
|
|
||||||
QCOMPARE(res1, 100);
|
QTest::addColumn<QString>("entryUrl");
|
||||||
QCOMPARE(res2, 40);
|
QTest::addColumn<QString>("siteUrl");
|
||||||
QCOMPARE(res3, 90);
|
QTest::addColumn<QString>("formUrl");
|
||||||
QCOMPARE(res4, 0);
|
QTest::addColumn<int>("expectedScore");
|
||||||
QCOMPARE(res5, 0);
|
|
||||||
QCOMPARE(res6, 0);
|
QTest::newRow("Exact Match") << siteUrl << siteUrl << siteUrl << 100;
|
||||||
QCOMPARE(res7, 0);
|
QTest::newRow("Exact Match (site)") << siteUrl << siteUrl << formUrl << 100;
|
||||||
QCOMPARE(res8, 0);
|
QTest::newRow("Exact Match (form)") << siteUrl << "https://github.net" << siteUrl << 100;
|
||||||
QCOMPARE(res9, 0);
|
QTest::newRow("Exact Match No Trailing Slash") << "https://github.com"
|
||||||
QCOMPARE(res10, 0);
|
<< "https://github.com/" << formUrl << 100;
|
||||||
QCOMPARE(res11, 100);
|
QTest::newRow("Exact Match No Scheme") << "github.com/login" << siteUrl << formUrl << 100;
|
||||||
|
QTest::newRow("Exact Match with Query") << "https://github.com/login?test=test#fragment"
|
||||||
|
<< "https://github.com/login?test=test" << formUrl << 100;
|
||||||
|
|
||||||
|
QTest::newRow("Site Query Mismatch") << siteUrl << siteUrl + "?test=test" << formUrl << 90;
|
||||||
|
|
||||||
|
QTest::newRow("Path Mismatch (site)") << "https://github.com/" << siteUrl << formUrl << 80;
|
||||||
|
QTest::newRow("Path Mismatch (site) No Scheme") << "github.com" << siteUrl << formUrl << 80;
|
||||||
|
QTest::newRow("Path Mismatch (form)") << "https://github.com/"
|
||||||
|
<< "https://github.net" << formUrl << 70;
|
||||||
|
|
||||||
|
QTest::newRow("Subdomain Mismatch (site)") << siteUrl << "https://sub.github.com/"
|
||||||
|
<< "https://github.net/" << 60;
|
||||||
|
QTest::newRow("Subdomain Mismatch (form)") << siteUrl << "https://github.net/"
|
||||||
|
<< "https://sub.github.com/" << 50;
|
||||||
|
|
||||||
|
QTest::newRow("Scheme Mismatch") << "http://github.com" << siteUrl << formUrl << 0;
|
||||||
|
QTest::newRow("Scheme Mismatch w/path") << "http://github.com/login" << siteUrl << formUrl << 0;
|
||||||
|
QTest::newRow("Invalid URL") << "http://github" << siteUrl << formUrl << 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestBrowser::testSearchEntries()
|
void TestBrowser::testSearchEntries()
|
||||||
@ -393,14 +386,14 @@ void TestBrowser::testSubdomainsAndPaths()
|
|||||||
|
|
||||||
createEntries(entryURLs, root);
|
createEntries(entryURLs, root);
|
||||||
|
|
||||||
result = m_browserService->searchEntries(db, "https://accounts.example.com", "https://accounts.example.com");
|
result = m_browserService->searchEntries(db, "https://accounts.example.com/", "https://accounts.example.com/");
|
||||||
QCOMPARE(result.length(), 3);
|
QCOMPARE(result.length(), 3);
|
||||||
QCOMPARE(result[0]->url(), QString("https://accounts.example.com"));
|
QCOMPARE(result[0]->url(), QString("https://accounts.example.com"));
|
||||||
QCOMPARE(result[1]->url(), QString("https://accounts.example.com/path"));
|
QCOMPARE(result[1]->url(), QString("https://accounts.example.com/path"));
|
||||||
QCOMPARE(result[2]->url(), QString("https://example.com/")); // Accepts any subdomain
|
QCOMPARE(result[2]->url(), QString("https://example.com/")); // Accepts any subdomain
|
||||||
|
|
||||||
result = m_browserService->searchEntries(
|
result = m_browserService->searchEntries(
|
||||||
db, "https://another.accounts.example.com", "https://another.accounts.example.com");
|
db, "https://another.accounts.example.com/", "https://another.accounts.example.com/");
|
||||||
QCOMPARE(result.length(), 4);
|
QCOMPARE(result.length(), 4);
|
||||||
QCOMPARE(result[0]->url(),
|
QCOMPARE(result[0]->url(),
|
||||||
QString("https://accounts.example.com")); // Accepts any subdomain under accounts.example.com
|
QString("https://accounts.example.com")); // Accepts any subdomain under accounts.example.com
|
||||||
@ -430,33 +423,32 @@ void TestBrowser::testSortEntries()
|
|||||||
"http://github.com",
|
"http://github.com",
|
||||||
"http://github.com/login",
|
"http://github.com/login",
|
||||||
"github.com",
|
"github.com",
|
||||||
"github.com/login",
|
"github.com/login?test=test",
|
||||||
"https://github", // Invalid URL
|
"https://github", // Invalid URL
|
||||||
"github.com"};
|
"github.com"};
|
||||||
|
|
||||||
auto entries = createEntries(urls, root);
|
auto entries = createEntries(urls, root);
|
||||||
|
|
||||||
browserSettings()->setBestMatchOnly(false);
|
browserSettings()->setBestMatchOnly(false);
|
||||||
auto result = m_browserService->sortEntries(
|
browserSettings()->setSortByUsername(true);
|
||||||
entries, "github.com", "https://github.com/session", "https://github.com"); // entries, host, submitUrl
|
auto result = m_browserService->sortEntries(entries, "https://github.com/login", "https://github.com/session");
|
||||||
QCOMPARE(result.size(), 10);
|
QCOMPARE(result.size(), 10);
|
||||||
QCOMPARE(result[0]->username(), QString("User 2"));
|
QCOMPARE(result[0]->username(), QString("User 1"));
|
||||||
QCOMPARE(result[0]->url(), QString("https://github.com/"));
|
QCOMPARE(result[0]->url(), urls[1]);
|
||||||
QCOMPARE(result[1]->username(), QString("User 0"));
|
QCOMPARE(result[1]->username(), QString("User 3"));
|
||||||
QCOMPARE(result[1]->url(), QString("https://github.com/login_page"));
|
QCOMPARE(result[1]->url(), urls[3]);
|
||||||
QCOMPARE(result[2]->username(), QString("User 1"));
|
QCOMPARE(result[2]->username(), QString("User 7"));
|
||||||
QCOMPARE(result[2]->url(), QString("https://github.com/login"));
|
QCOMPARE(result[2]->url(), urls[7]);
|
||||||
QCOMPARE(result[3]->username(), QString("User 3"));
|
QCOMPARE(result[3]->username(), QString("User 0"));
|
||||||
QCOMPARE(result[3]->url(), QString("github.com/login"));
|
QCOMPARE(result[3]->url(), urls[0]);
|
||||||
|
|
||||||
// Test with a perfect match. That should be first in the list.
|
// Test with a perfect match. That should be first in the list.
|
||||||
result = m_browserService->sortEntries(
|
result = m_browserService->sortEntries(entries, "https://github.com/login_page", "https://github.com/session");
|
||||||
entries, "github.com", "https://github.com/session", "https://github.com/login_page");
|
|
||||||
QCOMPARE(result.size(), 10);
|
QCOMPARE(result.size(), 10);
|
||||||
QCOMPARE(result[0]->username(), QString("User 0"));
|
QCOMPARE(result[0]->username(), QString("User 0"));
|
||||||
QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
|
QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
|
||||||
QCOMPARE(result[1]->username(), QString("User 2"));
|
QCOMPARE(result[1]->username(), QString("User 1"));
|
||||||
QCOMPARE(result[1]->url(), QString("https://github.com/"));
|
QCOMPARE(result[1]->url(), QString("https://github.com/login"));
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const
|
QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const
|
||||||
@ -487,6 +479,7 @@ void TestBrowser::testValidURLs()
|
|||||||
urls["http:/example.com"] = false;
|
urls["http:/example.com"] = false;
|
||||||
urls["cmd://C:/Toolchains/msys2/usr/bin/mintty \"ssh jon@192.168.0.1:22\""] = true;
|
urls["cmd://C:/Toolchains/msys2/usr/bin/mintty \"ssh jon@192.168.0.1:22\""] = true;
|
||||||
urls["file:///Users/testUser/Code/test.html"] = true;
|
urls["file:///Users/testUser/Code/test.html"] = true;
|
||||||
|
urls["{REF:A@I:46C9B1FFBD4ABC4BBB260C6190BAD20C} "] = true;
|
||||||
|
|
||||||
QHashIterator<QString, bool> i(urls);
|
QHashIterator<QString, bool> i(urls);
|
||||||
while (i.hasNext()) {
|
while (i.hasNext()) {
|
||||||
@ -507,45 +500,128 @@ void TestBrowser::testBestMatchingCredentials()
|
|||||||
|
|
||||||
browserSettings()->setBestMatchOnly(true);
|
browserSettings()->setBestMatchOnly(true);
|
||||||
|
|
||||||
auto result = m_browserService->searchEntries(db, "https://github.com/loginpage", "https://github.com/loginpage");
|
QString siteUrl = "https://github.com/loginpage";
|
||||||
QCOMPARE(result.size(), 1);
|
auto result = m_browserService->searchEntries(db, siteUrl, siteUrl);
|
||||||
QCOMPARE(result[0]->url(), QString("https://github.com/loginpage"));
|
auto sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||||
|
QCOMPARE(sorted.size(), 1);
|
||||||
|
QCOMPARE(sorted[0]->url(), siteUrl);
|
||||||
|
|
||||||
result = m_browserService->searchEntries(db, "https://github.com/justsomepage", "https://github.com/justsomepage");
|
siteUrl = "https://github.com/justsomepage";
|
||||||
QCOMPARE(result.size(), 1);
|
result = m_browserService->searchEntries(db, siteUrl, siteUrl);
|
||||||
QCOMPARE(result[0]->url(), QString("https://github.com/justsomepage"));
|
sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||||
|
QCOMPARE(sorted.size(), 1);
|
||||||
|
QCOMPARE(sorted[0]->url(), siteUrl);
|
||||||
|
|
||||||
result = m_browserService->searchEntries(db, "https://github.com/", "https://github.com/");
|
siteUrl = "https://github.com/";
|
||||||
m_browserService->sortEntries(entries, "github.com", "https://github.com/", "https://github.com/");
|
result = m_browserService->searchEntries(db, siteUrl, siteUrl);
|
||||||
QCOMPARE(result.size(), 1);
|
sorted = m_browserService->sortEntries(entries, siteUrl, siteUrl);
|
||||||
QCOMPARE(result[0]->url(), QString("https://github.com/"));
|
QCOMPARE(sorted.size(), 1);
|
||||||
|
QCOMPARE(sorted[0]->url(), siteUrl);
|
||||||
|
|
||||||
|
// Without best-matching the URL with the path should be returned first
|
||||||
browserSettings()->setBestMatchOnly(false);
|
browserSettings()->setBestMatchOnly(false);
|
||||||
result = m_browserService->searchEntries(db, "https://github.com/loginpage", "https://github.com/loginpage");
|
siteUrl = "https://github.com/loginpage";
|
||||||
QCOMPARE(result.size(), 3);
|
result = m_browserService->searchEntries(db, siteUrl, siteUrl);
|
||||||
QCOMPARE(result[0]->url(), QString("https://github.com/loginpage"));
|
sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||||
|
QCOMPARE(sorted.size(), 3);
|
||||||
|
QCOMPARE(sorted[0]->url(), siteUrl);
|
||||||
|
|
||||||
// Test with subdomains
|
// Test with subdomains
|
||||||
QStringList subdomainsUrls = {"https://sub.github.com/loginpage",
|
QStringList subdomainsUrls = {"https://sub.github.com/loginpage",
|
||||||
"https://sub.github.com/justsomepage",
|
"https://sub.github.com/justsomepage",
|
||||||
"https://bus.github.com/justsomepage"};
|
"https://bus.github.com/justsomepage",
|
||||||
|
"https://subdomain.example.com/",
|
||||||
|
"https://subdomain.example.com",
|
||||||
|
"https://example.com"};
|
||||||
|
|
||||||
entries = createEntries(subdomainsUrls, root);
|
entries = createEntries(subdomainsUrls, root);
|
||||||
|
|
||||||
browserSettings()->setBestMatchOnly(true);
|
browserSettings()->setBestMatchOnly(true);
|
||||||
|
siteUrl = "https://sub.github.com/justsomepage";
|
||||||
|
result = m_browserService->searchEntries(db, siteUrl, siteUrl);
|
||||||
|
sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||||
|
QCOMPARE(sorted.size(), 1);
|
||||||
|
QCOMPARE(sorted[0]->url(), siteUrl);
|
||||||
|
|
||||||
result = m_browserService->searchEntries(
|
siteUrl = "https://github.com/justsomepage";
|
||||||
db, "https://sub.github.com/justsomepage", "https://sub.github.com/justsomepage");
|
result = m_browserService->searchEntries(db, siteUrl, siteUrl);
|
||||||
QCOMPARE(result.size(), 1);
|
sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||||
QCOMPARE(result[0]->url(), QString("https://sub.github.com/justsomepage"));
|
QCOMPARE(sorted.size(), 1);
|
||||||
|
QCOMPARE(sorted[0]->url(), siteUrl);
|
||||||
|
|
||||||
result = m_browserService->searchEntries(db, "https://github.com/justsomepage", "https://github.com/justsomepage");
|
siteUrl = "https://sub.github.com/justsomepage?wehavesomeextra=here";
|
||||||
QCOMPARE(result.size(), 1);
|
result = m_browserService->searchEntries(db, siteUrl, siteUrl);
|
||||||
QCOMPARE(result[0]->url(), QString("https://github.com/justsomepage"));
|
sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||||
|
QCOMPARE(sorted.size(), 1);
|
||||||
|
QCOMPARE(sorted[0]->url(), QString("https://sub.github.com/justsomepage"));
|
||||||
|
|
||||||
result = m_browserService->searchEntries(db,
|
// The matching should not care if there's a / path or not.
|
||||||
"https://sub.github.com/justsomepage?wehavesomeextra=here",
|
siteUrl = "https://subdomain.example.com/";
|
||||||
"https://sub.github.com/justsomepage?wehavesomeextra=here");
|
result = m_browserService->searchEntries(db, siteUrl, siteUrl);
|
||||||
QCOMPARE(result.size(), 1);
|
sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||||
QCOMPARE(result[0]->url(), QString("https://sub.github.com/justsomepage"));
|
QCOMPARE(sorted.size(), 2);
|
||||||
|
QCOMPARE(sorted[0]->url(), QString("https://subdomain.example.com/"));
|
||||||
|
QCOMPARE(sorted[1]->url(), QString("https://subdomain.example.com"));
|
||||||
|
|
||||||
|
// Entries with https://example.com should be still returned even if the site URL has a subdomain. Those have the
|
||||||
|
// best match.
|
||||||
|
db = QSharedPointer<Database>::create();
|
||||||
|
root = db->rootGroup();
|
||||||
|
QStringList domainUrls = {"https://example.com", "https://example.com", "https://other.example.com"};
|
||||||
|
entries = createEntries(domainUrls, root);
|
||||||
|
siteUrl = "https://subdomain.example.com";
|
||||||
|
result = m_browserService->searchEntries(db, siteUrl, siteUrl);
|
||||||
|
sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
|
||||||
|
|
||||||
|
QCOMPARE(sorted.size(), 2);
|
||||||
|
QCOMPARE(sorted[0]->url(), QString("https://example.com"));
|
||||||
|
QCOMPARE(sorted[1]->url(), QString("https://example.com"));
|
||||||
|
|
||||||
|
// https://github.com/keepassxreboot/keepassxc/issues/4754
|
||||||
|
db = QSharedPointer<Database>::create();
|
||||||
|
root = db->rootGroup();
|
||||||
|
QStringList fooUrls = {"https://example.com/foo", "https://example.com/bar"};
|
||||||
|
entries = createEntries(fooUrls, root);
|
||||||
|
|
||||||
|
for (const auto& url : fooUrls) {
|
||||||
|
result = m_browserService->searchEntries(db, url, url);
|
||||||
|
sorted = m_browserService->sortEntries(result, url, url);
|
||||||
|
QCOMPARE(sorted.size(), 1);
|
||||||
|
QCOMPARE(sorted[0]->url(), QString(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/keepassxreboot/keepassxc/issues/4734
|
||||||
|
db = QSharedPointer<Database>::create();
|
||||||
|
root = db->rootGroup();
|
||||||
|
QStringList testUrls = {"http://some.domain.tld/somePath", "http://some.domain.tld/otherPath"};
|
||||||
|
entries = createEntries(testUrls, root);
|
||||||
|
|
||||||
|
for (const auto& url : testUrls) {
|
||||||
|
result = m_browserService->searchEntries(db, url, url);
|
||||||
|
sorted = m_browserService->sortEntries(result, url, url);
|
||||||
|
QCOMPARE(sorted.size(), 1);
|
||||||
|
QCOMPARE(sorted[0]->url(), QString(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestBrowser::testBestMatchingWithAdditionalURLs()
|
||||||
|
{
|
||||||
|
auto db = QSharedPointer<Database>::create();
|
||||||
|
auto* root = db->rootGroup();
|
||||||
|
|
||||||
|
QStringList urls = {"https://github.com/loginpage", "https://test.github.com/", "https://github.com/"};
|
||||||
|
|
||||||
|
auto entries = createEntries(urls, root);
|
||||||
|
browserSettings()->setBestMatchOnly(true);
|
||||||
|
|
||||||
|
// Add an additional URL to the first entry
|
||||||
|
entries.first()->attributes()->set(BrowserService::ADDITIONAL_URL, "https://test.github.com/anotherpage");
|
||||||
|
|
||||||
|
// The first entry should be triggered
|
||||||
|
auto result = m_browserService->searchEntries(
|
||||||
|
db, "https://test.github.com/anotherpage", "https://test.github.com/anotherpage");
|
||||||
|
auto sorted = m_browserService->sortEntries(
|
||||||
|
result, "https://test.github.com/anotherpage", "https://test.github.com/anotherpage");
|
||||||
|
QCOMPARE(sorted.length(), 1);
|
||||||
|
QCOMPARE(sorted[0]->url(), urls[0]);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ private slots:
|
|||||||
|
|
||||||
void testBaseDomain();
|
void testBaseDomain();
|
||||||
void testSortPriority();
|
void testSortPriority();
|
||||||
|
void testSortPriority_data();
|
||||||
void testSearchEntries();
|
void testSearchEntries();
|
||||||
void testSearchEntriesByUUID();
|
void testSearchEntriesByUUID();
|
||||||
void testSearchEntriesWithPort();
|
void testSearchEntriesWithPort();
|
||||||
@ -49,6 +50,7 @@ private slots:
|
|||||||
void testSortEntries();
|
void testSortEntries();
|
||||||
void testValidURLs();
|
void testValidURLs();
|
||||||
void testBestMatchingCredentials();
|
void testBestMatchingCredentials();
|
||||||
|
void testBestMatchingWithAdditionalURLs();
|
||||||
|
|
||||||
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