warning the user when urls contain directional overrides and allowing them to confirm the url
This commit is contained in:
parent
fdaaed430e
commit
913c6b0f14
|
@ -0,0 +1 @@
|
||||||
|
Security - Asking for user confirmation when tapping URLs which contain unicode directional overrides
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.core.extensions
|
||||||
|
|
||||||
|
private const val RTL_OVERRIDE_CHAR = '\u202E'
|
||||||
|
private const val LTR_OVERRIDE_CHAR = '\u202D'
|
||||||
|
|
||||||
|
fun String.ensureEndsLeftToRight() = if (containsRtLOverride()) "$this$LTR_OVERRIDE_CHAR" else this
|
||||||
|
|
||||||
|
fun String.containsRtLOverride() = contains(RTL_OVERRIDE_CHAR)
|
||||||
|
|
||||||
|
fun String.filterDirectionOverrides() = filterNot { it == RTL_OVERRIDE_CHAR || it == LTR_OVERRIDE_CHAR }
|
|
@ -74,6 +74,9 @@ import im.vector.app.core.dialogs.ConfirmationDialogBuilder
|
||||||
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
|
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
|
||||||
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
|
import im.vector.app.core.extensions.containsRtLOverride
|
||||||
|
import im.vector.app.core.extensions.ensureEndsLeftToRight
|
||||||
|
import im.vector.app.core.extensions.filterDirectionOverrides
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
import im.vector.app.core.extensions.hideKeyboard
|
||||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.extensions.setTextOrHide
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
|
@ -1922,23 +1925,13 @@ class TimelineFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (!isManaged) {
|
if (!isManaged) {
|
||||||
if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) {
|
when {
|
||||||
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
|
url.containsRtLOverride() || (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) -> {
|
||||||
.setTitle(R.string.external_link_confirmation_title)
|
displayUrlConfirmationDialog(title.ensureEndsLeftToRight(), url.filterDirectionOverrides())
|
||||||
.setMessage(
|
}
|
||||||
getString(R.string.external_link_confirmation_message, title, url)
|
else -> {
|
||||||
.toSpannable()
|
openUrlInExternalBrowser(requireContext(), url)
|
||||||
.colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
|
}
|
||||||
.colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
|
|
||||||
)
|
|
||||||
.setPositiveButton(R.string._continue) { _, _ ->
|
|
||||||
openUrlInExternalBrowser(requireContext(), url)
|
|
||||||
}
|
|
||||||
.setNegativeButton(R.string.action_cancel, null)
|
|
||||||
.show()
|
|
||||||
} else {
|
|
||||||
// Open in external browser, in a new Tab
|
|
||||||
openUrlInExternalBrowser(requireContext(), url)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1946,6 +1939,22 @@ class TimelineFragment @Inject constructor(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun displayUrlConfirmationDialog(title: String, url: String) {
|
||||||
|
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
|
||||||
|
.setTitle(R.string.external_link_confirmation_title)
|
||||||
|
.setMessage(
|
||||||
|
getString(R.string.external_link_confirmation_message, title, url)
|
||||||
|
.toSpannable()
|
||||||
|
.colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
|
||||||
|
.colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
|
||||||
|
)
|
||||||
|
.setPositiveButton(R.string._continue) { _, _ ->
|
||||||
|
openUrlInExternalBrowser(requireContext(), url)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.action_cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onUrlLongClicked(url: String): Boolean {
|
override fun onUrlLongClicked(url: String): Boolean {
|
||||||
if (url != getString(R.string.edited_suffix) && url.isValidUrl()) {
|
if (url != getString(R.string.edited_suffix) && url.isValidUrl()) {
|
||||||
// Copy the url to the clipboard
|
// Copy the url to the clipboard
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.core.extensions
|
||||||
|
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class StringExtensionsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given text with RtL unicode override, when checking contains RtL Override, then returns true`() {
|
||||||
|
val textWithRtlOverride = "hello\u202Eworld"
|
||||||
|
|
||||||
|
val result = textWithRtlOverride.containsRtLOverride()
|
||||||
|
|
||||||
|
result shouldBeEqualTo true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given text without RtL unicode override, when checking contains RtL Override, then returns false`() {
|
||||||
|
val textWithRtlOverride = "hello world"
|
||||||
|
|
||||||
|
val result = textWithRtlOverride.containsRtLOverride()
|
||||||
|
|
||||||
|
result shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given text with RtL unicode override, when ensuring ends LtR, then appends a LtR unicode override`() {
|
||||||
|
val textWithRtlOverride = "123\u202E456"
|
||||||
|
|
||||||
|
val result = textWithRtlOverride.ensureEndsLeftToRight()
|
||||||
|
|
||||||
|
result shouldBeEqualTo "$textWithRtlOverride\u202D"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given text with unicode direction overrides, when filtering direction overrides, then removes all overrides`() {
|
||||||
|
val textWithDirectionOverrides = "123\u202E456\u202d789"
|
||||||
|
|
||||||
|
val result = textWithDirectionOverrides.filterDirectionOverrides()
|
||||||
|
|
||||||
|
result shouldBeEqualTo "123456789"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue