From 1025321b5f330900e7027ff29287e436dfdf4dab Mon Sep 17 00:00:00 2001 From: AliReza Date: Mon, 5 Oct 2026 09:44:47 +0330 Subject: [PATCH] Android WebView: bridge only on our own https page, external schemes blocked, file access off; payment URL must be http(s) Co-Authored-By: Claude Sonnet 5.5 --- .../java/com/tadrisino/app/MainActivity.kt | 21 +++++++++++++++---- src/screens/SubscriptionScreen.tsx | 2 ++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/tadrisino/app/MainActivity.kt b/android/app/src/main/java/com/tadrisino/app/MainActivity.kt index 83e00d9..c9fc606 100644 --- a/android/app/src/main/java/com/tadrisino/app/MainActivity.kt +++ b/android/app/src/main/java/com/tadrisino/app/MainActivity.kt @@ -40,6 +40,9 @@ class MainActivity : AppCompatActivity() { private var connection: Connection? = null private val webHost: String = Uri.parse(BuildConfig.WEB_URL).host ?: "" + /** فقط خودِ سایت و فقط با https؛ «http://دامنه‌ی ما» قابلِ دست‌کاریِ شبکه است و نباید پل داشته باشد. */ + private fun isOurPage(uri: Uri?): Boolean = uri != null && uri.scheme == "https" && uri.host == webHost + @SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -48,12 +51,19 @@ class MainActivity : AppCompatActivity() { webView.settings.javaScriptEnabled = true webView.settings.domStorageEnabled = true // توکنِ ورود در localStorage است + // WebView فقط آدرسِ وب را می‌خواند؛ دسترسی به فایل و content:// لازم نیست. + webView.settings.allowFileAccess = false + webView.settings.allowContentAccess = false webView.webViewClient = object : WebViewClient() { // هر آدرسی بیرونِ دامنه‌ی خودمان در مرورگرِ گوشی باز می‌شود، نه داخلِ اپ؛ پس پل فقط // در صفحه‌های خودمان در دسترس است. override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { - if (request.url.host == webHost) return false - startActivity(Intent(Intent.ACTION_VIEW, request.url)) + if (isOurPage(request.url)) return false + // فقط http(s) به مرورگرِ گوشی می‌رود؛ intent:، file: و schemeهای سفارشی بی‌صدا بسته می‌شوند + // (پیش‌تر هر آدرسی بیرونِ دامنه به ACTION_VIEW می‌رفت و صفحه می‌توانست اپِ دلخواه باز کند). + if (request.url.scheme == "https" || request.url.scheme == "http") { + startActivity(Intent(Intent.ACTION_VIEW, request.url)) + } return true } } @@ -145,14 +155,17 @@ class MainActivity : AppCompatActivity() { @JavascriptInterface fun bazaarPurchase(sku: String, orderId: String) { runOnUiThread { - if (Uri.parse(webView.url ?: "").host != webHost) return@runOnUiThread + if (!isOurPage(Uri.parse(webView.url ?: ""))) return@runOnUiThread purchase(sku, orderId) } } @JavascriptInterface fun bazaarConsume(purchaseToken: String) { - runOnUiThread { consume(purchaseToken) } + runOnUiThread { + if (!isOurPage(Uri.parse(webView.url ?: ""))) return@runOnUiThread + consume(purchaseToken) + } } } diff --git a/src/screens/SubscriptionScreen.tsx b/src/screens/SubscriptionScreen.tsx index a809fde..abf6cb1 100644 --- a/src/screens/SubscriptionScreen.tsx +++ b/src/screens/SubscriptionScreen.tsx @@ -210,6 +210,8 @@ function CheckoutSheet({ plan, bazaar, onClose, onPaid }: { plan: Plan | null; b } } if (!order.paymentUrl) throw new Error('order without paymentUrl'); + // فقط http(s): یک «javascript:» یا «data:» از پاسخِ API نباید با assign اجرا شود. + if (!/^https?:\/\//i.test(order.paymentUrl)) throw new Error('paymentUrl is not http(s)'); window.location.assign(order.paymentUrl); } catch (err) { setError(friendlyError(err, 'اتصال به درگاه انجام نشد، دوباره امتحان کن'));