From 3d6e4c9942c6ddca3ffbbc63583a5dab954521dd Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 28 Mar 2025 12:40:17 +0100 Subject: [PATCH] limit links to actual URIs and include more well known schemes links without scheme like 'example.com' or no longer linked to avoid false positives. however now mailto and tel are linked as well --- .../siacs/conversations/entities/Message.java | 4 +- .../ui/ConferenceDetailsActivity.java | 2 +- .../ui/ConversationFragment.java | 3 +- .../ui/adapter/MessageAdapter.java | 2 +- .../conversations/ui/text/FixedURLSpan.java | 9 +- .../conversations/ui/util/MyLinkify.java | 115 +--- .../conversations/ui/util/ShareUtil.java | 2 +- .../siacs/conversations/utils/GeoHelper.java | 289 +++++----- .../siacs/conversations/utils/Patterns.java | 521 +----------------- .../conversations/xmpp/XmppConnection.java | 4 +- 10 files changed, 209 insertions(+), 742 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index d0262b25d50dd6b3d4fc71289c232304fe7774f2..7516e8caa38fab6cec2b9933d888539cec797b24 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -16,9 +16,9 @@ import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.ui.util.PresenceSelector; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.Emoticons; -import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.MessageUtils; import eu.siacs.conversations.utils.MimeUtils; +import eu.siacs.conversations.utils.Patterns; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.Jid; import java.lang.ref.WeakReference; @@ -796,7 +796,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable public synchronized boolean isGeoUri() { if (isGeoUri == null) { - isGeoUri = GeoHelper.GEO_URI.matcher(body).matches(); + isGeoUri = Patterns.URI_GEO.matcher(body).matches(); } return isGeoUri; } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index bc526d8c216137adfc423809539a077c686d6187..4d6268c7070cdf5981a084f3c6122a17eae9ce09 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -558,7 +558,7 @@ public class ConferenceDetailsActivity extends XmppActivity if (printableValue(subject)) { SpannableStringBuilder spannable = new SpannableStringBuilder(subject); StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor()); - MyLinkify.addLinks(spannable, false); + MyLinkify.addLinks(spannable); this.binding.mucSubject.setText(spannable); this.binding.mucSubject.setTextAppearance( subject.length() > (hasTitle ? 128 : 196) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 498b08773cbfc37e7f9024a27bbb967bb2b5169b..bd3a34555c25f54bd56a757368d380bf7cbdf7be 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -112,6 +112,7 @@ import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.MessageUtils; import eu.siacs.conversations.utils.NickValidityChecker; +import eu.siacs.conversations.utils.Patterns; import eu.siacs.conversations.utils.PermissionUtils; import eu.siacs.conversations.utils.QuickLoader; import eu.siacs.conversations.utils.StylingHelper; @@ -2614,7 +2615,7 @@ public class ConversationFragment extends XmppFragment } } } else { - if (text != null && GeoHelper.GEO_URI.matcher(text).matches()) { + if (text != null && Patterns.URI_GEO.matcher(text).matches()) { mediaPreviewAdapter.addMediaPreviews( Attachment.of(getActivity(), Uri.parse(text), Attachment.Type.LOCATION)); toggleInputMethod(); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 3eba1fd367a13c9fd960bbbf7d9a8f839aacf2ca..00bf4b17ef5e6760ddd66a294c272a688fe1ea67 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -576,7 +576,7 @@ public class MessageAdapter extends ArrayAdapter { } StylingHelper.format(body, viewHolder.messageBody().getCurrentTextColor()); - MyLinkify.addLinks(body, true); + MyLinkify.addLinks(body); if (highlightedTerm != null) { StylingHelper.highlight(viewHolder.messageBody(), body, highlightedTerm); } diff --git a/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java b/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java index e84c04fbf88488983757f2bdf8a0e67592822fff..aba355214b764ab28db0d0ea641e68837717aee4 100644 --- a/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java +++ b/src/main/java/eu/siacs/conversations/ui/text/FixedURLSpan.java @@ -34,24 +34,21 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.text.Editable; import android.text.Spanned; import android.text.style.URLSpan; import android.view.SoundEffectConstants; import android.view.View; import android.widget.Toast; - -import java.util.Arrays; - import eu.siacs.conversations.R; import eu.siacs.conversations.ui.ConversationsActivity; import eu.siacs.conversations.ui.ShowLocationActivity; +import java.util.Arrays; @SuppressLint("ParcelCreator") public class FixedURLSpan extends URLSpan { - private FixedURLSpan(String url) { + private FixedURLSpan(final String url) { super(url); } @@ -93,7 +90,7 @@ public class FixedURLSpan extends URLSpan { try { context.startActivity(intent); widget.playSoundEffect(SoundEffectConstants.CLICK); - } catch (ActivityNotFoundException e) { + } catch (final ActivityNotFoundException e) { Toast.makeText(context, R.string.no_application_found_to_open_link, Toast.LENGTH_SHORT) .show(); } diff --git a/src/main/java/eu/siacs/conversations/ui/util/MyLinkify.java b/src/main/java/eu/siacs/conversations/ui/util/MyLinkify.java index d5d3a82da519d79309822751581796de549b01ae..3e7db20d03c05d6ad7ee1c9c1a16c9c74b02b9de 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/MyLinkify.java +++ b/src/main/java/eu/siacs/conversations/ui/util/MyLinkify.java @@ -29,107 +29,47 @@ package eu.siacs.conversations.ui.util; -import android.os.Build; import android.text.Editable; import android.text.style.URLSpan; import android.text.util.Linkify; - +import com.google.common.base.Splitter; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; - +import eu.siacs.conversations.ui.text.FixedURLSpan; +import eu.siacs.conversations.utils.Patterns; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.List; -import java.util.Locale; import java.util.Objects; -import eu.siacs.conversations.ui.text.FixedURLSpan; -import eu.siacs.conversations.utils.GeoHelper; -import eu.siacs.conversations.utils.Patterns; -import eu.siacs.conversations.utils.XmppUri; - public class MyLinkify { - private static final Linkify.TransformFilter WEBURL_TRANSFORM_FILTER = (matcher, url) -> { - if (url == null) { - return null; - } - final String lcUrl = url.toLowerCase(Locale.US); - if (lcUrl.startsWith("http://") || lcUrl.startsWith("https://")) { - return removeTrailingBracket(url); - } else { - return "http://" + removeTrailingBracket(url); - } - }; - - private static String removeTrailingBracket(final String url) { - int numOpenBrackets = 0; - for (char c : url.toCharArray()) { - if (c == '(') { - ++numOpenBrackets; - } else if (c == ')') { - --numOpenBrackets; - } - } - if (numOpenBrackets != 0 && url.charAt(url.length() - 1) == ')') { - return url.substring(0, url.length() - 1); - } else { - return url; - } - } - - private static final Linkify.MatchFilter WEBURL_MATCH_FILTER = (cs, start, end) -> { - if (start > 0) { - if (cs.charAt(start - 1) == '@' || cs.charAt(start - 1) == '.' - || cs.subSequence(Math.max(0, start - 3), start).equals("://")) { - return false; - } - } - - if (end < cs.length()) { - // Reject strings that were probably matched only because they contain a dot followed by - // by some known TLD (see also comment for WORD_BOUNDARY in Patterns.java) - return !isAlphabetic(cs.charAt(end - 1)) || !isAlphabetic(cs.charAt(end)); - } - - return true; - }; - - private static final Linkify.MatchFilter XMPPURI_MATCH_FILTER = (s, start, end) -> { - XmppUri uri = new XmppUri(s.subSequence(start, end).toString()); - return uri.isValidJid(); - }; - - private static boolean isAlphabetic(final int code) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - return Character.isAlphabetic(code); - } - - switch (Character.getType(code)) { - case Character.UPPERCASE_LETTER: - case Character.LOWERCASE_LETTER: - case Character.TITLECASE_LETTER: - case Character.MODIFIER_LETTER: - case Character.OTHER_LETTER: - case Character.LETTER_NUMBER: - return true; - default: - return false; - } - } - - public static void addLinks(Editable body, boolean includeGeo) { - Linkify.addLinks(body, Patterns.XMPP_PATTERN, "xmpp", XMPPURI_MATCH_FILTER, null); - Linkify.addLinks(body, Patterns.AUTOLINK_WEB_URL, "http", WEBURL_MATCH_FILTER, WEBURL_TRANSFORM_FILTER); - if (includeGeo) { - Linkify.addLinks(body, GeoHelper.GEO_URI, "geo"); - } + private static final Linkify.MatchFilter MATCH_FILTER = + (s, start, end) -> { + final var match = s.subSequence(start, end); + final var scheme = + Iterables.getFirst(Splitter.on(':').limit(2).splitToList(match), null); + if (scheme == null) { + return false; + } + return switch (scheme) { + case "tel" -> Patterns.URI_TEL.matcher(match).matches(); + case "http", "https" -> Patterns.URI_HTTP.matcher(match).matches(); + case "geo" -> Patterns.URI_GEO.matcher(match).matches(); + default -> true; + }; + }; + + public static void addLinks(final Editable body) { + Linkify.addLinks(body, Patterns.URI_GENERIC, null, MATCH_FILTER, null); FixedURLSpan.fix(body); } public static List extractLinks(final Editable body) { - MyLinkify.addLinks(body, false); + MyLinkify.addLinks(body); final Collection spans = Arrays.asList(body.getSpans(0, body.length() - 1, URLSpan.class)); final Collection urlWrappers = @@ -140,11 +80,10 @@ public class MyLinkify { s == null ? null : new UrlWrapper(body.getSpanStart(s), s.getURL())), - uw -> uw != null); - List sorted = ImmutableList.sortedCopyOf( - (a, b) -> Integer.compare(a.position, b.position), urlWrappers); + Objects::nonNull); + List sorted = + ImmutableList.sortedCopyOf(Comparator.comparingInt(a -> a.position), urlWrappers); return Lists.transform(sorted, uw -> uw.url); - } private static class UrlWrapper { diff --git a/src/main/java/eu/siacs/conversations/ui/util/ShareUtil.java b/src/main/java/eu/siacs/conversations/ui/util/ShareUtil.java index 99b63fe2746b8aa9995f22647e7a3f7af2a26230..bee0734676d1d252365f7ad3815bdd3110a01b5f 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/ShareUtil.java +++ b/src/main/java/eu/siacs/conversations/ui/util/ShareUtil.java @@ -149,7 +149,7 @@ public class ShareUtil { } public static String getLinkScheme(final SpannableStringBuilder body) { - MyLinkify.addLinks(body, false); + MyLinkify.addLinks(body); for (final String url : MyLinkify.extractLinks(body)) { final Uri uri = Uri.parse(url); if ("xmpp".equals(uri.getScheme())) { diff --git a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java index 46de870ad3d527a5cf726e1baf84c8a07c0bb4cb..03cc78cee3f9252de5669a3722071a1467ec112d 100644 --- a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java @@ -5,148 +5,167 @@ import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.preference.PreferenceManager; - -import org.osmdroid.util.GeoPoint; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ShareLocationActivity; import eu.siacs.conversations.ui.ShowLocationActivity; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.regex.Matcher; +import org.osmdroid.util.GeoPoint; public class GeoHelper { - private static final String SHARE_LOCATION_PACKAGE_NAME = "eu.siacs.conversations.location.request"; - private static final String SHOW_LOCATION_PACKAGE_NAME = "eu.siacs.conversations.location.show"; - - public static Pattern GEO_URI = Pattern.compile("geo:(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)(?:,-?\\d+(?:\\.\\d+)?)?(?:;crs=[\\w-]+)?(?:;u=\\d+(?:\\.\\d+)?)?(?:;[\\w-]+=(?:[\\w-_.!~*'()]|%[\\da-f][\\da-f])+)*(\\?z=\\d+)?", Pattern.CASE_INSENSITIVE); - - public static boolean isLocationPluginInstalled(Context context) { - return new Intent(SHARE_LOCATION_PACKAGE_NAME).resolveActivity(context.getPackageManager()) != null; - } - - public static boolean isLocationPluginInstalledAndDesired(Context context) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - final boolean configured = preferences.getBoolean("use_share_location_plugin", context.getResources().getBoolean(R.bool.use_share_location_plugin)); - return configured && isLocationPluginInstalled(context); - } - - public static Intent getFetchIntent(Context context) { - if (isLocationPluginInstalledAndDesired(context)) { - return new Intent(SHARE_LOCATION_PACKAGE_NAME); - } else { - return new Intent(context, ShareLocationActivity.class); - } - } - - public static GeoPoint parseGeoPoint(final Uri uri) { - return parseGeoPoint(uri.toString()); - } - - private static GeoPoint parseGeoPoint(String body) throws IllegalArgumentException { - final Matcher matcher = GEO_URI.matcher(body); - if (!matcher.matches()) { - throw new IllegalArgumentException("Invalid geo uri"); - } - final double latitude; - final double longitude; - try { - latitude = Double.parseDouble(matcher.group(1)); - if (latitude > 90.0 || latitude < -90.0) { - throw new IllegalArgumentException("Invalid geo uri"); - } - longitude = Double.parseDouble(matcher.group(2)); - if (longitude > 180.0 || longitude < -180.0) { - throw new IllegalArgumentException("Invalid geo uri"); - } - } catch (final NumberFormatException e) { - throw new IllegalArgumentException("Invalid geo uri",e); - } - return new GeoPoint(latitude, longitude); - } - - public static ArrayList createGeoIntentsFromMessage(Context context, Message message) { - final ArrayList intents = new ArrayList<>(); - final GeoPoint geoPoint; - try { - geoPoint = parseGeoPoint(message.getBody()); - } catch (IllegalArgumentException e) { - return intents; - } - final Conversational conversation = message.getConversation(); - final String label = getLabel(context, message); - - if (isLocationPluginInstalledAndDesired(context)) { - Intent locationPluginIntent = new Intent(SHOW_LOCATION_PACKAGE_NAME); - locationPluginIntent.putExtra("latitude", geoPoint.getLatitude()); - locationPluginIntent.putExtra("longitude", geoPoint.getLongitude()); - if (message.getStatus() != Message.STATUS_RECEIVED) { - locationPluginIntent.putExtra("jid", conversation.getAccount().getJid().toString()); - locationPluginIntent.putExtra("name", conversation.getAccount().getJid().getLocal()); - } else { - Contact contact = message.getContact(); - if (contact != null) { - locationPluginIntent.putExtra("name", contact.getDisplayName()); - locationPluginIntent.putExtra("jid", contact.getJid().toString()); - } else { - locationPluginIntent.putExtra("name", UIHelper.getDisplayedMucCounterpart(message.getCounterpart())); - } - } - intents.add(locationPluginIntent); - } else { - Intent intent = new Intent(context, ShowLocationActivity.class); - intent.setAction(SHOW_LOCATION_PACKAGE_NAME); - intent.putExtra("latitude", geoPoint.getLatitude()); - intent.putExtra("longitude", geoPoint.getLongitude()); - intents.add(intent); - } - - intents.add(geoIntent(geoPoint, label)); - - Intent httpIntent = new Intent(Intent.ACTION_VIEW); - httpIntent.setData(Uri.parse("https://maps.google.com/maps?q=loc:"+ geoPoint.getLatitude() + "," + geoPoint.getLongitude() +label)); - intents.add(httpIntent); - return intents; - } - - public static void view(Context context, Message message) { - final GeoPoint geoPoint = parseGeoPoint(message.getBody()); - final String label = getLabel(context, message); - context.startActivity(geoIntent(geoPoint,label)); - } - - private static Intent geoIntent(GeoPoint geoPoint, String label) { - Intent geoIntent = new Intent(Intent.ACTION_VIEW); - geoIntent.setData(Uri.parse("geo:" + geoPoint.getLatitude() + "," + geoPoint.getLongitude() + "?q=" + geoPoint.getLatitude() + "," + geoPoint.getLongitude() + "("+ label+")")); - return geoIntent; - } - - public static boolean openInOsmAnd(Context context, Message message) { - try { - final GeoPoint geoPoint = parseGeoPoint(message.getBody()); - final String label = getLabel(context, message); - return geoIntent(geoPoint, label).resolveActivity(context.getPackageManager()) != null; - } catch (IllegalArgumentException e) { - return false; - } - } - - private static String getLabel(Context context, Message message) { - if(message.getStatus() == Message.STATUS_RECEIVED) { - try { - return URLEncoder.encode(UIHelper.getMessageDisplayName(message),"UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } - } else { - return context.getString(R.string.me); - } - } + private static final String SHARE_LOCATION_PACKAGE_NAME = + "eu.siacs.conversations.location.request"; + private static final String SHOW_LOCATION_PACKAGE_NAME = "eu.siacs.conversations.location.show"; + + public static boolean isLocationPluginInstalled(Context context) { + return new Intent(SHARE_LOCATION_PACKAGE_NAME).resolveActivity(context.getPackageManager()) + != null; + } + + public static boolean isLocationPluginInstalledAndDesired(Context context) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + final boolean configured = + preferences.getBoolean( + "use_share_location_plugin", + context.getResources().getBoolean(R.bool.use_share_location_plugin)); + return configured && isLocationPluginInstalled(context); + } + + public static Intent getFetchIntent(Context context) { + if (isLocationPluginInstalledAndDesired(context)) { + return new Intent(SHARE_LOCATION_PACKAGE_NAME); + } else { + return new Intent(context, ShareLocationActivity.class); + } + } + + public static GeoPoint parseGeoPoint(final Uri uri) { + return parseGeoPoint(uri.toString()); + } + + private static GeoPoint parseGeoPoint(String body) throws IllegalArgumentException { + final Matcher matcher = Patterns.URI_GEO.matcher(body); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid geo uri"); + } + final double latitude; + final double longitude; + try { + latitude = Double.parseDouble(matcher.group(1)); + if (latitude > 90.0 || latitude < -90.0) { + throw new IllegalArgumentException("Invalid geo uri"); + } + longitude = Double.parseDouble(matcher.group(2)); + if (longitude > 180.0 || longitude < -180.0) { + throw new IllegalArgumentException("Invalid geo uri"); + } + } catch (final NumberFormatException e) { + throw new IllegalArgumentException("Invalid geo uri", e); + } + return new GeoPoint(latitude, longitude); + } + + public static ArrayList createGeoIntentsFromMessage(Context context, Message message) { + final ArrayList intents = new ArrayList<>(); + final GeoPoint geoPoint; + try { + geoPoint = parseGeoPoint(message.getBody()); + } catch (IllegalArgumentException e) { + return intents; + } + final Conversational conversation = message.getConversation(); + final String label = getLabel(context, message); + + if (isLocationPluginInstalledAndDesired(context)) { + Intent locationPluginIntent = new Intent(SHOW_LOCATION_PACKAGE_NAME); + locationPluginIntent.putExtra("latitude", geoPoint.getLatitude()); + locationPluginIntent.putExtra("longitude", geoPoint.getLongitude()); + if (message.getStatus() != Message.STATUS_RECEIVED) { + locationPluginIntent.putExtra("jid", conversation.getAccount().getJid().toString()); + locationPluginIntent.putExtra( + "name", conversation.getAccount().getJid().getLocal()); + } else { + Contact contact = message.getContact(); + if (contact != null) { + locationPluginIntent.putExtra("name", contact.getDisplayName()); + locationPluginIntent.putExtra("jid", contact.getJid().toString()); + } else { + locationPluginIntent.putExtra( + "name", UIHelper.getDisplayedMucCounterpart(message.getCounterpart())); + } + } + intents.add(locationPluginIntent); + } else { + Intent intent = new Intent(context, ShowLocationActivity.class); + intent.setAction(SHOW_LOCATION_PACKAGE_NAME); + intent.putExtra("latitude", geoPoint.getLatitude()); + intent.putExtra("longitude", geoPoint.getLongitude()); + intents.add(intent); + } + + intents.add(geoIntent(geoPoint, label)); + + Intent httpIntent = new Intent(Intent.ACTION_VIEW); + httpIntent.setData( + Uri.parse( + "https://maps.google.com/maps?q=loc:" + + geoPoint.getLatitude() + + "," + + geoPoint.getLongitude() + + label)); + intents.add(httpIntent); + return intents; + } + + public static void view(Context context, Message message) { + final GeoPoint geoPoint = parseGeoPoint(message.getBody()); + final String label = getLabel(context, message); + context.startActivity(geoIntent(geoPoint, label)); + } + + private static Intent geoIntent(GeoPoint geoPoint, String label) { + Intent geoIntent = new Intent(Intent.ACTION_VIEW); + geoIntent.setData( + Uri.parse( + "geo:" + + geoPoint.getLatitude() + + "," + + geoPoint.getLongitude() + + "?q=" + + geoPoint.getLatitude() + + "," + + geoPoint.getLongitude() + + "(" + + label + + ")")); + return geoIntent; + } + + public static boolean openInOsmAnd(Context context, Message message) { + try { + final GeoPoint geoPoint = parseGeoPoint(message.getBody()); + final String label = getLabel(context, message); + return geoIntent(geoPoint, label).resolveActivity(context.getPackageManager()) != null; + } catch (IllegalArgumentException e) { + return false; + } + } + + private static String getLabel(Context context, Message message) { + if (message.getStatus() == Message.STATUS_RECEIVED) { + try { + return URLEncoder.encode(UIHelper.getMessageDisplayName(message), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } else { + return context.getString(R.string.me); + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/Patterns.java b/src/main/java/eu/siacs/conversations/utils/Patterns.java index 026951b22eed95f2943f46c8a44266e1dabe8058..b4eaef9660f0ef3718a19847297f7a77c637596b 100644 --- a/src/main/java/eu/siacs/conversations/utils/Patterns.java +++ b/src/main/java/eu/siacs/conversations/utils/Patterns.java @@ -1,513 +1,24 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Download latest version here: - * https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/android/util/Patterns.java - * - * - */ package eu.siacs.conversations.utils; -import java.util.regex.Matcher; + import java.util.regex.Pattern; -/** - * Commonly used regular expression patterns. - */ + public class Patterns { - public static final Pattern XMPP_PATTERN = Pattern - .compile("xmpp\\:(?:(?:[" - + Patterns.GOOD_IRI_CHAR - + "\\;\\/\\?\\@\\&\\=\\#\\~\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])" - + "|(?:\\%[a-fA-F0-9]{2}))+"); + public static final Pattern URI_GENERIC = + Pattern.compile( + "(?<=^|\\s)(tel|xmpp|http|https|geo|mailto):[a-zA-Z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=%]+"); - /** - * Regular expression to match all IANA top-level domains. - * List accurate as of 2011/07/18. List taken from: - * http://data.iana.org/TLD/tlds-alpha-by-domain.txt - * This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py - * - * @deprecated Due to the recent profileration of gTLDs, this API is - * expected to become out-of-date very quickly. Therefore it is now - * deprecated. - */ - @Deprecated - public static final String TOP_LEVEL_DOMAIN_STR = - "((aero|arpa|asia|a[cdefgilmnoqrstuwxz])" - + "|(biz|b[abdefghijmnorstvwyz])" - + "|(cat|com|coop|c[acdfghiklmnoruvxyz])" - + "|d[ejkmoz]" - + "|(edu|e[cegrstu])" - + "|f[ijkmor]" - + "|(gov|g[abdefghilmnpqrstuwy])" - + "|h[kmnrtu]" - + "|(info|int|i[delmnoqrst])" - + "|(jobs|j[emop])" - + "|k[eghimnprwyz]" - + "|l[abcikrstuvy]" - + "|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])" - + "|(name|net|n[acefgilopruz])" - + "|(org|om)" - + "|(pro|p[aefghklmnrstwy])" - + "|qa" - + "|r[eosuw]" - + "|s[abcdeghijklmnortuvyz]" - + "|(tel|travel|t[cdfghjklmnoprtvwz])" - + "|u[agksyz]" - + "|v[aceginu]" - + "|w[fs]" - + "|(\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae|\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435|\u0440\u0444|\u0441\u0440\u0431|\u05d8\u05e2\u05e1\u05d8|\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc|\u0625\u062e\u062a\u0628\u0627\u0631|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u0631\u064a\u0629|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0645\u0635\u0631|\u092a\u0930\u0940\u0915\u094d\u0937\u093e|\u092d\u093e\u0930\u0924|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd|\u0baa\u0bb0\u0bbf\u0b9f\u0bcd\u0b9a\u0bc8|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e44\u0e17\u0e22|\u30c6\u30b9\u30c8|\u4e2d\u56fd|\u4e2d\u570b|\u53f0\u6e7e|\u53f0\u7063|\u65b0\u52a0\u5761|\u6d4b\u8bd5|\u6e2c\u8a66|\u9999\u6e2f|\ud14c\uc2a4\ud2b8|\ud55c\uad6d|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)" - + "|y[et]" - + "|z[amw])"; - /** - * Regular expression pattern to match all IANA top-level domains. - * @deprecated This API is deprecated. See {@link #TOP_LEVEL_DOMAIN_STR}. - */ - @Deprecated - public static final Pattern TOP_LEVEL_DOMAIN = - Pattern.compile(TOP_LEVEL_DOMAIN_STR); - /** - * Regular expression to match all IANA top-level domains for WEB_URL. - * List accurate as of 2011/07/18. List taken from: - * http://data.iana.org/TLD/tlds-alpha-by-domain.txt - * This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py - * - * @deprecated This API is deprecated. See {@link #TOP_LEVEL_DOMAIN_STR}. - */ - @Deprecated - public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL = - "(?:" - + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])" - + "|(?:biz|b[abdefghijmnorstvwyz])" - + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])" - + "|d[ejkmoz]" - + "|(?:edu|e[cegrstu])" - + "|f[ijkmor]" - + "|(?:gov|g[abdefghilmnpqrstuwy])" - + "|h[kmnrtu]" - + "|(?:info|int|i[delmnoqrst])" - + "|(?:jobs|j[emop])" - + "|k[eghimnprwyz]" - + "|l[abcikrstuvy]" - + "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])" - + "|(?:name|net|n[acefgilopruz])" - + "|(?:org|om)" - + "|(?:pro|p[aefghklmnrstwy])" - + "|qa" - + "|r[eosuw]" - + "|s[abcdeghijklmnortuvyz]" - + "|(?:tel|travel|t[cdfghjklmnoprtvwz])" - + "|u[agksyz]" - + "|v[aceginu]" - + "|w[fs]" - + "|(?:\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae|\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435|\u0440\u0444|\u0441\u0440\u0431|\u05d8\u05e2\u05e1\u05d8|\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc|\u0625\u062e\u062a\u0628\u0627\u0631|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u0631\u064a\u0629|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0645\u0635\u0631|\u092a\u0930\u0940\u0915\u094d\u0937\u093e|\u092d\u093e\u0930\u0924|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd|\u0baa\u0bb0\u0bbf\u0b9f\u0bcd\u0b9a\u0bc8|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e44\u0e17\u0e22|\u30c6\u30b9\u30c8|\u4e2d\u56fd|\u4e2d\u570b|\u53f0\u6e7e|\u53f0\u7063|\u65b0\u52a0\u5761|\u6d4b\u8bd5|\u6e2c\u8a66|\u9999\u6e2f|\ud14c\uc2a4\ud2b8|\ud55c\uad6d|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)" - + "|y[et]" - + "|z[amw]))"; - /** - * Regular expression to match all IANA top-level domains. - * - * List accurate as of 2015/11/24. List taken from: - * http://data.iana.org/TLD/tlds-alpha-by-domain.txt - * This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py - * - * @hide - */ - static final String IANA_TOP_LEVEL_DOMAINS = - "(?:" - + "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active" - + "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica|amsterdam" - + "|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia|associates" - + "|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])" - + "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva" - + "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz|black" - + "|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots|boutique" - + "|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build|builders|business" - + "|buzz|bzh|b[abdefghijmnorstvwyz])" - + "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards" - + "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center|ceo" - + "|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani|cisco" - + "|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed|coach" - + "|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec|condos" - + "|construction|consulting|contractors|cooking|cool|coop|corsica|country|coupons|courses" - + "|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])" - + "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta" - + "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount" - + "|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])" - + "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises" - + "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert|exposed" - + "|express|e[cegrstu])" - + "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|feedback|ferrero|film" - + "|final|finance|financial|firmdale|fish|fishing|fit|fitness|flights|florist|flowers|flsmidth" - + "|fly|foo|football|forex|forsale|forum|foundation|frl|frogans|fund|furniture|futbol|fyi" - + "|f[ijkmor])" - + "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving" - + "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov|grainger" - + "|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru|g[abdefghilmnpqrstuwy])" - + "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey|holdings" - + "|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hsbc|hyundai" - + "|h[kmnrtu])" - + "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink|institute" - + "|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau|iwc|i[delmnoqrst])" - + "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])" - + "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto|k[eghimnprwyz])" - + "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease|leclerc" - + "|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde|link|live" - + "|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury|l[abcikrstuvy])" - + "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba" - + "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi|moda" - + "|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar" - + "|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])" - + "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk" - + "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])" - + "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka" - + "|otsuka|ovh|om)" - + "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography" - + "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation|plumbing" - + "|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties|property" - + "|protection|pub|p[aefghklmnrstwy])" - + "|(?:qpon|quebec|qa)" - + "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent|rentals" - + "|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip|rocher|rocks" - + "|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])" - + "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo" - + "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat|security" - + "|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles|site|ski" - + "|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space|spiegel|spreadbetting" - + "|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study|style|sucks|supplies" - + "|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems|s[abcdeghijklmnortuvxyz])" - + "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica" - + "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo|tools" - + "|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust|tui|t[cdfghjklmnortvwz])" - + "|(?:ubs|university|uno|uol|u[agksyz])" - + "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin" - + "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])" - + "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki|williamhill" - + "|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])" - + "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c|\u043c\u043a\u0434" - + "|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430|\u043e\u043d\u043b\u0430\u0439\u043d" - + "|\u043e\u0440\u0433|\u0440\u0443\u0441|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431" - + "|\u0443\u043a\u0440|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd|\u0627\u0631\u0627\u0645\u0643\u0648" - + "|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629" - + "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0627\u06cc\u0631\u0627\u0646" - + "|\u0628\u0627\u0632\u0627\u0631|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633" - + "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629|\u0634\u0628\u0643\u0629" - + "|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646|\u0641\u0644\u0633\u0637\u064a\u0646" - + "|\u0642\u0637\u0631|\u0643\u0648\u0645|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627" - + "|\u0645\u0648\u0642\u0639|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924" - + "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4" - + "|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd" - + "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21|\u0e44\u0e17\u0e22" - + "|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb|\u30b3\u30e0|\u4e16\u754c" - + "|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51|\u4f01\u4e1a|\u4f5b\u5c71" - + "|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063" - + "|\u5546\u57ce|\u5546\u5e97|\u5546\u6807|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c" - + "|\u5e7f\u4e1c|\u6148\u5584|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c" - + "|\u65b0\u52a0\u5761|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f" - + "|\u70b9\u770b|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc" - + "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137" - + "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox" - + "|xerox|xin|xn\\-\\-11b4c3d|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g" - + "|xn\\-\\-3e0b707e|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim" - + "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks" - + "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais|xn\\-\\-9dbq2a" - + "|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd" - + "|xn\\-\\-czr694b|xn\\-\\-czrs0t|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h" - + "|xn\\-\\-estv75g|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s" - + "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c" - + "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i" - + "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d|xn\\-\\-kpry57d" - + "|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf|xn\\-\\-mgba3a3ejt" - + "|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e" - + "|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab" - + "|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema" - + "|xn\\-\\-nyqy26a|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh" - + "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g|xn\\-\\-s9brj9c" - + "|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y|xn\\-\\-vermgensberater\\-ctb" - + "|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a" - + "|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o" - + "|xn\\-\\-ygbi2ammx|xn\\-\\-zfr164b|xperia|xxx|xyz)" - + "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])" - + "|(?:zara|zip|zone|zuerich|z[amw]))"; - /** - * Kept for backward compatibility reasons. - * - * @deprecated Deprecated since it does not include all IRI characters defined in RFC 3987 - */ - @Deprecated - public static final String GOOD_IRI_CHAR = - "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; - public static final Pattern IP_ADDRESS - = Pattern.compile( - "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" - + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" - + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" - + "|[1-9][0-9]|[0-9]))"); + public static final Pattern URI_TEL = + Pattern.compile("^tel:\\+?(\\d{1,4}[-./()\\s]?)*\\d{1,4}(;.*)?$"); - /** - * IPv6 address matcher for - * IPv6 addresses - * zero compressed IPv6 addresses (section 2.2 of rfc5952) - * link-local IPv6 addresses with zone index (section 11 of rfc4007) - * IPv4-Embedded IPv6 Address (section 2 of rfc6052) - * IPv4-mapped IPv6 addresses (section 2.1 of rfc2765) - * IPv4-translated addresses (section 2.1 of rfc2765) - * - * Taken from https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses/17871737#17871737 - */ - public static final Pattern IP6_ADDRESS - = Pattern.compile( - "\\[" + - "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + - "([0-9a-fA-F]{1,4}:){1,7}:|" + - "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + - "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + - "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + - "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + - "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + - "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + - ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + - "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + - "::(ffff(:0{1,4}){0,1}:){0,1}" + - "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + - "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|" + - "([0-9a-fA-F]{1,4}:){1,4}:" + - "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + - "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" + - "\\]" - ); - /** - * Valid UCS characters defined in RFC 3987. Excludes space characters. - */ - private static final String UCS_CHAR = "[" + - "\u00A0-\uD7FF" + - "\uF900-\uFDCF" + - "\uFDF0-\uFFEF" + - "\uD800\uDC00-\uD83F\uDFFD" + - "\uD840\uDC00-\uD87F\uDFFD" + - "\uD880\uDC00-\uD8BF\uDFFD" + - "\uD8C0\uDC00-\uD8FF\uDFFD" + - "\uD900\uDC00-\uD93F\uDFFD" + - "\uD940\uDC00-\uD97F\uDFFD" + - "\uD980\uDC00-\uD9BF\uDFFD" + - "\uD9C0\uDC00-\uD9FF\uDFFD" + - "\uDA00\uDC00-\uDA3F\uDFFD" + - "\uDA40\uDC00-\uDA7F\uDFFD" + - "\uDA80\uDC00-\uDABF\uDFFD" + - "\uDAC0\uDC00-\uDAFF\uDFFD" + - "\uDB00\uDC00-\uDB3F\uDFFD" + - "\uDB44\uDC00-\uDB7F\uDFFD" + - "&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]"; - /** - * Valid characters for IRI label defined in RFC 3987. - */ - private static final String LABEL_CHAR = "a-zA-Z0-9" + UCS_CHAR; - /** - * Valid characters for IRI TLD defined in RFC 3987. - */ - private static final String TLD_CHAR = "a-zA-Z" + UCS_CHAR; - /** - * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets. - */ - private static final String IRI_LABEL = - "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "\\-]{0,61}[" + LABEL_CHAR + "]){0,1}"; - /** - * RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters. - */ - private static final String PUNYCODE_TLD = "xn\\-\\-[\\w\\-]{0,58}\\w"; - private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" +")"; - private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD; - public static final Pattern DOMAIN_NAME - = Pattern.compile("(" + HOST_NAME + "|" + IP6_ADDRESS + "|" + IP_ADDRESS +")"); - private static final String PROTOCOL = "(?i:http|https|rtsp):\\/\\/"; - /* A word boundary or end of input. This is to stop foo.sure from matching as foo.su */ - private static final String WORD_BOUNDARY = "(?:\\b|$|^)"; - private static final String USER_INFO = "(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" - + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" - + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@"; - private static final String PORT_NUMBER = "\\:\\d{1,5}"; - private static final String PATH_AND_QUERY = "\\/(?:(?:[" + LABEL_CHAR - + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus optional query params - + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_\\$])|(?:\\%[a-fA-F0-9]{2}))*"; - /** - * Regular expression pattern to match most part of RFC 3987 - * Internationalized URLs, aka IRIs. - */ - public static final Pattern WEB_URL = Pattern.compile("(" - + "(" - + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")?" - + "(?:" + DOMAIN_NAME + ")" - + "(?:" + PORT_NUMBER + ")?" - + ")" - + "(" + PATH_AND_QUERY + ")?" - + WORD_BOUNDARY - + ")"); - /** - * Regular expression that matches known TLDs and punycode TLDs - */ - private static final String STRICT_TLD = "(?:" + - IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")"; - /** - * Regular expression that matches host names using {@link #STRICT_TLD} - */ - private static final String STRICT_HOST_NAME = "(?:(?:" + IRI_LABEL + "\\.)+" - + STRICT_TLD + ")"; - /** - * Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or - * {@link #IP_ADDRESS} - */ - private static final Pattern STRICT_DOMAIN_NAME - = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + "|" + IP6_ADDRESS + ")"); - /** - * Regular expression that matches domain names without a TLD - */ - private static final String RELAXED_DOMAIN_NAME = - "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + "|" + IP6_ADDRESS + ")"; - /** - * Regular expression to match strings that do not start with a supported protocol. The TLDs - * are expected to be one of the known TLDs. - */ - private static final String WEB_URL_WITHOUT_PROTOCOL = "(" - + WORD_BOUNDARY - + "(? The pattern matches the following: - *
    - *
  • Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes - * may follow. - *
  • Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes. - *
  • A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes. - *
- */ - public static final Pattern PHONE - = Pattern.compile( // sdd = space, dot, or dash - "(\\+[0-9]+[\\- \\.]*)?" // +* - + "(\\([0-9]+\\)[\\- \\.]*)?" // ()* - + "([0-9][0-9\\- \\.]+[0-9])"); // + - /** - * Convenience method to take all of the non-null matching groups in a - * regex Matcher and return them as a concatenated string. - * - * @param matcher The Matcher object from which grouped text will - * be extracted - * - * @return A String comprising all of the non-null matched - * groups concatenated together - */ - public static final String concatGroups(Matcher matcher) { - StringBuilder b = new StringBuilder(); - final int numGroups = matcher.groupCount(); - for (int i = 1; i <= numGroups; i++) { - String s = matcher.group(i); - if (s != null) { - b.append(s); - } - } - return b.toString(); - } - /** - * Convenience method to return only the digits and plus signs - * in the matching string. - * - * @param matcher The Matcher object from which digits and plus will - * be extracted - * - * @return A String comprising all of the digits and plus in - * the match - */ - public static final String digitsAndPlusOnly(Matcher matcher) { - StringBuilder buffer = new StringBuilder(); - String matchingRegion = matcher.group(); - for (int i = 0, size = matchingRegion.length(); i < size; i++) { - char character = matchingRegion.charAt(i); - if (character == '+' || Character.isDigit(character)) { - buffer.append(character); - } - } - return buffer.toString(); + public static final Pattern URI_HTTP = Pattern.compile("https?://\\S+"); + + public static Pattern URI_GEO = + Pattern.compile( + "geo:(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)(?:,-?\\d+(?:\\.\\d+)?)?(?:;crs=[\\w-]+)?(?:;u=\\d+(?:\\.\\d+)?)?(?:;[\\w-]+=(?:[\\w-_.!~*'()]|%[\\da-f][\\da-f])+)*(\\?z=\\d+)?", + Pattern.CASE_INSENSITIVE); + + private Patterns() { + throw new AssertionError("Do not instantiate me"); } - /** - * Do not create this static utility class. - */ - private Patterns() {} } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 654a7e7e8b52e7a975dd9c7da6980a21a10732cc..3bb877721a6eafa2ef454864589fec4b29031d71 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -1034,7 +1034,7 @@ public class XmppConnection implements Runnable { if (Strings.isNullOrEmpty(text)) { throw new StateChangingException(Account.State.UNAUTHORIZED); } - final Matcher matcher = Patterns.AUTOLINK_WEB_URL.matcher(text); + final Matcher matcher = Patterns.URI_HTTP.matcher(text); if (matcher.find()) { final HttpUrl url; try { @@ -1925,7 +1925,7 @@ public class XmppConnection implements Runnable { if (url != null) { setAccountCreationFailed(url); } else if (instructions != null) { - final Matcher matcher = Patterns.AUTOLINK_WEB_URL.matcher(instructions); + final Matcher matcher = Patterns.URI_HTTP.matcher(instructions); if (matcher.find()) { setAccountCreationFailed( instructions.substring(matcher.start(), matcher.end()));