]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8436 escape param "tags" of WS api/rules/tags
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 30 Nov 2016 13:04:12 +0000 (14:04 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 30 Nov 2016 13:04:12 +0000 (14:04 +0100)
server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java
server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/RuleServiceMediumTest.java

index 9c74c0e3a4a065522c3d7632b4af72f3be912f4d..523b9a1f88e81e9c02cb40ca1647bf0b907cc344 100644 (file)
@@ -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<D extends BaseDoc> implements Iterator<D> {
 
     private final EsClient esClient;
index 4f2994f06918c627c1e0e3c75146402022057ac6..0cd7a2f152c8b32ad2bc29507dc96bac83f4cde1 100644 (file)
@@ -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)
index 25c6f83b40347a4fc6745331f817037d5d334d8e..95d81be899ca76f42229cdbaf3d442c44243974c 100644 (file)
@@ -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");
+  }
 }
index e6fc5e610faa4e32404bc00265c1db783e6743aa..bf0a7d289e0685f25700daece9975099db628fd8 100644 (file)
@@ -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)