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..2ad3a79fc9d4 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+:(=|=~|<|<=|>|>=)?'(?:[^'\\\\]|\\\\.)*')|" + // Field-specific terms with single-quotes: title:'value' + "(\\S+:(=|=~|<|<=|>|>=)?\"(?:[^\"\\\\]|\\\\.)*\")|" + // Field-specific terms with double-quotes title:"value" + "['\"][^\\\\]*?(?:\\\\.[^\\\\]*)*?['\"]|" + // Single quoted value: "value one" + "\\S+:(=|=~|<|<=|>|>=)?\\S+|" + // 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",