Spoof or remove X-Requested-With header from webview (mihonapp/mihon#1812)
(cherry picked from commit 793d7fbe40c87ed233da8cc99d544d01024ed4f5)
This commit is contained in:
parent
2167ceba04
commit
6bfb80fe37
5 changed files with 89 additions and 20 deletions
|
|
@ -34,6 +34,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
|
||||
|
|
|
|||
|
|
@ -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<NetworkHelper>() }
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -286,17 +286,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", "<init>")
|
||||
}
|
||||
|
||||
if (isChromiumCall) return WebViewUtil.spoofedPackageName(applicationContext)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
|
||||
return super.getPackageName()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,19 +30,16 @@ import kotlin.random.Random
|
|||
|
||||
/* SY --> */ open /* SY <-- */val cookieJar = AndroidCookieJar()
|
||||
|
||||
/* SY --> */ open /* SY <-- */val client: OkHttpClient =
|
||||
// KMK -->
|
||||
clientWithTimeOut()
|
||||
|
||||
/**
|
||||
* Timeout in unit of seconds.
|
||||
*/
|
||||
fun clientWithTimeOut(
|
||||
private /* KMK --> */ fun /* KMK <-- */ clientBuilder(
|
||||
// KMK -->
|
||||
connectTimeout: Long = 30,
|
||||
readTimeout: Long = 30,
|
||||
callTimeout: Long = 120,
|
||||
// KMK <--
|
||||
): OkHttpClient = run {
|
||||
): OkHttpClient.Builder = run {
|
||||
val builder = OkHttpClient.Builder()
|
||||
.cookieJar(cookieJar)
|
||||
// KMK -->
|
||||
|
|
@ -68,10 +65,6 @@ import kotlin.random.Random
|
|||
builder.addNetworkInterceptor(httpLoggingInterceptor)
|
||||
}
|
||||
|
||||
builder.addInterceptor(
|
||||
CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider),
|
||||
)
|
||||
|
||||
when (preferences.dohProvider().get()) {
|
||||
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
|
||||
PREF_DOH_GOOGLE -> builder.dohGoogle()
|
||||
|
|
@ -85,12 +78,34 @@ import kotlin.random.Random
|
|||
PREF_DOH_CONTROLD -> builder.dohControlD()
|
||||
PREF_DOH_NJALLA -> builder.dohNajalla()
|
||||
PREF_DOH_SHECAN -> builder.dohShecan()
|
||||
else -> builder
|
||||
}
|
||||
}
|
||||
|
||||
builder.build()
|
||||
val nonCloudflareClient /* KMK --> */ by lazy /* KMK <-- */ { clientBuilder().build() }
|
||||
|
||||
/* SY --> */ open /* SY <-- */ val client /* KMK --> */ by lazy /* KMK <-- */ {
|
||||
clientBuilder()
|
||||
.addInterceptor(
|
||||
CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
/**
|
||||
* Timeout in unit of seconds.
|
||||
*/
|
||||
private fun clientWithTimeOut(
|
||||
connectTimeout: Long = 30,
|
||||
readTimeout: Long = 30,
|
||||
callTimeout: Long = 120,
|
||||
) = clientBuilder(connectTimeout, readTimeout, callTimeout)
|
||||
.addInterceptor(
|
||||
CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider),
|
||||
)
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Allow to download a big file with retry & resume capability because
|
||||
* normally it would get a Timeout exception.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue