Additional Auto-Type improvements based on PR feedback

* Improve documentation and remove external links to keepass.info documentation
This commit is contained in:
Jonathan White 2021-02-15 17:26:23 -05:00
parent 02446af743
commit 8d058cbd04
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
11 changed files with 292 additions and 194 deletions

View File

@ -34,3 +34,5 @@ include::topics/AutoType.adoc[tags=*]
include::topics/KeeShare.adoc[tags=*] include::topics/KeeShare.adoc[tags=*]
include::topics/SSHAgent.adoc[tags=*] include::topics/SSHAgent.adoc[tags=*]
include::topics/Reference.adoc[tags=*]

View File

@ -31,7 +31,7 @@ To configure Auto-Type sequences for your entries, perform the following steps:
.Auto-Type entry sequences .Auto-Type entry sequences
image::autotype_entry_sequences.png[] image::autotype_entry_sequences.png[]
2. _(Optional)_ Define a custom Auto-Type sequence for each window title match by selecting the _Use specific sequence for this association_ checkbox. Sequence action codes and field placeholders are detailed in the following table. Beyond the most important ones detailed below, there are additional action codes and placeholders available: https://keepass.info/help/base/autotype.html#autoseq[KeePass Auto-Type Action Codes, window=_blank] and https://keepass.info/help/base/placeholders.html[KeePass Placeholders, window=_blank]. Action codes and placeholders are not case sensitive. 2. _(Optional)_ Define a custom Auto-Type sequence for each window title match by selecting the _Use specific sequence for this association_ checkbox. Sequence action codes and field placeholders are detailed in the following table. Beyond the most important ones detailed below, there are additional action codes and placeholders available: xref:UserGuide.adoc#_auto_type_actions[Auto-Type Actions Reference] and xref:UserGuide.adoc#_entry_placeholders[Entry Placeholders Reference]. Action codes and placeholders are not case sensitive.
+ +
[grid=rows, frame=none, width=90%] [grid=rows, frame=none, width=90%]
|=== |===
@ -43,10 +43,7 @@ image::autotype_entry_sequences.png[]
|{URL} |URL |{URL} |URL
|{NOTES} |Notes |{NOTES} |Notes
|{TOTP} |Current TOTP value (if configured) |{TOTP} |Current TOTP value (if configured)
|{DT_SIMPLE} |Current date-time
|{DB_DIR} |Absolute directory path for database file
|{S:<ATTRIBUTE_NAME>} |Value for the given attribute name |{S:<ATTRIBUTE_NAME>} |Value for the given attribute name
|{REF:<FIELD>@<SEARCH_IN>:<SEARCH_TEXT>} |Search for a field in another entry using the reference syntax. https://keepass.info/help/base/fieldrefs.html[Read more…, window=_blank]
|=== |===
+ +
[grid=rows, frame=none, width=90%] [grid=rows, frame=none, width=90%]
@ -57,22 +54,13 @@ image::autotype_entry_sequences.png[]
|Press the corresponding keyboard key |Press the corresponding keyboard key
|{UP}, {DOWN}, {LEFT}, {RIGHT} |Press the corresponding arrow key |{UP}, {DOWN}, {LEFT}, {RIGHT} |Press the corresponding arrow key
|{F1}, {F2}, ..., {F16} |Press F1, F2, etc.
|{LEFTBRACE}, {RIGHTBRACE} |Press `{` or `}`, respectively |{LEFTBRACE}, {RIGHTBRACE} |Press `{` or `}`, respectively
|{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces) |{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces)
|{DELAY=X} |Set delay between key presses to X milliseconds |{DELAY=X} |Set delay between key presses to X milliseconds
|{DELAY X} |Pause typing for X milliseconds |{DELAY X} |Pause typing for X milliseconds
|{CLEARFIELD} |Clear the input field |{CLEARFIELD} |Clear the input field
|{PICKCHARS} |Pick specific password characters from a dialog |{PICKCHARS} |Pick specific password characters from a dialog
|=== |===
+
*Text Conversions:*
+
*{T-CONV:/<PLACEHOLDER>/<METHOD>/}* +
Convert resolved placeholder (e.g., {USERNAME}, {PASSWORD}, etc.) using the following methods: UPPER, LOWER, BASE64, HEX, URI, URI-DEC. https://keepass.info/help/base/placeholders.html#conv[Read more…, window=_blank]
+
*{T-REPLACE-RX:/<PLACEHOLDER>/<SEARCH>/<REPLACE>/}* +
Use regular expressions to find and replace data from a resolved placeholder. Refer to match groups using $1, $2, etc. https://keepass.info/help/base/placeholders.html#replacerx[Read more…, window=_blank]
=== Performing Global Auto-Type === Performing Global Auto-Type
The global Auto-Type keyboard shortcut is used when you have focus on the window you want to type into. To make use of this feature, you must have previously configured an Auto-Type hotkey. The global Auto-Type keyboard shortcut is used when you have focus on the window you want to type into. To make use of this feature, you must have previously configured an Auto-Type hotkey.

View File

@ -120,18 +120,7 @@ image::clone_entry_dialog.png[,70%]
.References in a cloned entry .References in a cloned entry
image::clone_entry_references.png[] image::clone_entry_references.png[]
4. You can create your own references using the following syntax: 4. You can create your own references using the xref:UserGuide.adoc#_entry_cross_reference[Entry Reference Syntax]
+
`{REF:<ShortCode>@I:<UUID>}`
+
Where `<UUID>` is the Unique Identifier of the entry to pull data from and `<ShortCode>` is from the following:
+
* T - Title
* U - Username
* P - Password
* A - URL
* N - Notes
* I - UUID
== Searching the Database == Searching the Database
KeePassXC provides an enhanced and granular search features the enables you to search for specific entries in the databases using the different modifiers, wild card characters, and logical operators. KeePassXC provides an enhanced and granular search features the enables you to search for specific entries in the databases using the different modifiers, wild card characters, and logical operators.
@ -287,7 +276,7 @@ image::database_security.png[]
+ +
.Database credentials .Database credentials
image::database_security_credentials.png[] image::database_security_credentials.png[]
+
WARNING: Consider creating a backup of your YubiKey. Please refer to <<Creating a YubiKey backup>> WARNING: Consider creating a backup of your YubiKey. Please refer to <<Creating a YubiKey backup>>
5. Encryption settings allows you to change the average time it takes to encrypt and decrypt the database. The longer time that is chosen, the harder it will be to brute force attack your database. *We recommend a setting of one second.* 5. Encryption settings allows you to change the average time it takes to encrypt and decrypt the database. The longer time that is chosen, the harder it will be to brute force attack your database. *We recommend a setting of one second.*

View File

