diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java
deleted file mode 100644
index 396a9d4d55..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java
+++ /dev/null
@@ -1,239 +0,0 @@
-package org.thoughtcrime.securesms.linkpreview;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-import android.text.Html;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.style.URLSpan;
-import android.text.util.Linkify;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import okhttp3.HttpUrl;
-import org.session.libsession.utilities.Util;
-import org.session.libsignal.utilities.Log;
-
-public final class LinkPreviewUtil {
-
- private static final Pattern DOMAIN_PATTERN = Pattern.compile("^(https?://)?([^/]+).*$", Pattern.CASE_INSENSITIVE);
- private static final Pattern ALL_ASCII_PATTERN = Pattern.compile("^[\\x00-\\x7F]*$", Pattern.CASE_INSENSITIVE);
- private static final Pattern ALL_NON_ASCII_PATTERN = Pattern.compile("^[^\\x00-\\x7F]*$", Pattern.CASE_INSENSITIVE);
- private static final Pattern OPEN_GRAPH_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*og:([^\"]+)\"[^>]*/?\\s*>", Pattern.CASE_INSENSITIVE);
- private static final Pattern ARTICLE_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*article:([^\"]+)\"[^>]*/?\\s*>", Pattern.CASE_INSENSITIVE);
- private static final Pattern OPEN_GRAPH_CONTENT_PATTERN = Pattern.compile("content\\s*=\\s*\"([^\"]*)\"", Pattern.CASE_INSENSITIVE);
- private static final Pattern TITLE_PATTERN = Pattern.compile("<\\s*title[^>]*>(.*)<\\s*/title[^>]*>", Pattern.CASE_INSENSITIVE);
- private static final Pattern FAVICON_PATTERN = Pattern.compile("<\\s*link[^>]*rel\\s*=\\s*\".*icon.*\"[^>]*>", Pattern.CASE_INSENSITIVE);
- private static final Pattern FAVICON_HREF_PATTERN = Pattern.compile("href\\s*=\\s*\"([^\"]*)\"", Pattern.CASE_INSENSITIVE);
-
- /**
- * @return All whitelisted URLs in the source text.
- */
- public static @NonNull List findWhitelistedUrls(@NonNull String text) {
- SpannableString spannable = new SpannableString(text);
- boolean found = Linkify.addLinks(spannable, Linkify.WEB_URLS);
-
- if (!found) {
- return Collections.emptyList();
- }
-
- URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
- List links = new java.util.ArrayList<>(spans.length);
-
- for (URLSpan span : spans) {
- Link link = new Link(span.getURL(), spannable.getSpanStart(span));
- if (isValidLinkUrl(link.getUrl())) {
- links.add(link);
- }
- }
-
- return links;
- }
-
- /**
- * @return True if the host is valid.
- */
- public static boolean isValidLinkUrl(@Nullable String linkUrl) {
- if (linkUrl == null) return false;
-
- HttpUrl url = HttpUrl.parse(linkUrl);
- return url != null &&
- !TextUtils.isEmpty(url.scheme()) &&
- "https".equals(url.scheme()) &&
- isLegalUrl(linkUrl);
- }
-
- /**
- * @return True if the top-level domain is valid.
- */
- public static boolean isValidMediaUrl(@Nullable String mediaUrl) {
- if (mediaUrl == null) return false;
-
- HttpUrl url = HttpUrl.parse(mediaUrl);
- return url != null &&
- !TextUtils.isEmpty(url.scheme()) &&
- "https".equals(url.scheme()) &&
- isLegalUrl(mediaUrl);
- }
-
- public static boolean isLegalUrl(@NonNull String url) {
- Matcher matcher = DOMAIN_PATTERN.matcher(url);
-
- if (matcher.matches()) {
- String domain = matcher.group(2);
- String cleanedDomain = domain.replaceAll("\\.", "");
-
- return ALL_ASCII_PATTERN.matcher(cleanedDomain).matches() ||
- ALL_NON_ASCII_PATTERN.matcher(cleanedDomain).matches();
- } else {
- return false;
- }
- }
-
- public static boolean isValidMimeType(@NonNull String url) {
- String[] validMimeType = {".jpg", ".png", ".gif", ".jpeg"};
- if (url.contains(".")) {
- for (String mimeType : validMimeType) {
- if (url.contains(mimeType)) {
- return true;
- }
- }
- return false;
- }
- return true;
- }
-
- public static @NonNull OpenGraph parseOpenGraphFields(@Nullable String html) {
- return parseOpenGraphFields(html, text -> Html.fromHtml(text).toString());
- }
-
- static @NonNull OpenGraph parseOpenGraphFields(@Nullable String html, @NonNull HtmlDecoder htmlDecoder) {
- if (html == null) {
- return new OpenGraph(Collections.emptyMap(), null, null);
- }
-
- Map openGraphTags = new HashMap<>();
- Matcher openGraphMatcher = OPEN_GRAPH_TAG_PATTERN.matcher(html);
-
- while (openGraphMatcher.find()) {
- String tag = openGraphMatcher.group();
- String property = openGraphMatcher.groupCount() > 0 ? openGraphMatcher.group(1) : null;
-
- if (property != null) {
- Matcher contentMatcher = OPEN_GRAPH_CONTENT_PATTERN.matcher(tag);
- if (contentMatcher.find() && contentMatcher.groupCount() > 0) {
- String content = htmlDecoder.fromEncoded(contentMatcher.group(1));
- openGraphTags.put(property.toLowerCase(), content);
- }
- }
- }
-
- Matcher articleMatcher = ARTICLE_TAG_PATTERN.matcher(html);
-
- while (articleMatcher.find()) {
- String tag = articleMatcher.group();
- String property = articleMatcher.groupCount() > 0 ? articleMatcher.group(1) : null;
-
- if (property != null) {
- Matcher contentMatcher = OPEN_GRAPH_CONTENT_PATTERN.matcher(tag);
- if (contentMatcher.find() && contentMatcher.groupCount() > 0) {
- String content = htmlDecoder.fromEncoded(contentMatcher.group(1));
- openGraphTags.put(property.toLowerCase(), content);
- }
- }
- }
-
- String htmlTitle = "";
- String faviconUrl = "";
-
- Matcher titleMatcher = TITLE_PATTERN.matcher(html);
- if (titleMatcher.find() && titleMatcher.groupCount() > 0) {
- htmlTitle = htmlDecoder.fromEncoded(titleMatcher.group(1));
- }
-
- Matcher faviconMatcher = FAVICON_PATTERN.matcher(html);
- if (faviconMatcher.find()) {
- Matcher faviconHrefMatcher = FAVICON_HREF_PATTERN.matcher(faviconMatcher.group());
- if (faviconHrefMatcher.find() && faviconHrefMatcher.groupCount() > 0) {
- faviconUrl = faviconHrefMatcher.group(1);
- }
- }
-
- return new OpenGraph(openGraphTags, htmlTitle, faviconUrl);
- }
-
- public static final class OpenGraph {
-
- private final Map values;
-
- private final @Nullable String htmlTitle;
- private final @Nullable String faviconUrl;
-
- private static final String KEY_TITLE = "title";
- private static final String KEY_DESCRIPTION_URL = "description";
- private static final String KEY_IMAGE_URL = "image";
- private static final String KEY_PUBLISHED_TIME_1 = "published_time";
- private static final String KEY_PUBLISHED_TIME_2 = "article:published_time";
- private static final String KEY_MODIFIED_TIME_1 = "modified_time";
- private static final String KEY_MODIFIED_TIME_2 = "article:modified_time";
-
- public OpenGraph(@NonNull Map values, @Nullable String htmlTitle, @Nullable String faviconUrl) {
- this.values = values;
- this.htmlTitle = htmlTitle;
- this.faviconUrl = faviconUrl;
- }
-
- public String getTitle() {
- return Util.getFirstNonEmpty(values.get(KEY_TITLE), htmlTitle);
- }
-
- public String getImageUrl() {
- return Util.getFirstNonEmpty(values.get(KEY_IMAGE_URL), faviconUrl);
- }
-
- private static long parseISO8601(String date) {
-
- if (date == null || date.isEmpty()) { return -1L; }
-
- SimpleDateFormat format;
- format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
-
- try {
- return format.parse(date).getTime();
- } catch (ParseException pe) {
- Log.w("OpenGraph", "Failed to parse date.", pe);
- return -1L;
- }
- }
-
- @SuppressLint("ObsoleteSdkInt")
- public long getDate() {
- String[] candidates = new String[] {
- values.get(KEY_PUBLISHED_TIME_1),
- values.get(KEY_PUBLISHED_TIME_2),
- values.get(KEY_MODIFIED_TIME_1),
- values.get(KEY_MODIFIED_TIME_2)
- };
-
- for (String c : candidates) {
- long t = parseISO8601(c);
- if (t > 0) return t;
- }
-
- return 0L;
- }
- }
-
- public interface HtmlDecoder {
- @NonNull String fromEncoded(@NonNull String html);
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.kt
new file mode 100644
index 0000000000..2cba231222
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.kt
@@ -0,0 +1,209 @@
+package org.thoughtcrime.securesms.linkpreview
+
+import android.annotation.SuppressLint
+import android.text.SpannableString
+import android.text.style.URLSpan
+import androidx.core.text.HtmlCompat
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import org.session.libsession.utilities.Util
+import org.session.libsignal.utilities.Log
+import org.thoughtcrime.securesms.conversation.v2.Util.addUrlSpansWithAutolink
+import java.net.URI
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+object LinkPreviewUtil {
+
+ private val DOMAIN_PATTERN = Regex("^(https?://)?([^/]+).*$", RegexOption.IGNORE_CASE)
+ private val ALL_ASCII_PATTERN = Regex("^[\\x00-\\x7F]*$", RegexOption.IGNORE_CASE)
+ private val ALL_NON_ASCII_PATTERN = Regex("^[^\\x00-\\x7F]*$", RegexOption.IGNORE_CASE)
+
+ private val OPEN_GRAPH_TAG_PATTERN = Regex(
+ "<\\s*meta[^>]*property\\s*=\\s*\"\\s*og:([^\"]+)\"[^>]*/?\\s*>",
+ RegexOption.IGNORE_CASE
+ )
+ private val ARTICLE_TAG_PATTERN = Regex(
+ "<\\s*meta[^>]*property\\s*=\\s*\"\\s*article:([^\"]+)\"[^>]*/?\\s*>",
+ RegexOption.IGNORE_CASE
+ )
+ private val OPEN_GRAPH_CONTENT_PATTERN =
+ Regex("content\\s*=\\s*\"([^\"]*)\"", RegexOption.IGNORE_CASE)
+ private val TITLE_PATTERN =
+ Regex("<\\s*title[^>]*>(.*)<\\s*/title[^>]*>", RegexOption.IGNORE_CASE)
+ private val FAVICON_PATTERN =
+ Regex("<\\s*link[^>]*rel\\s*=\\s*\".*icon.*\"[^>]*>", RegexOption.IGNORE_CASE)
+ private val FAVICON_HREF_PATTERN = Regex("href\\s*=\\s*\"([^\"]*)\"", RegexOption.IGNORE_CASE)
+
+ /**
+ * @return All whitelisted URLs in the source text.
+ */
+ fun findWhitelistedUrls(text: String): List {
+ val spannable = SpannableString(text)
+ spannable.addUrlSpansWithAutolink()
+
+ val spans = spannable.getSpans(0, spannable.length, URLSpan::class.java)
+ val links = ArrayList(spans.size)
+
+ for (span in spans) {
+ val link = Link(span.url, spannable.getSpanStart(span))
+ if (isValidLinkUrl(link.url)) {
+ links.add(link)
+ }
+ }
+
+ return links
+ }
+
+ /**
+ * @return True if the host is valid.
+ */
+ fun isValidLinkUrl(linkUrl: String?): Boolean {
+ if (linkUrl.isNullOrEmpty()) return false
+
+ val url = linkUrl.toHttpUrlOrNull() ?: return false
+ return url.scheme == "https" && isLegalUrl(linkUrl)
+ }
+
+ /**
+ * @return True if the top-level domain is valid.
+ */
+ fun isValidMediaUrl(mediaUrl: String?): Boolean {
+ if (mediaUrl.isNullOrEmpty()) return false
+
+ val url = mediaUrl.toHttpUrlOrNull() ?: return false
+ return url.scheme == "https" && isLegalUrl(mediaUrl)
+ }
+
+ fun isLegalUrl(url: String): Boolean {
+ val match = DOMAIN_PATTERN.matchEntire(url) ?: return false
+ val domain = match.groupValues.getOrNull(2) ?: return false
+ val cleanedDomain = domain.replace(".", "")
+
+ return ALL_ASCII_PATTERN.matches(cleanedDomain) || ALL_NON_ASCII_PATTERN.matches(
+ cleanedDomain
+ )
+ }
+
+ fun isValidMimeType(url: String): Boolean {
+ val path = try {
+ URI(url).path.lowercase(Locale.ROOT)
+ } catch (e: Exception) {
+ return false
+ }
+
+ val validExtensions = arrayOf(".jpg", ".png", ".gif", ".jpeg")
+
+ if (!path.contains('.')) return true
+
+ return validExtensions.any { path.endsWith(it) }
+ }
+
+ fun parseOpenGraphFields(html: String?): OpenGraph {
+ return parseOpenGraphFields(html) { encoded ->
+ HtmlCompat.fromHtml(encoded, HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
+ }
+ }
+
+ internal fun parseOpenGraphFields(html: String?, htmlDecoder: HtmlDecoder): OpenGraph {
+ if (html == null) {
+ return OpenGraph(emptyMap(), null, null)
+ }
+
+ val openGraphTags = HashMap()
+
+ fun extractTags(tagRegex: Regex) {
+ tagRegex.findAll(html).forEach { match ->
+ val fullTag = match.value
+ val property = match.groupValues.getOrNull(1)
+ if (!property.isNullOrEmpty()) {
+ val contentMatch = OPEN_GRAPH_CONTENT_PATTERN.find(fullTag)
+ val content = contentMatch?.groupValues?.getOrNull(1)
+ if (content != null) {
+ val decoded = htmlDecoder.fromEncoded(content)
+ // Store the lower-cased property name.
+ openGraphTags[property.lowercase()] = decoded
+ }
+ }
+ }
+ }
+
+ extractTags(OPEN_GRAPH_TAG_PATTERN)
+ extractTags(ARTICLE_TAG_PATTERN)
+
+ var htmlTitle = ""
+ var faviconUrl = ""
+
+ TITLE_PATTERN.find(html)?.let { titleMatch ->
+ val title = titleMatch.groupValues.getOrNull(1)
+ if (title != null) {
+ htmlTitle = htmlDecoder.fromEncoded(title)
+ }
+ }
+
+ FAVICON_PATTERN.find(html)?.let { faviconTagMatch ->
+ val hrefMatch = FAVICON_HREF_PATTERN.find(faviconTagMatch.value)
+ val href = hrefMatch?.groupValues?.getOrNull(1)
+ if (href != null) {
+ faviconUrl = href
+ }
+ }
+
+ return OpenGraph(openGraphTags, htmlTitle, faviconUrl)
+ }
+
+ class OpenGraph(
+ private val values: Map,
+ private val htmlTitle: String?,
+ private val faviconUrl: String?
+ ) {
+
+ companion object {
+ private const val KEY_TITLE = "title"
+ private const val KEY_IMAGE_URL = "image"
+ private const val KEY_PUBLISHED_TIME_1 = "published_time"
+ private const val KEY_PUBLISHED_TIME_2 = "article:published_time"
+ private const val KEY_MODIFIED_TIME_1 = "modified_time"
+ private const val KEY_MODIFIED_TIME_2 = "article:modified_time"
+ }
+
+ val title: String?
+ get() = Util.getFirstNonEmpty(values[KEY_TITLE], htmlTitle)
+
+ val imageUrl: String?
+ get() = Util.getFirstNonEmpty(values[KEY_IMAGE_URL], faviconUrl)
+
+ private fun parseISO8601(date: String?): Long {
+ if (date.isNullOrEmpty()) return -1L
+
+ val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault())
+ return try {
+ format.parse(date)?.time ?: -1L
+ } catch (pe: ParseException) {
+ Log.w("OpenGraph", "Failed to parse date.", pe)
+ -1L
+ }
+ }
+
+ @SuppressLint("ObsoleteSdkInt")
+ fun getDate(): Long {
+ val candidates = arrayOf(
+ values[KEY_PUBLISHED_TIME_1],
+ values[KEY_PUBLISHED_TIME_2],
+ values[KEY_MODIFIED_TIME_1],
+ values[KEY_MODIFIED_TIME_2]
+ )
+
+ for (c in candidates) {
+ val t = parseISO8601(c)
+ if (t > 0) return t
+ }
+
+ return 0L
+ }
+ }
+
+ fun interface HtmlDecoder {
+ fun fromEncoded(html: String): String
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/net/ContentProxySafetyInterceptor.java b/app/src/main/java/org/thoughtcrime/securesms/net/ContentProxySafetyInterceptor.java
deleted file mode 100644
index 983a3374ef..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/net/ContentProxySafetyInterceptor.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.thoughtcrime.securesms.net;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
-import org.session.libsignal.utilities.Log;
-
-import java.io.IOException;
-
-import okhttp3.HttpUrl;
-import okhttp3.Interceptor;
-import okhttp3.Response;
-
-/**
- * Interceptor to do extra safety checks on requests through the {@link ContentProxySelector}
- * to prevent non-whitelisted requests from getting to it. In particular, this guards against
- * requests redirecting to non-whitelisted domains.
- *
- * Note that because of the way interceptors are ordered, OkHttp will hit the proxy with the
- * bad-redirected-domain before we can intercept the request, so we have to "look ahead" by
- * detecting a redirected response on the first pass.
- */
-public class ContentProxySafetyInterceptor implements Interceptor {
-
- private static final String TAG = Log.tag(ContentProxySafetyInterceptor.class);
-
- @Override
- public @NonNull Response intercept(@NonNull Chain chain) throws IOException {
- if (isWhitelisted(chain.request().url())) {
- Response response = chain.proceed(chain.request());
-
- if (response.isRedirect()) {
- if (isWhitelisted(response.header("location")) || isWhitelisted(response.header("Location"))) {
- return response;
- } else {
- Log.w(TAG, "Tried to redirect to a non-whitelisted domain!");
- chain.call().cancel();
- throw new IOException("Tried to redirect to a non-whitelisted domain!");
- }
- } else {
- return response;
- }
- } else {
- Log.w(TAG, "Request was for a non-whitelisted domain!");
- chain.call().cancel();
- throw new IOException("Request was for a non-whitelisted domain!");
- }
- }
-
- private static boolean isWhitelisted(@NonNull HttpUrl url) {
- return isWhitelisted(url.toString());
- }
-
- private static boolean isWhitelisted(@Nullable String url) {
- return LinkPreviewUtil.isValidLinkUrl(url) || LinkPreviewUtil.isValidMediaUrl(url);
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/net/ContentProxySafetyInterceptor.kt b/app/src/main/java/org/thoughtcrime/securesms/net/ContentProxySafetyInterceptor.kt
new file mode 100644
index 0000000000..5857964a32
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/net/ContentProxySafetyInterceptor.kt
@@ -0,0 +1,55 @@
+package org.thoughtcrime.securesms.net
+
+import okhttp3.HttpUrl
+import okhttp3.Interceptor
+import okhttp3.Response
+import org.session.libsignal.utilities.Log
+import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil.isValidLinkUrl
+import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil.isValidMediaUrl
+import java.io.IOException
+
+/**
+ * Interceptor to do extra safety checks on requests through the [ContentProxySelector]
+ * to prevent non-whitelisted requests from getting to it. In particular, this guards against
+ * requests redirecting to non-whitelisted domains.
+ *
+ * Note that because of the way interceptors are ordered, OkHttp will hit the proxy with the
+ * bad-redirected-domain before we can intercept the request, so we have to "look ahead" by
+ * detecting a redirected response on the first pass.
+ */
+class ContentProxySafetyInterceptor : Interceptor {
+ @Throws(IOException::class)
+ override fun intercept(chain: Interceptor.Chain): Response {
+ if (isWhitelisted(chain.request().url)) {
+ val response = chain.proceed(chain.request())
+
+ if (response.isRedirect) {
+ if (isWhitelisted(response.header("location")) || isWhitelisted(response.header("Location"))) {
+ return response
+ } else {
+ Log.w(TAG, "Tried to redirect to a non-whitelisted domain!")
+ chain.call().cancel()
+ throw IOException("Tried to redirect to a non-whitelisted domain!")
+ }
+ } else {
+ return response
+ }
+ } else {
+ Log.w(TAG, "Request was for a non-whitelisted domain!")
+ chain.call().cancel()
+ throw IOException("Request was for a non-whitelisted domain!")
+ }
+ }
+
+ companion object {
+ private val TAG: String = Log.tag(ContentProxySafetyInterceptor::class.java)
+
+ private fun isWhitelisted(url: HttpUrl): Boolean {
+ return isWhitelisted(url.toString())
+ }
+
+ private fun isWhitelisted(url: String?): Boolean {
+ return isValidLinkUrl(url) || isValidMediaUrl(url)
+ }
+ }
+}
diff --git a/app/src/test/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtilTest.java b/app/src/test/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtilTest.java
deleted file mode 100644
index e45d5b3897..0000000000
--- a/app/src/test/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtilTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.thoughtcrime.securesms.linkpreview;
-
-import org.junit.Test;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
-
-public class LinkPreviewUtilTest {
-
- @Test
- public void isLegal_allAscii_noProtocol() {
- assertTrue(LinkPreviewUtil.isLegalUrl("google.com"));
- }
-
- @Test
- public void isLegal_allAscii_noProtocol_subdomain() {
- assertTrue(LinkPreviewUtil.isLegalUrl("foo.google.com"));
- }
-
- @Test
- public void isLegal_allAscii_subdomain() {
- assertTrue(LinkPreviewUtil.isLegalUrl("https://foo.google.com"));
- }
-
- @Test
- public void isLegal_allAscii_subdomain_path() {
- assertTrue(LinkPreviewUtil.isLegalUrl("https://foo.google.com/some/path.html"));
- }
-
- @Test
- public void isLegal_cyrillicHostAsciiTld() {
- assertFalse(LinkPreviewUtil.isLegalUrl("http://кц.com"));
- }
-
- @Test
- public void isLegal_cyrillicHostAsciiTld_noProtocol() {
- assertFalse(LinkPreviewUtil.isLegalUrl("кц.com"));
- }
-
- @Test
- public void isLegal_mixedHost_noProtocol() {
- assertFalse(LinkPreviewUtil.isLegalUrl("http://asĸ.com"));
- }
-
- @Test
- public void isLegal_cyrillicHostAndTld_noProtocol() {
- assertTrue(LinkPreviewUtil.isLegalUrl("кц.рф"));
- }
-
- @Test
- public void isLegal_cyrillicHostAndTld_asciiPath_noProtocol() {
- assertTrue(LinkPreviewUtil.isLegalUrl("кц.рф/some/path"));
- }
-
- @Test
- public void isLegal_cyrillicHostAndTld_asciiPath() {
- assertTrue(LinkPreviewUtil.isLegalUrl("https://кц.рф/some/path"));
- }
-
- @Test
- public void isLegal_asciiSubdomain_cyrillicHostAndTld() {
- assertFalse(LinkPreviewUtil.isLegalUrl("http://foo.кц.рф"));
- }
-
- @Test
- public void isLegal_emptyUrl() {
- assertFalse(LinkPreviewUtil.isLegalUrl(""));
- }
-}
diff --git a/app/src/test/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtilTest.kt b/app/src/test/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtilTest.kt
new file mode 100644
index 0000000000..965b57b68f
--- /dev/null
+++ b/app/src/test/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtilTest.kt
@@ -0,0 +1,67 @@
+package org.thoughtcrime.securesms.linkpreview
+
+import junit.framework.TestCase
+import org.junit.Test
+import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil.isLegalUrl
+
+class LinkPreviewUtilTest {
+ @Test
+ fun isLegal_allAscii_noProtocol() {
+ TestCase.assertTrue(isLegalUrl("google.com"))
+ }
+
+ @Test
+ fun isLegal_allAscii_noProtocol_subdomain() {
+ TestCase.assertTrue(isLegalUrl("foo.google.com"))
+ }
+
+ @Test
+ fun isLegal_allAscii_subdomain() {
+ TestCase.assertTrue(isLegalUrl("https://foo.google.com"))
+ }
+
+ @Test
+ fun isLegal_allAscii_subdomain_path() {
+ TestCase.assertTrue(isLegalUrl("https://foo.google.com/some/path.html"))
+ }
+
+ @Test
+ fun isLegal_cyrillicHostAsciiTld() {
+ TestCase.assertFalse(isLegalUrl("http://кц.com"))
+ }
+
+ @Test
+ fun isLegal_cyrillicHostAsciiTld_noProtocol() {
+ TestCase.assertFalse(isLegalUrl("кц.com"))
+ }
+
+ @Test
+ fun isLegal_mixedHost_noProtocol() {
+ TestCase.assertFalse(isLegalUrl("http://asĸ.com"))
+ }
+
+ @Test
+ fun isLegal_cyrillicHostAndTld_noProtocol() {
+ TestCase.assertTrue(isLegalUrl("кц.рф"))
+ }
+
+ @Test
+ fun isLegal_cyrillicHostAndTld_asciiPath_noProtocol() {
+ TestCase.assertTrue(isLegalUrl("кц.рф/some/path"))
+ }
+
+ @Test
+ fun isLegal_cyrillicHostAndTld_asciiPath() {
+ TestCase.assertTrue(isLegalUrl("https://кц.рф/some/path"))
+ }
+
+ @Test
+ fun isLegal_asciiSubdomain_cyrillicHostAndTld() {
+ TestCase.assertFalse(isLegalUrl("http://foo.кц.рф"))
+ }
+
+ @Test
+ fun isLegal_emptyUrl() {
+ TestCase.assertFalse(isLegalUrl(""))
+ }
+}