From a66fbbffe4a12a21014ba08aa80edd31a0a6e5d9 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Wed, 30 Nov 2016 14:04:12 +0100 Subject: [PATCH] SONAR-8436 escape param "tags" of WS api/rules/tags --- .../main/java/org/sonar/server/es/EsUtils.java | 11 +++++++++++ .../org/sonar/server/rule/index/RuleIndex.java | 3 ++- .../java/org/sonar/server/es/EsUtilsTest.java | 16 ++++++++++++++++ .../server/rule/RuleServiceMediumTest.java | 17 ++++++++++++++++- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java b/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java index 9c74c0e3a4a..523b9a1f88e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java +++ b/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Queue; +import java.util.regex.Pattern; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.elasticsearch.action.search.SearchScrollRequestBuilder; @@ -43,6 +44,7 @@ import org.elasticsearch.search.aggregations.bucket.terms.Terms; public class EsUtils { public static final int SCROLL_TIME_IN_MINUTES = 3; + private static final Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\].+*?^$\\\\|]"); private EsUtils() { // only static methods @@ -94,6 +96,15 @@ public class EsUtils { return new DocScrollIterator<>(esClient, scrollId, docConverter); } + /** + * Escapes regexp special characters so that text can be forwarded from end-user input + * to Elasticsearch regexp query (for instance attributes "include" and "exclude" of + * term aggregations. + */ + public static String escapeSpecialRegexChars(String str) { + return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0"); + } + private static class DocScrollIterator implements Iterator { private final EsClient esClient; diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java index 4f2994f0691..0cd7a2f152c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java @@ -69,6 +69,7 @@ import org.sonar.server.es.StickyFacetBuilder; import static org.elasticsearch.index.query.QueryBuilders.matchQuery; import static org.elasticsearch.index.query.QueryBuilders.simpleQueryStringQuery; import static org.sonar.server.es.EsUtils.SCROLL_TIME_IN_MINUTES; +import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars; import static org.sonar.server.es.EsUtils.scrollIds; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_INHERITANCE; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_KEY; @@ -477,7 +478,7 @@ public class RuleIndex extends BaseIndex { .size(size) .minDocCount(1); if (query != null) { - termsAggregation.include(".*" + query + ".*"); + termsAggregation.include(".*" + escapeSpecialRegexChars(query) + ".*"); } SearchRequestBuilder request = getClient() .prepareSearch(INDEX) diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java b/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java index 25c6f83b403..95d81be899c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java @@ -33,6 +33,7 @@ import org.sonar.test.TestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars; public class EsUtilsTest { @@ -74,4 +75,19 @@ public class EsUtilsTest { assertThat(EsUtils.parseDateTime("2017-07-14T04:40:00.000+02:00").getTime()).isEqualTo(1_500_000_000_000L); assertThat(EsUtils.parseDateTime(null)).isNull(); } + + @Test + public void test_escapeSpecialRegexChars() { + assertThat(escapeSpecialRegexChars("")).isEqualTo(""); + assertThat(escapeSpecialRegexChars("foo")).isEqualTo("foo"); + assertThat(escapeSpecialRegexChars("FOO")).isEqualTo("FOO"); + assertThat(escapeSpecialRegexChars("foo++")).isEqualTo("foo\\+\\+"); + assertThat(escapeSpecialRegexChars("foo[]")).isEqualTo("foo\\[\\]"); + assertThat(escapeSpecialRegexChars(".*")).isEqualTo("\\.\\*"); + assertThat(escapeSpecialRegexChars("foo\\d")).isEqualTo("foo\\\\d"); + assertThat(escapeSpecialRegexChars("^")).isEqualTo("\\^"); + assertThat(escapeSpecialRegexChars("$")).isEqualTo("\\$"); + assertThat(escapeSpecialRegexChars("|")).isEqualTo("\\|"); + assertThat(escapeSpecialRegexChars("a bit of | $ .* ^ everything")).isEqualTo("a bit of \\| \\$ \\.\\* \\^ everything"); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/RuleServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/RuleServiceMediumTest.java index e6fc5e610fa..bf0a7d289e0 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/RuleServiceMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/RuleServiceMediumTest.java @@ -77,17 +77,32 @@ public class RuleServiceMediumTest { @Test public void listTags_returns_tags_filtered_by_name() { - insertRule(RuleKey.of("javascript", "S001"), newHashSet("tag1"), newHashSet("sys1", "sys2")); + insertRule(RuleKey.of("javascript", "S001"), newHashSet("tag1", "misra++"), newHashSet("sys1", "sys2")); insertRule(RuleKey.of("java", "S001"), newHashSet("tag2"), newHashSet()); assertThat(service.listTags("missing", 10)).isEmpty(); + assertThat(service.listTags("", 10)).containsOnly("tag1", "misra++", "tag2", "sys1", "sys2"); assertThat(service.listTags("tag", 10)).containsOnly("tag1", "tag2"); assertThat(service.listTags("sys", 10)).containsOnly("sys1", "sys2"); + assertThat(service.listTags("misra", 10)).containsOnly("misra++"); + assertThat(service.listTags("misra+", 10)).containsOnly("misra++"); + assertThat(service.listTags("++", 10)).containsOnly("misra++"); // LIMITATION: case sensitive assertThat(service.listTags("TAG", 10)).isEmpty(); assertThat(service.listTags("TAg", 10)).isEmpty(); assertThat(service.listTags("MISSing", 10)).isEmpty(); + + assertThat(service.listTags("misra-", 10)).isEmpty(); + } + + @Test + public void listTags_returns_empty_results_if_filter_contains_regexp_special_characters() { + insertRule(RuleKey.of("javascript", "S001"), newHashSet("misra++"), newHashSet("sys1", "sys2")); + + assertThat(service.listTags("mis[", 10)).isEmpty(); + assertThat(service.listTags("mis\\d", 10)).isEmpty(); + assertThat(service.listTags(".*", 10)).isEmpty(); } @Test(expected = UnauthorizedException.class) -- 2.39.5