diff --git a/GenericApp/app/build.gradle b/GenericApp/app/build.gradle index 52886d0..1fd0ec2 100644 --- a/GenericApp/app/build.gradle +++ b/GenericApp/app/build.gradle @@ -74,6 +74,9 @@ dependencies { implementation 'com.google.firebase:firebase-messaging-ktx' implementation 'com.google.android.material:material:1.12.0' implementation project(':ORLib') + + // Unit testing framework + testImplementation 'junit:junit:4.13.2' } task sourcesJar(type: Jar) { diff --git a/GenericApp/app/src/main/java/io/openremote/app/ui/HostSelectionFragment.kt b/GenericApp/app/src/main/java/io/openremote/app/ui/HostSelectionFragment.kt index 5c4141f..1331c73 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/ui/HostSelectionFragment.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/ui/HostSelectionFragment.kt @@ -51,12 +51,7 @@ class HostSelectionFragment : Fragment() { private fun connectToHost(host: String) { parentActivity.binding.progressBar.visibility = View.VISIBLE - val url = when { - URLUtil.isValidUrl(host) -> host.plus("/api/master") - UrlUtils.isIpAddress(host) -> "https://${host}/api/master" - !UrlUtils.startsWithHttp(host) && UrlUtils.endsWithTld(host) -> "https://${host}/api/master" - else -> "https://${host}.openremote.app/api/master" - } + val url = UrlUtils.hostToUrl(host).plus("/api/master") parentActivity.apiManager = ApiManager(url) parentActivity.apiManager.getConsoleConfig { statusCode, consoleConfig, error -> when (statusCode) { diff --git a/GenericApp/app/src/main/java/io/openremote/app/util/UrlUtils.kt b/GenericApp/app/src/main/java/io/openremote/app/util/UrlUtils.kt index 7d8f1e2..86fac3c 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/util/UrlUtils.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/util/UrlUtils.kt @@ -1,20 +1,25 @@ package io.openremote.app.util object UrlUtils { - fun isIpAddress(url: String): Boolean { - val ipPattern = Regex( - "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:\\d{1,5})?$" + fun isIpV6NoScheme(url: String): Boolean { + val ipv6Pattern = Regex( + "^(?:([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([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}))$" ) - return ipPattern.matches(url) + return ipv6Pattern.matches(url) } - fun startsWithHttp(url: String): Boolean { - return url.startsWith("http://") || url.startsWith("https://") + + fun startsWithScheme(url: String): Boolean { + val schemePattern = Regex("^[a-zA-Z]+://.*$") + return schemePattern.matches(url) } - fun endsWithTld(url: String): Boolean { - val tldPattern = Regex( - "(?:[a-zA-Z]*\\.)+([a-zA-Z]+)(?:\\/.*)?" - ) - return tldPattern.matches(url) + fun hostToUrl(host: String): String { + return when { + isIpV6NoScheme(host) -> "https://[${host}]" + startsWithScheme(host) -> + if (host.contains(".") || host.contains("[")) host else "${host}.openremote.app" + (host.contains(".") || host.contains("[")) -> "https://${host}" + else -> "https://${host}.openremote.app" + } } } \ No newline at end of file diff --git a/GenericApp/app/src/test/java/io/openremote/app/util/UrlUtilsTest.kt b/GenericApp/app/src/test/java/io/openremote/app/util/UrlUtilsTest.kt new file mode 100644 index 0000000..e4488b7 --- /dev/null +++ b/GenericApp/app/src/test/java/io/openremote/app/util/UrlUtilsTest.kt @@ -0,0 +1,130 @@ +import io.openremote.app.util.UrlUtils +import org.junit.Test +import org.junit.Assert.assertEquals + +class UrlUtilsTest { + + @Test + fun fqdnWithScheme() { + assertEquals("http://www.example.com", UrlUtils.hostToUrl("http://www.example.com")) + assertEquals("https://www.example.com", UrlUtils.hostToUrl("https://www.example.com")) + } + + @Test + fun fqdnWithNonWebScheme() { + assertEquals("ftp://www.example.com", UrlUtils.hostToUrl("ftp://www.example.com")) + } + + @Test + fun fqdnNoScheme() { + assertEquals("https://www.example.com", UrlUtils.hostToUrl("www.example.com")) + } + + @Test + fun fqdnAndPortWithScheme() { + assertEquals("http://www.example.com:8080", UrlUtils.hostToUrl("http://www.example.com:8080")) + assertEquals("https://www.example.com:443", UrlUtils.hostToUrl("https://www.example.com:443")) + } + + @Test + fun fqdnAndPortWithNonWebScheme() { + assertEquals("ftp://www.example.com:21", UrlUtils.hostToUrl("ftp://www.example.com:21")) + } + + @Test + fun fqdnAndPortNoScheme() { + assertEquals("https://www.example.com:8080", UrlUtils.hostToUrl("www.example.com:8080")) + } + + @Test + fun hostnameNoScheme() { + assertEquals("https://example.openremote.app", UrlUtils.hostToUrl("example")) + } + + @Test + fun ipAddressWithScheme() { + assertEquals("http://192.168.1.1", UrlUtils.hostToUrl("http://192.168.1.1")) + } + + @Test + fun ipAddressWithNonWebScheme() { + assertEquals("ftp://192.168.1.1", UrlUtils.hostToUrl("ftp://192.168.1.1")) + } + + @Test + fun ipAddressAndPortWithScheme() { + assertEquals("http://192.168.1.1:8080", UrlUtils.hostToUrl("http://192.168.1.1:8080")) + } + + @Test + fun ipAddressAndPortWithNonWebScheme() { + assertEquals("ftp://192.168.1.1:25", UrlUtils.hostToUrl("ftp://192.168.1.1:25")) + } + + @Test + fun ipAddressAndInvalidPortWithScheme() { + assertEquals("http://192.168.1.1:InvalidPort", UrlUtils.hostToUrl("http://192.168.1.1:InvalidPort")) + } + + @Test + fun ipAddressNoScheme() { + assertEquals("https://192.168.1.1", UrlUtils.hostToUrl("192.168.1.1")) + } + + @Test + fun ipAddressAndPortNoScheme() { + assertEquals("https://192.168.1.1:8080", UrlUtils.hostToUrl("192.168.1.1:8080")) + } + + @Test + fun ipv6AddressWithScheme() { + assertEquals("http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", + UrlUtils.hostToUrl("http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]")) + } + + @Test + fun ipv6AddressAndPortWithScheme() { + assertEquals("http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080", + UrlUtils.hostToUrl("http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080")) + } + + @Test + fun ipv6AddressNoScheme() { + assertEquals("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", + UrlUtils.hostToUrl("2001:0db8:85a3:0000:0000:8a2e:0370:7334")) + assertEquals("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", + UrlUtils.hostToUrl("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]")) + } + + @Test + fun ipv6AddressAndPortNoScheme() { + assertEquals("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080", + UrlUtils.hostToUrl("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080")) + } + + @Test + fun ipv6CompressedAddressWithScheme() { + assertEquals("http://[2001:db8:85a3::8a2e:370:7334]", + UrlUtils.hostToUrl("http://[2001:db8:85a3::8a2e:370:7334]")) + } + + @Test + fun ipv6CompressedAddressAndPortWithScheme() { + assertEquals("http://[2001:db8:85a3::8a2e:370:7334]:8080", + UrlUtils.hostToUrl("http://[2001:db8:85a3::8a2e:370:7334]:8080")) + } + + @Test + fun ipv6CompressedAddressNoScheme() { + assertEquals("https://[2001:db8:85a3::8a2e:370:7334]", + UrlUtils.hostToUrl("2001:db8:85a3::8a2e:370:7334")) + assertEquals("https://[2001:db8:85a3::8a2e:370:7334]", + UrlUtils.hostToUrl("[2001:db8:85a3::8a2e:370:7334]")) + } + + @Test + fun ipv6CompressedAddressAndPortNoScheme() { + assertEquals("https://[2001:db8:85a3::8a2e:370:7334]:8080", + UrlUtils.hostToUrl("[2001:db8:85a3::8a2e:370:7334]:8080")) + } +} \ No newline at end of file