浏览代码

Clean up interceptors a bit

arkon 2 年之前
父节点
当前提交
dc62d0ea8b

+ 5 - 4
app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt

@@ -17,12 +17,13 @@ class NetworkHelper(context: Context) {
     private val preferences: PreferencesHelper by injectLazy()
 
     private val cacheDir = File(context.cacheDir, "network_cache")
-
     private val cacheSize = 5L * 1024 * 1024 // 5 MiB
 
     val cookieManager = AndroidCookieJar()
 
-    private val http103Interceptor = Http103Interceptor(context)
+    private val userAgentInterceptor by lazy { UserAgentInterceptor() }
+    private val http103Interceptor by lazy { Http103Interceptor(context) }
+    private val cloudflareInterceptor by lazy { CloudflareInterceptor(context) }
 
     private val baseClientBuilder: OkHttpClient.Builder
         get() {
@@ -32,7 +33,7 @@ class NetworkHelper(context: Context) {
                 .readTimeout(30, TimeUnit.SECONDS)
                 .callTimeout(2, TimeUnit.MINUTES)
                 // .fastFallback(true) // TODO: re-enable when OkHttp 5 is stabler
-                .addInterceptor(UserAgentInterceptor())
+                .addInterceptor(userAgentInterceptor)
                 .addNetworkInterceptor(http103Interceptor)
 
             if (preferences.verboseLogging()) {
@@ -64,7 +65,7 @@ class NetworkHelper(context: Context) {
     @Suppress("UNUSED")
     val cloudflareClient by lazy {
         client.newBuilder()
-            .addInterceptor(CloudflareInterceptor(context))
+            .addInterceptor(cloudflareInterceptor)
             .build()
     }
 

+ 17 - 54
app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt

@@ -2,17 +2,12 @@ package eu.kanade.tachiyomi.network.interceptor
 
 import android.annotation.SuppressLint
 import android.content.Context
-import android.os.Build
-import android.webkit.WebSettings
 import android.webkit.WebView
 import android.widget.Toast
 import androidx.core.content.ContextCompat
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.network.NetworkHelper
-import eu.kanade.tachiyomi.util.lang.launchUI
-import eu.kanade.tachiyomi.util.system.DeviceUtil
 import eu.kanade.tachiyomi.util.system.WebViewClientCompat
-import eu.kanade.tachiyomi.util.system.WebViewUtil
 import eu.kanade.tachiyomi.util.system.isOutdated
 import eu.kanade.tachiyomi.util.system.setDefaultSettings
 import eu.kanade.tachiyomi.util.system.toast
@@ -26,56 +21,26 @@ import java.io.IOException
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 
-class CloudflareInterceptor(private val context: Context) : Interceptor {
+class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(context) {
 
     private val executor = ContextCompat.getMainExecutor(context)
 
     private val networkHelper: NetworkHelper by injectLazy()
 
-    /**
-     * When this is called, it initializes the WebView if it wasn't already. We use this to avoid
-     * blocking the main thread too much. If used too often we could consider moving it to the
-     * Application class.
-     */
-    private val initWebView by lazy {
-        // Crashes on some devices. We skip this in some cases since the only impact is slower
-        // WebView init in those rare cases.
-        // See https://bugs.chromium.org/p/chromium/issues/detail?id=1279562
-        if (DeviceUtil.isMiui || Build.VERSION.SDK_INT == Build.VERSION_CODES.S && DeviceUtil.isSamsung) {
-            return@lazy
-        }
-
-        WebSettings.getDefaultUserAgent(context)
-    }
-
-    @Synchronized
-    override fun intercept(chain: Interceptor.Chain): Response {
-        val originalRequest = chain.request()
-
-        if (!WebViewUtil.supportsWebView(context)) {
-            launchUI {
-                context.toast(R.string.information_webview_required, Toast.LENGTH_LONG)
-            }
-            return chain.proceed(originalRequest)
-        }
-
-        initWebView
-
-        val response = chain.proceed(originalRequest)
-
+    override fun shouldIntercept(response: Response): Boolean {
         // Check if Cloudflare anti-bot is on
-        if (response.code !in ERROR_CODES || response.header("Server") !in SERVER_CHECK) {
-            return response
-        }
+        return response.code in ERROR_CODES && response.header("Server") in SERVER_CHECK
+    }
 
+    override fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response {
         try {
             response.close()
-            networkHelper.cookieManager.remove(originalRequest.url, COOKIE_NAMES, 0)
-            val oldCookie = networkHelper.cookieManager.get(originalRequest.url)
+            networkHelper.cookieManager.remove(request.url, COOKIE_NAMES, 0)
+            val oldCookie = networkHelper.cookieManager.get(request.url)
                 .firstOrNull { it.name == "cf_clearance" }
-            resolveWithWebView(originalRequest, oldCookie)
+            resolveWithWebView(request, oldCookie)
 
-            return chain.proceed(originalRequest)
+            return chain.proceed(request)
         }
         // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
         // we don't crash the entire app
@@ -87,7 +52,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
     }
 
     @SuppressLint("SetJavaScriptEnabled")
-    private fun resolveWithWebView(request: Request, oldCookie: Cookie?) {
+    private fun resolveWithWebView(originalRequest: Request, oldCookie: Cookie?) {
         // We need to lock this thread until the WebView finds the challenge solution url, because
         // OkHttp doesn't support asynchronous interceptors.
         val latch = CountDownLatch(1)
@@ -98,8 +63,8 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
         var cloudflareBypassed = false
         var isWebViewOutdated = false
 
-        val origRequestUrl = request.url.toString()
-        val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
+        val origRequestUrl = originalRequest.url.toString()
+        val headers = originalRequest.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
 
         executor.execute {
             val webview = WebView(context)
@@ -107,7 +72,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
             webview.setDefaultSettings()
 
             // Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
-            webview.settings.userAgentString = request.header("User-Agent")
+            webview.settings.userAgentString = originalRequest.header("User-Agent")
                 ?: networkHelper.defaultUserAgent
 
             webview.webViewClient = object : WebViewClientCompat() {
@@ -175,12 +140,10 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
             throw CloudflareBypassException()
         }
     }
-
-    companion object {
-        private val ERROR_CODES = listOf(403, 503)
-        private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
-        private val COOKIE_NAMES = listOf("cf_clearance")
-    }
 }
 
+private val ERROR_CODES = listOf(403, 503)
+private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
+private val COOKIE_NAMES = listOf("cf_clearance")
+
 private class CloudflareBypassException : Exception()

+ 25 - 68
app/src/main/java/eu/kanade/tachiyomi/network/interceptor/Http103Interceptor.kt

@@ -2,67 +2,31 @@ package eu.kanade.tachiyomi.network.interceptor
 
 import android.annotation.SuppressLint
 import android.content.Context
-import android.os.Build
 import android.webkit.JavascriptInterface
-import android.webkit.WebSettings
 import android.webkit.WebView
-import android.widget.Toast
 import androidx.core.content.ContextCompat
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.network.NetworkHelper
-import eu.kanade.tachiyomi.util.lang.launchUI
-import eu.kanade.tachiyomi.util.system.DeviceUtil
 import eu.kanade.tachiyomi.util.system.WebViewClientCompat
-import eu.kanade.tachiyomi.util.system.WebViewUtil
 import eu.kanade.tachiyomi.util.system.logcat
-import eu.kanade.tachiyomi.util.system.toast
 import okhttp3.Interceptor
 import okhttp3.MediaType.Companion.toMediaType
 import okhttp3.Protocol
 import okhttp3.Request
 import okhttp3.Response
 import okhttp3.ResponseBody.Companion.toResponseBody
-import uy.kohesive.injekt.injectLazy
 import java.io.IOException
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 
-// TODO: Remove when OkHttp can handle http 103 responses
-class Http103Interceptor(private val context: Context) : Interceptor {
+// TODO: Remove when OkHttp can handle HTTP 103 responses
+class Http103Interceptor(context: Context) : WebViewInterceptor(context) {
 
     private val executor = ContextCompat.getMainExecutor(context)
 
-    private val networkHelper: NetworkHelper by injectLazy()
-
-    /**
-     * When this is called, it initializes the WebView if it wasn't already. We use this to avoid
-     * blocking the main thread too much. If used too often we could consider moving it to the
-     * Application class.
-     */
-    private val initWebView by lazy {
-        // Crashes on some devices. We skip this in some cases since the only impact is slower
-        // WebView init in those rare cases.
-        // See https://bugs.chromium.org/p/chromium/issues/detail?id=1279562
-        if (DeviceUtil.isMiui || Build.VERSION.SDK_INT == Build.VERSION_CODES.S && DeviceUtil.isSamsung) {
-            return@lazy
-        }
-
-        WebSettings.getDefaultUserAgent(context)
+    override fun shouldIntercept(response: Response): Boolean {
+        return response.code == 103
     }
 
-    override fun intercept(chain: Interceptor.Chain): Response {
-        val request = chain.request()
-        val response = chain.proceed(request)
-        if (response.code != 103) return response
-        if (!WebViewUtil.supportsWebView(context)) {
-            launchUI {
-                context.toast(R.string.information_webview_required, Toast.LENGTH_LONG)
-            }
-            return response
-        }
-
-        initWebView
-
+    override fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response {
         logcat { "Proceeding with WebView for request $request" }
         try {
             return proceedWithWebView(request, response)
@@ -71,23 +35,9 @@ class Http103Interceptor(private val context: Context) : Interceptor {
         }
     }
 
-    internal class JsInterface(private val latch: CountDownLatch, var payload: String? = null) {
-        @JavascriptInterface
-        fun passPayload(passedPayload: String) {
-            payload = passedPayload
-            latch.countDown()
-        }
-    }
-
-    companion object {
-        const val jsScript = "window.android.passPayload(document.querySelector('html').outerHTML)"
-
-        val htmlMediaType = "text/html".toMediaType()
-    }
-
     @SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
-    private fun proceedWithWebView(ogRequest: Request, ogResponse: Response): Response {
-        // We need to lock this thread until the WebView finds the challenge solution url, because
+    private fun proceedWithWebView(originalRequest: Request, originalResponse: Response): Response {
+        // We need to lock this thread until the WebView loads the page, because
         // OkHttp doesn't support asynchronous interceptors.
         val latch = CountDownLatch(1)
 
@@ -97,16 +47,11 @@ class Http103Interceptor(private val context: Context) : Interceptor {
 
         var exception: Exception? = null
 
-        val requestUrl = ogRequest.url.toString()
-        val headers = ogRequest.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
+        val requestUrl = originalRequest.url.toString()
+        val headers = originalRequest.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
 
         executor.execute {
-            val webview = WebView(context).also { outerWebView = it }
-            with(webview.settings) {
-                javaScriptEnabled = true
-                userAgentString = ogRequest.header("User-Agent") ?: networkHelper.defaultUserAgent
-            }
-
+            val webview = createWebView(originalRequest).also { outerWebView = it }
             webview.addJavascriptInterface(jsInterface, "android")
 
             webview.webViewClient = object : WebViewClientCompat() {
@@ -143,13 +88,25 @@ class Http103Interceptor(private val context: Context) : Interceptor {
 
         exception?.let { throw it }
 
-        val payload = jsInterface.payload ?: throw Exception("Couldn't fetch site through webview")
+        val responseHtml = jsInterface.responseHtml ?: throw Exception("Couldn't fetch site through webview")
 
-        return ogResponse.newBuilder()
+        return originalResponse.newBuilder()
             .code(200)
             .protocol(Protocol.HTTP_1_1)
             .message("OK")
-            .body(payload.toResponseBody(htmlMediaType))
+            .body(responseHtml.toResponseBody(htmlMediaType))
             .build()
     }
 }
+
+internal class JsInterface(private val latch: CountDownLatch, var responseHtml: String? = null) {
+    @Suppress("UNUSED")
+    @JavascriptInterface
+    fun passPayload(passedPayload: String) {
+        responseHtml = passedPayload
+        latch.countDown()
+    }
+}
+
+private const val jsScript = "window.android.passPayload(document.querySelector('html').outerHTML)"
+private val htmlMediaType = "text/html".toMediaType()

+ 68 - 0
app/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt

@@ -0,0 +1,68 @@
+package eu.kanade.tachiyomi.network.interceptor
+
+import android.content.Context
+import android.os.Build
+import android.webkit.WebSettings
+import android.webkit.WebView
+import android.widget.Toast
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.network.NetworkHelper
+import eu.kanade.tachiyomi.util.lang.launchUI
+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.Interceptor
+import okhttp3.Request
+import okhttp3.Response
+import uy.kohesive.injekt.injectLazy
+
+abstract class WebViewInterceptor(private val context: Context) : Interceptor {
+
+    private val networkHelper: NetworkHelper by injectLazy()
+
+    /**
+     * When this is called, it initializes the WebView if it wasn't already. We use this to avoid
+     * blocking the main thread too much. If used too often we could consider moving it to the
+     * Application class.
+     */
+    private val initWebView by lazy {
+        // Crashes on some devices. We skip this in some cases since the only impact is slower
+        // WebView init in those rare cases.
+        // See https://bugs.chromium.org/p/chromium/issues/detail?id=1279562
+        if (DeviceUtil.isMiui || Build.VERSION.SDK_INT == Build.VERSION_CODES.S && DeviceUtil.isSamsung) {
+            return@lazy
+        }
+
+        WebSettings.getDefaultUserAgent(context)
+    }
+
+    abstract fun shouldIntercept(response: Response): Boolean
+
+    abstract fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response
+
+    override fun intercept(chain: Interceptor.Chain): Response {
+        val request = chain.request()
+        val response = chain.proceed(request)
+        if (!shouldIntercept(response)) {
+            return response
+        }
+
+        if (!WebViewUtil.supportsWebView(context)) {
+            launchUI {
+                context.toast(R.string.information_webview_required, Toast.LENGTH_LONG)
+            }
+            return response
+        }
+        initWebView
+
+        return intercept(chain, request, response)
+    }
+
+    fun createWebView(request: Request): WebView {
+        val webview = WebView(context)
+        webview.setDefaultSettings()
+        webview.settings.userAgentString = request.header("User-Agent") ?: networkHelper.defaultUserAgent
+        return webview
+    }
+}

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt

@@ -11,7 +11,7 @@ import logcat.LogPriority
 object WebViewUtil {
     const val SPOOF_PACKAGE_NAME = "org.chromium.chrome"
 
-    const val MINIMUM_WEBVIEW_VERSION = 99
+    const val MINIMUM_WEBVIEW_VERSION = 100
 
     fun supportsWebView(context: Context): Boolean {
         try {