Cleanup webview interceptors (#8067)

* Cleanup webview interceptors

* Review changes + Improvement

* Review Changes 2
This commit is contained in:
AntsyLich 2022-09-25 23:09:40 +06:00 committed by GitHub
parent ec272f6c4e
commit a35f947892
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 44 additions and 28 deletions

View file

@ -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

View file

@ -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 }

View file

@ -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")