diff --git a/src/main/java/redis/clients/jedis/search/RediSearchUtil.java b/src/main/java/redis/clients/jedis/search/RediSearchUtil.java index d1ffdb61ae..14cb963bde 100644 --- a/src/main/java/redis/clients/jedis/search/RediSearchUtil.java +++ b/src/main/java/redis/clients/jedis/search/RediSearchUtil.java @@ -3,8 +3,11 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import redis.clients.jedis.util.SafeEncoder; @@ -18,6 +21,18 @@ public class RediSearchUtil { * @return map with string value */ public static Map toStringMap(Map input) { + return toStringMap(input, false); + } + + /** + * Jedis' {@code hset} methods do not support {@link Object}s as values. This method eases process + * of converting a {@link Map} with Objects as values so that the returning Map can be set to a + * {@code hset} method. + * @param input map with object value + * @param stringEscape whether to escape the String objects + * @return map with string value + */ + public static Map toStringMap(Map input, boolean stringEscape) { Map output = new HashMap<>(input.size()); for (Map.Entry entry : input.entrySet()) { String key = entry.getKey(); @@ -32,9 +47,9 @@ public static Map toStringMap(Map input) { redis.clients.jedis.GeoCoordinate geo = (redis.clients.jedis.GeoCoordinate) obj; str = geo.getLongitude() + "," + geo.getLatitude(); } else if (obj instanceof String) { - str = (String) obj; + str = stringEscape ? escape((String) obj) : (String) obj; } else { - str = obj.toString(); + str = String.valueOf(obj); } output.put(key, str); } @@ -62,6 +77,39 @@ public static byte[] ToByteArray(float[] input) { return toByteArray(input); } + private static final Set ESCAPE_CHARS = new HashSet<>(Arrays.asList(// + ',', '.', '<', '>', '{', '}', '[', // + ']', '"', '\'', ':', ';', '!', '@', // + '#', '$', '%', '^', '&', '*', '(', // + ')', '-', '+', '=', '~', '|' // + )); + + public static String escape(String text) { + return escape(text, false); + } + + public static String escapeQuery(String query) { + return escape(query, true); + } + + public static String escape(String text, boolean querying) { + char[] chars = text.toCharArray(); + + StringBuilder sb = new StringBuilder(); + for (char ch : chars) { + if (ESCAPE_CHARS.contains(ch) + || (querying && ch == ' ')) { + sb.append("\\"); + } + sb.append(ch); + } + return sb.toString(); + } + + public static String unescape(String text) { + return text.replace("\\", ""); + } + private RediSearchUtil() { throw new InstantiationError("Must not instantiate this class"); } diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java index 98bc5968e8..4784eaa3d3 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java @@ -1210,4 +1210,26 @@ public void searchIterationCollect() { "pupil:4444", "student:5555", "teacher:6666").stream().collect(Collectors.toSet()), collect.stream().map(Document::getId).collect(Collectors.toSet())); } + + @Test + public void escapeUtil() { + assertOK(client.ftCreate(index, TextField.of("txt"))); + + client.hset("doc1", "txt", RediSearchUtil.escape("hello-world")); + assertNotEquals("hello-world", client.hget("doc1", "txt")); + assertEquals("hello-world", RediSearchUtil.unescape(client.hget("doc1", "txt"))); + + SearchResult resultNoEscape = client.ftSearch(index, "hello-world"); + assertEquals(0, resultNoEscape.getTotalResults()); + + SearchResult resultEscaped = client.ftSearch(index, RediSearchUtil.escapeQuery("hello-world")); + assertEquals(1, resultEscaped.getTotalResults()); + } + + @Test + public void escapeMapUtil() { + client.hset("doc2", RediSearchUtil.toStringMap(Collections.singletonMap("txt", "hello-world"), true)); + assertNotEquals("hello-world", client.hget("doc2", "txt")); + assertEquals("hello-world", RediSearchUtil.unescape(client.hget("doc2", "txt"))); + } }