Skip to content

Commit 443a377

Browse files
committed
Escape utils for RediSearch queries (#3544)
* Escape utils for RediSearch queries * Escape strings in Map
1 parent de38bda commit 443a377

File tree

2 files changed

+72
-2
lines changed

2 files changed

+72
-2
lines changed

src/main/java/redis/clients/jedis/search/RediSearchUtil.java

+50-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import java.nio.ByteBuffer;
44
import java.nio.ByteOrder;
55

6+
import java.util.Arrays;
67
import java.util.HashMap;
8+
import java.util.HashSet;
79
import java.util.Map;
10+
import java.util.Set;
811

912
import redis.clients.jedis.util.SafeEncoder;
1013

@@ -18,6 +21,18 @@ public class RediSearchUtil {
1821
* @return map with string value
1922
*/
2023
public static Map<String, String> toStringMap(Map<String, Object> input) {
24+
return toStringMap(input, false);
25+
}
26+
27+
/**
28+
* Jedis' {@code hset} methods do not support {@link Object}s as values. This method eases process
29+
* of converting a {@link Map} with Objects as values so that the returning Map can be set to a
30+
* {@code hset} method.
31+
* @param input map with object value
32+
* @param stringEscape whether to escape the String objects
33+
* @return map with string value
34+
*/
35+
public static Map<String, String> toStringMap(Map<String, Object> input, boolean stringEscape) {
2136
Map<String, String> output = new HashMap<>(input.size());
2237
for (Map.Entry<String, Object> entry : input.entrySet()) {
2338
String key = entry.getKey();
@@ -32,9 +47,9 @@ public static Map<String, String> toStringMap(Map<String, Object> input) {
3247
redis.clients.jedis.GeoCoordinate geo = (redis.clients.jedis.GeoCoordinate) obj;
3348
str = geo.getLongitude() + "," + geo.getLatitude();
3449
} else if (obj instanceof String) {
35-
str = (String) obj;
50+
str = stringEscape ? escape((String) obj) : (String) obj;
3651
} else {
37-
str = obj.toString();
52+
str = String.valueOf(obj);
3853
}
3954
output.put(key, str);
4055
}
@@ -54,6 +69,39 @@ public static byte[] ToByteArray(float[] input) {
5469
return bytes;
5570
}
5671

72+
private static final Set<Character> ESCAPE_CHARS = new HashSet<>(Arrays.asList(//
73+
',', '.', '<', '>', '{', '}', '[', //
74+
']', '"', '\'', ':', ';', '!', '@', //
75+
'#', '$', '%', '^', '&', '*', '(', //
76+
')', '-', '+', '=', '~', '|' //
77+
));
78+
79+
public static String escape(String text) {
80+
return escape(text, false);
81+
}
82+
83+
public static String escapeQuery(String query) {
84+
return escape(query, true);
85+
}
86+
87+
public static String escape(String text, boolean querying) {
88+
char[] chars = text.toCharArray();
89+
90+
StringBuilder sb = new StringBuilder();
91+
for (char ch : chars) {
92+
if (ESCAPE_CHARS.contains(ch)
93+
|| (querying && ch == ' ')) {
94+
sb.append("\\");
95+
}
96+
sb.append(ch);
97+
}
98+
return sb.toString();
99+
}
100+
101+
public static String unescape(String text) {
102+
return text.replace("\\", "");
103+
}
104+
57105
private RediSearchUtil() {
58106
throw new InstantiationError("Must not instantiate this class");
59107
}

src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java

+22
Original file line numberDiff line numberDiff line change
@@ -1157,4 +1157,26 @@ public void searchIterationCollect() {
11571157
"pupil:4444", "student:5555", "teacher:6666").stream().collect(Collectors.toSet()),
11581158
collect.stream().map(Document::getId).collect(Collectors.toSet()));
11591159
}
1160+
1161+
@Test
1162+
public void escapeUtil() {
1163+
assertOK(client.ftCreate(index, TextField.of("txt")));
1164+
1165+
client.hset("doc1", "txt", RediSearchUtil.escape("hello-world"));
1166+
assertNotEquals("hello-world", client.hget("doc1", "txt"));
1167+
assertEquals("hello-world", RediSearchUtil.unescape(client.hget("doc1", "txt")));
1168+
1169+
SearchResult resultNoEscape = client.ftSearch(index, "hello-world");
1170+
assertEquals(0, resultNoEscape.getTotalResults());
1171+
1172+
SearchResult resultEscaped = client.ftSearch(index, RediSearchUtil.escapeQuery("hello-world"));
1173+
assertEquals(1, resultEscaped.getTotalResults());
1174+
}
1175+
1176+
@Test
1177+
public void escapeMapUtil() {
1178+
client.hset("doc2", RediSearchUtil.toStringMap(Collections.singletonMap("txt", "hello-world"), true));
1179+
assertNotEquals("hello-world", client.hget("doc2", "txt"));
1180+
assertEquals("hello-world", RediSearchUtil.unescape(client.hget("doc2", "txt")));
1181+
}
11601182
}

0 commit comments

Comments
 (0)