From 19e8116742267235a222b83abbc99ad2ce5d2895 Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Sat, 7 Apr 2018 22:59:43 +0200 Subject: detect irregular unicode in domain part --- .../messenger/ui/ContactDetailsActivity.java | 4 +- .../de/pixart/messenger/ui/TrustKeysActivity.java | 4 +- .../messenger/ui/adapter/ListItemAdapter.java | 4 +- .../utils/IrregularUnicodeBlockDetector.java | 200 ----------------- .../messenger/utils/IrregularUnicodeDetector.java | 250 +++++++++++++++++++++ 5 files changed, 256 insertions(+), 206 deletions(-) delete mode 100644 src/main/java/de/pixart/messenger/utils/IrregularUnicodeBlockDetector.java create mode 100644 src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java (limited to 'src/main/java/de/pixart') diff --git a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java index 984a9c58a..4726922bf 100644 --- a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java @@ -44,7 +44,7 @@ import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.services.XmppConnectionService.OnAccountUpdate; import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate; import de.pixart.messenger.utils.CryptoHelper; -import de.pixart.messenger.utils.IrregularUnicodeBlockDetector; +import de.pixart.messenger.utils.IrregularUnicodeDetector; import de.pixart.messenger.utils.Namespace; import de.pixart.messenger.utils.TimeframeUtils; import de.pixart.messenger.utils.UIHelper; @@ -509,7 +509,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; if (Config.DOMAIN_LOCK != null) { account = contact.getAccount().getJid().getLocal(); diff --git a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java index d401a3c39..1f408138d 100644 --- a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java +++ b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java @@ -31,7 +31,7 @@ import de.pixart.messenger.databinding.KeysCardBinding; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.utils.CryptoHelper; -import de.pixart.messenger.utils.IrregularUnicodeBlockDetector; +import de.pixart.messenger.utils.IrregularUnicodeDetector; import de.pixart.messenger.utils.XmppUri; import de.pixart.messenger.xmpp.OnKeyStatusUpdated; import rocks.xmpp.addr.Jid; @@ -191,7 +191,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat hasForeignKeys = true; KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.keys_card, binding.foreignKeys,false); 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))); final Map fingerprints = entry.getValue(); for (final String fingerprint : fingerprints.keySet()) { diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java index 821483f33..432bc0d4b 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java @@ -27,7 +27,7 @@ import de.pixart.messenger.databinding.ContactBinding; import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.ui.SettingsActivity; import de.pixart.messenger.ui.XmppActivity; -import de.pixart.messenger.utils.IrregularUnicodeBlockDetector; +import de.pixart.messenger.utils.IrregularUnicodeDetector; import de.pixart.messenger.utils.UIHelper; import rocks.xmpp.addr.Jid; @@ -92,7 +92,7 @@ public class ListItemAdapter extends ArrayAdapter { final Jid jid = item.getJid(); if (jid != null) { viewHolder.jid.setVisibility(View.VISIBLE); - viewHolder.jid.setText(IrregularUnicodeBlockDetector.style(activity, jid)); + viewHolder.jid.setText(IrregularUnicodeDetector.style(activity, jid)); } else { viewHolder.jid.setVisibility(View.GONE); } diff --git a/src/main/java/de/pixart/messenger/utils/IrregularUnicodeBlockDetector.java b/src/main/java/de/pixart/messenger/utils/IrregularUnicodeBlockDetector.java deleted file mode 100644 index c1e4ba88b..000000000 --- a/src/main/java/de/pixart/messenger/utils/IrregularUnicodeBlockDetector.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (c) 2018, Daniel Gultsch All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package de.pixart.messenger.utils; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.support.annotation.ColorInt; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.style.ForegroundColorSpan; -import android.util.LruCache; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import de.pixart.messenger.R; -import de.pixart.messenger.ui.util.Color; -import rocks.xmpp.addr.Jid; - -public class IrregularUnicodeBlockDetector { - - private static final Map NORMALIZATION_MAP; - - static { - Map temp = new HashMap<>(); - temp.put(Character.UnicodeBlock.LATIN_1_SUPPLEMENT, Character.UnicodeBlock.BASIC_LATIN); - NORMALIZATION_MAP = Collections.unmodifiableMap(temp); - } - - private static Character.UnicodeBlock normalize(Character.UnicodeBlock in) { - if (NORMALIZATION_MAP.containsKey(in)) { - return NORMALIZATION_MAP.get(in); - } else { - return in; - } - } - - private static final LruCache CACHE = new LruCache<>(100); - - public static Spannable style(Context context, Jid jid) { - return style(jid, Color.get(context, R.attr.color_warning)); - } - - private static Spannable style(Jid jid, @ColorInt int color) { - SpannableStringBuilder builder = new SpannableStringBuilder(); - if (jid.getLocal() != null) { - SpannableString local = new SpannableString(jid.getLocal()); - Matcher matcher = find(jid).matcher(local); - 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('@'); - } - if (jid.getDomain() != null) { - builder.append(jid.getDomain()); - } - if (builder.length() != 0 && jid.getResource() != null) { - builder.append('/'); - builder.append(jid.getResource()); - } - return builder; - } - - private static Map> mapCompat(Jid jid) { - Map> map = new HashMap<>(); - String local = jid.getLocal(); - final int length = local.length(); - for (int offset = 0; offset < length; ) { - final int codePoint = local.codePointAt(offset); - Character.UnicodeBlock block = normalize(Character.UnicodeBlock.of(codePoint)); - List codePoints; - if (map.containsKey(block)) { - codePoints = map.get(block); - } else { - codePoints = new ArrayList<>(); - map.put(block, codePoints); - } - codePoints.add(String.copyValueOf(Character.toChars(codePoint))); - offset += Character.charCount(codePoint); - } - return map; - } - - @TargetApi(Build.VERSION_CODES.N) - private static Map> map(Jid jid) { - Map> map = new HashMap<>(); - String local = jid.getLocal(); - final int length = local.length(); - for (int offset = 0; offset < length; ) { - final int codePoint = local.codePointAt(offset); - Character.UnicodeScript script = Character.UnicodeScript.of(codePoint); - if (script != Character.UnicodeScript.COMMON) { - List codePoints; - if (map.containsKey(script)) { - codePoints = map.get(script); - } else { - codePoints = new ArrayList<>(); - map.put(script, codePoints); - } - codePoints.add(String.copyValueOf(Character.toChars(codePoint))); - } - offset += Character.charCount(codePoint); - } - return map; - } - - private static Set eliminateFirstAndGetCodePointsCompat(Map> map) { - return eliminateFirstAndGetCodePoints(map, Character.UnicodeBlock.BASIC_LATIN); - } - - @TargetApi(Build.VERSION_CODES.N) - private static Set eliminateFirstAndGetCodePoints(Map> map) { - return eliminateFirstAndGetCodePoints(map, Character.UnicodeScript.COMMON); - } - - private static Set eliminateFirstAndGetCodePoints(Map> map, T defaultPick) { - T pick = defaultPick; - int size = 0; - for (Map.Entry> entry : map.entrySet()) { - if (entry.getValue().size() > size) { - size = entry.getValue().size(); - pick = entry.getKey(); - } - } - map.remove(pick); - Set all = new HashSet<>(); - for (List codePoints : map.values()) { - all.addAll(codePoints); - } - return all; - } - - private static Pattern find(Jid jid) { - synchronized (CACHE) { - Pattern pattern = CACHE.get(jid); - if (pattern != null) { - return pattern; - } - Set codePoints; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - codePoints = eliminateFirstAndGetCodePointsCompat(mapCompat(jid)); - } else { - codePoints = eliminateFirstAndGetCodePoints(map(jid)); - } - pattern = create(codePoints); - CACHE.put(jid, pattern); - return pattern; - } - } - - private static Pattern create(Set codePoints) { - final StringBuilder pattern = new StringBuilder(); - for (String codePoint : codePoints) { - if (pattern.length() != 0) { - pattern.append('|'); - } - pattern.append(Pattern.quote(codePoint)); - } - return Pattern.compile(pattern.toString()); - } -} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java b/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java new file mode 100644 index 000000000..2e7f221c9 --- /dev/null +++ b/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.utils; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.annotation.ColorInt; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; +import android.util.LruCache; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.pixart.messenger.R; +import de.pixart.messenger.ui.util.Color; +import rocks.xmpp.addr.Jid; + +public class IrregularUnicodeDetector { + + private static final Map NORMALIZATION_MAP; + private static final LruCache CACHE = new LruCache<>(100); + + static { + Map temp = new HashMap<>(); + temp.put(Character.UnicodeBlock.LATIN_1_SUPPLEMENT, Character.UnicodeBlock.BASIC_LATIN); + NORMALIZATION_MAP = Collections.unmodifiableMap(temp); + } + + private static Character.UnicodeBlock normalize(Character.UnicodeBlock in) { + if (NORMALIZATION_MAP.containsKey(in)) { + return NORMALIZATION_MAP.get(in); + } else { + return in; + } + } + + public static Spannable style(Context context, Jid jid) { + return style(jid, Color.get(context, R.attr.color_warning)); + } + + private static Spannable style(Jid jid, @ColorInt int color) { + PatternTuple patternTuple = find(jid); + SpannableStringBuilder builder = new SpannableStringBuilder(); + if (jid.getLocal() != null && patternTuple.local != null) { + SpannableString local = new SpannableString(jid.getLocal()); + colorize(local, patternTuple.local, color); + builder.append(local); + builder.append('@'); + } + if (jid.getDomain() != null) { + 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) { + builder.append('/'); + builder.append(jid.getResource()); + } + return builder; + } + + 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> mapCompat(String word) { + Map> map = new HashMap<>(); + final int length = word.length(); + for (int offset = 0; offset < length; ) { + final int codePoint = word.codePointAt(offset); + Character.UnicodeBlock block = normalize(Character.UnicodeBlock.of(codePoint)); + List codePoints; + if (map.containsKey(block)) { + codePoints = map.get(block); + } else { + codePoints = new ArrayList<>(); + map.put(block, codePoints); + } + codePoints.add(String.copyValueOf(Character.toChars(codePoint))); + offset += Character.charCount(codePoint); + } + return map; + } + + @TargetApi(Build.VERSION_CODES.N) + private static Map> map(String word) { + Map> map = new HashMap<>(); + final int length = word.length(); + for (int offset = 0; offset < length; ) { + final int codePoint = word.codePointAt(offset); + Character.UnicodeScript script = Character.UnicodeScript.of(codePoint); + if (script != Character.UnicodeScript.COMMON) { + List codePoints; + if (map.containsKey(script)) { + codePoints = map.get(script); + } else { + codePoints = new ArrayList<>(); + map.put(script, codePoints); + } + codePoints.add(String.copyValueOf(Character.toChars(codePoint))); + } + offset += Character.charCount(codePoint); + } + return map; + } + + private static Set eliminateFirstAndGetCodePointsCompat(Map> map) { + return eliminateFirstAndGetCodePoints(map, Character.UnicodeBlock.BASIC_LATIN); + } + + @TargetApi(Build.VERSION_CODES.N) + private static Set eliminateFirstAndGetCodePoints(Map> map) { + return eliminateFirstAndGetCodePoints(map, Character.UnicodeScript.COMMON); + } + + private static Set eliminateFirstAndGetCodePoints(Map> map, T defaultPick) { + T pick = defaultPick; + int size = 0; + for (Map.Entry> entry : map.entrySet()) { + if (entry.getValue().size() > size) { + size = entry.getValue().size(); + pick = entry.getKey(); + } + } + map.remove(pick); + Set all = new HashSet<>(); + for (List codePoints : map.values()) { + all.addAll(codePoints); + } + return all; + } + + private static Set findIrregularCodePoints(String word) { + Set 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) { + PatternTuple pattern = CACHE.get(jid); + if (pattern != null) { + return pattern; + } + ; + pattern = PatternTuple.of(jid); + CACHE.put(jid, pattern); + return pattern; + } + } + + private static Pattern create(Set codePoints) { + final StringBuilder pattern = new StringBuilder(); + for (String codePoint : codePoints) { + if (pattern.length() != 0) { + pattern.append('|'); + } + pattern.append(Pattern.quote(codePoint)); + } + 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); + } + } +} \ No newline at end of file -- cgit v1.2.3