Merge branch 'release/2.6.1' into develop

This commit is contained in:
Jonathan White 2020-07-22 12:10:05 -04:00
commit 71b05dbcf4
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
28 changed files with 466 additions and 82 deletions

View File

@ -24,18 +24,20 @@ set(DOC_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
# Build html documentation on all platforms # 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 add_custom_command(OUTPUT KeePassXC_GettingStarted.html
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_GettingStarted.html ${DOC_DIR}/GettingStarted.adoc 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) VERBATIM)
add_custom_command(OUTPUT KeePassXC_UserGuide.html add_custom_command(OUTPUT KeePassXC_UserGuide.html
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_UserGuide.html ${DOC_DIR}/UserGuide.adoc 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} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM) VERBATIM)
file(GLOB styles_depends ${DOC_DIR}/styles/*)
add_custom_command(OUTPUT KeePassXC_KeyboardShortcuts.html add_custom_command(OUTPUT KeePassXC_KeyboardShortcuts.html
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_KeyboardShortcuts.html ${DOC_DIR}/topics/KeyboardShortcuts.adoc 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) VERBATIM)
add_custom_target(docs ALL DEPENDS KeePassXC_GettingStarted.html KeePassXC_UserGuide.html KeePassXC_KeyboardShortcuts.html) 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) if(APPLE OR UNIX)
add_custom_command(OUTPUT keepassxc.1 add_custom_command(OUTPUT keepassxc.1
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -b manpage ${DOC_DIR}/man/keepassxc.1.adoc 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) VERBATIM)
add_custom_command(OUTPUT keepassxc-cli.1 add_custom_command(OUTPUT keepassxc-cli.1
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -b manpage ${DOC_DIR}/man/keepassxc-cli.1.adoc 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) VERBATIM)
add_custom_target(manpages ALL DEPENDS keepassxc.1 keepassxc-cli.1) add_custom_target(manpages ALL DEPENDS keepassxc.1 keepassxc-cli.1)

View File

@ -1,10 +1,28 @@
// Copyright (C) 2017 Manolis Agkopian <m.agkopian@gmail.com>
// Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
//
// 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 <http://www.gnu.org/licenses/>.
= keepassxc-cli(1) = keepassxc-cli(1)
:docdate: 2020-07-05 :docdate: 2020-07-10
:doctype: manpage :doctype: manpage
:revnumber: 2.6.0
:mansource: KeePassXC {revnumber}
:manmanual: General Commands Manual :manmanual: General Commands Manual
== NAME == NAME
keepassxc-cli - command line interface for the KeePassXC password manager. keepassxc-cli - command line interface for the KeePassXC password manager
== SYNOPSIS == SYNOPSIS
*keepassxc-cli* _command_ [_options_] *keepassxc-cli* _command_ [_options_]
@ -16,21 +34,21 @@ It provides the ability to query and modify the entries of a KeePass database, d
== COMMANDS == COMMANDS
*add* [_options_] <__database__> <__entry__>:: *add* [_options_] <__database__> <__entry__>::
Adds a new entry to a database. 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). 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. The same password generation options as documented for the generate command can be used when the *-g* option is set.
*analyze* [_options_] <__database__>:: *analyze* [_options_] <__database__>::
Analyzes passwords in a database for weaknesses. Analyzes passwords in a database for weaknesses.
*clip* [_options_] <__database__> <__entry__> [_timeout_]:: *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. 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 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. 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. 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. Optionally, a timeout in seconds can be specified to automatically clear the clipboard.
*close*:: *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__>:: *db-create* [_options_] <__database__>::
Creates a new database with a password and/or a key file. 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__>:: *edit* [_options_] <__database__> <__entry__>::
Edits a 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). 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. The same password generation options as documented for the generate command can be used when the *-g* option is set.
*estimate* [_options_] [_password_]:: *estimate* [_options_] [_password_]::
Estimates the entropy of a 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*:: *exit*::
Exits interactive mode. Exits interactive mode.
Synonymous with _quit_. Synonymous with *quit*.
*export* [_options_] <__database__>:: *export* [_options_] <__database__>::
Exports the content of a database to standard output in the specified format (defaults to XML). 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__>:: *merge* [_options_] <__database1__> <__database2__>::
Merges two databases together. 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. 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__>:: *mkdir* [_options_] <__database__> <__group__>::
Adds a new group to a database. 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__>:: *open* [_options_] <__database__>::
Opens the given database in a shell-style interactive mode. 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*:: *quit*::
Exits interactive mode. Exits interactive mode.
Synonymous with _exit_. Synonymous with *exit*.
*rm* [_options_] <__database__> <__entry__>:: *rm* [_options_] <__database__> <__entry__>::
Removes an entry from a database. 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__>:: *show* [_options_] <__database__> <__entry__>::
Shows the title, username, password, URL and notes of a database entry. Shows the title, username, password, URL and notes of a database entry.
Can also show the current TOTP. 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 == OPTIONS
=== General 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. Uses the same credentials for unlocking both databases.
=== Add and edit options === 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__>:: *-u*, *--username* <__username__>::
Specifies the username of the entry. Specifies the username of the entry.
@ -183,7 +201,7 @@ The same password generation options as documented for the generate command can
*-a*, *--attribute*:: *-a*, *--attribute*::
Copies the specified attribute to the clipboard. Copies the specified attribute to the clipboard.
If no attribute is specified, the password attribute is the default. 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] [Default: password]
*-t*, *--totp*:: *-t*, *--totp*::
@ -204,7 +222,7 @@ The same password generation options as documented for the generate command can
*-a*, *--attributes* <__attribute__>...:: *-a*, *--attributes* <__attribute__>...::
Shows the named attributes. Shows the named attributes.
This option can be specified more than once, with each attribute shown one-per-line in the given order. 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. Protected attributes will be displayed in clear text if specified explicitly by this option.
*-s*, *--show-protected*:: *-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. Include characters from every selected group.
[Default: Disabled] [Default: Disabled]
== REPORTING BUGS include::section-notes.adoc[]
Bugs and feature requests can be reported on GitHub at https://github.com/keepassxreboot/keepassxc/issues.
== AUTHOR == AUTHOR
This manual page was originally written by Manolis Agkopian <m.agkopian@gmail.com>, This manual page was originally written by Manolis Agkopian <m.agkopian@gmail.com>.
and is maintained by the KeePassXC Team <team@keepassxc.org>.
include::section-reporting-bugs.adoc[]
include::section-copyright.adoc[]

View File

@ -1,10 +1,28 @@
// Copyright (C) 2019 Janek Bevendorff <janek@jbev.net>
// Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
//
// 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 <http://www.gnu.org/licenses/>.
= keepassxc(1) = keepassxc(1)
:docdate: 2020-07-05 :docdate: 2020-07-10
:doctype: manpage :doctype: manpage
:revnumber: 2.6.0
:mansource: KeePassXC {revnumber}
:manmanual: General Commands Manual :manmanual: General Commands Manual
== NAME == NAME
keepassxc - password manager keepassxc - a modern open-source password manager
== SYNOPSIS == SYNOPSIS
*keepassxc* [_options_] [_filename(s)_] *keepassxc* [_options_] [_filename(s)_]
@ -23,19 +41,25 @@ Your wallet works offline and requires no Internet connection.
Displays version information. Displays version information.
*--config* <__config__>:: *--config* <__config__>::
Path to a custom config file Path to a custom config file.
*--keyfile* <__keyfile__>:: *--keyfile* <__keyfile__>::
Key file of the database Key file of the database.
*--pw-stdin*:: *--pw-stdin*::
Read password of the database from stdin Read password of the database from stdin.
*--pw*, *--parent-window* <__handle__>:: *--pw*, *--parent-window* <__handle__>::
Parent window handle Parent window handle.
*--debug-info*:: *--debug-info*::
Displays debugging information. Displays debugging information.
include::section-notes.adoc[]
== AUTHOR == AUTHOR
This manual page is maintained by the KeePassXC Team <team@keepassxc.org>. This manual page was originally written by Janek Bevendorff <janek@jbev.net>.
include::section-reporting-bugs.adoc[]
include::section-copyright.adoc[]

View File

@ -0,0 +1,19 @@
// Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
//
// 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 <http://www.gnu.org/licenses/>.
== COPYRIGHT
Copyright \(C) 2016-2020 KeePassXC Team <team@keepassxc.org>
*KeePassXC* code is licensed under GPL-2 or GPL-3.

View File

@ -0,0 +1,27 @@
// Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
//
// 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 <http://www.gnu.org/licenses/>.
== 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

View File

@ -0,0 +1,17 @@
// Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
//
// 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 <http://www.gnu.org/licenses/>.
== REPORTING BUGS
Bugs and feature requests can be reported on GitHub at https://github.com/keepassxreboot/keepassxc/issues.

View File

@ -28,23 +28,23 @@
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<image>https://keepassxc.org/images/screenshots/linux/screen_001.png</image> <image>https://keepassxc.org/images/screenshots/thumbs/welcome_screen.png</image>
<caption>Create, Import or Open Databases</caption> <caption>Create, Import or Open Databases</caption>
</screenshot> </screenshot>
<screenshot> <screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_002.png</image> <image>https://keepassxc.org/images/screenshots/thumbs/database_view.png</image>
<caption>Organize with Groups and Entries</caption> <caption>Organize with Groups and Entries</caption>
</screenshot> </screenshot>
<screenshot> <screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_003.png</image> <image>https://keepassxc.org/images/screenshots/thumbs/edit_entry.png</image>
<caption>Database Entry</caption> <caption>Database Entry</caption>
</screenshot> </screenshot>
<screenshot> <screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_004.png</image> <image>https://keepassxc.org/images/screenshots/thumbs/edit_entry_icons.png</image>
<caption>Icon Selection for Entry</caption> <caption>Icon Selection for Entry</caption>
</screenshot> </screenshot>
<screenshot> <screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_006.png</image> <image>https://keepassxc.org/images/screenshots/thumbs/password_generator_advanced.png</image>
<caption>Password Generator</caption> <caption>Password Generator</caption>
</screenshot> </screenshot>
</screenshots> </screenshots>
@ -614,4 +614,5 @@
</description> </description>
</release> </release>
</releases> </releases>
<content_rating type="oars-1.0" />
</component> </component>

View File

@ -1,13 +1,39 @@
[Desktop Entry] [Desktop Entry]
Name=KeePassXC Name=KeePassXC
GenericName=Password Manager GenericName=Password Manager
GenericName[ar]=مدير كلمات المرور
GenericName[bg]=Мениджър на пароли
GenericName[ca]=Gestor de contrasenyes
GenericName[cs]=Aplikace pro správu hesel
GenericName[da]=Adgangskodehåndtering GenericName[da]=Adgangskodehåndtering
GenericName[de]=Passwortverwaltung GenericName[de]=Passwortverwaltung
GenericName[es]=Gestor de contraseñas GenericName[es]=Gestor de contraseñas
GenericName[et]=Paroolihaldur
GenericName[fi]=Salasanamanageri
GenericName[fr]=Gestionnaire de mot de passe 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[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=Community-driven port of the Windows application KeePass Password Safe
Comment[da]=Fællesskabsdrevet port af Windows-programmet 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 Exec=keepassxc %f
TryExec=keepassxc TryExec=keepassxc
Icon=keepassxc Icon=keepassxc

View File

@ -269,7 +269,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
/** /**
* Single Autotype entry-point function * 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) 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()); 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() void AutoType::startGlobalAutoType()
{ {
m_windowForGlobal = m_plugin->activeWindow(); m_windowForGlobal = m_plugin->activeWindow();

View File

@ -48,6 +48,7 @@ public:
static bool checkHighDelay(const QString& string); static bool checkHighDelay(const QString& string);
static bool verifyAutoTypeSyntax(const QString& sequence); static bool verifyAutoTypeSyntax(const QString& sequence);
void performAutoType(const Entry* entry, QWidget* hideWindow = nullptr); void performAutoType(const Entry* entry, QWidget* hideWindow = nullptr);
void performAutoTypeWithSequence(const Entry* entry, const QString& sequence, QWidget* hideWindow = nullptr);
inline bool isAvailable() inline bool isAvailable()
{ {

View File

@ -413,7 +413,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
} }
// Sort results // Sort results
pwEntries = sortEntries(pwEntries, host, submitUrl); pwEntries = sortEntries(pwEntries, host, submitUrl, url);
// Fill the list // Fill the list
QJsonArray result; QJsonArray result;
@ -698,7 +698,10 @@ void BrowserService::convertAttributesToCustomData(QSharedPointer<Database> db)
} }
} }
QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& entryUrl) QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries,
const QString& host,
const QString& entryUrl,
const QString& fullUrl)
{ {
QUrl url(entryUrl); QUrl url(entryUrl);
if (url.scheme().isEmpty()) { if (url.scheme().isEmpty()) {
@ -712,7 +715,7 @@ QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QStrin
// 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), entry); priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl, fullUrl), entry);
} }
QList<Entry*> results; QList<Entry*> results;
@ -895,7 +898,8 @@ Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& sele
int BrowserService::sortPriority(const Entry* entry, int BrowserService::sortPriority(const Entry* entry,
const QString& host, const QString& host,
const QString& submitUrl, const QString& submitUrl,
const QString& baseSubmitUrl) const const QString& baseSubmitUrl,
const QString& fullUrl) const
{ {
QUrl url(entry->url()); QUrl url(entry->url());
if (url.scheme().isEmpty()) { if (url.scheme().isEmpty()) {
@ -914,9 +918,12 @@ int BrowserService::sortPriority(const Entry* entry,
if (!url.host().contains(".") && url.host() != "localhost") { if (!url.host().contains(".") && url.host() != "localhost") {
return 0; return 0;
} }
if (submitUrl == entryURL) { if (fullUrl == entryURL) {
return 100; return 100;
} }
if (submitUrl == entryURL) {
return 95;
}
if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL) { if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL) {
return 90; return 90;
} }
@ -1025,7 +1032,17 @@ 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())) {
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; return false;

View File

@ -119,7 +119,8 @@ private:
QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& url, const QString& submitUrl); 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*> searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList);
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl); QList<Entry*>
sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl, const QString& fullUrl);
QList<Entry*> confirmEntries(QList<Entry*>& pwEntriesToConfirm, QList<Entry*> confirmEntries(QList<Entry*>& pwEntriesToConfirm,
const QString& url, const QString& url,
const QString& host, const QString& host,
@ -130,8 +131,11 @@ private:
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& host, const QString& submitHost, const QString& realm);
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {}); Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
int int sortPriority(const Entry* entry,
sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const; 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 handleURL(const QString& entryUrl, const QString& url, const QString& submitUrl); bool handleURL(const QString& entryUrl, const QString& url, const QString& submitUrl);

View File

@ -27,7 +27,7 @@
Info::Info() Info::Info()
{ {
name = QString("db-show"); name = QString("db-info");
description = QObject::tr("Show a database's information."); description = QObject::tr("Show a database's information.");
} }

View File

@ -481,6 +481,8 @@ void Entry::updateTotp()
m_attributes->value(Totp::ATTRIBUTE_SEED)); m_attributes->value(Totp::ATTRIBUTE_SEED));
} else if (m_attributes->contains(Totp::ATTRIBUTE_OTP)) { } else if (m_attributes->contains(Totp::ATTRIBUTE_OTP)) {
m_data.totpSettings = Totp::parseSettings(m_attributes->value(Totp::ATTRIBUTE_OTP)); m_data.totpSettings = Totp::parseSettings(m_attributes->value(Totp::ATTRIBUTE_OTP));
} else {
m_data.totpSettings.reset();
} }
} }

View File

@ -51,6 +51,13 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget* parent)
this, // receiver this, // receiver
SLOT(gnomeSessionStatusChanged(uint))); 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 systemBus.connect("org.freedesktop.login1", // service
"/org/freedesktop/login1", // path "/org/freedesktop/login1", // path
"org.freedesktop.login1.Manager", // interface "org.freedesktop.login1.Manager", // interface

View File

@ -331,11 +331,15 @@ namespace Tools
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
QRegularExpression varRe("\\%([A-Za-z][A-Za-z0-9_]*)\\%"); QRegularExpression varRe("\\%([A-Za-z][A-Za-z0-9_]*)\\%");
QString homeEnv = "USERPROFILE";
#else #else
QRegularExpression varRe("\\$([A-Za-z][A-Za-z0-9_]*)"); QRegularExpression varRe("\\$([A-Za-z][A-Za-z0-9_]*)");
subbed.replace("~", environment.value("HOME")); QString homeEnv = "HOME";
#endif #endif
if (subbed.startsWith("~/") || subbed.startsWith("~\\"))
subbed.replace(0, 1, environment.value(homeEnv));
QRegularExpressionMatch match; QRegularExpressionMatch match;
do { do {

View File

@ -71,6 +71,8 @@ bool Translator::installTranslator(const QStringList& languages, const QString&
QScopedPointer<QTranslator> translator(new QTranslator(qApp)); QScopedPointer<QTranslator> translator(new QTranslator(qApp));
if (translator->load(locale, "keepassx_", "", path)) { if (translator->load(locale, "keepassx_", "", path)) {
return QCoreApplication::installTranslator(translator.take()); return QCoreApplication::installTranslator(translator.take());
} else if (translator->load(locale, "keepassx_", "", QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
return QCoreApplication::installTranslator(translator.take());
} }
} }

View File

@ -27,6 +27,7 @@
#include "core/Global.h" #include "core/Global.h"
#include "core/Resources.h" #include "core/Resources.h"
#include "core/Translator.h" #include "core/Translator.h"
#include "gui/MainWindow.h"
#include "gui/osutils/OSUtils.h" #include "gui/osutils/OSUtils.h"
#include "MessageBox.h" #include "MessageBox.h"
@ -324,7 +325,15 @@ void ApplicationSettingsWidget::saveSettings()
config()->set(Config::AutoTypeEntryURLMatch, m_generalUi->autoTypeEntryURLMatchCheckBox->isChecked()); config()->set(Config::AutoTypeEntryURLMatch, m_generalUi->autoTypeEntryURLMatchCheckBox->isChecked());
config()->set(Config::FaviconDownloadTimeout, m_generalUi->faviconTimeoutSpinBox->value()); 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_MovableToolbar, m_generalUi->toolbarMovableCheckBox->isChecked());
config()->set(Config::GUI_MonospaceNotes, m_generalUi->monospaceNotesCheckBox->isChecked()); config()->set(Config::GUI_MonospaceNotes, m_generalUi->monospaceNotesCheckBox->isChecked());

View File

@ -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() void DatabaseWidget::openUrl()
{ {
auto currentEntry = currentSelectedEntry(); auto currentEntry = currentSelectedEntry();
@ -1813,7 +1845,7 @@ bool DatabaseWidget::save()
m_blockAutoSave = true; m_blockAutoSave = true;
++m_saveAttempts; ++m_saveAttempts;
auto focusWidget = qApp->focusWidget(); QPointer<QWidget> focusWidget(qApp->focusWidget());
// TODO: Make this async // TODO: Make this async
// Lock out interactions // Lock out interactions
@ -1887,7 +1919,7 @@ bool DatabaseWidget::saveAs()
bool ok = false; bool ok = false;
if (!newFilePath.isEmpty()) { if (!newFilePath.isEmpty()) {
auto focusWidget = qApp->focusWidget(); QPointer<QWidget> focusWidget(qApp->focusWidget());
// Lock out interactions // Lock out interactions
m_entryView->setDisabled(true); m_entryView->setDisabled(true);

View File

@ -186,6 +186,10 @@ public slots:
void removeFromAgent(); void removeFromAgent();
#endif #endif
void performAutoType(); void performAutoType();
void performAutoTypeUsername();
void performAutoTypeUsernameEnter();
void performAutoTypePassword();
void performAutoTypePasswordEnter();
void openUrl(); void openUrl();
void downloadSelectedFavicons(); void downloadSelectedFavicons();
void downloadAllFavicons(); void downloadAllFavicons();

View File

@ -126,6 +126,7 @@ MainWindow::MainWindow()
m_entryContextMenu->addAction(m_ui->menuEntryTotp->menuAction()); m_entryContextMenu->addAction(m_ui->menuEntryTotp->menuAction());
m_entryContextMenu->addSeparator(); m_entryContextMenu->addSeparator();
m_entryContextMenu->addAction(m_ui->actionEntryAutoType); m_entryContextMenu->addAction(m_ui->actionEntryAutoType);
m_entryContextMenu->addAction(m_ui->menuEntryAutoTypeWithSequence->menuAction());
m_entryContextMenu->addSeparator(); m_entryContextMenu->addSeparator();
m_entryContextMenu->addAction(m_ui->actionEntryEdit); m_entryContextMenu->addAction(m_ui->actionEntryEdit);
m_entryContextMenu->addAction(m_ui->actionEntryClone); m_entryContextMenu->addAction(m_ui->actionEntryClone);
@ -220,7 +221,12 @@ MainWindow::MainWindow()
m_ui->toolbarSeparator->setVisible(false); m_ui->toolbarSeparator->setVisible(false);
m_showToolbarSeparator = config()->get(Config::GUI_ApplicationTheme).toString() != "classic"; 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); m_inactivityTimer = new InactivityTimer(this);
connect(m_inactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(lockDatabasesAfterInactivity())); connect(m_inactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(lockDatabasesAfterInactivity()));
@ -352,6 +358,11 @@ MainWindow::MainWindow()
m_ui->actionEntryEdit->setIcon(resources()->icon("entry-edit")); m_ui->actionEntryEdit->setIcon(resources()->icon("entry-edit"));
m_ui->actionEntryDelete->setIcon(resources()->icon("entry-delete")); m_ui->actionEntryDelete->setIcon(resources()->icon("entry-delete"));
m_ui->actionEntryAutoType->setIcon(resources()->icon("auto-type")); 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->actionEntryMoveUp->setIcon(resources()->icon("move-up"));
m_ui->actionEntryMoveDown->setIcon(resources()->icon("move-down")); m_ui->actionEntryMoveDown->setIcon(resources()->icon("move-down"));
m_ui->actionEntryCopyUsername->setIcon(resources()->icon("username-copy")); 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->actionEntryCopyURL, SIGNAL(triggered()), SLOT(copyURL()));
m_actionMultiplexer.connect(m_ui->actionEntryCopyNotes, SIGNAL(triggered()), SLOT(copyNotes())); 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->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->actionEntryOpenUrl, SIGNAL(triggered()), SLOT(openUrl()));
m_actionMultiplexer.connect(m_ui->actionEntryDownloadIcon, SIGNAL(triggered()), SLOT(downloadSelectedFavicons())); m_actionMultiplexer.connect(m_ui->actionEntryDownloadIcon, SIGNAL(triggered()), SLOT(downloadSelectedFavicons()));
#ifdef WITH_XC_SSHAGENT #ifdef WITH_XC_SSHAGENT
@ -711,6 +730,13 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected); m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected);
m_ui->menuEntryTotp->setEnabled(singleEntrySelected); m_ui->menuEntryTotp->setEnabled(singleEntrySelected);
m_ui->actionEntryAutoType->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->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
m_ui->actionEntryCopyTotp->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->actionEntryCopyURL,
m_ui->actionEntryOpenUrl, m_ui->actionEntryOpenUrl,
m_ui->actionEntryAutoType, m_ui->actionEntryAutoType,
m_ui->menuEntryAutoTypeWithSequence->menuAction(),
m_ui->actionEntryDownloadIcon, m_ui->actionEntryDownloadIcon,
m_ui->actionEntryCopyNotes, m_ui->actionEntryCopyNotes,
m_ui->actionEntryCopyTitle, m_ui->actionEntryCopyTitle,
@ -1679,17 +1706,20 @@ void MainWindow::initViewMenu()
} }
} }
connect(themeActions, &QActionGroup::triggered, this, [this](QAction* action) { connect(themeActions, &QActionGroup::triggered, this, [this, theme](QAction* action) {
if (action->data() != config()->get(Config::GUI_ApplicationTheme)) { config()->set(Config::GUI_ApplicationTheme, action->data());
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?")); 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()); bool compact = config()->get(Config::GUI_CompactMode).toBool();
connect(m_ui->actionCompactMode, &QAction::toggled, this, [this](bool checked) { m_ui->actionCompactMode->setChecked(compact);
connect(m_ui->actionCompactMode, &QAction::toggled, this, [this, compact](bool checked) {
config()->set(Config::GUI_CompactMode, 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()); m_ui->actionShowToolbar->setChecked(!config()->get(Config::GUI_HideToolbar).toBool());

View File

@ -310,6 +310,18 @@
<addaction name="actionEntryTotpQRCode"/> <addaction name="actionEntryTotpQRCode"/>
<addaction name="actionEntrySetupTotp"/> <addaction name="actionEntrySetupTotp"/>
</widget> </widget>
<widget class="QMenu" name="menuEntryAutoTypeWithSequence">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Perform Auto-Type Sequence</string>
</property>
<addaction name="actionEntryAutoTypeUsername"/>
<addaction name="actionEntryAutoTypeUsernameEnter"/>
<addaction name="actionEntryAutoTypePassword"/>
<addaction name="actionEntryAutoTypePasswordEnter"/>
</widget>
<addaction name="actionEntryNew"/> <addaction name="actionEntryNew"/>
<addaction name="actionEntryEdit"/> <addaction name="actionEntryEdit"/>
<addaction name="actionEntryClone"/> <addaction name="actionEntryClone"/>
@ -324,6 +336,7 @@
<addaction name="menuEntryTotp"/> <addaction name="menuEntryTotp"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionEntryAutoType"/> <addaction name="actionEntryAutoType"/>
<addaction name="menuEntryAutoTypeWithSequence"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionEntryOpenUrl"/> <addaction name="actionEntryOpenUrl"/>
<addaction name="actionEntryDownloadIcon"/> <addaction name="actionEntryDownloadIcon"/>
@ -680,6 +693,38 @@
<string>Perform &amp;Auto-Type</string> <string>Perform &amp;Auto-Type</string>
</property> </property>
</action> </action>
<action name="actionEntryAutoTypeUsername">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>{USERNAME}</string>
</property>
</action>
<action name="actionEntryAutoTypeUsernameEnter">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>{USERNAME}{ENTER}</string>
</property>
</action>
<action name="actionEntryAutoTypePassword">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>{PASSWORD}</string>
</property>
</action>
<action name="actionEntryAutoTypePasswordEnter">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>{PASSWORD}{ENTER}</string>
</property>
</action>
<action name="actionEntryDownloadIcon"> <action name="actionEntryDownloadIcon">
<property name="text"> <property name="text">
<string>Download &amp;Favicon</string> <string>Download &amp;Favicon</string>

View File

@ -50,21 +50,28 @@ PasswordEdit::PasswordEdit(QWidget* parent)
passwordFont.setLetterSpacing(QFont::PercentageSpacing, 110); passwordFont.setLetterSpacing(QFont::PercentageSpacing, 110);
setFont(passwordFont); 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( m_toggleVisibleAction = new QAction(
resources()->icon("password-show-off"), 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); nullptr);
m_toggleVisibleAction->setCheckable(true); m_toggleVisibleAction->setCheckable(true);
m_toggleVisibleAction->setShortcut(Qt::CTRL + Qt::Key_H); m_toggleVisibleAction->setShortcut(modifier + Qt::Key_H);
m_toggleVisibleAction->setShortcutContext(Qt::WidgetShortcut); m_toggleVisibleAction->setShortcutContext(Qt::WidgetShortcut);
addAction(m_toggleVisibleAction, QLineEdit::TrailingPosition); addAction(m_toggleVisibleAction, QLineEdit::TrailingPosition);
connect(m_toggleVisibleAction, &QAction::triggered, this, &PasswordEdit::setShowPassword); connect(m_toggleVisibleAction, &QAction::triggered, this, &PasswordEdit::setShowPassword);
m_passwordGeneratorAction = new QAction( m_passwordGeneratorAction = new QAction(
resources()->icon("password-generator"), 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); nullptr);
m_passwordGeneratorAction->setShortcut(Qt::CTRL + Qt::Key_G); m_passwordGeneratorAction->setShortcut(modifier + Qt::Key_G);
m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut); m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut);
addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition); addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition);
m_passwordGeneratorAction->setVisible(false); m_passwordGeneratorAction->setVisible(false);

View File

@ -447,12 +447,8 @@ void SSHAgent::databaseLocked()
if (!removeIdentity(key)) { if (!removeIdentity(key)) {
emit error(m_error); 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);
} }
} }

View File

@ -113,7 +113,7 @@ QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, c
} }
// Bound digits and step // Bound digits and step
settings->digits = qMax(1u, settings->digits); settings->digits = qBound(1u, settings->digits, 10u);
settings->step = qBound(1u, settings->step, 60u); settings->step = qBound(1u, settings->step, 60u);
// Detect custom settings, used by setup GUI // Detect custom settings, used by setup GUI

View File

@ -38,6 +38,7 @@ void TestBrowser::initTestCase()
{ {
QVERIFY(Crypto::init()); QVERIFY(Crypto::init());
m_browserService = browserService(); m_browserService = browserService();
browserSettings()->setBestMatchOnly(false);
} }
void TestBrowser::init() void TestBrowser::init()
@ -130,6 +131,7 @@ void TestBrowser::testSortPriority()
QString host = "github.com"; QString host = "github.com";
QString submitUrl = "https://github.com/session"; QString submitUrl = "https://github.com/session";
QString baseSubmitUrl = "https://github.com"; QString baseSubmitUrl = "https://github.com";
QString fullUrl = "https://github.com/login";
QScopedPointer<Entry> entry1(new Entry()); QScopedPointer<Entry> entry1(new Entry());
QScopedPointer<Entry> entry2(new Entry()); QScopedPointer<Entry> entry2(new Entry());
@ -141,6 +143,7 @@ void TestBrowser::testSortPriority()
QScopedPointer<Entry> entry8(new Entry()); QScopedPointer<Entry> entry8(new Entry());
QScopedPointer<Entry> entry9(new Entry()); QScopedPointer<Entry> entry9(new Entry());
QScopedPointer<Entry> entry10(new Entry()); QScopedPointer<Entry> entry10(new Entry());
QScopedPointer<Entry> entry11(new Entry());
entry1->setUrl("https://github.com/login"); entry1->setUrl("https://github.com/login");
entry2->setUrl("https://github.com/login"); entry2->setUrl("https://github.com/login");
@ -152,18 +155,20 @@ void TestBrowser::testSortPriority()
entry8->setUrl("github.com/login"); entry8->setUrl("github.com/login");
entry9->setUrl("https://github"); // Invalid URL entry9->setUrl("https://github"); // Invalid URL
entry10->setUrl("github.com"); entry10->setUrl("github.com");
entry11->setUrl("https://github.com/login"); // Exact match
// The extension uses the submitUrl as default for comparison // The extension uses the submitUrl as default for comparison
auto res1 = m_browserService->sortPriority(entry1.data(), host, "https://github.com/login", 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); auto res2 = m_browserService->sortPriority(entry2.data(), host, submitUrl, baseSubmitUrl, baseSubmitUrl);
auto res3 = m_browserService->sortPriority(entry3.data(), host, submitUrl, baseSubmitUrl); auto res3 = m_browserService->sortPriority(entry3.data(), host, submitUrl, baseSubmitUrl, fullUrl);
auto res4 = m_browserService->sortPriority(entry4.data(), host, submitUrl, baseSubmitUrl); auto res4 = m_browserService->sortPriority(entry4.data(), host, submitUrl, baseSubmitUrl, fullUrl);
auto res5 = m_browserService->sortPriority(entry5.data(), host, submitUrl, baseSubmitUrl); auto res5 = m_browserService->sortPriority(entry5.data(), host, submitUrl, baseSubmitUrl, fullUrl);
auto res6 = m_browserService->sortPriority(entry6.data(), host, submitUrl, baseSubmitUrl); auto res6 = m_browserService->sortPriority(entry6.data(), host, submitUrl, baseSubmitUrl, fullUrl);
auto res7 = m_browserService->sortPriority(entry7.data(), host, submitUrl, baseSubmitUrl); auto res7 = m_browserService->sortPriority(entry7.data(), host, submitUrl, baseSubmitUrl, fullUrl);
auto res8 = m_browserService->sortPriority(entry8.data(), host, submitUrl, baseSubmitUrl); auto res8 = m_browserService->sortPriority(entry8.data(), host, submitUrl, baseSubmitUrl, fullUrl);
auto res9 = m_browserService->sortPriority(entry9.data(), host, submitUrl, baseSubmitUrl); auto res9 = m_browserService->sortPriority(entry9.data(), host, submitUrl, baseSubmitUrl, fullUrl);
auto res10 = m_browserService->sortPriority(entry10.data(), host, submitUrl, baseSubmitUrl); 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(res1, 100);
QCOMPARE(res2, 40); QCOMPARE(res2, 40);
@ -175,6 +180,7 @@ void TestBrowser::testSortPriority()
QCOMPARE(res8, 0); QCOMPARE(res8, 0);
QCOMPARE(res9, 0); QCOMPARE(res9, 0);
QCOMPARE(res10, 0); QCOMPARE(res10, 0);
QCOMPARE(res11, 100);
} }
void TestBrowser::testSearchEntries() void TestBrowser::testSearchEntries()
@ -382,8 +388,8 @@ void TestBrowser::testSortEntries()
auto entries = createEntries(urls, root); auto entries = createEntries(urls, root);
browserSettings()->setBestMatchOnly(false); browserSettings()->setBestMatchOnly(false);
auto result = auto result = m_browserService->sortEntries(
m_browserService->sortEntries(entries, "github.com", "https://github.com/session"); // entries, host, submitUrl entries, "github.com", "https://github.com/session", "https://github.com"); // entries, host, submitUrl
QCOMPARE(result.size(), 10); QCOMPARE(result.size(), 10);
QCOMPARE(result[0]->username(), QString("User 2")); QCOMPARE(result[0]->username(), QString("User 2"));
QCOMPARE(result[0]->url(), QString("https://github.com/")); 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[2]->url(), QString("https://github.com/login"));
QCOMPARE(result[3]->username(), QString("User 3")); QCOMPARE(result[3]->username(), QString("User 3"));
QCOMPARE(result[3]->url(), QString("github.com/login")); 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<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const
@ -429,3 +444,58 @@ void TestBrowser::testValidURLs()
QCOMPARE(Tools::checkUrlValid(i.key()), i.value()); QCOMPARE(Tools::checkUrlValid(i.key()), i.value());
} }
} }
void TestBrowser::testBestMatchingCredentials()
{
auto db = QSharedPointer<Database>::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"));
}

View File

@ -47,6 +47,7 @@ private slots:
void testSubdomainsAndPaths(); void testSubdomainsAndPaths();
void testSortEntries(); void testSortEntries();
void testValidURLs(); void testValidURLs();
void testBestMatchingCredentials();
private: private:
QList<Entry*> createEntries(QStringList& urls, Group* root) const; QList<Entry*> createEntries(QStringList& urls, Group* root) const;

View File

@ -72,10 +72,14 @@ void TestTools::testEnvSubstitute()
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
environment.insert("HOMEDRIVE", "C:"); environment.insert("HOMEDRIVE", "C:");
environment.insert("HOMEPATH", "\\Users\\User"); environment.insert("HOMEPATH", "\\Users\\User");
environment.insert("USERPROFILE", "C:\\Users\\User");
QCOMPARE(Tools::envSubstitute("%HOMEDRIVE%%HOMEPATH%\\.ssh\\id_rsa", environment), QCOMPARE(Tools::envSubstitute("%HOMEDRIVE%%HOMEPATH%\\.ssh\\id_rsa", environment),
QString("C:\\Users\\User\\.ssh\\id_rsa")); QString("C:\\Users\\User\\.ssh\\id_rsa"));
QCOMPARE(Tools::envSubstitute("start%EMPTY%%EMPTY%%%HOMEDRIVE%%end", environment), QString("start%C:%end")); 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 #else
environment.insert("HOME", QString("/home/user")); environment.insert("HOME", QString("/home/user"));
environment.insert("USER", QString("user")); environment.insert("USER", QString("user"));