Fix wildcard window title matching in Auto-Type

* Fixes #6413
This commit is contained in:
Jonathan White 2021-12-30 09:19:07 -05:00
parent 4a21cee98c
commit 7f92504a2d
7 changed files with 85 additions and 23 deletions

View File

@ -281,7 +281,7 @@ QString Entry::effectiveAutoTypeSequence() const
}
/**
* Retrive the autotype sequences matches for a given windowTitle
* Retrieve the Auto-Type sequences matches for a given windowTitle
* This returns a list with priority ordering. If you don't want duplicates call .toSet() on it.
*/
QList<QString> Entry::autoTypeSequences(const QString& windowTitle) const
@ -295,13 +295,14 @@ QList<QString> Entry::autoTypeSequences(const QString& windowTitle) const
auto windowMatches = [&](const QString& pattern) {
// Regex searching
if (pattern.startsWith("//") && pattern.endsWith("//") && pattern.size() >= 4) {
QRegExp regExp(pattern.mid(2, pattern.size() - 4), Qt::CaseInsensitive, QRegExp::RegExp2);
return (regExp.indexIn(windowTitle) != -1);
QRegularExpression regExp(pattern.mid(2, pattern.size() - 4), QRegularExpression::CaseInsensitiveOption);
return regExp.match(windowTitle).hasMatch();
}
// Wildcard searching
auto regex = Tools::convertToRegex(pattern, true, false, false);
return windowTitle.contains(regex);
const auto regExp = Tools::convertToRegex(
pattern, Tools::RegexConvertOpts::EXACT_MATCH | Tools::RegexConvertOpts::WILDCARD_UNLIMITED_MATCH);
return regExp.match(windowTitle).hasMatch();
};
auto windowMatchesTitle = [&](const QString& entryTitle) {

View File

@ -277,7 +277,14 @@ void EntrySearcher::parseSearchTerms(const QString& searchString)
auto mods = result.captured(1);
// Convert term to regex
term.regex = Tools::convertToRegex(term.word, !mods.contains("*"), mods.contains("+"), m_caseSensitive);
int opts = m_caseSensitive ? Tools::RegexConvertOpts::CASE_SENSITIVE : Tools::RegexConvertOpts::DEFAULT;
if (!mods.contains("*")) {
opts |= Tools::RegexConvertOpts::WILDCARD_ALL;
}
if (mods.contains("+")) {
opts |= Tools::RegexConvertOpts::EXACT_MATCH;
}
term.regex = Tools::convertToRegex(term.word, opts);
// Exclude modifier
term.exclude = mods.contains("-") || mods.contains("!");

View File

@ -296,27 +296,35 @@ namespace Tools
return true;
}
// Escape common regex symbols except for *, ?, and |
auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re");
// Escape regex symbols
auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#|*?]))re");
QRegularExpression convertToRegex(const QString& string, bool useWildcards, bool exactMatch, bool caseSensitive)
QRegularExpression convertToRegex(const QString& string, int opts)
{
QString pattern = string;
// Wildcard support (*, ?, |)
if (useWildcards) {
if (opts & RegexConvertOpts::WILDCARD_ALL || opts & RegexConvertOpts::ESCAPE_REGEX) {
pattern.replace(regexEscape, "\\\\1");
pattern.replace("*", ".*");
pattern.replace("?", ".");
if (opts & RegexConvertOpts::WILDCARD_UNLIMITED_MATCH) {
pattern.replace("\\*", ".*");
}
if (opts & RegexConvertOpts::WILDCARD_SINGLE_MATCH) {
pattern.replace("\\?", ".");
}
if (opts & RegexConvertOpts::WILDCARD_LOGICAL_OR) {
pattern.replace("\\|", "|");
}
}
// Exact modifier
if (exactMatch) {
if (opts & RegexConvertOpts::EXACT_MATCH) {
pattern = "^" + pattern + "$";
}
auto regex = QRegularExpression(pattern);
if (!caseSensitive) {
if ((opts & RegexConvertOpts::CASE_SENSITIVE) != RegexConvertOpts::CASE_SENSITIVE) {
regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
}

View File

@ -42,13 +42,23 @@ namespace Tools
QString uuidToHex(const QUuid& uuid);
QUuid hexToUuid(const QString& uuid);
bool isValidUuid(const QString& uuidStr);
QRegularExpression convertToRegex(const QString& string,
bool useWildcards = false,
bool exactMatch = false,
bool caseSensitive = false);
QString envSubstitute(const QString& filepath,
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment());
enum RegexConvertOpts
{
DEFAULT = 0,
WILDCARD_UNLIMITED_MATCH = 0x1,
WILDCARD_SINGLE_MATCH = 0x2,
WILDCARD_LOGICAL_OR = 0x4,
WILDCARD_ALL = WILDCARD_UNLIMITED_MATCH | WILDCARD_SINGLE_MATCH | WILDCARD_LOGICAL_OR,
EXACT_MATCH = 0x8,
CASE_SENSITIVE = 0x16,
ESCAPE_REGEX = 0x32,
};
QRegularExpression convertToRegex(const QString& string, int opts = RegexConvertOpts::DEFAULT);
template <typename RandomAccessIterator, typename T>
RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value)
{

View File

@ -294,11 +294,10 @@ namespace FdoSecrets
term.field = attrKeyToField.value(key, EntrySearcher::Field::AttributeValue);
term.word = key;
term.exclude = false;
const auto useWildcards = false;
const auto exactMatch = true;
const auto caseSensitive = true;
term.regex = Tools::convertToRegex(QRegularExpression::escape(value), useWildcards, exactMatch, caseSensitive);
term.regex =
Tools::convertToRegex(value,
Tools::RegexConvertOpts::EXACT_MATCH | Tools::RegexConvertOpts::CASE_SENSITIVE
| Tools::RegexConvertOpts::ESCAPE_REGEX);
return term;
}

View File

@ -19,6 +19,7 @@
#include "core/Clock.h"
#include <QRegularExpression>
#include <QTest>
#include <QUuid>
@ -163,3 +164,37 @@ void TestTools::testBackupFilePatternSubstitution()
QCOMPARE(Tools::substituteBackupFilePath(pattern, dbFilePath), expectedSubstitution);
}
void TestTools::testConvertToRegex()
{
QFETCH(QString, input);
QFETCH(int, options);
QFETCH(QString, expected);
auto regex = Tools::convertToRegex(input, options).pattern();
QCOMPARE(regex, expected);
}
void TestTools::testConvertToRegex_data()
{
const QString input = R"(te|st*t?[5]^(test);',.)";
QTest::addColumn<QString>("input");
QTest::addColumn<int>("options");
QTest::addColumn<QString>("expected");
QTest::newRow("No Options") << input << static_cast<int>(Tools::RegexConvertOpts::DEFAULT)
<< QString(R"(te|st*t?[5]^(test);',.)");
QTest::newRow("Exact Match") << input << static_cast<int>(Tools::RegexConvertOpts::EXACT_MATCH)
<< QString(R"(^te|st*t?[5]^(test);',.$)");
QTest::newRow("Exact Match & Wildcard")
<< input << static_cast<int>(Tools::RegexConvertOpts::EXACT_MATCH | Tools::RegexConvertOpts::WILDCARD_ALL)
<< QString(R"(^te|st.*t.\[5\]\^\(test\);'\,\.$)");
QTest::newRow("Wildcard Single Match") << input << static_cast<int>(Tools::RegexConvertOpts::WILDCARD_SINGLE_MATCH)
<< QString(R"(te\|st\*t.\[5\]\^\(test\);'\,\.)");
QTest::newRow("Wildcard OR") << input << static_cast<int>(Tools::RegexConvertOpts::WILDCARD_LOGICAL_OR)
<< QString(R"(te|st\*t\?\[5\]\^\(test\);'\,\.)");
QTest::newRow("Wildcard Unlimited Match")
<< input << static_cast<int>(Tools::RegexConvertOpts::WILDCARD_UNLIMITED_MATCH)
<< QString(R"(te\|st.*t\?\[5\]\^\(test\);'\,\.)");
}

View File

@ -31,6 +31,8 @@ private slots:
void testValidUuid();
void testBackupFilePatternSubstitution_data();
void testBackupFilePatternSubstitution();
void testConvertToRegex();
void testConvertToRegex_data();
};
#endif // KEEPASSX_TESTTOOLS_H