From dfde271f7f0063583ed6e4042f75944d27247557 Mon Sep 17 00:00:00 2001 From: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> Date: Sun, 2 Mar 2025 20:16:42 +0500 Subject: [PATCH] Spoof or remove `X-Requested-With` header from webview (#1812) (cherry picked from commit 793d7fbe40c87ed233da8cc99d544d01024ed4f5) # Conflicts: # CHANGELOG.md # app/src/main/java/eu/kanade/tachiyomi/App.kt # core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt --- .../webview/WebViewScreenContent.kt | 44 +++++++++++++++++++ app/src/main/java/eu/kanade/tachiyomi/App.kt | 14 +++--- .../kanade/tachiyomi/network/NetworkHelper.kt | 19 ++++---- .../tachiyomi/util/system/WebViewUtil.kt | 13 +++++- 4 files changed, 73 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt index db4bc9309..7a947979b 100644 --- a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt +++ b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt @@ -3,6 +3,7 @@ package eu.kanade.presentation.webview import android.content.pm.ApplicationInfo import android.graphics.Bitmap import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse import android.webkit.WebView import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -26,6 +27,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.unit.dp import com.kevinnzou.web.AccompanistWebViewClient @@ -37,13 +39,18 @@ import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.components.WarningBanner import eu.kanade.tachiyomi.BuildConfig +import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.getHtml import eu.kanade.tachiyomi.util.system.setDefaultSettings import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.launch +import okhttp3.Request import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get @Composable fun WebViewScreenContent( @@ -58,8 +65,11 @@ fun WebViewScreenContent( ) { val state = rememberWebViewState(url = url, additionalHttpHeaders = headers) val navigator = rememberWebViewNavigator() + val context = LocalContext.current val uriHandler = LocalUriHandler.current val scope = rememberCoroutineScope() + val network = remember { Injekt.get() } + val spoofedPackageName = remember { WebViewUtil.spoofedPackageName(context) } var currentUrl by remember { mutableStateOf(url) } var showCloudflareHelp by remember { mutableStateOf(false) } @@ -114,6 +124,40 @@ fun WebViewScreenContent( } return super.shouldOverrideUrlLoading(view, request) } + + override fun shouldInterceptRequest( + view: WebView?, + request: WebResourceRequest?, + ): WebResourceResponse? { + return try { + val internalRequest = Request.Builder().apply { + url(request!!.url.toString()) + request.requestHeaders.forEach { (key, value) -> + if (key == "X-Requested-With" && value in setOf(context.packageName, spoofedPackageName)) { + return@forEach + } + addHeader(key, value) + } + method(request.method, null) + }.build() + + val response = network.nonCloudflareClient.newCall(internalRequest).execute() + + val contentType = response.body.contentType()?.let { "${it.type}/${it.subtype}" } ?: "text/html" + val contentEncoding = response.body.contentType()?.charset()?.name() ?: "utf-8" + + WebResourceResponse( + contentType, + contentEncoding, + response.code, + response.message, + response.headers.associate { it.first to it.second }, + response.body.byteStream(), + ) + } catch (e: Throwable) { + super.shouldInterceptRequest(view, request) + } + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index fe0a1ab4b..831e04d6a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -277,18 +277,16 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor try { // Override the value passed as X-Requested-With in WebView requests val stackTrace = Looper.getMainLooper().thread.stackTrace - val chromiumElement = stackTrace.find { - it.className.equals( - "org.chromium.base.BuildInfo", - ignoreCase = true, - ) - } - if (chromiumElement?.methodName.equals("getAll", ignoreCase = true)) { - return WebViewUtil.SPOOF_PACKAGE_NAME + val isChromiumCall = stackTrace.any { + it.className.startsWith("org.chromium.") && + it.methodName in setOf("getAll", "getPackageName", "") } + + if (isChromiumCall) return WebViewUtil.spoofedPackageName(applicationContext) } catch (_: Exception) { } } + return super.getPackageName() } diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt index f508e6e08..5b0c5f8c9 100755 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -24,8 +24,7 @@ open /* SY <-- */ class NetworkHelper( /* SY --> */ open /* SY <-- */val cookieJar = AndroidCookieJar() - /* SY --> */ - open /* SY <-- */val client: OkHttpClient = run { + private val clientBuilder: OkHttpClient.Builder = run { val builder = OkHttpClient.Builder() .cookieJar(cookieJar) .connectTimeout(30, TimeUnit.SECONDS) @@ -49,10 +48,6 @@ open /* SY <-- */ class NetworkHelper( builder.addNetworkInterceptor(httpLoggingInterceptor) } - builder.addInterceptor( - CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider), - ) - when (preferences.dohProvider().get()) { PREF_DOH_CLOUDFLARE -> builder.dohCloudflare() PREF_DOH_GOOGLE -> builder.dohGoogle() @@ -66,11 +61,19 @@ open /* SY <-- */ class NetworkHelper( PREF_DOH_CONTROLD -> builder.dohControlD() PREF_DOH_NJALLA -> builder.dohNajalla() PREF_DOH_SHECAN -> builder.dohShecan() + else -> builder } - - builder.build() } + val nonCloudflareClient = clientBuilder.build() + + /* SY --> */ + open /* SY <-- */ val client = clientBuilder + .addInterceptor( + CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider), + ) + .build() + /** * @deprecated Since extension-lib 1.5 */ diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt index 7dc399671..ed9adac0c 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt @@ -13,7 +13,8 @@ import tachiyomi.core.common.util.system.logcat import kotlin.coroutines.resume object WebViewUtil { - const val SPOOF_PACKAGE_NAME = "org.chromium.chrome" + private const val CHROME_PACKAGE = "com.android.chrome" + private const val SYSTEM_SETTINGS_PACKAGE = "com.android.settings" const val MINIMUM_WEBVIEW_VERSION = 118 @@ -58,6 +59,16 @@ object WebViewUtil { return context.packageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW) } + + fun spoofedPackageName(context: Context): String { + return try { + context.packageManager.getPackageInfo(CHROME_PACKAGE, PackageManager.GET_META_DATA) + + CHROME_PACKAGE + } catch (_: PackageManager.NameNotFoundException) { + SYSTEM_SETTINGS_PACKAGE + } + } } fun WebView.isOutdated(): Boolean {