@ -0,0 +1,94 @@
= KeePassXC - Reference
include::.sharedheader[]
:imagesdir: ../images
// tag::content[]
== Reference
This section contains full details on advanced features available in KeePassXC.
=== Entry Placeholders
[grid=rows, frame=none, width=90%]
|===
|Placeholder |Description
|{TITLE} |Entry Title
|{USERNAME} |Username
|{PASSWORD} |Password
|{URL} |URL
|{NOTES} |Notes
|{TOTP} |Current TOTP value (if configured)
|{S:<ATTRIBUTE_NAME>} |Value for the given attribute (case sensitive)
|{URL:RMVSCM} |URL without scheme (e.g., https)
|{URL:WITHOUTSCHEME} |URL without scheme
|{URL:SCM} |URL Scheme
|{URL:SCHEME} |URL Scheme
|{URL:HOST} |URL Host (e.g., example.com)
|{URL:PORT} |URL Port
|{URL:PATH} |URL Path (e.g., /path/to/page.html)
|{URL:QUERY} |URL Query String
|{URL:FRAGMENT} |URL Fragment
|{URL:USERINFO} |URL Username:Password
|{URL:USERNAME} |URL Username
|{URL:PASSWORD} |URL Password
|{DT_SIMPLE} |Current Date-Time (yyyyMMddhhmmss)
|{DT_YEAR} |Current Year (yyyy)
|{DT_MONTH} |Current Month (MM)
|{DT_DAY} |Current Day (dd)
|{DT_HOUR} |Current Hour (hh)
|{DT_MINUTE} |Current Minutes (mm)
|{DT_SECOND} |Current Seconds (ss)
|{DT_UTC_SIMPLE} |Current UTC Date-Time (yyyyMMddhhmmss)
|{DT_UTC_YEAR} |Current UTC Year (yyyy)
|{DT_UTC_MONTH} |Current UTC Month (MM)
|{DT_UTC_DAY} |Current UTC Day (dd)
|{DT_UTC_HOUR} |Current UTC Hour (hh)
|{DT_UTC_MINUTE} |Current UTC Minutes (mm)
|{DT_UTC_SECOND} |Current UTC Seconds (ss)
|{DB_DIR} |Absolute directory path of database file
|===
=== Entry Cross-Reference
A reference to another entry's field is possible using the short-hand syntax:
`{REF:<FIELD>@<SEARCH_IN>:<SEARCH_TEXT>}`
`<FIELD>` and `<SEARCH_IN>` can be one of following:
* T - Title
* U - Username
* P - Password
* A - URL
* N - Notes
* I - UUID (found on entry properties page)
* O - Custom Attribute _(SEARCH_IN only)_
Examples: +
`{REF:U@I:033054D445C648C59092CC1D661B1B71}` +
`{REF:P@T:Other Entry}` +
`{REF:A@O:Attribute 1}`
=== Auto-Type Actions
[grid=rows, frame=none, width=90%]
|===
|Action Code |Description
|{TAB}, {ENTER}, {SPACE}, {INSERT}, {DELETE}, {HOME}, {END}, {PGUP}, {PGDN}, {BACKSPACE}, {CAPSLOCK}, {ESC}
|Press the corresponding keyboard key
|{UP}, {DOWN}, {LEFT}, {RIGHT} |Press the corresponding arrow key
|{F1}, {F2}, ..., {F16} |Press F1, F2, etc.
|{LEFTBRACE}, {RIGHTBRACE} |Press `{` or `}`, respectively
|{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces)
|{DELAY=X} |Set delay between key presses to X milliseconds
|{DELAY X} |Pause typing for X milliseconds
|{CLEARFIELD} |Clear the input field
|{PICKCHARS} |Pick specific password characters from a dialog
|===
*Text Conversions:*
*{T-CONV:/<PLACEHOLDER>/<METHOD>/}* +
Convert resolved placeholder (e.g., {USERNAME}, {PASSWORD}, etc.) using the following methods: UPPER, LOWER, BASE64, HEX, URI, URI-DEC.
*{T-REPLACE-RX:/<PLACEHOLDER>/<SEARCH>/<REPLACE>/}* +
Use regular expressions to find and replace data from a resolved placeholder. Refer to match groups using $1, $2, etc.
// end::content[]

View File

