diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/LinkUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/LinkUtil.kt index 142af786a9..3fdae29784 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/LinkUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/LinkUtil.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.util +import com.google.common.net.InetAddresses import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.thoughtcrime.securesms.stickers.StickerUrl import java.net.URI @@ -43,7 +44,23 @@ object LinkUtil { return false } - return linkUrl.toHttpUrlOrNull()?.scheme == "https" + val httpUrl = linkUrl.toHttpUrlOrNull() ?: return false + + if (httpUrl.scheme != "https") { + return false + } + + val host = httpUrl.host + + if (host.matches(INVALID_DOMAINS_REGEX)) { + return false + } + + if (isPrivateOrLocalHost(host)) { + return false + } + + return true } /** @@ -105,5 +122,26 @@ object LinkUtil { } } + private fun isPrivateOrLocalHost(host: String): Boolean { + if (!InetAddresses.isInetAddress(host)) { + return false + } + + val address = InetAddresses.forString(host) + + if (address.isAnyLocalAddress || + address.isLoopbackAddress || + address.isLinkLocalAddress || + address.isSiteLocalAddress || + address.isMulticastAddress + ) { + return true + } + + // IPv6 unique local addresses (fc00::/7) are not covered by the standard helpers above. + val bytes = address.address + return bytes.size == 16 && (bytes[0].toInt() and 0xfe) == 0xfc + } + private data class LegalCharactersResult(val isLegal: Boolean, val domain: String? = null) } diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/LinkUtilTest_isValidPreviewUrl.kt b/app/src/test/java/org/thoughtcrime/securesms/util/LinkUtilTest_isValidPreviewUrl.kt index fd5dd777fb..ab56645e28 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/util/LinkUtilTest_isValidPreviewUrl.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/util/LinkUtilTest_isValidPreviewUrl.kt @@ -52,7 +52,27 @@ class LinkUtilTest_isValidPreviewUrl(private val input: String, private val outp arrayOf("https://cool.invalid.com", true), arrayOf("https://cool.localhost.signal.org", true), arrayOf("https://cool.test.blarg.gov", true), - arrayOf("https://github.com/signalapp/Signal-Android/compare/v6.23.2...v6.23.3", true) + arrayOf("https://github.com/signalapp/Signal-Android/compare/v6.23.2...v6.23.3", true), + arrayOf("https://x@localhost/", false), + arrayOf("https://x@localhost", false), + arrayOf("https://user:pass@localhost/", false), + arrayOf("https://user@foo.google.com/", true), + arrayOf("https://127.0.0.1", false), + arrayOf("https://127.0.0.1/some/path", false), + arrayOf("https://127.1.2.3", false), + arrayOf("https://[::1]/", false), + arrayOf("https://x@127.0.0.1/", false), + arrayOf("https://0.0.0.0", false), + arrayOf("https://10.0.0.1", false), + arrayOf("https://172.16.0.1", false), + arrayOf("https://192.168.1.1", false), + arrayOf("https://169.254.1.1", false), + arrayOf("https://169.254.169.254", false), + arrayOf("https://[fe80::1]/", false), + arrayOf("https://[fc00::1]/", false), + arrayOf("https://[fd12:3456:789a::1]/", false), + arrayOf("https://8.8.8.8", true), + arrayOf("https://[2001:4860:4860::8888]/", true) ) } }