Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Escape utils for RediSearch queries #3544

Merged
merged 6 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions src/main/java/redis/clients/jedis/search/RediSearchUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -18,6 +21,18 @@ public class RediSearchUtil {
* @return map with string value
*/
public static Map<String, String> toStringMap(Map<String, Object> 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<String, String> toStringMap(Map<String, Object> input, boolean stringEscape) {
Map<String, String> output = new HashMap<>(input.size());
for (Map.Entry<String, Object> entry : input.entrySet()) {
String key = entry.getKey();
Expand All @@ -32,9 +47,9 @@ public static Map<String, String> toStringMap(Map<String, Object> 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);
}
Expand Down Expand Up @@ -62,6 +77,39 @@ public static byte[] ToByteArray(float[] input) {
return toByteArray(input);
}

private static final Set<Character> 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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")));
}
}
Loading