diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/util/StringExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/util/StringExtensions.kt index 909cb2ef5..cfb84dbb8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/util/StringExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/util/StringExtensions.kt @@ -56,6 +56,7 @@ fun String.getWebHostFromAndroidUriOrNull(): String? = fun String.getDomainOrNull(resourceCacheManager: ResourceCacheManager): String? = this .toUriOrNull() + ?.addSchemeToUriIfNecessary() ?.parseDomainOrNull(resourceCacheManager = resourceCacheManager) /** @@ -63,7 +64,10 @@ fun String.getDomainOrNull(resourceCacheManager: ResourceCacheManager): String? */ @OmitFromCoverage fun String.hasPort(): Boolean { - val uri = this.toUriOrNull() ?: return false + val uri = this + .toUriOrNull() + ?.addSchemeToUriIfNecessary() + ?: return false return uri.port != -1 } @@ -71,14 +75,19 @@ fun String.hasPort(): Boolean { * Extract the host from this [String] if possible, otherwise return null. */ @OmitFromCoverage -fun String.getHostOrNull(): String? = this.toUriOrNull()?.host +fun String.getHostOrNull(): String? = this.toUriOrNull() + ?.addSchemeToUriIfNecessary() + ?.host /** * Extract the host with optional port from this [String] if possible, otherwise return null. */ @OmitFromCoverage fun String.getHostWithPortOrNull(): String? { - val uri = this.toUriOrNull() ?: return null + val uri = this + .toUriOrNull() + ?.addSchemeToUriIfNecessary() + ?: return null return uri.host?.let { host -> val port = uri.port if (port != -1) { diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/util/URIExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/util/URIExtensions.kt index 1d6647222..eb96af197 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/util/URIExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/util/URIExtensions.kt @@ -45,6 +45,27 @@ fun URI.parseDomainNameOrNull(resourceCacheManager: ResourceCacheManager): Domai ) } +/** + * Adds and HTTPS scheme to a valid URI if that URI has a valid host in the raw string but + * the standard parsing of `URI("foo.com")` is not able to determine a valid host. + * (i.e. val uri = URI("foo.bar:1090") -> uri.host == null) + */ +fun URI.addSchemeToUriIfNecessary(): URI { + val uriString = this.toString() + return if ( + // see if the string contains a host pattern + uriString.contains(".") && + // if it does but the URI's host is null, add an https scheme + this.host == null && + // provided that scheme does not exist already. + !uriString.hasHttpProtocol() + ) { + URI("https://$uriString") + } else { + this + } +} + /** * The internal implementation of [parseDomainNameOrNull]. This doesn't extend URI and has a * non-null [host] parameter. Technically, URI.host could be null and we want to avoid issues with diff --git a/app/src/test/java/com/x8bit/bitwarden/data/util/StringExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/util/StringExtensionsTest.kt index fa064d77c..b4055a0f4 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/util/StringExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/util/StringExtensionsTest.kt @@ -3,8 +3,11 @@ package com.x8bit.bitwarden.data.util import com.x8bit.bitwarden.data.platform.manager.ResourceCacheManager import com.x8bit.bitwarden.data.platform.util.findLastSubstringIndicesOrNull import com.x8bit.bitwarden.data.platform.util.getDomainOrNull +import com.x8bit.bitwarden.data.platform.util.getHostOrNull +import com.x8bit.bitwarden.data.platform.util.getHostWithPortOrNull import com.x8bit.bitwarden.data.platform.util.getWebHostFromAndroidUriOrNull import com.x8bit.bitwarden.data.platform.util.hasHttpProtocol +import com.x8bit.bitwarden.data.platform.util.hasPort import com.x8bit.bitwarden.data.platform.util.isAndroidApp import com.x8bit.bitwarden.data.platform.util.parseDomainOrNull import com.x8bit.bitwarden.data.platform.util.toUriOrNull @@ -154,4 +157,71 @@ class StringExtensionsTest { // Verify assertEquals(expected, actual) } + + @Test + fun `getHostOrNull should return host when one is present`() { + val expectedHost = "www.google.com" + assertEquals(expectedHost, expectedHost.getHostOrNull()) + } + + @Test + fun `getHostOrNull should return null when no host is present`() { + assertNull("boo".getHostOrNull()) + } + + @Test + fun `getHostOrNull should return host from URI string when present and custom URI scheme`() { + val expectedHost = "www.google.com" + val hostWithScheme = "androidapp://$expectedHost" + assertEquals(expectedHost, hostWithScheme.getHostOrNull()) + } + + @Suppress("MaxLineLength") + @Test + fun `getHostOrNull should return host from URI string when present and has port but no scheme`() { + val expectedHost = "www.google.com" + val hostWithPort = "$expectedHost:8080" + assertEquals(expectedHost, hostWithPort.getHostOrNull()) + } + + @Test + fun `hasPort returns true when port is present`() { + val uriString = "www.google.com:8080" + assertTrue("www.google.com:8080".hasPort()) + } + + @Test + fun `hasPort returns false when port is not present`() { + assertFalse("www.google.com".hasPort()) + } + + @Test + fun `hasPort return true when port is present and custom scheme is present`() { + val uriString = "androidapp://www.google.com:8080" + assertTrue(uriString.hasPort()) + } + + @Test + fun `getHostWithPortOrNull should return host with port when present`() { + val uriString = "www.google.com:8080" + assertEquals("www.google.com:8080", uriString.getHostWithPortOrNull()) + } + + @Test + fun `getHostWithPortOrNull should return host when no port is present`() { + val uriString = "www.google.com" + assertEquals("www.google.com", uriString.getHostWithPortOrNull()) + } + + @Test + fun `getHostWithPortOrNull should return null when no host is present`() { + assertNull("boo".getHostWithPortOrNull()) + } + + @Suppress("MaxLineLength") + @Test + fun `getHostWithPortOrNull should return host with port when present and custom scheme is present`() { + val uriString = "androidapp://www.google.com:8080" + assertEquals("www.google.com:8080", uriString.getHostWithPortOrNull()) + } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/util/UriExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/util/UriExtensionsTest.kt index 0c5090014..deaf3baf6 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/util/UriExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/util/UriExtensionsTest.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.util import com.x8bit.bitwarden.data.platform.manager.ResourceCacheManager import com.x8bit.bitwarden.data.platform.manager.model.DomainName +import com.x8bit.bitwarden.data.platform.util.addSchemeToUriIfNecessary import com.x8bit.bitwarden.data.platform.util.parseDomainNameOrNull import com.x8bit.bitwarden.data.platform.util.parseDomainOrNull import io.mockk.every @@ -321,4 +322,38 @@ class UriExtensionsTest { assertEquals(expected[index], actual) } } + + @Test + fun `addSchemeToUriIfNecessary should add https when missing`() { + val uriWithNoScheme = URI("example.com") + assertEquals(URI("https://example.com"), uriWithNoScheme.addSchemeToUriIfNecessary()) + } + + @Suppress("MaxLineLength") + @Test + fun `addSchemeToUriIfNecessary should add https when https scheme is missing and a port is present`() { + val uriWithPort = URI("example.com:8080") + assertEquals(URI("https://example.com:8080"), uriWithPort.addSchemeToUriIfNecessary()) + } + + @Test + fun `addSchemeToUriIfNecessary should not add https when http scheme is present`() { + val uriWithHttpScheme = URI("http://example.com") + assertEquals(URI("http://example.com"), uriWithHttpScheme.addSchemeToUriIfNecessary()) + } + + @Test + fun `addSchemeToUriIfNecessary should not add https when https scheme is present`() { + val uriWithHttpsScheme = URI("https://example.com") + assertEquals(URI("https://example.com"), uriWithHttpsScheme.addSchemeToUriIfNecessary()) + } + + @Test + fun `addSchemeToUriIfNecessary should not add https when custom scheme is already present`() { + val uriWithCustomScheme = URI("bitwarden://example.com") + assertEquals( + URI("bitwarden://example.com"), + uriWithCustomScheme.addSchemeToUriIfNecessary(), + ) + } }