diff --git a/CHANGELOG.md b/CHANGELOG.md index 086af9adf..716f9c0e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - Fix certain Infinix, Xiaomi devices being unable to use any "Open link in browser" actions, including tracker setup ([@MajorTanya](https://github.com/MajorTanya)) ([#1684](https://github.com/mihonapp/mihon/pull/1684)) ([#1776](https://github.com/mihonapp/mihon/pull/1776)) - Fix App's preferences referencing deleted categories ([@cuong-tran](https://github.com/cuong-tran)) ([#1734](https://github.com/mihonapp/mihon/pull/1734)) - Fix backup/restore of category related preferences ([@cuong-tran](https://github.com/cuong-tran)) ([#1726](https://github.com/mihonapp/mihon/pull/1726)) +- Fix WebView sending app's package name in `X-Requested-With` header, which led to sources blocking access ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#1812](https://github.com/mihonapp/mihon/pull/1812)) ### Removed - Remove alphabetical category sort option 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 5eb94ecf8..4f18930c8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -219,17 +219,15 @@ 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 5516ed748..0148ca862 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -19,7 +19,7 @@ class NetworkHelper( val cookieJar = AndroidCookieJar() - val client: OkHttpClient = run { + private val clientBuilder: OkHttpClient.Builder = run { val builder = OkHttpClient.Builder() .cookieJar(cookieJar) .connectTimeout(30, TimeUnit.SECONDS) @@ -43,10 +43,6 @@ 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() @@ -60,11 +56,18 @@ 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() + + 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 51069398a..a66ca6994 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 @@ -12,7 +12,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 @@ -53,6 +54,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 {