detect irregular unicode in domain part

This commit is contained in:
Daniel Gultsch 2018-03-08 22:02:19 +01:00
parent 7ae3bdd3c6
commit e2e5c04ef7
4 changed files with 84 additions and 39 deletions

View file

@ -38,7 +38,7 @@ import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
import eu.siacs.conversations.utils.IrregularUnicodeBlockDetector; import eu.siacs.conversations.utils.IrregularUnicodeDetector;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
@ -384,7 +384,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
} }
} }
binding.detailsContactjid.setText(IrregularUnicodeBlockDetector.style(this,contact.getJid())); binding.detailsContactjid.setText(IrregularUnicodeDetector.style(this,contact.getJid()));
String account; String account;
if (Config.DOMAIN_LOCK != null) { if (Config.DOMAIN_LOCK != null) {
account = contact.getAccount().getJid().getLocal(); account = contact.getAccount().getJid().getLocal();

View file

@ -10,16 +10,12 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -35,7 +31,7 @@ import eu.siacs.conversations.databinding.KeysCardBinding;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.IrregularUnicodeBlockDetector; import eu.siacs.conversations.utils.IrregularUnicodeDetector;
import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import rocks.xmpp.addr.Jid; import rocks.xmpp.addr.Jid;
@ -197,7 +193,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
hasForeignKeys = true; hasForeignKeys = true;
KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.keys_card, binding.foreignKeys,false); KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.keys_card, binding.foreignKeys,false);
final Jid jid = entry.getKey(); final Jid jid = entry.getKey();
keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeBlockDetector.style(this,jid)); keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeDetector.style(this,jid));
keysCardBinding.foreignKeysTitle.setOnClickListener(v -> switchToContactDetails(mAccount.getRoster().getContact(jid))); keysCardBinding.foreignKeysTitle.setOnClickListener(v -> switchToContactDetails(mAccount.getRoster().getContact(jid)));
final Map<String, Boolean> fingerprints = entry.getValue(); final Map<String, Boolean> fingerprints = entry.getValue();
for (final String fingerprint : fingerprints.keySet()) { for (final String fingerprint : fingerprints.keySet()) {

View file

@ -26,9 +26,8 @@ import eu.siacs.conversations.databinding.ContactBinding;
import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.ui.SettingsActivity; import eu.siacs.conversations.ui.SettingsActivity;
import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.util.Color;
import eu.siacs.conversations.utils.EmojiWrapper; import eu.siacs.conversations.utils.EmojiWrapper;
import eu.siacs.conversations.utils.IrregularUnicodeBlockDetector; import eu.siacs.conversations.utils.IrregularUnicodeDetector;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import rocks.xmpp.addr.Jid; import rocks.xmpp.addr.Jid;
@ -113,7 +112,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
final Jid jid = item.getJid(); final Jid jid = item.getJid();
if (jid != null) { if (jid != null) {
viewHolder.jid.setVisibility(View.VISIBLE); viewHolder.jid.setVisibility(View.VISIBLE);
viewHolder.jid.setText(IrregularUnicodeBlockDetector.style(activity, jid)); viewHolder.jid.setText(IrregularUnicodeDetector.style(activity, jid));
} else { } else {
viewHolder.jid.setVisibility(View.GONE); viewHolder.jid.setVisibility(View.GONE);
} }

View file

@ -53,9 +53,10 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.ui.util.Color; import eu.siacs.conversations.ui.util.Color;
import rocks.xmpp.addr.Jid; import rocks.xmpp.addr.Jid;
public class IrregularUnicodeBlockDetector { public class IrregularUnicodeDetector {
private static final Map<Character.UnicodeBlock, Character.UnicodeBlock> NORMALIZATION_MAP; private static final Map<Character.UnicodeBlock, Character.UnicodeBlock> NORMALIZATION_MAP;
private static final LruCache<Jid, PatternTuple> CACHE = new LruCache<>(100);
static { static {
Map<Character.UnicodeBlock, Character.UnicodeBlock> temp = new HashMap<>(); Map<Character.UnicodeBlock, Character.UnicodeBlock> temp = new HashMap<>();
@ -71,27 +72,31 @@ public class IrregularUnicodeBlockDetector {
} }
} }
private static final LruCache<Jid, Pattern> CACHE = new LruCache<>(100);
public static Spannable style(Context context, Jid jid) { public static Spannable style(Context context, Jid jid) {
return style(jid, Color.get(context, R.attr.color_warning)); return style(jid, Color.get(context, R.attr.color_warning));
} }
private static Spannable style(Jid jid, @ColorInt int color) { private static Spannable style(Jid jid, @ColorInt int color) {
PatternTuple patternTuple = find(jid);
SpannableStringBuilder builder = new SpannableStringBuilder(); SpannableStringBuilder builder = new SpannableStringBuilder();
if (jid.getLocal() != null) { if (jid.getLocal() != null && patternTuple.local != null) {
SpannableString local = new SpannableString(jid.getLocal()); SpannableString local = new SpannableString(jid.getLocal());
Matcher matcher = find(jid).matcher(local); colorize(local, patternTuple.local, color);
while (matcher.find()) {
if (matcher.start() < matcher.end()) {
local.setSpan(new ForegroundColorSpan(color), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
builder.append(local); builder.append(local);
builder.append('@'); builder.append('@');
} }
if (jid.getDomain() != null) { if (jid.getDomain() != null) {
builder.append(jid.getDomain()); int i = jid.getDomain().lastIndexOf('.');
if (i != -1) {
String second = jid.getDomain().substring(0, i);
String top = jid.getDomain().substring(i, jid.getDomain().length());
SpannableString secondSpannableString = new SpannableString(second);
colorize(secondSpannableString, patternTuple.domain, color);
builder.append(secondSpannableString);
builder.append(top);
} else {
builder.append(jid.getDomain());
}
} }
if (builder.length() != 0 && jid.getResource() != null) { if (builder.length() != 0 && jid.getResource() != null) {
builder.append('/'); builder.append('/');
@ -100,12 +105,20 @@ public class IrregularUnicodeBlockDetector {
return builder; return builder;
} }
private static Map<Character.UnicodeBlock, List<String>> mapCompat(Jid jid) { private static void colorize(SpannableString spannableString, Pattern pattern, @ColorInt int color) {
Matcher matcher = pattern.matcher(spannableString);
while (matcher.find()) {
if (matcher.start() < matcher.end()) {
spannableString.setSpan(new ForegroundColorSpan(color), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private static Map<Character.UnicodeBlock, List<String>> mapCompat(String word) {
Map<Character.UnicodeBlock, List<String>> map = new HashMap<>(); Map<Character.UnicodeBlock, List<String>> map = new HashMap<>();
String local = jid.getLocal(); final int length = word.length();
final int length = local.length();
for (int offset = 0; offset < length; ) { for (int offset = 0; offset < length; ) {
final int codePoint = local.codePointAt(offset); final int codePoint = word.codePointAt(offset);
Character.UnicodeBlock block = normalize(Character.UnicodeBlock.of(codePoint)); Character.UnicodeBlock block = normalize(Character.UnicodeBlock.of(codePoint));
List<String> codePoints; List<String> codePoints;
if (map.containsKey(block)) { if (map.containsKey(block)) {
@ -121,12 +134,11 @@ public class IrregularUnicodeBlockDetector {
} }
@TargetApi(Build.VERSION_CODES.N) @TargetApi(Build.VERSION_CODES.N)
private static Map<Character.UnicodeScript, List<String>> map(Jid jid) { private static Map<Character.UnicodeScript, List<String>> map(String word) {
Map<Character.UnicodeScript, List<String>> map = new HashMap<>(); Map<Character.UnicodeScript, List<String>> map = new HashMap<>();
String local = jid.getLocal(); final int length = word.length();
final int length = local.length();
for (int offset = 0; offset < length; ) { for (int offset = 0; offset < length; ) {
final int codePoint = local.codePointAt(offset); final int codePoint = word.codePointAt(offset);
Character.UnicodeScript script = Character.UnicodeScript.of(codePoint); Character.UnicodeScript script = Character.UnicodeScript.of(codePoint);
if (script != Character.UnicodeScript.COMMON) { if (script != Character.UnicodeScript.COMMON) {
List<String> codePoints; List<String> codePoints;
@ -169,19 +181,24 @@ public class IrregularUnicodeBlockDetector {
return all; return all;
} }
private static Pattern find(Jid jid) { private static Set<String> findIrregularCodePoints(String word) {
Set<String> codePoints;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
codePoints = eliminateFirstAndGetCodePointsCompat(mapCompat(word));
} else {
codePoints = eliminateFirstAndGetCodePoints(map(word));
}
return codePoints;
}
private static PatternTuple find(Jid jid) {
synchronized (CACHE) { synchronized (CACHE) {
Pattern pattern = CACHE.get(jid); PatternTuple pattern = CACHE.get(jid);
if (pattern != null) { if (pattern != null) {
return pattern; return pattern;
} }
Set<String> codePoints; ;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { pattern = PatternTuple.of(jid);
codePoints = eliminateFirstAndGetCodePointsCompat(mapCompat(jid));
} else {
codePoints = eliminateFirstAndGetCodePoints(map(jid));
}
pattern = create(codePoints);
CACHE.put(jid, pattern); CACHE.put(jid, pattern);
return pattern; return pattern;
} }
@ -197,4 +214,37 @@ public class IrregularUnicodeBlockDetector {
} }
return Pattern.compile(pattern.toString()); return Pattern.compile(pattern.toString());
} }
private static class PatternTuple {
private final Pattern local;
private final Pattern domain;
private PatternTuple(Pattern local, Pattern domain) {
this.local = local;
this.domain = domain;
}
private static PatternTuple of(Jid jid) {
final Pattern localPattern;
if (jid.getLocal() != null) {
localPattern = create(findIrregularCodePoints(jid.getLocal()));
} else {
localPattern = null;
}
String domain = jid.getDomain();
final Pattern domainPattern;
if (domain != null) {
int i = domain.lastIndexOf('.');
if (i != -1) {
String secondLevel = domain.substring(0, i);
domainPattern = create(findIrregularCodePoints(secondLevel));
} else {
domainPattern = null;
}
} else {
domainPattern = null;
}
return new PatternTuple(localPattern, domainPattern);
}
}
} }