From 7f92504a2d0fbd04d9ec811804b8a633dddde4b1 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Thu, 30 Dec 2021 09:19:07 -0500 Subject: [PATCH] Fix wildcard window title matching in Auto-Type * Fixes #6413 --- src/core/Entry.cpp | 11 +++++---- src/core/EntrySearcher.cpp | 9 ++++++- src/core/Tools.cpp | 24 ++++++++++++------ src/core/Tools.h | 18 +++++++++++--- src/fdosecrets/objects/Collection.cpp | 9 +++---- tests/TestTools.cpp | 35 +++++++++++++++++++++++++++ tests/TestTools.h | 2 ++ 7 files changed, 85 insertions(+), 23 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 650a26ad1..c90760765 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -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 Entry::autoTypeSequences(const QString& windowTitle) const @@ -295,13 +295,14 @@ QList 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) { diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp index 2dde80698..e52033a04 100644 --- a/src/core/EntrySearcher.cpp +++ b/src/core/EntrySearcher.cpp @@ -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("!"); diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 7495952f7..5d3dff602 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -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); } diff --git a/src/core/Tools.h b/src/core/Tools.h index c605143b7..d8a1371f1 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -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 RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value) { diff --git a/src/fdosecrets/objects/Collection.cpp b/src/fdosecrets/objects/Collection.cpp index 610a4b99e..1c870beaa 100644 --- a/src/fdosecrets/objects/Collection.cpp +++ b/src/fdosecrets/objects/Collection.cpp @@ -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; } diff --git a/tests/TestTools.cpp b/tests/TestTools.cpp index 9e3dadc45..438606c7e 100644 --- a/tests/TestTools.cpp +++ b/tests/TestTools.cpp @@ -19,6 +19,7 @@ #include "core/Clock.h" +#include #include #include @@ -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("input"); + QTest::addColumn("options"); + QTest::addColumn("expected"); + + QTest::newRow("No Options") << input << static_cast(Tools::RegexConvertOpts::DEFAULT) + << QString(R"(te|st*t?[5]^(test);',.)"); + QTest::newRow("Exact Match") << input << static_cast(Tools::RegexConvertOpts::EXACT_MATCH) + << QString(R"(^te|st*t?[5]^(test);',.$)"); + QTest::newRow("Exact Match & Wildcard") + << input << static_cast(Tools::RegexConvertOpts::EXACT_MATCH | Tools::RegexConvertOpts::WILDCARD_ALL) + << QString(R"(^te|st.*t.\[5\]\^\(test\);'\,\.$)"); + QTest::newRow("Wildcard Single Match") << input << static_cast(Tools::RegexConvertOpts::WILDCARD_SINGLE_MATCH) + << QString(R"(te\|st\*t.\[5\]\^\(test\);'\,\.)"); + QTest::newRow("Wildcard OR") << input << static_cast(Tools::RegexConvertOpts::WILDCARD_LOGICAL_OR) + << QString(R"(te|st\*t\?\[5\]\^\(test\);'\,\.)"); + QTest::newRow("Wildcard Unlimited Match") + << input << static_cast(Tools::RegexConvertOpts::WILDCARD_UNLIMITED_MATCH) + << QString(R"(te\|st.*t\?\[5\]\^\(test\);'\,\.)"); +} diff --git a/tests/TestTools.h b/tests/TestTools.h index 29f2bf0f5..f590a53a1 100644 --- a/tests/TestTools.h +++ b/tests/TestTools.h @@ -31,6 +31,8 @@ private slots: void testValidUuid(); void testBackupFilePatternSubstitution_data(); void testBackupFilePatternSubstitution(); + void testConvertToRegex(); + void testConvertToRegex_data(); }; #endif // KEEPASSX_TESTTOOLS_H