@ -20,7 +20,6 @@
#include <QApplication> #include <QApplication>
#include <QPluginLoader> #include <QPluginLoader>
#include <QRegularExpression>
#include <QWindow> #include <QWindow>
#include "config-keepassx.h" #include "config-keepassx.h"
@ -32,7 +31,6 @@
#include "core/Database.h" #include "core/Database.h"
#include "core/Entry.h" #include "core/Entry.h"
#include "core/Group.h" #include "core/Group.h"
#include "core/ListDeleter.h"
#include "core/Resources.h" #include "core/Resources.h"
#include "core/Tools.h" #include "core/Tools.h"
#include "gui/MainWindow.h" #include "gui/MainWindow.h"
@ -131,7 +129,7 @@ AutoType::AutoType(QObject* parent, bool test)
// prevent crash when the plugin has unresolved symbols // prevent crash when the plugin has unresolved symbols
m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint); m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint);
QString pluginName = "keepassx-autotype-"; QString pluginName = "keepassxc-autotype-";
if (!test) { if (!test) {
pluginName += QApplication::platformName(); pluginName += QApplication::platformName();
} else { } else {
@ -169,7 +167,7 @@ void AutoType::loadPlugin(const QString& pluginPath)
if (m_plugin) { if (m_plugin) {
if (m_plugin->isAvailable()) { if (m_plugin->isAvailable()) {
m_executor = m_plugin->createExecutor(); m_executor = m_plugin->createExecutor();
connect(osUtils, &OSUtilsBase::globalShortcutTriggered, this, [this](QString name) { connect(osUtils, &OSUtilsBase::globalShortcutTriggered, this, [this](const QString& name) {
if (name == "autotype") { if (name == "autotype") {
startGlobalAutoType(); startGlobalAutoType();
} }
@ -249,6 +247,20 @@ void AutoType::unregisterGlobalShortcut()
*/ */
void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window) void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window)
{ {
QString error;
auto actions = parseSequence(sequence, entry, error);
if (!error.isEmpty()) {
auto errorMsg = tr("The requested Auto-Type sequence cannot be used due to an error:");
errorMsg.append(QString("\n%1\n%2").arg(sequence, error));
if (getMainWindow()) {
MessageBox::critical(getMainWindow(), tr("Auto-Type Error"), errorMsg);
}
qWarning() << errorMsg;
emit autotypeRejected();
return;
}
if (!m_inAutoType.tryLock()) { if (!m_inAutoType.tryLock()) {
return; return;
} }
@ -272,11 +284,9 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
#endif #endif
} }
auto actions = parseActions(sequence, entry);
// Restore window state in case app stole focus // Restore window state in case app stole focus
restoreWindowState(); restoreWindowState();
QApplication::processEvents(); QCoreApplication::processEvents();
m_plugin->raiseWindow(m_windowForGlobal); m_plugin->raiseWindow(m_windowForGlobal);
// Used only for selected entry auto-type // Used only for selected entry auto-type
@ -286,7 +296,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
Tools::wait(qMax(100, config()->get(Config::AutoTypeStartDelay).toInt())); Tools::wait(qMax(100, config()->get(Config::AutoTypeStartDelay).toInt()));
for (auto action : asConst(actions)) { for (const auto& action : asConst(actions)) {
if (m_plugin->activeWindow() != window) { if (m_plugin->activeWindow() != window) {
qWarning("Active window changed, interrupting auto-type."); qWarning("Active window changed, interrupting auto-type.");
emit autotypeRejected(); emit autotypeRejected();
@ -301,7 +311,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
m_windowForGlobal = 0; m_windowForGlobal = 0;
m_windowTitleForGlobal.clear(); m_windowTitleForGlobal.clear();
// emit signal only if autotype performed correctly
emit autotypePerformed(); emit autotypePerformed();
m_inAutoType.unlock(); m_inAutoType.unlock();
} }
@ -426,7 +435,7 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
selectDialog->setMatches(matchList, dbList); selectDialog->setMatches(matchList, dbList);
connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject); connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject);
connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](AutoTypeMatch match) { connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](const AutoTypeMatch& match) {
executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal); executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal);
resetAutoTypeState(); resetAutoTypeState();
}); });
@ -480,7 +489,7 @@ void AutoType::resetAutoTypeState()
* If error is provided then syntax checking will be performed. * If error is provided then syntax checking will be performed.
*/ */
QList<QSharedPointer<AutoTypeAction>> QList<QSharedPointer<AutoTypeAction>>
AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString* error) AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QString& error, bool syntaxOnly)
{ {
const int maxTypeDelay = 100; const int maxTypeDelay = 100;
const int maxWaitDelay = 10000; const int maxWaitDelay = 10000;
@ -495,8 +504,8 @@ AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString
sequence.replace("{}}", "{RIGHTBRACE}"); sequence.replace("{}}", "{RIGHTBRACE}");
// Quick test for bracket syntax // Quick test for bracket syntax
if ((sequence.count("{") + sequence.count("}")) % 2 != 0 && error) { if (sequence.count("{") != sequence.count("}")) {
*error = tr("Bracket imbalance detected, found extra { or }"); error = tr("Bracket imbalance detected, found extra { or }");
return {}; return {};
} }
@ -530,147 +539,163 @@ AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString
} }
auto character = match.captured(5); auto character = match.captured(5);
if (!placeholder.isEmpty()) { if (placeholder.isEmpty()) {
if (g_placeholderToKey.contains(placeholder)) { if (!character.isEmpty()) {
// Basic key placeholder, allow repeat // Type a single character with modifier
if (repeat > maxRepetition && error) { actions << QSharedPointer<AutoTypeKey>::create(character[0], modifiers);
*error = tr("Too many repetitions detected, max is %1: %2").arg(maxRepetition).arg(fullPlaceholder); }
return {}; continue;
} }
auto action = QSharedPointer<AutoTypeKey>::create(g_placeholderToKey[placeholder], modifiers);
for (int i = 1; i <= repeat && i <= maxRepetition; ++i) {
actions << action;
}
} else if (placeholder.startsWith("delay=")) {
// Change keypress delay
int delay = placeholder.replace("delay=", "").toInt();
if (delay > maxTypeDelay && error) {
*error = tr("Very slow key press detected, max is %1: %2").arg(maxTypeDelay).arg(fullPlaceholder);
return {};
}
actions << QSharedPointer<AutoTypeDelay>::create(qBound(0, delay, maxTypeDelay), true);
} else if (placeholder == "delay") {
// Mid typing delay (wait)
if (repeat > maxWaitDelay && error) {
*error = tr("Very long delay detected, max is %1: %2").arg(maxWaitDelay).arg(fullPlaceholder);
return {};
}
actions << QSharedPointer<AutoTypeDelay>::create(qBound(0, repeat, maxWaitDelay));
} else if (placeholder == "clearfield") {
// Platform-specific field clearing
actions << QSharedPointer<AutoTypeClearField>::create();
} else if (placeholder == "totp") {
// Entry totp (requires special handling)
QString totp = entry->totp();
for (const auto& ch : totp) {
actions << QSharedPointer<AutoTypeKey>::create(ch);
}
} else if (placeholder == "pickchars") {
if (error) {
// Ignore this if we are syntax checking
continue;
}
// Show pickchars dialog for entry's password
auto password = entry->resolvePlaceholder(entry->password());
if (!password.isEmpty()) {
PickcharsDialog pickcharsDialog(password);
if (pickcharsDialog.exec() == QDialog::Accepted && !pickcharsDialog.selectedChars().isEmpty()) {
auto chars = pickcharsDialog.selectedChars();
auto iter = chars.begin();
while (iter != chars.end()) {
actions << QSharedPointer<AutoTypeKey>::create(*iter);
++iter;
if (pickcharsDialog.pressTab() && iter != chars.end()) {
actions << QSharedPointer<AutoTypeKey>::create(g_placeholderToKey["tab"]);
}
}
}
}
} else if (placeholder.startsWith("t-conv:")) {
// Reset to the original capture to preserve case
placeholder = match.captured(3);
placeholder.replace("t-conv:", "", Qt::CaseInsensitive);
if (!placeholder.isEmpty()) {
auto sep = placeholder[0];
auto parts = placeholder.split(sep);
if (parts.size() >= 4) {
auto resolved = entry->resolveMultiplePlaceholders(parts[1]);
auto type = parts[2].toLower();
if (type == "base64") { if (g_placeholderToKey.contains(placeholder)) {
resolved = resolved.toUtf8().toBase64(); // Basic key placeholder, allow repeat
} else if (type == "hex") { if (repeat > maxRepetition) {
resolved = resolved.toUtf8().toHex(); error = tr("Too many repetitions detected, max is %1: %2").arg(maxRepetition).arg(fullPlaceholder);
} else if (type == "uri") { return {};
resolved = QUrl::toPercentEncoding(resolved.toUtf8()); }
} else if (type == "uri-dec") { auto action = QSharedPointer<AutoTypeKey>::create(g_placeholderToKey[placeholder], modifiers);
resolved = QUrl::fromPercentEncoding(resolved.toUtf8()); for (int i = 1; i <= repeat && i <= maxRepetition; ++i) {
} else if (type.startsWith("u")) { actions << action;
resolved = resolved.toUpper(); }
} else if (type.startsWith("l")) { } else if (placeholder.startsWith("delay=")) {
resolved = resolved.toLower(); // Change keypress delay
} else if (error) { int delay = placeholder.replace("delay=", "").toInt();
*error = tr("Invalid conversion type: %1").arg(type); if (delay > maxTypeDelay) {
continue; error = tr("Very slow key press detected, max is %1: %2").arg(maxTypeDelay).arg(fullPlaceholder);
} return {};
for (const QChar& ch : resolved) { }
actions << QSharedPointer<AutoTypeKey>::create(ch); actions << QSharedPointer<AutoTypeDelay>::create(qBound(0, delay, maxTypeDelay), true);
} } else if (placeholder == "delay") {
} else if (error) { // Mid typing delay (wait)
*error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder); if (repeat > maxWaitDelay) {
} error = tr("Very long delay detected, max is %1: %2").arg(maxWaitDelay).arg(fullPlaceholder);
} else if (error) { return {};
*error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder); }
} actions << QSharedPointer<AutoTypeDelay>::create(qBound(0, repeat, maxWaitDelay));
} else if (placeholder.startsWith("t-replace-rx:")) { } else if (placeholder == "clearfield") {
// Reset to the original capture to preserve case // Platform-specific field clearing
placeholder = match.captured(3); actions << QSharedPointer<AutoTypeClearField>::create();
placeholder.replace("t-replace-rx:", "", Qt::CaseInsensitive); } else if (placeholder == "totp") {
if (!placeholder.isEmpty()) { // Entry totp (requires special handling)
auto sep = placeholder[0]; QString totp = entry->totp();
auto parts = placeholder.split(sep); for (const auto& ch : totp) {
if (parts.size() >= 5) { actions << QSharedPointer<AutoTypeKey>::create(ch);
auto resolvedText = entry->resolveMultiplePlaceholders(parts[1]); }
auto resolvedSearch = entry->resolveMultiplePlaceholders(parts[2]); } else if (placeholder == "pickchars") {
auto resolvedReplace = entry->resolveMultiplePlaceholders(parts[3]); // Ignore this if we are syntax checking
// Replace $<num> with \<num>s to support Qt substitutions if (syntaxOnly) {
resolvedReplace.replace(QRegularExpression("\\$(\\d+)"), "\\\\1"); continue;
auto resolved = resolvedText.replace(QRegularExpression(resolvedSearch), resolvedReplace); }
for (const QChar& ch : resolved) { // Show pickchars dialog for entry's password
actions << QSharedPointer<AutoTypeKey>::create(ch); auto password = entry->resolvePlaceholder(entry->password());
} if (!password.isEmpty()) {
} else if (error) { PickcharsDialog pickcharsDialog(password);
*error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder); if (pickcharsDialog.exec() == QDialog::Accepted && !pickcharsDialog.selectedChars().isEmpty()) {
} for (const auto& ch : pickcharsDialog.selectedChars()) {
} else if (error) { actions << QSharedPointer<AutoTypeKey>::create(ch);
*error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder); if (pickcharsDialog.pressTab()) {
}
} else if (placeholder == "beep" || placeholder.startsWith("vkey") || placeholder.startsWith("appactivate")
|| placeholder.startsWith("c:")) {
// Ignore these commands
} else {
// Attempt to resolve an entry attribute
auto resolved = entry->resolvePlaceholder(fullPlaceholder);
if (resolved != fullPlaceholder) {
// Attribute resolved, add characters to action list
for (const QChar& ch : resolved) {
if (ch == '\n') {
actions << QSharedPointer<AutoTypeKey>::create(g_placeholderToKey["enter"]);
} else if (ch == '\t') {
actions << QSharedPointer<AutoTypeKey>::create(g_placeholderToKey["tab"]); actions << QSharedPointer<AutoTypeKey>::create(g_placeholderToKey["tab"]);
} else {
actions << QSharedPointer<AutoTypeKey>::create(ch);
} }
} }
} else if (error) { if (pickcharsDialog.pressTab()) {
// Invalid placeholder, issue error and stop processing // Remove extra tab
*error = tr("Invalid placeholder: %1").arg(fullPlaceholder); actions.removeLast();
return {}; }
} }
} }
} else if (!character.isEmpty()) { } else if (placeholder.startsWith("t-conv:")) {
// Type a single character with modifier // Reset to the original capture to preserve case
actions << QSharedPointer<AutoTypeKey>::create(character[0], modifiers); placeholder = match.captured(3);
placeholder.replace("t-conv:", "", Qt::CaseInsensitive);
if (!placeholder.isEmpty()) {
auto sep = placeholder[0];
auto parts = placeholder.split(sep);
if (parts.size() >= 4) {
auto resolved = entry->resolveMultiplePlaceholders(parts[1]);
auto type = parts[2].toLower();
if (type == "base64") {
resolved = resolved.toUtf8().toBase64();
} else if (type == "hex") {
resolved = resolved.toUtf8().toHex();
} else if (type == "uri") {
resolved = QUrl::toPercentEncoding(resolved.toUtf8());
} else if (type == "uri-dec") {
resolved = QUrl::fromPercentEncoding(resolved.toUtf8());
} else if (type.startsWith("u")) {
resolved = resolved.toUpper();
} else if (type.startsWith("l")) {
resolved = resolved.toLower();
} else {
error = tr("Invalid conversion type: %1").arg(type);
return {};
}
for (const QChar& ch : resolved) {
actions << QSharedPointer<AutoTypeKey>::create(ch);
}
} else {
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
return {};
}
} else {
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
return {};
}
} else if (placeholder.startsWith("t-replace-rx:")) {
// Reset to the original capture to preserve case
placeholder = match.captured(3);
placeholder.replace("t-replace-rx:", "", Qt::CaseInsensitive);
if (!placeholder.isEmpty()) {
auto sep = placeholder[0];
auto parts = placeholder.split(sep);
if (parts.size() >= 5) {
auto resolvedText = entry->resolveMultiplePlaceholders(parts[1]);
auto resolvedSearch = entry->resolveMultiplePlaceholders(parts[2]);
auto resolvedReplace = entry->resolveMultiplePlaceholders(parts[3]);
// Replace $<num> with \\<num> to support Qt substitutions
resolvedReplace.replace(QRegularExpression(R"(\$(\d+))"), R"(\\1)");
auto searchRegex = QRegularExpression(resolvedSearch);
if (!searchRegex.isValid()) {
error = tr("Invalid regular expression syntax %1\n%2")
.arg(resolvedSearch, searchRegex.errorString());
return {};
}
auto resolved = resolvedText.replace(searchRegex, resolvedReplace);
for (const QChar& ch : resolved) {
actions << QSharedPointer<AutoTypeKey>::create(ch);
}
} else {
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
return {};
}
} else {
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
return {};
}
} else if (placeholder == "beep" || placeholder.startsWith("vkey") || placeholder.startsWith("appactivate")
|| placeholder.startsWith("c:")) {
// Ignore these commands
} else {
// Attempt to resolve an entry attribute
auto resolved = entry->resolvePlaceholder(fullPlaceholder);
if (resolved != fullPlaceholder) {
// Attribute resolved, add characters to action list
for (const QChar& ch : resolved) {
if (ch == '\n') {
actions << QSharedPointer<AutoTypeKey>::create(g_placeholderToKey["enter"]);
} else if (ch == '\t') {
actions << QSharedPointer<AutoTypeKey>::create(g_placeholderToKey["tab"]);
} else {
actions << QSharedPointer<AutoTypeKey>::create(ch);
}
}
} else {
// Invalid placeholder, issue error and stop processing
error = tr("Invalid placeholder: %1").arg(fullPlaceholder);
return {};
}
} }
} }
@ -684,7 +709,7 @@ bool AutoType::verifyAutoTypeSyntax(const QString& sequence, const Entry* entry,
{ {
error.clear(); error.clear();
if (!sequence.isEmpty()) { if (!sequence.isEmpty()) {
parseActions(sequence, entry, &error); parseSequence(sequence, entry, error, true);
} }
return error.isEmpty(); return error.isEmpty();
} }

View File

@ -86,7 +86,7 @@ private:
void resetAutoTypeState(); void resetAutoTypeState();
static QList<QSharedPointer<AutoTypeAction>> static QList<QSharedPointer<AutoTypeAction>>
parseActions(const QString& entrySequence, const Entry* entry, QString* error = nullptr); parseSequence(const QString& entrySequence, const Entry* entry, QString& error, bool syntaxOnly = false);
QMutex m_inAutoType; QMutex m_inAutoType;
QMutex m_inGlobalAutoTypeDialog; QMutex m_inGlobalAutoTypeDialog;

View File

@ -1,18 +1,18 @@
set(autotype_mac_SOURCES AutoTypeMac.cpp) set(autotype_mac_SOURCES AutoTypeMac.cpp)
add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES}) add_library(keepassxc-autotype-cocoa MODULE ${autotype_mac_SOURCES})
set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon") set_target_properties(keepassxc-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets) target_link_libraries(keepassxc-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
if(WITH_APP_BUNDLE) if(WITH_APP_BUNDLE)
add_custom_command(TARGET keepassx-autotype-cocoa add_custom_command(TARGET keepassxc-autotype-cocoa
POST_BUILD POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassxc-autotype-cocoa.so ${PLUGIN_INSTALL_DIR}/libkeepassxc-autotype-cocoa.so
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins 2> /dev/null COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassxc-autotype-cocoa.so -no-plugins 2> /dev/null
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
COMMENT "Deploying autotype plugin") COMMENT "Deploying autotype plugin")
else() else()
install(TARGETS keepassx-autotype-cocoa install(TARGETS keepassxc-autotype-cocoa
BUNDLE DESTINATION . COMPONENT Runtime BUNDLE DESTINATION . COMPONENT Runtime
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
endif() endif()

View File

@ -1,4 +1,4 @@
set(autotype_test_SOURCES AutoTypeTest.cpp) set(autotype_test_SOURCES AutoTypeTest.cpp)
add_library(keepassx-autotype-test MODULE ${autotype_test_SOURCES}) add_library(keepassxc-autotype-test MODULE ${autotype_test_SOURCES})
target_link_libraries(keepassx-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets) target_link_libraries(keepassxc-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)

View File

@ -1,7 +1,7 @@
set(autotype_win_SOURCES AutoTypeWindows.cpp) set(autotype_win_SOURCES AutoTypeWindows.cpp)
add_library(keepassx-autotype-windows MODULE ${autotype_win_SOURCES}) add_library(keepassxc-autotype-windows MODULE ${autotype_win_SOURCES})
target_link_libraries(keepassx-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets) target_link_libraries(keepassxc-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
install(TARGETS keepassx-autotype-windows install(TARGETS keepassxc-autotype-windows
BUNDLE DESTINATION . COMPONENT Runtime BUNDLE DESTINATION . COMPONENT Runtime
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)

View File

@ -2,8 +2,8 @@ include_directories(SYSTEM ${X11_X11_INCLUDE_PATH})
set(autotype_XCB_SOURCES AutoTypeXCB.cpp) set(autotype_XCB_SOURCES AutoTypeXCB.cpp)
add_library(keepassx-autotype-xcb MODULE ${autotype_XCB_SOURCES}) add_library(keepassxc-autotype-xcb MODULE ${autotype_XCB_SOURCES})
target_link_libraries(keepassx-autotype-xcb keepassx_core Qt5::Core Qt5::Widgets Qt5::X11Extras ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_XTest_LIB}) target_link_libraries(keepassxc-autotype-xcb keepassx_core Qt5::Core Qt5::Widgets Qt5::X11Extras ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_XTest_LIB})
install(TARGETS keepassx-autotype-xcb install(TARGETS keepassxc-autotype-xcb
BUNDLE DESTINATION . COMPONENT Runtime BUNDLE DESTINATION . COMPONENT Runtime
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)

View File

@ -40,7 +40,7 @@ void TestAutoType::initTestCase()
config()->set(Config::Security_AutoTypeAsk, false); config()->set(Config::Security_AutoTypeAsk, false);
AutoType::createTestInstance(); AutoType::createTestInstance();
QPluginLoader loader(resources()->pluginPath("keepassx-autotype-test")); QPluginLoader loader(resources()->pluginPath("keepassxc-autotype-test"));
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
QVERIFY(loader.instance()); QVERIFY(loader.instance());
@ -312,7 +312,7 @@ void TestAutoType::testAutoTypeResults_data()
QTest::newRow("T-CONV HEX") << QString("{T-CONV:/{USERNAME}/HEX/}") << QString("557365726e616d65"); QTest::newRow("T-CONV HEX") << QString("{T-CONV:/{USERNAME}/HEX/}") << QString("557365726e616d65");
QTest::newRow("T-CONV URI ENCODE") << QString("{T-CONV:/{URL}/URI/}") << QString("https%3A%2F%2Fexample.com"); QTest::newRow("T-CONV URI ENCODE") << QString("{T-CONV:/{URL}/URI/}") << QString("https%3A%2F%2Fexample.com");
QTest::newRow("T-CONV URI DECODE") << QString("{T-CONV:/{S:attr2}/URI-DEC/}") << QString("decode me"); QTest::newRow("T-CONV URI DECODE") << QString("{T-CONV:/{S:attr2}/URI-DEC/}") << QString("decode me");
QTest::newRow("T-REPLACE-RX") << QString("{T-REPLACE-RX:/{USERNAME}/User/Pass/}") << QString("Passname"); QTest::newRow("T-REPLACE-RX") << QString("{T-REPLACE-RX:/{USERNAME}/(User)/$1Pass/}") << QString("UserPassname");
} }
void TestAutoType::testAutoTypeSyntaxChecks() void TestAutoType::testAutoTypeSyntaxChecks()