Cleanup webview interceptors (#8067)
* Cleanup webview interceptors * Review changes + Improvement * Review Changes 2
This commit is contained in:
parent
ec272f6c4e
commit
a35f947892
@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.core.R
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
||||
import eu.kanade.tachiyomi.util.system.isOutdated
|
||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
@ -57,25 +56,19 @@ class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(c
|
||||
// OkHttp doesn't support asynchronous interceptors.
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
var webView: WebView? = null
|
||||
var webview: WebView? = null
|
||||
|
||||
var challengeFound = false
|
||||
var cloudflareBypassed = false
|
||||
var isWebViewOutdated = false
|
||||
|
||||
val origRequestUrl = originalRequest.url.toString()
|
||||
val headers = originalRequest.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
val headers = parseHeaders(originalRequest.headers)
|
||||
|
||||
executor.execute {
|
||||
val webview = WebView(context)
|
||||
webView = webview
|
||||
webview.setDefaultSettings()
|
||||
webview = createWebView(originalRequest)
|
||||
|
||||
// Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
|
||||
webview.settings.userAgentString = originalRequest.header("User-Agent")
|
||||
?: networkHelper.defaultUserAgent
|
||||
|
||||
webview.webViewClient = object : WebViewClientCompat() {
|
||||
webview?.webViewClient = object : WebViewClientCompat() {
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
fun isCloudFlareBypassed(): Boolean {
|
||||
return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl())
|
||||
@ -113,7 +106,7 @@ class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(c
|
||||
}
|
||||
}
|
||||
|
||||
webView?.loadUrl(origRequestUrl, headers)
|
||||
webview?.loadUrl(origRequestUrl, headers)
|
||||
}
|
||||
|
||||
// Wait a reasonable amount of time to retrieve the solution. The minimum should be
|
||||
@ -122,12 +115,13 @@ class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(c
|
||||
|
||||
executor.execute {
|
||||
if (!cloudflareBypassed) {
|
||||
isWebViewOutdated = webView?.isOutdated() == true
|
||||
isWebViewOutdated = webview?.isOutdated() == true
|
||||
}
|
||||
|
||||
webView?.stopLoading()
|
||||
webView?.destroy()
|
||||
webView = null
|
||||
webview?.run {
|
||||
stopLoading()
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
|
||||
// Throw exception if we failed to bypass Cloudflare
|
||||
|
@ -43,18 +43,18 @@ class Http103Interceptor(context: Context) : WebViewInterceptor(context) {
|
||||
|
||||
val jsInterface = JsInterface(latch)
|
||||
|
||||
var outerWebView: WebView? = null
|
||||
var webview: WebView? = null
|
||||
|
||||
var exception: Exception? = null
|
||||
|
||||
val requestUrl = originalRequest.url.toString()
|
||||
val headers = originalRequest.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
val headers = parseHeaders(originalRequest.headers)
|
||||
|
||||
executor.execute {
|
||||
val webview = createWebView(originalRequest).also { outerWebView = it }
|
||||
webview.addJavascriptInterface(jsInterface, "android")
|
||||
webview = createWebView(originalRequest)
|
||||
webview?.addJavascriptInterface(jsInterface, "android")
|
||||
|
||||
webview.webViewClient = object : WebViewClientCompat() {
|
||||
webview?.webViewClient = object : WebViewClientCompat() {
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
view.evaluateJavascript(jsScript) {}
|
||||
}
|
||||
@ -73,17 +73,16 @@ class Http103Interceptor(context: Context) : WebViewInterceptor(context) {
|
||||
}
|
||||
}
|
||||
|
||||
webview.loadUrl(requestUrl, headers)
|
||||
webview?.loadUrl(requestUrl, headers)
|
||||
}
|
||||
|
||||
latch.await(10, TimeUnit.SECONDS)
|
||||
|
||||
executor.execute {
|
||||
outerWebView?.run {
|
||||
webview?.run {
|
||||
stopLoading()
|
||||
destroy()
|
||||
}
|
||||
outerWebView = null
|
||||
}
|
||||
|
||||
exception?.let { throw it }
|
||||
|
@ -12,10 +12,12 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Locale
|
||||
|
||||
abstract class WebViewInterceptor(private val context: Context) : Interceptor {
|
||||
|
||||
@ -59,10 +61,31 @@ abstract class WebViewInterceptor(private val context: Context) : Interceptor {
|
||||
return intercept(chain, request, response)
|
||||
}
|
||||
|
||||
fun parseHeaders(headers: Headers): Map<String, String> {
|
||||
return headers
|
||||
// Keeping unsafe header makes webview throw [net::ERR_INVALID_ARGUMENT]
|
||||
.filter { (name, value) ->
|
||||
isRequestHeaderSafe(name, value)
|
||||
}
|
||||
.groupBy(keySelector = { (name, _) -> name }) { (_, value) -> value }
|
||||
.mapValues { it.value.getOrNull(0).orEmpty() }
|
||||
}
|
||||
|
||||
fun createWebView(request: Request): WebView {
|
||||
val webview = WebView(context)
|
||||
webview.setDefaultSettings()
|
||||
webview.settings.userAgentString = request.header("User-Agent") ?: networkHelper.defaultUserAgent
|
||||
return webview
|
||||
return WebView(context).apply {
|
||||
setDefaultSettings()
|
||||
// Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
|
||||
settings.userAgentString = request.header("User-Agent") ?: networkHelper.defaultUserAgent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Based on [IsRequestHeaderSafe] in https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/header_util.cc
|
||||
private fun isRequestHeaderSafe(_name: String, _value: String): Boolean {
|
||||
val name = _name.lowercase(Locale.ENGLISH)
|
||||
val value = _value.lowercase(Locale.ENGLISH)
|
||||
if (name in unsafeHeaderNames || name.startsWith("proxy-")) return false
|
||||
if (name == "connection" && value == "upgrade") return false
|
||||
return true
|
||||
}
|
||||
private val unsafeHeaderNames = listOf("content-length", "host", "trailer", "te", "upgrade", "cookie2", "keep-alive", "transfer-encoding", "set-cookie")
|
||||
|
Loading…
Reference in New Issue
Block a user