diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 8a64701cc..9753dad29 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -24,18 +24,20 @@ set(DOC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) # Build html documentation on all platforms +file(GLOB html_depends ${DOC_DIR}/topics/* ${DOC_DIR}/styles/* ${DOC_DIR}/images/*) add_custom_command(OUTPUT KeePassXC_GettingStarted.html COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_GettingStarted.html ${DOC_DIR}/GettingStarted.adoc - DEPENDS ${DOC_DIR}/topics/* ${DOC_DIR}/styles/* ${DOC_DIR}/images/* ${DOC_DIR}/GettingStarted.adoc + DEPENDS ${html_depends} ${DOC_DIR}/GettingStarted.adoc VERBATIM) add_custom_command(OUTPUT KeePassXC_UserGuide.html COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_UserGuide.html ${DOC_DIR}/UserGuide.adoc - DEPENDS ${DOC_DIR}/topics/* ${DOC_DIR}/styles/* ${DOC_DIR}/images/* ${DOC_DIR}/UserGuide.adoc + DEPENDS ${html_depends} ${DOC_DIR}/UserGuide.adoc WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} VERBATIM) +file(GLOB styles_depends ${DOC_DIR}/styles/*) add_custom_command(OUTPUT KeePassXC_KeyboardShortcuts.html COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_KeyboardShortcuts.html ${DOC_DIR}/topics/KeyboardShortcuts.adoc - DEPENDS ${DOC_DIR}/topics/KeyboardShortcuts.adoc ${DOC_DIR}/styles/* + DEPENDS ${DOC_DIR}/topics/KeyboardShortcuts.adoc ${styles_depends} VERBATIM) add_custom_target(docs ALL DEPENDS KeePassXC_GettingStarted.html KeePassXC_UserGuide.html KeePassXC_KeyboardShortcuts.html) @@ -50,11 +52,11 @@ install(FILES if(APPLE OR UNIX) add_custom_command(OUTPUT keepassxc.1 COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -b manpage ${DOC_DIR}/man/keepassxc.1.adoc - DEPENDS ${DOC_DIR}/man/* + DEPENDS ${DOC_DIR}/man/keepassxc.1.adoc VERBATIM) add_custom_command(OUTPUT keepassxc-cli.1 COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -b manpage ${DOC_DIR}/man/keepassxc-cli.1.adoc - DEPENDS ${DOC_DIR}/man/* + DEPENDS ${DOC_DIR}/man/keepassxc-cli.1.adoc VERBATIM) add_custom_target(manpages ALL DEPENDS keepassxc.1 keepassxc-cli.1) diff --git a/docs/man/keepassxc-cli.1.adoc b/docs/man/keepassxc-cli.1.adoc index 13d3ec011..d36e00014 100644 --- a/docs/man/keepassxc-cli.1.adoc +++ b/docs/man/keepassxc-cli.1.adoc @@ -1,10 +1,28 @@ +// Copyright (C) 2017 Manolis Agkopian +// Copyright (C) 2020 KeePassXC Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 or (at your option) +// version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + = keepassxc-cli(1) -:docdate: 2020-07-05 +:docdate: 2020-07-10 :doctype: manpage +:revnumber: 2.6.0 +:mansource: KeePassXC {revnumber} :manmanual: General Commands Manual == NAME -keepassxc-cli - command line interface for the KeePassXC password manager. +keepassxc-cli - command line interface for the KeePassXC password manager == SYNOPSIS *keepassxc-cli* _command_ [_options_] @@ -16,21 +34,21 @@ It provides the ability to query and modify the entries of a KeePass database, d == COMMANDS *add* [_options_] <__database__> <__entry__>:: Adds a new entry to a database. - A password can be generated (_-g_ option), or a prompt can be displayed to input the password (_-p_ option). - The same password generation options as documented for the generate command can be used when the _-g_ option is set. + A password can be generated (*-g* option), or a prompt can be displayed to input the password (*-p* option). + The same password generation options as documented for the generate command can be used when the *-g* option is set. *analyze* [_options_] <__database__>:: Analyzes passwords in a database for weaknesses. *clip* [_options_] <__database__> <__entry__> [_timeout_]:: - Copies an attribute or the current TOTP (if the _-t_ option is specified) of a database entry to the clipboard. - If no attribute name is specified using the _-a_ option, the password is copied. + Copies an attribute or the current TOTP (if the *-t* option is specified) of a database entry to the clipboard. + If no attribute name is specified using the *-a* option, the password is copied. If multiple entries with the same name exist in different groups, only the attribute for the first one is copied. For copying the attribute of an entry in a specific group, the group path to the entry should be specified as well, instead of just the name. Optionally, a timeout in seconds can be specified to automatically clear the clipboard. *close*:: - In interactive mode, closes the currently opened database (see _open_). + In interactive mode, closes the currently opened database (see *open*). *db-create* [_options_] <__database__>:: Creates a new database with a password and/or a key file. @@ -45,8 +63,8 @@ It provides the ability to query and modify the entries of a KeePass database, d *edit* [_options_] <__database__> <__entry__>:: Edits a database entry. - A password can be generated (_-g_ option), or a prompt can be displayed to input the password (_-p_ option). - The same password generation options as documented for the generate command can be used when the _-g_ option is set. + A password can be generated (*-g* option), or a prompt can be displayed to input the password (*-p* option). + The same password generation options as documented for the generate command can be used when the *-g* option is set. *estimate* [_options_] [_password_]:: Estimates the entropy of a password. @@ -54,7 +72,7 @@ It provides the ability to query and modify the entries of a KeePass database, d *exit*:: Exits interactive mode. - Synonymous with _quit_. + Synonymous with *quit*. *export* [_options_] <__database__>:: Exports the content of a database to standard output in the specified format (defaults to XML). @@ -78,7 +96,7 @@ It provides the ability to query and modify the entries of a KeePass database, d *merge* [_options_] <__database1__> <__database2__>:: Merges two databases together. The first database file is going to be replaced by the result of the merge, for that reason it is advisable to keep a backup of the two database files before attempting a merge. - In the case that both databases make use of the same credentials, the _--same-credentials_ or _-s_ option can be used. + In the case that both databases make use of the same credentials, the *--same-credentials* or *-s* option can be used. *mkdir* [_options_] <__database__> <__group__>:: Adds a new group to a database. @@ -88,11 +106,11 @@ It provides the ability to query and modify the entries of a KeePass database, d *open* [_options_] <__database__>:: Opens the given database in a shell-style interactive mode. - This is useful for performing multiple operations on a single database (e.g. _ls_ followed by _show_). + This is useful for performing multiple operations on a single database (e.g. *ls* followed by *show*). *quit*:: Exits interactive mode. - Synonymous with _exit_. + Synonymous with *exit*. *rm* [_options_] <__database__> <__entry__>:: Removes an entry from a database. @@ -107,7 +125,7 @@ It provides the ability to query and modify the entries of a KeePass database, d *show* [_options_] <__database__> <__entry__>:: Shows the title, username, password, URL and notes of a database entry. Can also show the current TOTP. - Regarding the occurrence of multiple entries with the same name in different groups, everything stated in the _clip_ command section also applies here. + Regarding the occurrence of multiple entries with the same name in different groups, everything stated in the *clip* command section also applies here. == OPTIONS === General options @@ -151,7 +169,7 @@ It provides the ability to query and modify the entries of a KeePass database, d Uses the same credentials for unlocking both databases. === Add and edit options -The same password generation options as documented for the generate command can be used with those 2 commands when the -g option is set. +The same password generation options as documented for the generate command can be used with those 2 commands when the *-g* option is set. *-u*, *--username* <__username__>:: Specifies the username of the entry. @@ -183,7 +201,7 @@ The same password generation options as documented for the generate command can *-a*, *--attribute*:: Copies the specified attribute to the clipboard. If no attribute is specified, the password attribute is the default. - For example, "_-a_ username" would copy the username to the clipboard. + For example, "*-a* *username*" would copy the username to the clipboard. [Default: password] *-t*, *--totp*:: @@ -204,7 +222,7 @@ The same password generation options as documented for the generate command can *-a*, *--attributes* <__attribute__>...:: Shows the named attributes. This option can be specified more than once, with each attribute shown one-per-line in the given order. - If no attributes are specified and _-t_ is not specified, a summary of the default attributes is given. + If no attributes are specified and *-t* is not specified, a summary of the default attributes is given. Protected attributes will be displayed in clear text if specified explicitly by this option. *-s*, *--show-protected*:: @@ -274,9 +292,11 @@ The same password generation options as documented for the generate command can Include characters from every selected group. [Default: Disabled] -== REPORTING BUGS -Bugs and feature requests can be reported on GitHub at https://github.com/keepassxreboot/keepassxc/issues. +include::section-notes.adoc[] == AUTHOR -This manual page was originally written by Manolis Agkopian , -and is maintained by the KeePassXC Team . +This manual page was originally written by Manolis Agkopian . + +include::section-reporting-bugs.adoc[] + +include::section-copyright.adoc[] diff --git a/docs/man/keepassxc.1.adoc b/docs/man/keepassxc.1.adoc index 965f7ac46..eb1a44480 100644 --- a/docs/man/keepassxc.1.adoc +++ b/docs/man/keepassxc.1.adoc @@ -1,10 +1,28 @@ +// Copyright (C) 2019 Janek Bevendorff +// Copyright (C) 2020 KeePassXC Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 or (at your option) +// version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + = keepassxc(1) -:docdate: 2020-07-05 +:docdate: 2020-07-10 :doctype: manpage +:revnumber: 2.6.0 +:mansource: KeePassXC {revnumber} :manmanual: General Commands Manual == NAME -keepassxc - password manager +keepassxc - a modern open-source password manager == SYNOPSIS *keepassxc* [_options_] [_filename(s)_] @@ -23,19 +41,25 @@ Your wallet works offline and requires no Internet connection. Displays version information. *--config* <__config__>:: - Path to a custom config file + Path to a custom config file. *--keyfile* <__keyfile__>:: - Key file of the database + Key file of the database. *--pw-stdin*:: - Read password of the database from stdin + Read password of the database from stdin. *--pw*, *--parent-window* <__handle__>:: - Parent window handle + Parent window handle. *--debug-info*:: Displays debugging information. +include::section-notes.adoc[] + == AUTHOR -This manual page is maintained by the KeePassXC Team . +This manual page was originally written by Janek Bevendorff . + +include::section-reporting-bugs.adoc[] + +include::section-copyright.adoc[] diff --git a/docs/man/section-copyright.adoc b/docs/man/section-copyright.adoc new file mode 100644 index 000000000..ae35017c1 --- /dev/null +++ b/docs/man/section-copyright.adoc @@ -0,0 +1,19 @@ +// Copyright (C) 2020 KeePassXC Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 or (at your option) +// version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +== COPYRIGHT +Copyright \(C) 2016-2020 KeePassXC Team + +*KeePassXC* code is licensed under GPL-2 or GPL-3. diff --git a/docs/man/section-notes.adoc b/docs/man/section-notes.adoc new file mode 100644 index 000000000..4c87dfe0b --- /dev/null +++ b/docs/man/section-notes.adoc @@ -0,0 +1,27 @@ +// Copyright (C) 2020 KeePassXC Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 or (at your option) +// version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +== NOTES +*Project homepage*:: + https://keepassxc.org + +*QuickStart Guide*:: + https://keepassxc.org/docs/KeePassXC_GettingStarted.html + +*User Guide*:: + https://keepassxc.org/docs/KeePassXC_UserGuide.html + +*Git repository*:: + https://github.com/keepassxreboot/keepassxc.git diff --git a/docs/man/section-reporting-bugs.adoc b/docs/man/section-reporting-bugs.adoc new file mode 100644 index 000000000..e0c0cee37 --- /dev/null +++ b/docs/man/section-reporting-bugs.adoc @@ -0,0 +1,17 @@ +// Copyright (C) 2020 KeePassXC Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 or (at your option) +// version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +== REPORTING BUGS +Bugs and feature requests can be reported on GitHub at https://github.com/keepassxreboot/keepassxc/issues. diff --git a/share/linux/org.keepassxc.KeePassXC.appdata.xml b/share/linux/org.keepassxc.KeePassXC.appdata.xml index 9227251d6..62c17b333 100644 --- a/share/linux/org.keepassxc.KeePassXC.appdata.xml +++ b/share/linux/org.keepassxc.KeePassXC.appdata.xml @@ -28,23 +28,23 @@ - https://keepassxc.org/images/screenshots/linux/screen_001.png + https://keepassxc.org/images/screenshots/thumbs/welcome_screen.png Create, Import or Open Databases - https://keepassxc.org/images/screenshots/linux/screen_002.png + https://keepassxc.org/images/screenshots/thumbs/database_view.png Organize with Groups and Entries - https://keepassxc.org/images/screenshots/linux/screen_003.png + https://keepassxc.org/images/screenshots/thumbs/edit_entry.png Database Entry - https://keepassxc.org/images/screenshots/linux/screen_004.png + https://keepassxc.org/images/screenshots/thumbs/edit_entry_icons.png Icon Selection for Entry - https://keepassxc.org/images/screenshots/linux/screen_006.png + https://keepassxc.org/images/screenshots/thumbs/password_generator_advanced.png Password Generator @@ -614,4 +614,5 @@ + diff --git a/share/linux/org.keepassxc.KeePassXC.desktop b/share/linux/org.keepassxc.KeePassXC.desktop index 541af3c02..693232e9e 100644 --- a/share/linux/org.keepassxc.KeePassXC.desktop +++ b/share/linux/org.keepassxc.KeePassXC.desktop @@ -1,13 +1,39 @@ [Desktop Entry] Name=KeePassXC GenericName=Password Manager +GenericName[ar]=مدير كلمات المرور +GenericName[bg]=Мениджър на пароли +GenericName[ca]=Gestor de contrasenyes +GenericName[cs]=Aplikace pro správu hesel GenericName[da]=Adgangskodehåndtering GenericName[de]=Passwortverwaltung GenericName[es]=Gestor de contraseñas +GenericName[et]=Paroolihaldur +GenericName[fi]=Salasanamanageri GenericName[fr]=Gestionnaire de mot de passe +GenericName[hu]=Jelszókezelő +GenericName[id]=Pengelola Sandi +GenericName[it]=Gestione password +GenericName[ja]=パスワードマネージャー +GenericName[ko]=암호 관리자 +GenericName[lt]=Slaptažodžių tvarkytuvė +GenericName[nb]=Passordhåndterer +GenericName[nl]=Wachtwoordbeheer +GenericName[pl]=Menedżer haseł +GenericName[pt_BR]=Gerenciador de Senhas +GenericName[pt]=Gestor de palavras-passe +GenericName[ro]=Manager de parole GenericName[ru]=менеджер паролей +GenericName[sk]=Správca hesiel +GenericName[sv]=Lösenordshanterare +GenericName[th]=แอพจัดการรหัสผ่าน +GenericName[tr]=Parola yöneticisi +GenericName[uk]=Розпорядник паролів +GenericName[zh_CN]=密码管理器 +GenericName[zh_TW]=密碼管理員 Comment=Community-driven port of the Windows application “KeePass Password Safe” Comment[da]=Fællesskabsdrevet port af Windows-programmet “KeePass Password Safe” +Comment[et]=Kogukonna arendatav port Windowsi programmist KeePass Password Safe Exec=keepassxc %f TryExec=keepassxc Icon=keepassxc diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 01ef9d762..913a3c2ad 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -269,7 +269,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c /** * Single Autotype entry-point function - * Perfom autotype sequence in the active window + * Look up the Auto-Type sequence for the given entry then perfom Auto-Type in the active window */ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) { @@ -285,6 +285,19 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) executeAutoTypeActions(entry, hideWindow, sequences.first()); } +/** + * Extra Autotype entry-point function + * Perfom Auto-Type of the directly specified sequence in the active window + */ +void AutoType::performAutoTypeWithSequence(const Entry* entry, const QString& sequence, QWidget* hideWindow) +{ + if (!m_plugin) { + return; + } + + executeAutoTypeActions(entry, hideWindow, sequence); +} + void AutoType::startGlobalAutoType() { m_windowForGlobal = m_plugin->activeWindow(); diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index 7f9e3ab22..78cd42f88 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -48,6 +48,7 @@ public: static bool checkHighDelay(const QString& string); static bool verifyAutoTypeSyntax(const QString& sequence); void performAutoType(const Entry* entry, QWidget* hideWindow = nullptr); + void performAutoTypeWithSequence(const Entry* entry, const QString& sequence, QWidget* hideWindow = nullptr); inline bool isAvailable() { diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index 1f54e33ca..e0b8dacc2 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -413,7 +413,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid, } // Sort results - pwEntries = sortEntries(pwEntries, host, submitUrl); + pwEntries = sortEntries(pwEntries, host, submitUrl, url); // Fill the list QJsonArray result; @@ -698,7 +698,10 @@ void BrowserService::convertAttributesToCustomData(QSharedPointer db) } } -QList BrowserService::sortEntries(QList& pwEntries, const QString& host, const QString& entryUrl) +QList BrowserService::sortEntries(QList& pwEntries, + const QString& host, + const QString& entryUrl, + const QString& fullUrl) { QUrl url(entryUrl); if (url.scheme().isEmpty()) { @@ -712,7 +715,7 @@ QList BrowserService::sortEntries(QList& pwEntries, const QStrin // Build map of prioritized entries QMultiMap priorities; for (auto* entry : pwEntries) { - priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry); + priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl, fullUrl), entry); } QList results; @@ -895,7 +898,8 @@ Group* BrowserService::getDefaultEntryGroup(const QSharedPointer& sele int BrowserService::sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, - const QString& baseSubmitUrl) const + const QString& baseSubmitUrl, + const QString& fullUrl) const { QUrl url(entry->url()); if (url.scheme().isEmpty()) { @@ -914,9 +918,12 @@ int BrowserService::sortPriority(const Entry* entry, if (!url.host().contains(".") && url.host() != "localhost") { return 0; } - if (submitUrl == entryURL) { + if (fullUrl == entryURL) { return 100; } + if (submitUrl == entryURL) { + return 95; + } if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL) { return 90; } @@ -1025,7 +1032,17 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& url, cons // Match the subdomains with the limited wildcard if (siteQUrl.host().endsWith(entryQUrl.host())) { - return true; + if (!browserSettings()->bestMatchOnly()) { + 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; diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index 6567a44d0..77635cfe1 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -119,7 +119,8 @@ private: QList searchEntries(const QSharedPointer& db, const QString& url, const QString& submitUrl); QList searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList); - QList sortEntries(QList& pwEntries, const QString& host, const QString& submitUrl); + QList + sortEntries(QList& pwEntries, const QString& host, const QString& submitUrl, const QString& fullUrl); QList confirmEntries(QList& pwEntriesToConfirm, const QString& url, const QString& host, @@ -130,8 +131,11 @@ private: QJsonArray getChildrenFromGroup(Group* group); Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm); Group* getDefaultEntryGroup(const QSharedPointer& selectedDb = {}); - int - sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const; + int sortPriority(const Entry* entry, + const QString& host, + const QString& submitUrl, + const QString& baseSubmitUrl, + const QString& fullUrl) const; bool schemeFound(const QString& url); bool removeFirstDomain(QString& hostname); bool handleURL(const QString& entryUrl, const QString& url, const QString& submitUrl); diff --git a/src/cli/Info.cpp b/src/cli/Info.cpp index c57472770..800996d64 100644 --- a/src/cli/Info.cpp +++ b/src/cli/Info.cpp @@ -27,7 +27,7 @@ Info::Info() { - name = QString("db-show"); + name = QString("db-info"); description = QObject::tr("Show a database's information."); } diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 65a271c2e..0322d353c 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -481,6 +481,8 @@ void Entry::updateTotp() m_attributes->value(Totp::ATTRIBUTE_SEED)); } else if (m_attributes->contains(Totp::ATTRIBUTE_OTP)) { m_data.totpSettings = Totp::parseSettings(m_attributes->value(Totp::ATTRIBUTE_OTP)); + } else { + m_data.totpSettings.reset(); } } diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index 66970aee3..2086e3302 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -51,6 +51,13 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget* parent) this, // receiver SLOT(gnomeSessionStatusChanged(uint))); + sessionBus.connect("org.xfce.ScreenSaver", // service + "/org/xfce/ScreenSaver", // path + "org.xfce.ScreenSaver", // interface + "ActiveChanged", // signal name + this, // receiver + SLOT(freedesktopScreenSaver(bool))); + systemBus.connect("org.freedesktop.login1", // service "/org/freedesktop/login1", // path "org.freedesktop.login1.Manager", // interface diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 1b3eafcca..d29e92bff 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -331,11 +331,15 @@ namespace Tools #if defined(Q_OS_WIN) QRegularExpression varRe("\\%([A-Za-z][A-Za-z0-9_]*)\\%"); + QString homeEnv = "USERPROFILE"; #else QRegularExpression varRe("\\$([A-Za-z][A-Za-z0-9_]*)"); - subbed.replace("~", environment.value("HOME")); + QString homeEnv = "HOME"; #endif + if (subbed.startsWith("~/") || subbed.startsWith("~\\")) + subbed.replace(0, 1, environment.value(homeEnv)); + QRegularExpressionMatch match; do { diff --git a/src/core/Translator.cpp b/src/core/Translator.cpp index ff7dafde5..d97a35dd1 100644 --- a/src/core/Translator.cpp +++ b/src/core/Translator.cpp @@ -71,6 +71,8 @@ bool Translator::installTranslator(const QStringList& languages, const QString& QScopedPointer translator(new QTranslator(qApp)); if (translator->load(locale, "keepassx_", "", path)) { return QCoreApplication::installTranslator(translator.take()); + } else if (translator->load(locale, "keepassx_", "", QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { + return QCoreApplication::installTranslator(translator.take()); } } diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp index 691115368..8d958ab7a 100644 --- a/src/gui/ApplicationSettingsWidget.cpp +++ b/src/gui/ApplicationSettingsWidget.cpp @@ -27,6 +27,7 @@ #include "core/Global.h" #include "core/Resources.h" #include "core/Translator.h" +#include "gui/MainWindow.h" #include "gui/osutils/OSUtils.h" #include "MessageBox.h" @@ -324,7 +325,15 @@ void ApplicationSettingsWidget::saveSettings() config()->set(Config::AutoTypeEntryURLMatch, m_generalUi->autoTypeEntryURLMatchCheckBox->isChecked()); config()->set(Config::FaviconDownloadTimeout, m_generalUi->faviconTimeoutSpinBox->value()); - config()->set(Config::GUI_Language, m_generalUi->languageComboBox->currentData().toString()); + auto language = m_generalUi->languageComboBox->currentData().toString(); + if (config()->get(Config::GUI_Language) != language) { + QTimer::singleShot(200, [] { + getMainWindow()->restartApp( + tr("You must restart the application to set the new language. Would you like to restart now?")); + }); + } + config()->set(Config::GUI_Language, language); + config()->set(Config::GUI_MovableToolbar, m_generalUi->toolbarMovableCheckBox->isChecked()); config()->set(Config::GUI_MonospaceNotes, m_generalUi->monospaceNotesCheckBox->isChecked()); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 61f2b2163..3e1d3192b 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -799,6 +799,38 @@ void DatabaseWidget::performAutoType() } } +void DatabaseWidget::performAutoTypeUsername() +{ + auto currentEntry = currentSelectedEntry(); + if (currentEntry) { + autoType()->performAutoTypeWithSequence(currentEntry, QStringLiteral("{USERNAME}"), window()); + } +} + +void DatabaseWidget::performAutoTypeUsernameEnter() +{ + auto currentEntry = currentSelectedEntry(); + if (currentEntry) { + autoType()->performAutoTypeWithSequence(currentEntry, QStringLiteral("{USERNAME}{ENTER}"), window()); + } +} + +void DatabaseWidget::performAutoTypePassword() +{ + auto currentEntry = currentSelectedEntry(); + if (currentEntry) { + autoType()->performAutoTypeWithSequence(currentEntry, QStringLiteral("{PASSWORD}"), window()); + } +} + +void DatabaseWidget::performAutoTypePasswordEnter() +{ + auto currentEntry = currentSelectedEntry(); + if (currentEntry) { + autoType()->performAutoTypeWithSequence(currentEntry, QStringLiteral("{PASSWORD}{ENTER}"), window()); + } +} + void DatabaseWidget::openUrl() { auto currentEntry = currentSelectedEntry(); @@ -1813,7 +1845,7 @@ bool DatabaseWidget::save() m_blockAutoSave = true; ++m_saveAttempts; - auto focusWidget = qApp->focusWidget(); + QPointer focusWidget(qApp->focusWidget()); // TODO: Make this async // Lock out interactions @@ -1887,7 +1919,7 @@ bool DatabaseWidget::saveAs() bool ok = false; if (!newFilePath.isEmpty()) { - auto focusWidget = qApp->focusWidget(); + QPointer focusWidget(qApp->focusWidget()); // Lock out interactions m_entryView->setDisabled(true); diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index a31dfd37b..2564977dc 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -186,6 +186,10 @@ public slots: void removeFromAgent(); #endif void performAutoType(); + void performAutoTypeUsername(); + void performAutoTypeUsernameEnter(); + void performAutoTypePassword(); + void performAutoTypePasswordEnter(); void openUrl(); void downloadSelectedFavicons(); void downloadAllFavicons(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 9751a3e77..d3d624e91 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -126,6 +126,7 @@ MainWindow::MainWindow() m_entryContextMenu->addAction(m_ui->menuEntryTotp->menuAction()); m_entryContextMenu->addSeparator(); m_entryContextMenu->addAction(m_ui->actionEntryAutoType); + m_entryContextMenu->addAction(m_ui->menuEntryAutoTypeWithSequence->menuAction()); m_entryContextMenu->addSeparator(); m_entryContextMenu->addAction(m_ui->actionEntryEdit); m_entryContextMenu->addAction(m_ui->actionEntryClone); @@ -220,7 +221,12 @@ MainWindow::MainWindow() m_ui->toolbarSeparator->setVisible(false); m_showToolbarSeparator = config()->get(Config::GUI_ApplicationTheme).toString() != "classic"; - m_ui->actionEntryAutoType->setVisible(autoType()->isAvailable()); + bool isAutoTypeAvailable = autoType()->isAvailable(); + m_ui->actionEntryAutoType->setVisible(isAutoTypeAvailable); + m_ui->actionEntryAutoTypeUsername->setVisible(isAutoTypeAvailable); + m_ui->actionEntryAutoTypeUsernameEnter->setVisible(isAutoTypeAvailable); + m_ui->actionEntryAutoTypePassword->setVisible(isAutoTypeAvailable); + m_ui->actionEntryAutoTypePasswordEnter->setVisible(isAutoTypeAvailable); m_inactivityTimer = new InactivityTimer(this); connect(m_inactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(lockDatabasesAfterInactivity())); @@ -352,6 +358,11 @@ MainWindow::MainWindow() m_ui->actionEntryEdit->setIcon(resources()->icon("entry-edit")); m_ui->actionEntryDelete->setIcon(resources()->icon("entry-delete")); m_ui->actionEntryAutoType->setIcon(resources()->icon("auto-type")); + m_ui->menuEntryAutoTypeWithSequence->setIcon(resources()->icon("auto-type")); + m_ui->actionEntryAutoTypeUsername->setIcon(resources()->icon("auto-type")); + m_ui->actionEntryAutoTypeUsernameEnter->setIcon(resources()->icon("auto-type")); + m_ui->actionEntryAutoTypePassword->setIcon(resources()->icon("auto-type")); + m_ui->actionEntryAutoTypePasswordEnter->setIcon(resources()->icon("auto-type")); m_ui->actionEntryMoveUp->setIcon(resources()->icon("move-up")); m_ui->actionEntryMoveDown->setIcon(resources()->icon("move-down")); m_ui->actionEntryCopyUsername->setIcon(resources()->icon("username-copy")); @@ -446,6 +457,14 @@ MainWindow::MainWindow() m_actionMultiplexer.connect(m_ui->actionEntryCopyURL, SIGNAL(triggered()), SLOT(copyURL())); m_actionMultiplexer.connect(m_ui->actionEntryCopyNotes, SIGNAL(triggered()), SLOT(copyNotes())); m_actionMultiplexer.connect(m_ui->actionEntryAutoType, SIGNAL(triggered()), SLOT(performAutoType())); + m_actionMultiplexer.connect( + m_ui->actionEntryAutoTypeUsername, SIGNAL(triggered()), SLOT(performAutoTypeUsername())); + m_actionMultiplexer.connect( + m_ui->actionEntryAutoTypeUsernameEnter, SIGNAL(triggered()), SLOT(performAutoTypeUsernameEnter())); + m_actionMultiplexer.connect( + m_ui->actionEntryAutoTypePassword, SIGNAL(triggered()), SLOT(performAutoTypePassword())); + m_actionMultiplexer.connect( + m_ui->actionEntryAutoTypePasswordEnter, SIGNAL(triggered()), SLOT(performAutoTypePasswordEnter())); m_actionMultiplexer.connect(m_ui->actionEntryOpenUrl, SIGNAL(triggered()), SLOT(openUrl())); m_actionMultiplexer.connect(m_ui->actionEntryDownloadIcon, SIGNAL(triggered()), SLOT(downloadSelectedFavicons())); #ifdef WITH_XC_SSHAGENT @@ -711,6 +730,13 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected); m_ui->menuEntryTotp->setEnabled(singleEntrySelected); m_ui->actionEntryAutoType->setEnabled(singleEntrySelected); + m_ui->menuEntryAutoTypeWithSequence->setEnabled(singleEntrySelected); + m_ui->actionEntryAutoTypeUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername()); + m_ui->actionEntryAutoTypeUsernameEnter->setEnabled(singleEntrySelected + && dbWidget->currentEntryHasUsername()); + m_ui->actionEntryAutoTypePassword->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPassword()); + m_ui->actionEntryAutoTypePasswordEnter->setEnabled(singleEntrySelected + && dbWidget->currentEntryHasPassword()); m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl()); m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); @@ -761,6 +787,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryCopyURL, m_ui->actionEntryOpenUrl, m_ui->actionEntryAutoType, + m_ui->menuEntryAutoTypeWithSequence->menuAction(), m_ui->actionEntryDownloadIcon, m_ui->actionEntryCopyNotes, m_ui->actionEntryCopyTitle, @@ -1679,17 +1706,20 @@ void MainWindow::initViewMenu() } } - connect(themeActions, &QActionGroup::triggered, this, [this](QAction* action) { - if (action->data() != config()->get(Config::GUI_ApplicationTheme)) { - config()->set(Config::GUI_ApplicationTheme, action->data()); + connect(themeActions, &QActionGroup::triggered, this, [this, theme](QAction* action) { + config()->set(Config::GUI_ApplicationTheme, action->data()); + if (action->data() != theme) { restartApp(tr("You must restart the application to apply this setting. Would you like to restart now?")); } }); - m_ui->actionCompactMode->setChecked(config()->get(Config::GUI_CompactMode).toBool()); - connect(m_ui->actionCompactMode, &QAction::toggled, this, [this](bool checked) { + bool compact = config()->get(Config::GUI_CompactMode).toBool(); + m_ui->actionCompactMode->setChecked(compact); + connect(m_ui->actionCompactMode, &QAction::toggled, this, [this, compact](bool checked) { config()->set(Config::GUI_CompactMode, checked); - restartApp(tr("You must restart the application to apply this setting. Would you like to restart now?")); + if (checked != compact) { + restartApp(tr("You must restart the application to apply this setting. Would you like to restart now?")); + } }); m_ui->actionShowToolbar->setChecked(!config()->get(Config::GUI_HideToolbar).toBool()); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 10951f3c0..93488dc05 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -310,6 +310,18 @@ + + + false + + + Perform Auto-Type Sequence + + + + + + @@ -324,6 +336,7 @@ + @@ -680,6 +693,38 @@ Perform &Auto-Type + + + false + + + {USERNAME} + + + + + false + + + {USERNAME}{ENTER} + + + + + false + + + {PASSWORD} + + + + + false + + + {PASSWORD}{ENTER} + + Download &Favicon diff --git a/src/gui/PasswordEdit.cpp b/src/gui/PasswordEdit.cpp index 487db8768..943164d4c 100644 --- a/src/gui/PasswordEdit.cpp +++ b/src/gui/PasswordEdit.cpp @@ -50,21 +50,28 @@ PasswordEdit::PasswordEdit(QWidget* parent) passwordFont.setLetterSpacing(QFont::PercentageSpacing, 110); setFont(passwordFont); + // Prevent conflicts with global Mac shortcuts (force Control on all platforms) +#ifdef Q_OS_MAC + auto modifier = Qt::META; +#else + auto modifier = Qt::CTRL; +#endif + m_toggleVisibleAction = new QAction( resources()->icon("password-show-off"), - tr("Toggle Password (%1)").arg(QKeySequence(Qt::CTRL + Qt::Key_H).toString(QKeySequence::NativeText)), + tr("Toggle Password (%1)").arg(QKeySequence(modifier + Qt::Key_H).toString(QKeySequence::NativeText)), nullptr); m_toggleVisibleAction->setCheckable(true); - m_toggleVisibleAction->setShortcut(Qt::CTRL + Qt::Key_H); + m_toggleVisibleAction->setShortcut(modifier + Qt::Key_H); m_toggleVisibleAction->setShortcutContext(Qt::WidgetShortcut); addAction(m_toggleVisibleAction, QLineEdit::TrailingPosition); connect(m_toggleVisibleAction, &QAction::triggered, this, &PasswordEdit::setShowPassword); m_passwordGeneratorAction = new QAction( resources()->icon("password-generator"), - tr("Generate Password (%1)").arg(QKeySequence(Qt::CTRL + Qt::Key_G).toString(QKeySequence::NativeText)), + tr("Generate Password (%1)").arg(QKeySequence(modifier + Qt::Key_G).toString(QKeySequence::NativeText)), nullptr); - m_passwordGeneratorAction->setShortcut(Qt::CTRL + Qt::Key_G); + m_passwordGeneratorAction->setShortcut(modifier + Qt::Key_G); m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut); addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition); m_passwordGeneratorAction->setVisible(false); diff --git a/src/sshagent/SSHAgent.cpp b/src/sshagent/SSHAgent.cpp index 20284c685..c43cc37a6 100644 --- a/src/sshagent/SSHAgent.cpp +++ b/src/sshagent/SSHAgent.cpp @@ -447,12 +447,8 @@ void SSHAgent::databaseLocked() if (!removeIdentity(key)) { emit error(m_error); } - it = m_addedKeys.erase(it); - } else { - // don't remove it yet - m_addedKeys[key].second = false; - ++it; } + it = m_addedKeys.erase(it); } } diff --git a/src/totp/totp.cpp b/src/totp/totp.cpp index 105196fcd..1936cce75 100644 --- a/src/totp/totp.cpp +++ b/src/totp/totp.cpp @@ -113,7 +113,7 @@ QSharedPointer Totp::parseSettings(const QString& rawSettings, c } // Bound digits and step - settings->digits = qMax(1u, settings->digits); + settings->digits = qBound(1u, settings->digits, 10u); settings->step = qBound(1u, settings->step, 60u); // Detect custom settings, used by setup GUI diff --git a/tests/TestBrowser.cpp b/tests/TestBrowser.cpp index 5b2f61178..3e518c1e2 100644 --- a/tests/TestBrowser.cpp +++ b/tests/TestBrowser.cpp @@ -38,6 +38,7 @@ void TestBrowser::initTestCase() { QVERIFY(Crypto::init()); m_browserService = browserService(); + browserSettings()->setBestMatchOnly(false); } void TestBrowser::init() @@ -130,6 +131,7 @@ void TestBrowser::testSortPriority() QString host = "github.com"; QString submitUrl = "https://github.com/session"; QString baseSubmitUrl = "https://github.com"; + QString fullUrl = "https://github.com/login"; QScopedPointer entry1(new Entry()); QScopedPointer entry2(new Entry()); @@ -141,6 +143,7 @@ void TestBrowser::testSortPriority() QScopedPointer entry8(new Entry()); QScopedPointer entry9(new Entry()); QScopedPointer entry10(new Entry()); + QScopedPointer entry11(new Entry()); entry1->setUrl("https://github.com/login"); entry2->setUrl("https://github.com/login"); @@ -152,18 +155,20 @@ void TestBrowser::testSortPriority() 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 - auto res1 = m_browserService->sortPriority(entry1.data(), host, "https://github.com/login", baseSubmitUrl); - auto res2 = m_browserService->sortPriority(entry2.data(), host, submitUrl, baseSubmitUrl); - auto res3 = m_browserService->sortPriority(entry3.data(), host, submitUrl, baseSubmitUrl); - auto res4 = m_browserService->sortPriority(entry4.data(), host, submitUrl, baseSubmitUrl); - auto res5 = m_browserService->sortPriority(entry5.data(), host, submitUrl, baseSubmitUrl); - auto res6 = m_browserService->sortPriority(entry6.data(), host, submitUrl, baseSubmitUrl); - auto res7 = m_browserService->sortPriority(entry7.data(), host, submitUrl, baseSubmitUrl); - auto res8 = m_browserService->sortPriority(entry8.data(), host, submitUrl, baseSubmitUrl); - auto res9 = m_browserService->sortPriority(entry9.data(), host, submitUrl, baseSubmitUrl); - auto res10 = m_browserService->sortPriority(entry10.data(), host, submitUrl, baseSubmitUrl); + 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); + auto res3 = m_browserService->sortPriority(entry3.data(), host, submitUrl, baseSubmitUrl, fullUrl); + 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); QCOMPARE(res2, 40); @@ -175,6 +180,7 @@ void TestBrowser::testSortPriority() QCOMPARE(res8, 0); QCOMPARE(res9, 0); QCOMPARE(res10, 0); + QCOMPARE(res11, 100); } void TestBrowser::testSearchEntries() @@ -382,8 +388,8 @@ void TestBrowser::testSortEntries() auto entries = createEntries(urls, root); browserSettings()->setBestMatchOnly(false); - auto result = - m_browserService->sortEntries(entries, "github.com", "https://github.com/session"); // entries, host, submitUrl + auto result = m_browserService->sortEntries( + entries, "github.com", "https://github.com/session", "https://github.com"); // entries, host, submitUrl QCOMPARE(result.size(), 10); QCOMPARE(result[0]->username(), QString("User 2")); QCOMPARE(result[0]->url(), QString("https://github.com/")); @@ -393,6 +399,15 @@ void TestBrowser::testSortEntries() QCOMPARE(result[2]->url(), QString("https://github.com/login")); QCOMPARE(result[3]->username(), QString("User 3")); QCOMPARE(result[3]->url(), QString("github.com/login")); + + // Test with a perfect match. That should be first in the list. + result = m_browserService->sortEntries( + entries, "github.com", "https://github.com/session", "https://github.com/login_page"); + QCOMPARE(result.size(), 10); + QCOMPARE(result[0]->username(), QString("User 0")); + QCOMPARE(result[0]->url(), QString("https://github.com/login_page")); + QCOMPARE(result[1]->username(), QString("User 2")); + QCOMPARE(result[1]->url(), QString("https://github.com/")); } QList TestBrowser::createEntries(QStringList& urls, Group* root) const @@ -429,3 +444,58 @@ void TestBrowser::testValidURLs() QCOMPARE(Tools::checkUrlValid(i.key()), i.value()); } } + +void TestBrowser::testBestMatchingCredentials() +{ + auto db = QSharedPointer::create(); + auto* root = db->rootGroup(); + + // Test with simple URL entries + QStringList urls = {"https://github.com/loginpage", "https://github.com/justsomepage", "https://github.com/"}; + + auto entries = createEntries(urls, root); + + browserSettings()->setBestMatchOnly(true); + + auto result = m_browserService->searchEntries(db, "https://github.com/loginpage", "https://github.com/loginpage"); + QCOMPARE(result.size(), 1); + QCOMPARE(result[0]->url(), QString("https://github.com/loginpage")); + + result = m_browserService->searchEntries(db, "https://github.com/justsomepage", "https://github.com/justsomepage"); + QCOMPARE(result.size(), 1); + QCOMPARE(result[0]->url(), QString("https://github.com/justsomepage")); + + result = m_browserService->searchEntries(db, "https://github.com/", "https://github.com/"); + m_browserService->sortEntries(entries, "github.com", "https://github.com/", "https://github.com/"); + QCOMPARE(result.size(), 1); + QCOMPARE(result[0]->url(), QString("https://github.com/")); + + browserSettings()->setBestMatchOnly(false); + result = m_browserService->searchEntries(db, "https://github.com/loginpage", "https://github.com/loginpage"); + QCOMPARE(result.size(), 3); + QCOMPARE(result[0]->url(), QString("https://github.com/loginpage")); + + // Test with subdomains + QStringList subdomainsUrls = {"https://sub.github.com/loginpage", + "https://sub.github.com/justsomepage", + "https://bus.github.com/justsomepage"}; + + entries = createEntries(subdomainsUrls, root); + + browserSettings()->setBestMatchOnly(true); + + result = m_browserService->searchEntries( + db, "https://sub.github.com/justsomepage", "https://sub.github.com/justsomepage"); + QCOMPARE(result.size(), 1); + QCOMPARE(result[0]->url(), QString("https://sub.github.com/justsomepage")); + + result = m_browserService->searchEntries(db, "https://github.com/justsomepage", "https://github.com/justsomepage"); + QCOMPARE(result.size(), 1); + QCOMPARE(result[0]->url(), QString("https://github.com/justsomepage")); + + result = m_browserService->searchEntries(db, + "https://sub.github.com/justsomepage?wehavesomeextra=here", + "https://sub.github.com/justsomepage?wehavesomeextra=here"); + QCOMPARE(result.size(), 1); + QCOMPARE(result[0]->url(), QString("https://sub.github.com/justsomepage")); +} diff --git a/tests/TestBrowser.h b/tests/TestBrowser.h index 00f9d7528..c8be3d6ca 100644 --- a/tests/TestBrowser.h +++ b/tests/TestBrowser.h @@ -47,6 +47,7 @@ private slots: void testSubdomainsAndPaths(); void testSortEntries(); void testValidURLs(); + void testBestMatchingCredentials(); private: QList createEntries(QStringList& urls, Group* root) const; diff --git a/tests/TestTools.cpp b/tests/TestTools.cpp index 4809a8bc9..cdce6e04e 100644 --- a/tests/TestTools.cpp +++ b/tests/TestTools.cpp @@ -72,10 +72,14 @@ void TestTools::testEnvSubstitute() #if defined(Q_OS_WIN) environment.insert("HOMEDRIVE", "C:"); environment.insert("HOMEPATH", "\\Users\\User"); + environment.insert("USERPROFILE", "C:\\Users\\User"); QCOMPARE(Tools::envSubstitute("%HOMEDRIVE%%HOMEPATH%\\.ssh\\id_rsa", environment), QString("C:\\Users\\User\\.ssh\\id_rsa")); QCOMPARE(Tools::envSubstitute("start%EMPTY%%EMPTY%%%HOMEDRIVE%%end", environment), QString("start%C:%end")); + QCOMPARE(Tools::envSubstitute("%USERPROFILE%\\.ssh\\id_rsa", environment), + QString("C:\\Users\\User\\.ssh\\id_rsa")); + QCOMPARE(Tools::envSubstitute("~\\.ssh\\id_rsa", environment), QString("C:\\Users\\User\\.ssh\\id_rsa")); #else environment.insert("HOME", QString("/home/user")); environment.insert("USER", QString("user"));