kopia lustrzana https://github.com/ryukoposting/Signal-Android
rodzic
6563ea970f
commit
8a2f89b4f6
|
@ -106,7 +106,6 @@ import org.thoughtcrime.securesms.jobs.MmsSendJob;
|
||||||
import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
|
@ -123,6 +122,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.revealable.ViewOnceMessageView;
|
import org.thoughtcrime.securesms.revealable.ViewOnceMessageView;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan;
|
import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan;
|
||||||
|
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||||
import org.thoughtcrime.securesms.util.MessageRecordUtil;
|
import org.thoughtcrime.securesms.util.MessageRecordUtil;
|
||||||
import org.thoughtcrime.securesms.util.PlaceholderURLSpan;
|
import org.thoughtcrime.securesms.util.PlaceholderURLSpan;
|
||||||
|
@ -1362,7 +1362,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||||
|
|
||||||
if (hasLinks) {
|
if (hasLinks) {
|
||||||
Stream.of(messageBody.getSpans(0, messageBody.length(), URLSpan.class))
|
Stream.of(messageBody.getSpans(0, messageBody.length(), URLSpan.class))
|
||||||
.filterNot(url -> LinkPreviewUtil.isLegalUrl(url.getURL()))
|
.filterNot(url -> LinkUtil.isLegalUrl(url.getURL()))
|
||||||
.forEach(messageBody::removeSpan);
|
.forEach(messageBody::removeSpan);
|
||||||
|
|
||||||
URLSpan[] urlSpans = messageBody.getSpans(0, messageBody.length(), URLSpan.class);
|
URLSpan[] urlSpans = messageBody.getSpans(0, messageBody.length(), URLSpan.class);
|
||||||
|
|
|
@ -19,7 +19,7 @@ import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
||||||
|
|
||||||
public final class GroupDescriptionUtil {
|
public final class GroupDescriptionUtil {
|
||||||
|
@ -43,7 +43,7 @@ public final class GroupDescriptionUtil {
|
||||||
|
|
||||||
if (hasLinks) {
|
if (hasLinks) {
|
||||||
Stream.of(descriptionSpannable.getSpans(0, descriptionSpannable.length(), URLSpan.class))
|
Stream.of(descriptionSpannable.getSpans(0, descriptionSpannable.length(), URLSpan.class))
|
||||||
.filterNot(url -> LinkPreviewUtil.isLegalUrl(url.getURL()))
|
.filterNot(url -> LinkUtil.isLegalUrl(url.getURL()))
|
||||||
.forEach(descriptionSpannable::removeSpan);
|
.forEach(descriptionSpannable::removeSpan);
|
||||||
|
|
||||||
URLSpan[] urlSpans = descriptionSpannable.getSpans(0, descriptionSpannable.length(), URLSpan.class);
|
URLSpan[] urlSpans = descriptionSpannable.getSpans(0, descriptionSpannable.length(), URLSpan.class);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import androidx.core.util.Consumer;
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
|
import org.signal.core.util.Hex;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||||
|
@ -45,8 +46,8 @@ import org.thoughtcrime.securesms.stickers.StickerUrl;
|
||||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||||
import org.thoughtcrime.securesms.util.ByteUnit;
|
import org.thoughtcrime.securesms.util.ByteUnit;
|
||||||
import org.signal.core.util.Hex;
|
|
||||||
import org.thoughtcrime.securesms.util.ImageCompressionUtil;
|
import org.thoughtcrime.securesms.util.ImageCompressionUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.OkHttpUtil;
|
import org.thoughtcrime.securesms.util.OkHttpUtil;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||||
|
@ -95,7 +96,7 @@ public class LinkPreviewRepository {
|
||||||
|
|
||||||
CompositeRequestController compositeController = new CompositeRequestController();
|
CompositeRequestController compositeController = new CompositeRequestController();
|
||||||
|
|
||||||
if (!LinkPreviewUtil.isValidPreviewUrl(url)) {
|
if (!LinkUtil.isValidPreviewUrl(url)) {
|
||||||
Log.w(TAG, "Tried to get a link preview for a non-whitelisted domain.");
|
Log.w(TAG, "Tried to get a link preview for a non-whitelisted domain.");
|
||||||
callback.onError(Error.PREVIEW_NOT_AVAILABLE);
|
callback.onError(Error.PREVIEW_NOT_AVAILABLE);
|
||||||
return compositeController;
|
return compositeController;
|
||||||
|
@ -164,7 +165,7 @@ public class LinkPreviewRepository {
|
||||||
Optional<String> imageUrl = openGraph.getImageUrl();
|
Optional<String> imageUrl = openGraph.getImageUrl();
|
||||||
long date = openGraph.getDate();
|
long date = openGraph.getDate();
|
||||||
|
|
||||||
if (imageUrl.isPresent() && !LinkPreviewUtil.isValidPreviewUrl(imageUrl.get())) {
|
if (imageUrl.isPresent() && !LinkUtil.isValidPreviewUrl(imageUrl.get())) {
|
||||||
Log.i(TAG, "Image URL was invalid or for a non-whitelisted domain. Skipping.");
|
Log.i(TAG, "Image URL was invalid or for a non-whitelisted domain. Skipping.");
|
||||||
imageUrl = Optional.empty();
|
imageUrl = Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.linkpreview;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.style.URLSpan;
|
import android.text.style.URLSpan;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
|
|
||||||
|
@ -14,10 +13,8 @@ import androidx.core.text.util.LinkifyCompat;
|
||||||
import com.annimon.stream.Collectors;
|
import com.annimon.stream.Collectors;
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.signal.core.util.SetUtil;
|
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||||
|
|
||||||
|
@ -34,12 +31,6 @@ import okhttp3.HttpUrl;
|
||||||
|
|
||||||
public final class LinkPreviewUtil {
|
public final class LinkPreviewUtil {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(LinkPreviewUtil.class);
|
|
||||||
|
|
||||||
private static final Pattern DOMAIN_PATTERN = Pattern.compile("^(https?://)?([^/]+).*$");
|
|
||||||
private static final Pattern ALL_ASCII_PATTERN = Pattern.compile("^[\\x00-\\x7F]*$");
|
|
||||||
private static final Pattern ALL_NON_ASCII_PATTERN = Pattern.compile("^[^\\x00-\\x7F]*$");
|
|
||||||
private static final Pattern ILLEGAL_CHARACTERS_PATTERN = Pattern.compile("[\u202C\u202D\u202E\u2500-\u25FF]");
|
|
||||||
private static final Pattern OPEN_GRAPH_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*og:([^\"]+)\"[^>]*/?\\s*>");
|
private static final Pattern OPEN_GRAPH_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*og:([^\"]+)\"[^>]*/?\\s*>");
|
||||||
private static final Pattern ARTICLE_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*article:([^\"]+)\"[^>]*/?\\s*>");
|
private static final Pattern ARTICLE_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*article:([^\"]+)\"[^>]*/?\\s*>");
|
||||||
private static final Pattern OPEN_GRAPH_CONTENT_PATTERN = Pattern.compile("content\\s*=\\s*\"([^\"]*)\"");
|
private static final Pattern OPEN_GRAPH_CONTENT_PATTERN = Pattern.compile("content\\s*=\\s*\"([^\"]*)\"");
|
||||||
|
@ -47,8 +38,6 @@ public final class LinkPreviewUtil {
|
||||||
private static final Pattern FAVICON_PATTERN = Pattern.compile("<\\s*link[^>]*rel\\s*=\\s*\".*icon.*\"[^>]*>");
|
private static final Pattern FAVICON_PATTERN = Pattern.compile("<\\s*link[^>]*rel\\s*=\\s*\".*icon.*\"[^>]*>");
|
||||||
private static final Pattern FAVICON_HREF_PATTERN = Pattern.compile("href\\s*=\\s*\"([^\"]*)\"");
|
private static final Pattern FAVICON_HREF_PATTERN = Pattern.compile("href\\s*=\\s*\"([^\"]*)\"");
|
||||||
|
|
||||||
private static final Set<String> INVALID_TOP_LEVEL_DOMAINS = SetUtil.newHashSet("onion", "i2p");
|
|
||||||
|
|
||||||
public static @Nullable String getTopLevelDomain(@Nullable String urlString) {
|
public static @Nullable String getTopLevelDomain(@Nullable String urlString) {
|
||||||
if (!Util.isEmpty(urlString)) {
|
if (!Util.isEmpty(urlString)) {
|
||||||
HttpUrl url = HttpUrl.parse(urlString);
|
HttpUrl url = HttpUrl.parse(urlString);
|
||||||
|
@ -61,7 +50,7 @@ public final class LinkPreviewUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return All whitelisted URLs in the source text.
|
* @return All URLs allowed as previews in the source text.
|
||||||
*/
|
*/
|
||||||
public static @NonNull Links findValidPreviewUrls(@NonNull String text) {
|
public static @NonNull Links findValidPreviewUrls(@NonNull String text) {
|
||||||
SpannableString spannable = new SpannableString(text);
|
SpannableString spannable = new SpannableString(text);
|
||||||
|
@ -73,47 +62,10 @@ public final class LinkPreviewUtil {
|
||||||
|
|
||||||
return new Links(Stream.of(spannable.getSpans(0, spannable.length(), URLSpan.class))
|
return new Links(Stream.of(spannable.getSpans(0, spannable.length(), URLSpan.class))
|
||||||
.map(span -> new Link(span.getURL(), spannable.getSpanStart(span)))
|
.map(span -> new Link(span.getURL(), spannable.getSpanStart(span)))
|
||||||
.filter(link -> isValidPreviewUrl(link.getUrl()))
|
.filter(link -> LinkUtil.isValidPreviewUrl(link.getUrl()))
|
||||||
.toList());
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return True if the host is present in the link whitelist.
|
|
||||||
*/
|
|
||||||
public static boolean isValidPreviewUrl(@Nullable String linkUrl) {
|
|
||||||
if (linkUrl == null) return false;
|
|
||||||
if (StickerUrl.isValidShareLink(linkUrl)) return true;
|
|
||||||
|
|
||||||
HttpUrl url = HttpUrl.parse(linkUrl);
|
|
||||||
return url != null &&
|
|
||||||
!TextUtils.isEmpty(url.scheme()) &&
|
|
||||||
"https".equals(url.scheme()) &&
|
|
||||||
isLegalUrl(linkUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isLegalUrl(@NonNull String url) {
|
|
||||||
if (ILLEGAL_CHARACTERS_PATTERN.matcher(url).find()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Matcher matcher = DOMAIN_PATTERN.matcher(url);
|
|
||||||
|
|
||||||
if (matcher.matches()) {
|
|
||||||
String domain = matcher.group(2);
|
|
||||||
String cleanedDomain = domain.replaceAll("\\.", "");
|
|
||||||
String topLevelDomain = parseTopLevelDomain(domain);
|
|
||||||
|
|
||||||
boolean validCharacters = ALL_ASCII_PATTERN.matcher(cleanedDomain).matches() ||
|
|
||||||
ALL_NON_ASCII_PATTERN.matcher(cleanedDomain).matches();
|
|
||||||
|
|
||||||
boolean validTopLevelDomain = !INVALID_TOP_LEVEL_DOMAINS.contains(topLevelDomain);
|
|
||||||
|
|
||||||
return validCharacters && validTopLevelDomain;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NonNull OpenGraph parseOpenGraphFields(@Nullable String html) {
|
public static @NonNull OpenGraph parseOpenGraphFields(@Nullable String html) {
|
||||||
if (html == null) {
|
if (html == null) {
|
||||||
return new OpenGraph(Collections.emptyMap(), null, null);
|
return new OpenGraph(Collections.emptyMap(), null, null);
|
||||||
|
@ -169,16 +121,6 @@ public final class LinkPreviewUtil {
|
||||||
return new OpenGraph(openGraphTags, htmlTitle, faviconUrl);
|
return new OpenGraph(openGraphTags, htmlTitle, faviconUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable String parseTopLevelDomain(@NonNull String domain) {
|
|
||||||
int periodIndex = domain.lastIndexOf(".");
|
|
||||||
|
|
||||||
if (periodIndex >= 0 && periodIndex < domain.length() - 1) {
|
|
||||||
return domain.substring(periodIndex + 1);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NonNull String fromDoubleEncoded(@NonNull String html) {
|
private static @NonNull String fromDoubleEncoded(@NonNull String html) {
|
||||||
return HtmlCompat.fromHtml(HtmlCompat.fromHtml(html, 0).toString(), 0).toString();
|
return HtmlCompat.fromHtml(HtmlCompat.fromHtml(html, 0).toString(), 0).toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,8 @@ import org.thoughtcrime.securesms.components.FullScreenDialogFragment;
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||||
import org.thoughtcrime.securesms.conversation.colors.ColorizerView;
|
import org.thoughtcrime.securesms.conversation.colors.ColorizerView;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||||
import org.thoughtcrime.securesms.util.Projection;
|
import org.thoughtcrime.securesms.util.Projection;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.thoughtcrime.securesms.util.views.Stub;
|
import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
|
@ -152,7 +152,7 @@ public class LongMessageFragment extends FullScreenDialogFragment {
|
||||||
|
|
||||||
if (hasLinks) {
|
if (hasLinks) {
|
||||||
Stream.of(messageBody.getSpans(0, messageBody.length(), URLSpan.class))
|
Stream.of(messageBody.getSpans(0, messageBody.length(), URLSpan.class))
|
||||||
.filterNot(url -> LinkPreviewUtil.isLegalUrl(url.getURL()))
|
.filterNot(url -> LinkUtil.isLegalUrl(url.getURL()))
|
||||||
.forEach(messageBody::removeSpan);
|
.forEach(messageBody::removeSpan);
|
||||||
}
|
}
|
||||||
return messageBody;
|
return messageBody;
|
||||||
|
|
|
@ -128,6 +128,7 @@ import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
|
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
@ -2702,7 +2703,7 @@ public final class MessageContentProcessor {
|
||||||
Optional<String> description = Optional.ofNullable(preview.getDescription());
|
Optional<String> description = Optional.ofNullable(preview.getDescription());
|
||||||
boolean hasTitle = !TextUtils.isEmpty(title.orElse(""));
|
boolean hasTitle = !TextUtils.isEmpty(title.orElse(""));
|
||||||
boolean presentInBody = url.isPresent() && urlsInMessage.containsUrl(url.get());
|
boolean presentInBody = url.isPresent() && urlsInMessage.containsUrl(url.get());
|
||||||
boolean validDomain = url.isPresent() && LinkPreviewUtil.isValidPreviewUrl(url.get());
|
boolean validDomain = url.isPresent() && LinkUtil.isValidPreviewUrl(url.get());
|
||||||
|
|
||||||
if (hasTitle && (presentInBody || isStoryEmbed) && validDomain) {
|
if (hasTitle && (presentInBody || isStoryEmbed) && validDomain) {
|
||||||
LinkPreview linkPreview = new LinkPreview(url.get(), title.orElse(""), description.orElse(""), preview.getDate(), thumbnail);
|
LinkPreview linkPreview = new LinkPreview(url.get(), title.orElse(""), description.orElse(""), preview.getDate(), thumbnail);
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.signal.core.util.SetUtil;
|
||||||
|
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
|
public final class LinkUtil {
|
||||||
|
|
||||||
|
private static final Pattern DOMAIN_PATTERN = Pattern.compile("^(https?://)?([^/]+).*$");
|
||||||
|
private static final Pattern ALL_ASCII_PATTERN = Pattern.compile("^[\\x00-\\x7F]*$");
|
||||||
|
private static final Pattern ALL_NON_ASCII_PATTERN = Pattern.compile("^[^\\x00-\\x7F]*$");
|
||||||
|
private static final Pattern ILLEGAL_CHARACTERS_PATTERN = Pattern.compile("[\u202C\u202D\u202E\u2500-\u25FF]");
|
||||||
|
|
||||||
|
private static final Set<String> INVALID_TOP_LEVEL_DOMAINS = SetUtil.newHashSet("onion", "i2p");
|
||||||
|
|
||||||
|
private LinkUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True if URL is valid for link previews.
|
||||||
|
*/
|
||||||
|
public static boolean isValidPreviewUrl(@Nullable String linkUrl) {
|
||||||
|
if (linkUrl == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StickerUrl.isValidShareLink(linkUrl)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpUrl url = HttpUrl.parse(linkUrl);
|
||||||
|
return url != null &&
|
||||||
|
!TextUtils.isEmpty(url.scheme()) &&
|
||||||
|
"https".equals(url.scheme()) &&
|
||||||
|
isLegalUrl(linkUrl, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True if URL is valid, mostly useful for linkifying.
|
||||||
|
*/
|
||||||
|
public static boolean isLegalUrl(@NonNull String url) {
|
||||||
|
return isLegalUrl(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isLegalUrl(@NonNull String url, boolean skipTopLevelDomainValidation) {
|
||||||
|
if (ILLEGAL_CHARACTERS_PATTERN.matcher(url).find()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = DOMAIN_PATTERN.matcher(url);
|
||||||
|
|
||||||
|
if (matcher.matches()) {
|
||||||
|
String domain = Objects.requireNonNull(matcher.group(2));
|
||||||
|
String cleanedDomain = domain.replaceAll("\\.", "");
|
||||||
|
String topLevelDomain = parseTopLevelDomain(domain);
|
||||||
|
|
||||||
|
boolean validCharacters = ALL_ASCII_PATTERN.matcher(cleanedDomain).matches() ||
|
||||||
|
ALL_NON_ASCII_PATTERN.matcher(cleanedDomain).matches();
|
||||||
|
|
||||||
|
boolean validTopLevelDomain = skipTopLevelDomainValidation || !INVALID_TOP_LEVEL_DOMAINS.contains(topLevelDomain);
|
||||||
|
|
||||||
|
return validCharacters && validTopLevelDomain;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable String parseTopLevelDomain(@NonNull String domain) {
|
||||||
|
int periodIndex = domain.lastIndexOf(".");
|
||||||
|
|
||||||
|
if (periodIndex >= 0 && periodIndex < domain.length() - 1) {
|
||||||
|
return domain.substring(periodIndex + 1);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.thoughtcrime.securesms.linkpreview;
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -10,7 +10,7 @@ import java.util.Collection;
|
||||||
import static junit.framework.TestCase.assertEquals;
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class LinkPreviewUtilTest_isLegal {
|
public class LinkUtilTest_isLegal {
|
||||||
|
|
||||||
private final String input;
|
private final String input;
|
||||||
private final boolean output;
|
private final boolean output;
|
||||||
|
@ -24,12 +24,12 @@ public class LinkPreviewUtilTest_isLegal {
|
||||||
{ "https://foo.google.com/some/path.html", true },
|
{ "https://foo.google.com/some/path.html", true },
|
||||||
{ "кц.рф", true },
|
{ "кц.рф", true },
|
||||||
{ "https://кц.рф/some/path", true },
|
{ "https://кц.рф/some/path", true },
|
||||||
|
{ "https://abcdefg.onion", true },
|
||||||
|
{ "https://abcdefg.i2p", true },
|
||||||
{ "http://кц.com", false },
|
{ "http://кц.com", false },
|
||||||
{ "кц.com", false },
|
{ "кц.com", false },
|
||||||
{ "http://asĸ.com", false },
|
{ "http://asĸ.com", false },
|
||||||
{ "http://foo.кц.рф", false },
|
{ "http://foo.кц.рф", false },
|
||||||
{ "https://abcdefg.onion", false },
|
|
||||||
{ "https://abcdefg.i2p", false },
|
|
||||||
{ "кц.рф\u202C", false },
|
{ "кц.рф\u202C", false },
|
||||||
{ "кц.рф\u202D", false },
|
{ "кц.рф\u202D", false },
|
||||||
{ "кц.рф\u202E", false },
|
{ "кц.рф\u202E", false },
|
||||||
|
@ -40,13 +40,13 @@ public class LinkPreviewUtilTest_isLegal {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkPreviewUtilTest_isLegal(String input, boolean output) {
|
public LinkUtilTest_isLegal(String input, boolean output) {
|
||||||
this.input = input;
|
this.input = input;
|
||||||
this.output = output;
|
this.output = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isLegal() {
|
public void isLegal() {
|
||||||
assertEquals(output, LinkPreviewUtil.isLegalUrl(input));
|
assertEquals(output, LinkUtil.isLegalUrl(input));
|
||||||
}
|
}
|
||||||
}
|
}
|
Ładowanie…
Reference in New Issue