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.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||
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.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan;
|
||||
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
import org.thoughtcrime.securesms.util.MessageRecordUtil;
|
||||
import org.thoughtcrime.securesms.util.PlaceholderURLSpan;
|
||||
|
@ -1362,7 +1362,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||
|
||||
if (hasLinks) {
|
||||
Stream.of(messageBody.getSpans(0, messageBody.length(), URLSpan.class))
|
||||
.filterNot(url -> LinkPreviewUtil.isLegalUrl(url.getURL()))
|
||||
.filterNot(url -> LinkUtil.isLegalUrl(url.getURL()))
|
||||
.forEach(messageBody::removeSpan);
|
||||
|
||||
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.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
||||
|
||||
public final class GroupDescriptionUtil {
|
||||
|
@ -43,7 +43,7 @@ public final class GroupDescriptionUtil {
|
|||
|
||||
if (hasLinks) {
|
||||
Stream.of(descriptionSpannable.getSpans(0, descriptionSpannable.length(), URLSpan.class))
|
||||
.filterNot(url -> LinkPreviewUtil.isLegalUrl(url.getURL()))
|
||||
.filterNot(url -> LinkUtil.isLegalUrl(url.getURL()))
|
||||
.forEach(descriptionSpannable::removeSpan);
|
||||
|
||||
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 org.signal.core.util.Hex;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
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.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.ByteUnit;
|
||||
import org.signal.core.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.ImageCompressionUtil;
|
||||
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.OkHttpUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
|
@ -95,7 +96,7 @@ public class LinkPreviewRepository {
|
|||
|
||||
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.");
|
||||
callback.onError(Error.PREVIEW_NOT_AVAILABLE);
|
||||
return compositeController;
|
||||
|
@ -164,7 +165,7 @@ public class LinkPreviewRepository {
|
|||
Optional<String> imageUrl = openGraph.getImageUrl();
|
||||
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.");
|
||||
imageUrl = Optional.empty();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.linkpreview;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.URLSpan;
|
||||
import android.text.util.Linkify;
|
||||
|
||||
|
@ -14,10 +13,8 @@ import androidx.core.text.util.LinkifyCompat;
|
|||
import com.annimon.stream.Collectors;
|
||||
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.signal.core.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
|
||||
|
@ -34,12 +31,6 @@ import okhttp3.HttpUrl;
|
|||
|
||||
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 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*\"([^\"]*)\"");
|
||||
|
@ -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_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) {
|
||||
if (!Util.isEmpty(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) {
|
||||
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))
|
||||
.map(span -> new Link(span.getURL(), spannable.getSpanStart(span)))
|
||||
.filter(link -> isValidPreviewUrl(link.getUrl()))
|
||||
.filter(link -> LinkUtil.isValidPreviewUrl(link.getUrl()))
|
||||
.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) {
|
||||
if (html == null) {
|
||||
return new OpenGraph(Collections.emptyMap(), null, null);
|
||||
|
@ -169,16 +121,6 @@ public final class LinkPreviewUtil {
|
|||
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) {
|
||||
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.conversation.colors.ColorizerView;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||
import org.thoughtcrime.securesms.util.Projection;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
|
@ -152,7 +152,7 @@ public class LongMessageFragment extends FullScreenDialogFragment {
|
|||
|
||||
if (hasLinks) {
|
||||
Stream.of(messageBody.getSpans(0, messageBody.length(), URLSpan.class))
|
||||
.filterNot(url -> LinkPreviewUtil.isLegalUrl(url.getURL()))
|
||||
.filterNot(url -> LinkUtil.isLegalUrl(url.getURL()))
|
||||
.forEach(messageBody::removeSpan);
|
||||
}
|
||||
return messageBody;
|
||||
|
|
|
@ -128,6 +128,7 @@ import org.thoughtcrime.securesms.util.Base64;
|
|||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
@ -2702,7 +2703,7 @@ public final class MessageContentProcessor {
|
|||
Optional<String> description = Optional.ofNullable(preview.getDescription());
|
||||
boolean hasTitle = !TextUtils.isEmpty(title.orElse(""));
|
||||
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) {
|
||||
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.runner.RunWith;
|
||||
|
@ -10,7 +10,7 @@ import java.util.Collection;
|
|||
import static junit.framework.TestCase.assertEquals;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class LinkPreviewUtilTest_isLegal {
|
||||
public class LinkUtilTest_isLegal {
|
||||
|
||||
private final String input;
|
||||
private final boolean output;
|
||||
|
@ -24,12 +24,12 @@ public class LinkPreviewUtilTest_isLegal {
|
|||
{ "https://foo.google.com/some/path.html", true },
|
||||
{ "кц.рф", true },
|
||||
{ "https://кц.рф/some/path", true },
|
||||
{ "https://abcdefg.onion", true },
|
||||
{ "https://abcdefg.i2p", true },
|
||||
{ "http://кц.com", false },
|
||||
{ "кц.com", false },
|
||||
{ "http://asĸ.com", false },
|
||||
{ "http://foo.кц.рф", false },
|
||||
{ "https://abcdefg.onion", false },
|
||||
{ "https://abcdefg.i2p", false },
|
||||
{ "кц.рф\u202C", false },
|
||||
{ "кц.рф\u202D", 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.output = output;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isLegal() {
|
||||
assertEquals(output, LinkPreviewUtil.isLegalUrl(input));
|
||||
assertEquals(output, LinkUtil.isLegalUrl(input));
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue