diff --git a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt index f1c4291526..5992ccbc75 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt @@ -27,7 +27,7 @@ class CallIntentDataParser @Inject constructor() { val parsedUrl = data?.let { Uri.parse(data) } ?: return null val scheme = parsedUrl.scheme return when { - scheme in validHttpSchemes && parsedUrl.host == "call.element.io" -> data + scheme in validHttpSchemes && parsedUrl.host == "call.element.io" -> parsedUrl scheme == "element" && parsedUrl.host == "call" -> { // We use this custom scheme to load arbitrary URLs for other instances of Element Call, // so we can only verify it's an HTTP/HTTPs URL with a non-empty host @@ -40,14 +40,35 @@ class CallIntentDataParser @Inject constructor() { } // This should never be possible, but we still need to take into account the possibility else -> null - } + }?.withCustomParameters() } - private fun Uri.getUrlParameter(): String? { + private fun Uri.getUrlParameter(): Uri? { return getQueryParameter("url") - ?.takeIf { - val internalUri = Uri.parse(it) - internalUri.scheme in validHttpSchemes && !internalUri.host.isNullOrBlank() + ?.let { urlParameter -> + Uri.parse(urlParameter).takeIf { uri -> + uri.scheme in validHttpSchemes && !uri.host.isNullOrBlank() + } } } } + +/** + * Ensure the uri has the following parameters and value: + * - appPrompt=false + * - confineToRoom=true + */ +private fun Uri.withCustomParameters(): String { + val builder = buildUpon() + builder.clearQuery() + queryParameterNames.forEach { + if (it == APP_PROMPT_PARAMETER || it == CONFINE_TO_ROOM_PARAMETER) return@forEach + builder.appendQueryParameter(it, getQueryParameter(it)) + } + builder.appendQueryParameter(APP_PROMPT_PARAMETER, "false") + builder.appendQueryParameter(CONFINE_TO_ROOM_PARAMETER, "true") + return builder.build().toString() +} + +private const val APP_PROMPT_PARAMETER = "appPrompt" +private const val CONFINE_TO_ROOM_PARAMETER = "confineToRoom" diff --git a/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt b/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt index 4d5beef7e9..aee97ed982 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt @@ -62,9 +62,9 @@ class CallIntentDataParserTests { @Test fun `Element Call urls will be returned as is`() { val httpsBaseUrl = "https://call.element.io" - val httpsCallUrl = "https://call.element.io/some-actual-call?with=parameters" - assertThat(callIntentDataParser.parse(httpsBaseUrl)).isEqualTo(httpsBaseUrl) - assertThat(callIntentDataParser.parse(httpsCallUrl)).isEqualTo(httpsCallUrl) + val httpsCallUrl = VALID_CALL_URL_WITH_PARAM + assertThat(callIntentDataParser.parse(httpsBaseUrl)).isEqualTo("$httpsBaseUrl?$EXTRA_PARAMS") + assertThat(callIntentDataParser.parse(httpsCallUrl)).isEqualTo("$httpsCallUrl&$EXTRA_PARAMS") } @Test @@ -89,10 +89,10 @@ class CallIntentDataParserTests { @Test fun `element scheme with call host and url param gets url extracted`() { - val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "element://call?url=$encodedUrl" - assertThat(callIntentDataParser.parse(url)).isEqualTo(embeddedUrl) + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") } @Test @@ -105,10 +105,10 @@ class CallIntentDataParserTests { @Test fun `element scheme 2 with url param gets url extracted`() { - val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "io.element.call:/?url=$encodedUrl" - assertThat(callIntentDataParser.parse(url)).isEqualTo(embeddedUrl) + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") } @Test @@ -121,7 +121,7 @@ class CallIntentDataParserTests { @Test fun `element scheme 2 with no url returns null`() { - val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "io.element.call:/?no_url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() @@ -129,7 +129,7 @@ class CallIntentDataParserTests { @Test fun `element scheme with no call host returns null`() { - val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "element://no-call?url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() @@ -149,9 +149,39 @@ class CallIntentDataParserTests { @Test fun `element invalid scheme returns null`() { - val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "bad.scheme:/?url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() } + + @Test + fun `element scheme 2 with url extra param appPrompt gets url extracted`() { + val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}&appPrompt=true" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") + } + + @Test + fun `element scheme 2 with url extra param confineToRoom gets url extracted`() { + val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}&confineToRoom=false" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") + } + + @Test + fun `element scheme 2 with url fragment gets url extracted`() { + val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}#fragment" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS#fragment") + } + + + companion object { + const val VALID_CALL_URL_WITH_PARAM = "https://call.element.io/some-actual-call?with=parameters" + const val EXTRA_PARAMS = "appPrompt=false&confineToRoom=true" + } }