diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 439bcd41..4000f564 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -34,3 +34,5 @@ include::topics/AutoType.adoc[tags=*] include::topics/KeeShare.adoc[tags=*] include::topics/SSHAgent.adoc[tags=*] + +include::topics/Reference.adoc[tags=*] diff --git a/docs/topics/AutoType.adoc b/docs/topics/AutoType.adoc index a5376691..019afd2b 100644 --- a/docs/topics/AutoType.adoc +++ b/docs/topics/AutoType.adoc @@ -31,7 +31,7 @@ To configure Auto-Type sequences for your entries, perform the following steps: .Auto-Type entry sequences 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%] |=== @@ -43,10 +43,7 @@ image::autotype_entry_sequences.png[] |{URL} |URL |{NOTES} |Notes |{TOTP} |Current TOTP value (if configured) -|{DT_SIMPLE} |Current date-time -|{DB_DIR} |Absolute directory path for database file |{S:} |Value for the given attribute name -|{REF:@:} |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%] @@ -57,22 +54,13 @@ image::autotype_entry_sequences.png[] |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 -|{ X} |Repeat X times (e.g., {SPACE 5} inserts five spaces) +|{ X} |Repeat 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:///}* + -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:////}* + -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 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. diff --git a/docs/topics/DatabaseOperations.adoc b/docs/topics/DatabaseOperations.adoc index 0cb15ea3..0b5790bd 100644 --- a/docs/topics/DatabaseOperations.adoc +++ b/docs/topics/DatabaseOperations.adoc @@ -120,18 +120,7 @@ image::clone_entry_dialog.png[,70%] .References in a cloned entry image::clone_entry_references.png[] -4. You can create your own references using the following syntax: -+ -`{REF:@I:}` -+ -Where `` is the Unique Identifier of the entry to pull data from and `` is from the following: -+ - * T - Title - * U - Username - * P - Password - * A - URL - * N - Notes - * I - UUID +4. You can create your own references using the xref:UserGuide.adoc#_entry_cross_reference[Entry Reference Syntax] == 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. @@ -287,7 +276,7 @@ image::database_security.png[] + .Database credentials image::database_security_credentials.png[] - ++ WARNING: Consider creating a backup of your YubiKey. Please refer to <> 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.* diff --git a/docs/topics/Reference.adoc b/docs/topics/Reference.adoc new file mode 100644 index 00000000..3a19c23f --- /dev/null +++ b/docs/topics/Reference.adoc @@ -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:} |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:@:}` + +`` and `` 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 +|{ X} |Repeat 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:///}* + +Convert resolved placeholder (e.g., {USERNAME}, {PASSWORD}, etc.) using the following methods: UPPER, LOWER, BASE64, HEX, URI, URI-DEC. + +*{T-REPLACE-RX:////}* + +Use regular expressions to find and replace data from a resolved placeholder. Refer to match groups using $1, $2, etc. +// end::content[] diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 75c7f990..513d8590 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include "config-keepassx.h" @@ -32,7 +31,6 @@ #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" -#include "core/ListDeleter.h" #include "core/Resources.h" #include "core/Tools.h" #include "gui/MainWindow.h" @@ -131,7 +129,7 @@ AutoType::AutoType(QObject* parent, bool test) // prevent crash when the plugin has unresolved symbols m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint); - QString pluginName = "keepassx-autotype-"; + QString pluginName = "keepassxc-autotype-"; if (!test) { pluginName += QApplication::platformName(); } else { @@ -169,7 +167,7 @@ void AutoType::loadPlugin(const QString& pluginPath) if (m_plugin) { if (m_plugin->isAvailable()) { 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") { startGlobalAutoType(); } @@ -249,6 +247,20 @@ void AutoType::unregisterGlobalShortcut() */ 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()) { return; } @@ -272,11 +284,9 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c #endif } - auto actions = parseActions(sequence, entry); - // Restore window state in case app stole focus restoreWindowState(); - QApplication::processEvents(); + QCoreApplication::processEvents(); m_plugin->raiseWindow(m_windowForGlobal); // 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())); - for (auto action : asConst(actions)) { + for (const auto& action : asConst(actions)) { if (m_plugin->activeWindow() != window) { qWarning("Active window changed, interrupting auto-type."); emit autotypeRejected(); @@ -301,7 +311,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c m_windowForGlobal = 0; m_windowTitleForGlobal.clear(); - // emit signal only if autotype performed correctly emit autotypePerformed(); m_inAutoType.unlock(); } @@ -426,7 +435,7 @@ void AutoType::performGlobalAutoType(const QList>& dbLi selectDialog->setMatches(matchList, dbList); 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); resetAutoTypeState(); }); @@ -480,7 +489,7 @@ void AutoType::resetAutoTypeState() * If error is provided then syntax checking will be performed. */ QList> -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 maxWaitDelay = 10000; @@ -495,8 +504,8 @@ AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString sequence.replace("{}}", "{RIGHTBRACE}"); // Quick test for bracket syntax - if ((sequence.count("{") + sequence.count("}")) % 2 != 0 && error) { - *error = tr("Bracket imbalance detected, found extra { or }"); + if (sequence.count("{") != sequence.count("}")) { + error = tr("Bracket imbalance detected, found extra { or }"); return {}; } @@ -530,147 +539,163 @@ AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString } auto character = match.captured(5); - if (!placeholder.isEmpty()) { - if (g_placeholderToKey.contains(placeholder)) { - // Basic key placeholder, allow repeat - if (repeat > maxRepetition && error) { - *error = tr("Too many repetitions detected, max is %1: %2").arg(maxRepetition).arg(fullPlaceholder); - return {}; - } - auto action = QSharedPointer::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::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::create(qBound(0, repeat, maxWaitDelay)); - } else if (placeholder == "clearfield") { - // Platform-specific field clearing - actions << QSharedPointer::create(); - } else if (placeholder == "totp") { - // Entry totp (requires special handling) - QString totp = entry->totp(); - for (const auto& ch : totp) { - actions << QSharedPointer::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::create(*iter); - ++iter; - if (pickcharsDialog.pressTab() && iter != chars.end()) { - actions << QSharedPointer::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 (placeholder.isEmpty()) { + if (!character.isEmpty()) { + // Type a single character with modifier + actions << QSharedPointer::create(character[0], modifiers); + } + continue; + } - 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 if (error) { - *error = tr("Invalid conversion type: %1").arg(type); - continue; - } - for (const QChar& ch : resolved) { - actions << QSharedPointer::create(ch); - } - } else if (error) { - *error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder); - } - } else if (error) { - *error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder); - } - } 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 $ with \s to support Qt substitutions - resolvedReplace.replace(QRegularExpression("\\$(\\d+)"), "\\\\1"); - auto resolved = resolvedText.replace(QRegularExpression(resolvedSearch), resolvedReplace); - for (const QChar& ch : resolved) { - actions << QSharedPointer::create(ch); - } - } else if (error) { - *error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder); - } - } else if (error) { - *error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder); - } - } 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::create(g_placeholderToKey["enter"]); - } else if (ch == '\t') { + if (g_placeholderToKey.contains(placeholder)) { + // Basic key placeholder, allow repeat + if (repeat > maxRepetition) { + error = tr("Too many repetitions detected, max is %1: %2").arg(maxRepetition).arg(fullPlaceholder); + return {}; + } + auto action = QSharedPointer::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 = tr("Very slow key press detected, max is %1: %2").arg(maxTypeDelay).arg(fullPlaceholder); + return {}; + } + actions << QSharedPointer::create(qBound(0, delay, maxTypeDelay), true); + } else if (placeholder == "delay") { + // Mid typing delay (wait) + if (repeat > maxWaitDelay) { + error = tr("Very long delay detected, max is %1: %2").arg(maxWaitDelay).arg(fullPlaceholder); + return {}; + } + actions << QSharedPointer::create(qBound(0, repeat, maxWaitDelay)); + } else if (placeholder == "clearfield") { + // Platform-specific field clearing + actions << QSharedPointer::create(); + } else if (placeholder == "totp") { + // Entry totp (requires special handling) + QString totp = entry->totp(); + for (const auto& ch : totp) { + actions << QSharedPointer::create(ch); + } + } else if (placeholder == "pickchars") { + // Ignore this if we are syntax checking + if (syntaxOnly) { + 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()) { + for (const auto& ch : pickcharsDialog.selectedChars()) { + actions << QSharedPointer::create(ch); + if (pickcharsDialog.pressTab()) { actions << QSharedPointer::create(g_placeholderToKey["tab"]); - } else { - actions << QSharedPointer::create(ch); } } - } else if (error) { - // Invalid placeholder, issue error and stop processing - *error = tr("Invalid placeholder: %1").arg(fullPlaceholder); - return {}; + if (pickcharsDialog.pressTab()) { + // Remove extra tab + actions.removeLast(); + } } } - } else if (!character.isEmpty()) { - // Type a single character with modifier - actions << QSharedPointer::create(character[0], modifiers); + } 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") { + 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::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 $ with \\ 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::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::create(g_placeholderToKey["enter"]); + } else if (ch == '\t') { + actions << QSharedPointer::create(g_placeholderToKey["tab"]); + } else { + actions << QSharedPointer::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(); if (!sequence.isEmpty()) { - parseActions(sequence, entry, &error); + parseSequence(sequence, entry, error, true); } return error.isEmpty(); } diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index c11845d4..e9d7e25e 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -86,7 +86,7 @@ private: void resetAutoTypeState(); static QList> - 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_inGlobalAutoTypeDialog; diff --git a/src/autotype/mac/CMakeLists.txt b/src/autotype/mac/CMakeLists.txt index adc43922..883eeab7 100644 --- a/src/autotype/mac/CMakeLists.txt +++ b/src/autotype/mac/CMakeLists.txt @@ -1,18 +1,18 @@ set(autotype_mac_SOURCES AutoTypeMac.cpp) -add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES}) -set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon") -target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets) +add_library(keepassxc-autotype-cocoa MODULE ${autotype_mac_SOURCES}) +set_target_properties(keepassxc-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon") +target_link_libraries(keepassxc-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets) if(WITH_APP_BUNDLE) - add_custom_command(TARGET keepassx-autotype-cocoa + add_custom_command(TARGET keepassxc-autotype-cocoa POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so - COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins 2> /dev/null + 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}/libkeepassxc-autotype-cocoa.so -no-plugins 2> /dev/null WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src COMMENT "Deploying autotype plugin") else() - install(TARGETS keepassx-autotype-cocoa + install(TARGETS keepassxc-autotype-cocoa BUNDLE DESTINATION . COMPONENT Runtime LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) endif() diff --git a/src/autotype/test/CMakeLists.txt b/src/autotype/test/CMakeLists.txt index a9cf998d..453e36af 100644 --- a/src/autotype/test/CMakeLists.txt +++ b/src/autotype/test/CMakeLists.txt @@ -1,4 +1,4 @@ set(autotype_test_SOURCES AutoTypeTest.cpp) -add_library(keepassx-autotype-test MODULE ${autotype_test_SOURCES}) -target_link_libraries(keepassx-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets) +add_library(keepassxc-autotype-test MODULE ${autotype_test_SOURCES}) +target_link_libraries(keepassxc-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets) diff --git a/src/autotype/windows/CMakeLists.txt b/src/autotype/windows/CMakeLists.txt index be74b7aa..33bd54c5 100644 --- a/src/autotype/windows/CMakeLists.txt +++ b/src/autotype/windows/CMakeLists.txt @@ -1,7 +1,7 @@ set(autotype_win_SOURCES AutoTypeWindows.cpp) -add_library(keepassx-autotype-windows MODULE ${autotype_win_SOURCES}) -target_link_libraries(keepassx-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets) -install(TARGETS keepassx-autotype-windows +add_library(keepassxc-autotype-windows MODULE ${autotype_win_SOURCES}) +target_link_libraries(keepassxc-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets) +install(TARGETS keepassxc-autotype-windows BUNDLE DESTINATION . COMPONENT Runtime LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) diff --git a/src/autotype/xcb/CMakeLists.txt b/src/autotype/xcb/CMakeLists.txt index e41d4a09..0704de63 100644 --- a/src/autotype/xcb/CMakeLists.txt +++ b/src/autotype/xcb/CMakeLists.txt @@ -2,8 +2,8 @@ include_directories(SYSTEM ${X11_X11_INCLUDE_PATH}) set(autotype_XCB_SOURCES AutoTypeXCB.cpp) -add_library(keepassx-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}) -install(TARGETS keepassx-autotype-xcb +add_library(keepassxc-autotype-xcb MODULE ${autotype_XCB_SOURCES}) +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 keepassxc-autotype-xcb BUNDLE DESTINATION . COMPONENT Runtime LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index c053d317..ee1906dd 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -40,7 +40,7 @@ void TestAutoType::initTestCase() config()->set(Config::Security_AutoTypeAsk, false); AutoType::createTestInstance(); - QPluginLoader loader(resources()->pluginPath("keepassx-autotype-test")); + QPluginLoader loader(resources()->pluginPath("keepassxc-autotype-test")); loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); 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 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-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()