mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-03 15:11:42 +01:00
Sanitize donations webview intents.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
This commit is contained in:
committed by
jeffrey-signal
parent
53023517b3
commit
91f73b473f
@@ -10,11 +10,13 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.StripeApi
|
||||
import org.thoughtcrime.securesms.R
|
||||
import java.net.URISyntaxException
|
||||
|
||||
/**
|
||||
* Encapsulates the logic for navigating a user to a deeplink from within a webview or parsing out the fallback
|
||||
@@ -30,18 +32,31 @@ object ExternalNavigationHelper {
|
||||
return false
|
||||
}
|
||||
|
||||
val intent = try {
|
||||
Intent.parseUri(url.toString(), Intent.URI_INTENT_SCHEME).sanitizeWebIntent()
|
||||
} catch (e: URISyntaxException) {
|
||||
Log.w(TAG, "Failed to parse web intent URI.", e)
|
||||
return false
|
||||
}
|
||||
|
||||
val targetLabel = resolveTargetLabel(context, intent)
|
||||
val message = if (targetLabel != null) {
|
||||
context.getString(R.string.ExternalNavigationHelper__once_payment_confirmed_in_app, targetLabel)
|
||||
} else {
|
||||
context.getString(R.string.ExternalNavigationHelper__once_this_payment_is_confirmed)
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.ExternalNavigationHelper__leave_signal_to_confirm_payment)
|
||||
.setMessage(R.string.ExternalNavigationHelper__once_this_payment_is_confirmed)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> attemptIntentLaunch(context, url, launchIntent) }
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> attemptIntentLaunch(context, intent, launchIntent) }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun attemptIntentLaunch(context: Context, url: Uri, launchIntent: (Intent) -> Unit) {
|
||||
val intent = Intent.parseUri(url.toString(), Intent.URI_INTENT_SCHEME)
|
||||
private fun attemptIntentLaunch(context: Context, intent: Intent, launchIntent: (Intent) -> Unit) {
|
||||
try {
|
||||
launchIntent(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
@@ -50,7 +65,7 @@ object ExternalNavigationHelper {
|
||||
val fallback = intent.getStringExtra("browser_fallback_url")
|
||||
if (fallback.isNotNullOrBlank()) {
|
||||
try {
|
||||
launchIntent(Intent.parseUri(fallback, Intent.URI_INTENT_SCHEME))
|
||||
launchIntent(Intent.parseUri(fallback, Intent.URI_INTENT_SCHEME).sanitizeWebIntent())
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.w(TAG, "Failed to launch fallback URL.", e)
|
||||
toastOnActivityNotFound(context)
|
||||
@@ -59,6 +74,30 @@ object ExternalNavigationHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveTargetLabel(context: Context, intent: Intent): CharSequence? {
|
||||
val resolveInfo = context.packageManager.resolveActivity(intent, 0) ?: return null
|
||||
return resolveInfo.loadLabel(context.packageManager).toString().takeIf { it.isNotBlank() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize an intent parsed from a web-originated URI to prevent targeting
|
||||
* non-exported or internal activities. This mirrors the sanitization that
|
||||
* browsers apply to intent:// URIs before dispatching them.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun Intent.sanitizeWebIntent(): Intent {
|
||||
component = null
|
||||
selector = null
|
||||
addCategory(Intent.CATEGORY_BROWSABLE)
|
||||
flags = flags and (
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
|
||||
).inv()
|
||||
return this
|
||||
}
|
||||
|
||||
private fun toastOnActivityNotFound(context: Context) {
|
||||
Toast.makeText(context, R.string.CommunicationActions_no_browser_found, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user