Skip to content

Commit 724f9bc

Browse files
committed
Escape strings with quotes in custom query parameters.
(Backport of commit f8fbf77)
1 parent e6a1fe7 commit 724f9bc

File tree

5 files changed

+121
-70
lines changed

5 files changed

+121
-70
lines changed

src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,11 @@
1515
*/
1616
package org.springframework.data.elasticsearch.repository.query;
1717

18-
import java.util.regex.Matcher;
19-
import java.util.regex.Pattern;
20-
21-
import org.springframework.core.convert.support.GenericConversionService;
2218
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
23-
import org.springframework.data.elasticsearch.core.convert.DateTimeConverters;
2419
import org.springframework.data.elasticsearch.core.query.StringQuery;
20+
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
2521
import org.springframework.data.repository.query.ParametersParameterAccessor;
2622
import org.springframework.util.Assert;
27-
import org.springframework.util.NumberUtils;
2823

2924
/**
3025
* ElasticsearchStringQuery
@@ -33,26 +28,12 @@
3328
* @author Mohsin Husen
3429
* @author Mark Paluch
3530
* @author Taylor Ono
31+
* @author Peter-Josef Meisch
3632
*/
3733
public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQuery {
3834

39-
private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)");
4035
private String query;
4136

42-
private final GenericConversionService conversionService = new GenericConversionService();
43-
44-
{
45-
if (!conversionService.canConvert(java.util.Date.class, String.class)) {
46-
conversionService.addConverter(DateTimeConverters.JavaDateConverter.INSTANCE);
47-
}
48-
if (!conversionService.canConvert(org.joda.time.ReadableInstant.class, String.class)) {
49-
conversionService.addConverter(DateTimeConverters.JodaDateTimeConverter.INSTANCE);
50-
}
51-
if (!conversionService.canConvert(org.joda.time.LocalDateTime.class, String.class)) {
52-
conversionService.addConverter(DateTimeConverters.JodaLocalDateTimeConverter.INSTANCE);
53-
}
54-
}
55-
5637
public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
5738
String query) {
5839
super(queryMethod, elasticsearchOperations);
@@ -79,31 +60,7 @@ public Object execute(Object[] parameters) {
7960
}
8061

8162
protected StringQuery createQuery(ParametersParameterAccessor parameterAccessor) {
82-
String queryString = replacePlaceholders(this.query, parameterAccessor);
63+
String queryString = StringQueryUtil.replacePlaceholders(this.query, parameterAccessor);
8364
return new StringQuery(queryString);
8465
}
85-
86-
private String replacePlaceholders(String input, ParametersParameterAccessor accessor) {
87-
88-
Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input);
89-
String result = input;
90-
while (matcher.find()) {
91-
92-
String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)";
93-
int index = NumberUtils.parseNumber(matcher.group(1), Integer.class);
94-
result = result.replaceAll(placeholder, getParameterWithIndex(accessor, index));
95-
}
96-
return result;
97-
}
98-
99-
private String getParameterWithIndex(ParametersParameterAccessor accessor, int index) {
100-
Object parameter = accessor.getBindableValue(index);
101-
if (parameter == null) {
102-
return "null";
103-
}
104-
if (conversionService.canConvert(parameter.getClass(), String.class)) {
105-
return conversionService.convert(parameter, String.class);
106-
}
107-
return parameter.toString();
108-
}
10966
}

src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,20 @@
1515
*/
1616
package org.springframework.data.elasticsearch.repository.query;
1717

18-
import java.util.regex.Matcher;
19-
import java.util.regex.Pattern;
20-
2118
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
2219
import org.springframework.data.elasticsearch.core.query.StringQuery;
20+
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
2321
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2422
import org.springframework.expression.spel.standard.SpelExpressionParser;
25-
import org.springframework.util.NumberUtils;
26-
import org.springframework.util.ObjectUtils;
2723

2824
/**
2925
* @author Christoph Strobl
3026
* @author Taylor Ono
27+
* @author Peter-Josef Meisch
3128
* @since 3.2
3229
*/
3330
public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsearchRepositoryQuery {
3431

35-
private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)");
3632
private final String query;
3733

3834
public ReactiveElasticsearchStringQuery(ReactiveElasticsearchQueryMethod queryMethod,
@@ -52,27 +48,10 @@ public ReactiveElasticsearchStringQuery(String query, ReactiveElasticsearchQuery
5248

5349
@Override
5450
protected StringQuery createQuery(ElasticsearchParameterAccessor parameterAccessor) {
55-
String queryString = replacePlaceholders(this.query, parameterAccessor);
51+
String queryString = StringQueryUtil.replacePlaceholders(this.query, parameterAccessor);
5652
return new StringQuery(queryString);
5753
}
5854

59-
private String replacePlaceholders(String input, ElasticsearchParameterAccessor accessor) {
60-
61-
Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input);
62-
String result = input;
63-
while (matcher.find()) {
64-
65-
String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)";
66-
int index = NumberUtils.parseNumber(matcher.group(1), Integer.class);
67-
result = result.replaceAll(placeholder, getParameterWithIndex(accessor, index));
68-
}
69-
return result;
70-
}
71-
72-
private String getParameterWithIndex(ElasticsearchParameterAccessor accessor, int index) {
73-
return ObjectUtils.nullSafeToString(accessor.getBindableValue(index));
74-
}
75-
7655
@Override
7756
boolean isCountQuery() {
7857
return false;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.repository.support;
17+
18+
import java.util.regex.Matcher;
19+
import java.util.regex.Pattern;
20+
21+
import org.springframework.core.convert.support.GenericConversionService;
22+
import org.springframework.data.elasticsearch.core.convert.DateTimeConverters;
23+
import org.springframework.data.elasticsearch.repository.query.ElasticsearchStringQuery;
24+
import org.springframework.data.repository.query.ParameterAccessor;
25+
import org.springframework.util.ClassUtils;
26+
import org.springframework.util.NumberUtils;
27+
28+
/**
29+
* @author Peter-Josef Meisch
30+
*/
31+
final public class StringQueryUtil {
32+
33+
private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)");
34+
private static final GenericConversionService conversionService = new GenericConversionService();
35+
36+
{
37+
if (!conversionService.canConvert(java.util.Date.class, String.class)) {
38+
conversionService.addConverter(DateTimeConverters.JavaDateConverter.INSTANCE);
39+
}
40+
if (ClassUtils.isPresent("org.joda.time.DateTimeZone", ElasticsearchStringQuery.class.getClassLoader())) {
41+
if (!conversionService.canConvert(org.joda.time.ReadableInstant.class, String.class)) {
42+
conversionService.addConverter(DateTimeConverters.JodaDateTimeConverter.INSTANCE);
43+
}
44+
if (!conversionService.canConvert(org.joda.time.LocalDateTime.class, String.class)) {
45+
conversionService.addConverter(DateTimeConverters.JodaLocalDateTimeConverter.INSTANCE);
46+
}
47+
}
48+
}
49+
50+
private StringQueryUtil() {}
51+
52+
public static String replacePlaceholders(String input, ParameterAccessor accessor) {
53+
54+
Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input);
55+
String result = input;
56+
while (matcher.find()) {
57+
58+
String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)";
59+
int index = NumberUtils.parseNumber(matcher.group(1), Integer.class);
60+
result = result.replaceAll(placeholder, Matcher.quoteReplacement(getParameterWithIndex(accessor, index)));
61+
}
62+
return result;
63+
}
64+
65+
private static String getParameterWithIndex(ParameterAccessor accessor, int index) {
66+
67+
Object parameter = accessor.getBindableValue(index);
68+
String parameterValue = "null";
69+
70+
// noinspection ConstantConditions
71+
if (parameter != null) {
72+
73+
if (conversionService.canConvert(parameter.getClass(), String.class)) {
74+
String converted = conversionService.convert(parameter, String.class);
75+
76+
if (converted != null) {
77+
parameterValue = converted;
78+
}
79+
} else {
80+
parameterValue = parameter.toString();
81+
}
82+
}
83+
84+
parameterValue = parameterValue.replaceAll("\"", Matcher.quoteReplacement("\\\""));
85+
return parameterValue;
86+
87+
}
88+
89+
}

src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ public void shouldReplaceRepeatedParametersCorrectly() throws Exception {
8787
.isEqualTo("name:(zero, eleven, one, two, three, four, five, six, seven, eight, nine, ten, eleven, zero, one)");
8888
}
8989

90+
@Test // #1790
91+
public void shouldEscapeStringsInQueryParameters() throws Exception {
92+
93+
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByPrefix", "hello \"Stranger\"");
94+
95+
assertThat(query).isInstanceOf(StringQuery.class);
96+
assertThat(((StringQuery) query).getSource())
97+
.isEqualTo("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"hello \\\"Stranger\\\"\"}}]}}");
98+
}
99+
90100
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, String... args)
91101
throws NoSuchMethodException {
92102

@@ -166,6 +176,9 @@ private interface SampleRepository extends Repository<Person, String> {
166176
@Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)")
167177
Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5,
168178
String arg6, String arg7, String arg8, String arg9, String arg10, String arg11);
179+
180+
@Query("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"?0\"}}]}}")
181+
List<Book> findByPrefix(String prefix);
169182
}
170183

171184
/**

src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ public void shouldReplaceRepeatedParametersCorrectly() throws Exception {
124124
.isEqualTo("name:(zero, eleven, one, two, three, four, five, six, seven, eight, nine, ten, eleven, zero, one)");
125125
}
126126

127+
@Test // #1790
128+
public void shouldEscapeStringsInQueryParameters() throws Exception {
129+
130+
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByPrefix", "hello \"Stranger\"");
131+
132+
assertThat(query).isInstanceOf(StringQuery.class);
133+
assertThat(((StringQuery) query).getSource())
134+
.isEqualTo("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"hello \\\"Stranger\\\"\"}}]}}");
135+
}
136+
127137
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, String... args)
128138
throws NoSuchMethodException {
129139

@@ -168,6 +178,9 @@ Person findWithQuiteSomeParameters(String arg0, String arg1, String arg2, String
168178
@Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)")
169179
Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5,
170180
String arg6, String arg7, String arg8, String arg9, String arg10, String arg11);
181+
182+
@Query("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"?0\"}}]}}")
183+
Flux<Book> findByPrefix(String prefix);
171184
}
172185

173186
/**

0 commit comments

Comments
 (0)