Only allow emojis as reactions.

This commit is contained in:
Greyson Parrelli 2021-04-09 16:44:47 -04:00
parent d0986383ad
commit ac0216d916
4 changed files with 101 additions and 0 deletions

View file

@ -16,6 +16,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public final class EmojiUtil {
@ -42,6 +43,8 @@ public final class EmojiUtil {
MAX_EMOJI_LENGTH = max;
}
private static final Pattern EMOJI_PATTERN = Pattern.compile("^(?:(?:[\u00a9\u00ae\u203c\u2049\u2122\u2139\u2194-\u2199\u21a9-\u21aa\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa\u24c2\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614-\u2615\u2618\u261d\u2620\u2622-\u2623\u2626\u262a\u262e-\u262f\u2638-\u263a\u2648-\u2653\u2660\u2663\u2665-\u2666\u2668\u267b\u267f\u2692-\u2694\u2696-\u2697\u2699\u269b-\u269c\u26a0-\u26a1\u26aa-\u26ab\u26b0-\u26b1\u26bd-\u26be\u26c4-\u26c5\u26c8\u26ce-\u26cf\u26d1\u26d3-\u26d4\u26e9-\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733-\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763-\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934-\u2935\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\ud83c\udc04\ud83c\udccf\ud83c\udd70-\ud83c\udd71\ud83c\udd7e-\ud83c\udd7f\ud83c\udd8e\ud83c\udd91-\ud83c\udd9a\ud83c\ude01-\ud83c\ude02\ud83c\ude1a\ud83c\ude2f\ud83c\ude32-\ud83c\ude3a\ud83c\ude50-\ud83c\ude51\u200d\ud83c\udf00-\ud83d\uddff\ud83d\ude00-\ud83d\ude4f\ud83d\ude80-\ud83d\udeff\ud83e\udd00-\ud83e\uddff\udb40\udc20-\udb40\udc7f]|\u200d[\u2640\u2642]|[\ud83c\udde6-\ud83c\uddff]{2}|.[\u20e0\u20e3\ufe0f]+)+)+$");
private EmojiUtil() {}
public static List<EmojiPageModel> getDisplayPages() {
@ -90,4 +93,25 @@ public final class EmojiUtil {
return EmojiProvider.getInstance(context).getEmojiDrawable(emoji);
}
}
/**
* True if the text is likely a single, valid emoji. Otherwise false.
*
* We do a two-tier check: first using our own knowledge of emojis (which could be incomplete),
* followed by a more wide check for all of the valid emoji unicode ranges (which could lead to
* some false positives). YMMV.
*/
public static boolean isEmoji(@NonNull Context context, @Nullable String text) {
if (Util.isEmpty(text)) {
return false;
}
if (StringUtil.getGraphemeCount(text) != 1) {
return false;
}
EmojiParser.CandidateList candidates = EmojiProvider.getInstance(context).getCandidates(text);
return (candidates != null && candidates.size() > 0) || EMOJI_PATTERN.matcher(text).matches();
}
}

View file

@ -20,6 +20,8 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.attachments.PointerAttachment;
import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.components.emoji.Emoji;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
@ -690,6 +692,11 @@ public final class MessageContentProcessor {
private void handleReaction(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
SignalServiceDataMessage.Reaction reaction = message.getReaction().get();
if (!EmojiUtil.isEmoji(context, reaction.getEmoji())) {
Log.w(TAG, "Reaction text is not a valid emoji! Ignoring the message.");
return;
}
Recipient targetAuthor = Recipient.externalPush(context, reaction.getTargetAuthor());
MessageRecord targetMessage = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(reaction.getTargetSentTimestamp(), targetAuthor.getId());

View file

@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.text.BidiFormatter;
import org.signal.core.util.BreakIteratorCompat;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@ -265,4 +267,13 @@ public final class StringUtil {
int end = (maxChars - 1) - start;
return text.subSequence(0, start) + "" + text.subSequence(text.length() - end, text.length());
}
/**
* @return The number of graphemes in the provided string.
*/
public static int getGraphemeCount(@NonNull CharSequence text) {
BreakIteratorCompat iterator = BreakIteratorCompat.getInstance();
iterator.setText(text);
return iterator.countBreaks();
}
}

View file

@ -0,0 +1,59 @@
package org.thoughtcrime.securesms.components.emoji;
import android.app.Application;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.ParameterizedRobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
@RunWith(ParameterizedRobolectricTestRunner.class)
@Config(manifest = Config.NONE, application = Application.class)
public class EmojiUtilTest_isEmoji {
private final String input;
private final boolean output;
@ParameterizedRobolectricTestRunner.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{ null, false },
{ "", false },
{ "cat", false },
{ "ᑢᗩᖶ", false },
{ "♍︎♋︎⧫︎", false },
{ "", false },
{ "¯\\_(ツ)_/¯", false},
{ "\uD83D\uDE0D", true }, // Smiling face with heart-shaped eyes
{ "\uD83D\uDD77", true }, // Spider
{ "\uD83E\uDD37", true }, // Person shrugging
{ "\uD83E\uDD37\uD83C\uDFFF\u200D♂", true }, // Man shrugging dark skin tone
{ "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66", true }, // Family: Man, Woman, Girl, Boy
{ "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDC67\uD83C\uDFFB\u200D\uD83D\uDC66\uD83C\uDFFB", true }, // Family - Man: Light Skin Tone, Woman: Light Skin Tone, Girl: Light Skin Tone, Boy: Light Skin Tone (NOTE: Not widely supported, good stretch test)
{ "\uD83D\uDE0Dhi", false }, // Smiling face with heart-shaped eyes, text afterwards
{ "\uD83D\uDE0D ", false }, // Smiling face with heart-shaped eyes, space afterwards
{ "\uD83D\uDE0D\uD83D\uDE0D", false }, // Smiling face with heart-shaped eyes, twice
});
}
public EmojiUtilTest_isEmoji(String input, boolean output) {
this.input = input;
this.output = output;
}
@Test
public void isEmoji() {
Context context = ApplicationProvider.getApplicationContext();
assertEquals(output, EmojiUtil.isEmoji(context, input));
}
}