Auto-Type support for T-CONV, T-REPLACE-RX, and Comments

* Close #1825 - Add full support for T-CONV and T-REPLACE-RX placeholders. Exception is support for the "raw" type in T-CONV.

* Close #5333 - Allow comment syntax to be present in the Auto-Type sequence
This commit is contained in:
Jonathan White 2020-12-24 10:58:11 -05:00
parent 813ab47e29
commit 02446af743
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
4 changed files with 135 additions and 23 deletions

View File

@ -31,27 +31,9 @@ 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. A complete list of supported actions and placeholders can be found at https://keepass.info/help/base/autotype.html#autoseq[KeePass Auto-Type Action Codes] and https://keepass.info/help/base/placeholders.html[KeePass Placeholders]. 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: 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.
+
[grid=rows, frame=none, width=70%]
|===
|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
|{DELAY=X} |Set key press delay to X milliseconds
|{DELAY X} |Delay typing start by X milliseconds
|{CLEARFIELD} |Clear the input field before typing
|{TOTP} |Insert calculated TOTP value (if configured)
|{PICKCHARS} |Pick specific password characters from a dialog
|{<ACTION> X} |Repeat <ACTION> X times (e.g., {SPACE 5} inserts five spaces)
|===
+
[grid=rows, frame=none, width=70%]
[grid=rows, frame=none, width=90%]
|===
|Placeholder |Description
@ -64,8 +46,33 @@ image::autotype_entry_sequences.png[]
|{DT_SIMPLE} |Current date-time
|{DB_DIR} |Absolute directory path for database file
|{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.
|{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%]
|===
|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. 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
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

@ -586,8 +586,67 @@ AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString
}
}
}
} else if (placeholder == "beep" || placeholder.startsWith("vkey")
|| placeholder.startsWith("appactivate")) {
} 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 if (error) {
*error = tr("Invalid conversion type: %1").arg(type);
continue;
}
for (const QChar& ch : resolved) {
actions << QSharedPointer<AutoTypeKey>::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 $<num> with \<num>s to support Qt substitutions
resolvedReplace.replace(QRegularExpression("\\$(\\d+)"), "\\\\1");
auto resolved = resolvedText.replace(QRegularExpression(resolvedSearch), resolvedReplace);
for (const QChar& ch : resolved) {
actions << QSharedPointer<AutoTypeKey>::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

View File

@ -278,6 +278,43 @@ void TestAutoType::testGlobalAutoTypeRegExp()
m_test->clearActions();
}
void TestAutoType::testAutoTypeResults()
{
QScopedPointer<Entry> entry(new Entry());
entry->setUsername("Username");
entry->setPassword("Password@1");
entry->setUrl("https://example.com");
entry->attributes()->set("attr1", "value1");
entry->attributes()->set("attr2", "decode%20me");
QFETCH(QString, sequence);
QFETCH(QString, expectedResult);
m_autoType->performAutoTypeWithSequence(entry.data(), sequence);
QCOMPARE(m_test->actionChars(), expectedResult);
}
void TestAutoType::testAutoTypeResults_data()
{
QTest::addColumn<QString>("sequence");
QTest::addColumn<QString>("expectedResult");
// Normal Sequences
QTest::newRow("Sequence with Attributes") << QString("{USERNAME} {PASSWORD} {URL} {S:attr1}")
<< QString("Username Password@1 https://example.com value1");
QTest::newRow("Sequence with Comment") << QString("{USERNAME}{TAB}{C:Extra Tab}{TAB}{S:attr1}")
<< QString("Username[Key0x1000001][Key0x1000001]value1");
// Conversions and Replacements
QTest::newRow("T-CONV UPPER") << QString("{T-CONV:/{USERNAME}/UPPER/}") << QString("USERNAME");
QTest::newRow("T-CONV LOWER") << QString("{T-CONV:/{USERNAME}/LOWER/}") << QString("username");
QTest::newRow("T-CONV BASE64") << QString("{T-CONV:/{USERNAME}/BASE64/}") << QString("VXNlcm5hbWU=");
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");
}
void TestAutoType::testAutoTypeSyntaxChecks()
{
auto entry = new Entry();
@ -321,6 +358,13 @@ void TestAutoType::testAutoTypeSyntaxChecks()
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{LEFT 50000000}", entry, error), error.toLatin1());
QVERIFY2(AutoType::verifyAutoTypeSyntax("{SPACE 10}{TAB 3}{RIGHT 50}", entry, error), error.toLatin1());
QVERIFY2(AutoType::verifyAutoTypeSyntax("{delay 5000000000}", entry, error), error.toLatin1());
// Conversion and Regex
QVERIFY2(AutoType::verifyAutoTypeSyntax("{T-CONV:/{USERNAME}/base64/}", entry, error), error.toLatin1());
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{T-CONV:/{USERNAME}/junk/}", entry, error), error.toLatin1());
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{T-CONV:}", entry, error), error.toLatin1());
QVERIFY2(AutoType::verifyAutoTypeSyntax("{T-REPLACE-RX:/{USERNAME}/a/b/}", entry, error), error.toLatin1());
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{T-REPLACE-RX:/{USERNAME}/a/}", entry, error), error.toLatin1());
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{T-REPLACE-RX:}", entry, error), error.toLatin1());
}
void TestAutoType::testAutoTypeEffectiveSequences()

View File

@ -47,6 +47,8 @@ private slots:
void testGlobalAutoTypeUrlSubdomainMatch();
void testGlobalAutoTypeTitleMatchDisabled();
void testGlobalAutoTypeRegExp();
void testAutoTypeResults();
void testAutoTypeResults_data();
void testAutoTypeSyntaxChecks();
void testAutoTypeEffectiveSequences();