From 32fbea9125bfe253f7d4bed94aa71eca60a8843c Mon Sep 17 00:00:00 2001 From: Dan Torrey Date: Tue, 11 Feb 2025 11:16:56 -0600 Subject: [PATCH] Add support for quoted (default) MongoDB search term values in SearchQueryParser (#21567) * Add support for quoted default search term values * Add change log * Cleanup --- changelog/unreleased/issue-21565.toml | 5 ++++ .../graylog2/search/SearchQueryParser.java | 10 ++++++- .../search/SearchQueryParserTest.java | 26 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/issue-21565.toml diff --git a/changelog/unreleased/issue-21565.toml b/changelog/unreleased/issue-21565.toml new file mode 100644 index 000000000000..75d2958e908c --- /dev/null +++ b/changelog/unreleased/issue-21565.toml @@ -0,0 +1,5 @@ +type = "fixed" +message = "Fixed issue preventing quoted entity search values from finding exact matches on various pages throughout Graylog." + +issues = ["21565"] +pulls = ["21567"] diff --git a/graylog2-server/src/main/java/org/graylog2/search/SearchQueryParser.java b/graylog2-server/src/main/java/org/graylog2/search/SearchQueryParser.java index b6665072ece2..f145a0c121eb 100644 --- a/graylog2-server/src/main/java/org/graylog2/search/SearchQueryParser.java +++ b/graylog2-server/src/main/java/org/graylog2/search/SearchQueryParser.java @@ -77,8 +77,16 @@ public class SearchQueryParser { private static final Splitter FIELD_VALUE_SPLITTER = Splitter.on(":").limit(2).omitEmptyStrings().trimResults(); private static final Splitter VALUE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults(); + // Pattern to split a search query into individual parsable elements terms. + private static final String TERM_SPLIT_PATTERN = + "(\\S+:(=|=~|<|<=|>|>=)?'(?:[^'\\\\]|\\\\.)*')|" + // Split field-specific terms with single-quotes: title:'value' + "(\\S+:(=|=~|<|<=|>|>=)?\"(?:[^\"\\\\]|\\\\.)*\")|" + // Split field-specific terms with double-quotes title:"value" + "['\"][^\\\\]*?(?:\\\\.[^\\\\]*)*?['\"]|" + // Split single quoted value: "value one" + "\\S+:(=|=~|<|<=|>|>=)?\\S+|" + // Split field-specific terms without quotes title:value + "\\S+"; // Split the words of any other string value + // This needs to be updated if more operators are added - private static final Pattern QUERY_SPLITTER_PATTERN = Pattern.compile("(\\S+:(=|=~|<|<=|>|>=)?'(?:[^'\\\\]|\\\\.)*')|(\\S+:(=|=~|<|<=|>|>=)?\"(?:[^\"\\\\]|\\\\.)*\")|\\S+|\\S+:(=|=~|<|<=|>|>=)?\\S+"); + private static final Pattern QUERY_SPLITTER_PATTERN = Pattern.compile(TERM_SPLIT_PATTERN); private static final String INVALID_ENTRY_MESSAGE = "Chunk [%s] is not a valid entry"; private static final String QUOTE_REPLACE_REGEX = "^[\"']|[\"']$"; public static final SearchQueryOperator DEFAULT_STRING_OPERATOR = SearchQueryOperators.REGEXP; diff --git a/graylog2-server/src/test/java/org/graylog2/search/SearchQueryParserTest.java b/graylog2-server/src/test/java/org/graylog2/search/SearchQueryParserTest.java index 910d0df67910..a309f272e279 100644 --- a/graylog2-server/src/test/java/org/graylog2/search/SearchQueryParserTest.java +++ b/graylog2-server/src/test/java/org/graylog2/search/SearchQueryParserTest.java @@ -32,6 +32,7 @@ import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import static org.assertj.core.api.Assertions.assertThat; @@ -293,6 +294,31 @@ void emptyFieldPrefixDoesNotChangeDefaultBehavior() { assertThat(searchQuery.hasDisallowedKeys()).isFalse(); } + @Test + void unquotedEmptyFieldPrefixSingleSearchTerm() { + final SearchQueryParser parser = new SearchQueryParser("name", Set.of(), ""); + // Verify unquoted term is split into two search values. + final SearchQuery searchQuery = parser.parse("Bobby testerson"); + final Multimap queryMap = searchQuery.getQueryMap(); + assertThat(queryMap.keySet().size()).isEqualTo(1); + final Collection values = queryMap.get("name"); + assertThat(values.size()).isEqualTo(2); + assertThat(values).contains(new SearchQueryParser.FieldValue("Bobby", false)); + assertThat(values).contains(new SearchQueryParser.FieldValue("testerson", false)); + } + + @Test + void quotedEmptyFieldPrefixSingleSearchTerm() { + final SearchQueryParser parser = new SearchQueryParser("name", Set.of(), ""); + // Verify quoted term is maintained as a single search value. + final SearchQuery searchQuery = parser.parse("\"Bobby testerson\""); + final Multimap queryMap = searchQuery.getQueryMap(); + assertThat(queryMap.keySet().size()).isEqualTo(1); + final Collection values = queryMap.get("name"); + assertThat(values.size()).isEqualTo(1); + assertThat(values).containsOnly(new SearchQueryParser.FieldValue("Bobby testerson", false)); + } + @Test void booleanValuesSupported() { final SearchQueryParser parser = new SearchQueryParser("name",