Browse Source

CloudflareInterceptor update (#2537)

* CloudflareInterceptor update

* Changes

* Max-Age

* Tweaks
Mike 5 years ago
parent
commit
dcd3c709fe

+ 11 - 3
app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt

@@ -31,17 +31,25 @@ class AndroidCookieJar : CookieJar {
         }
     }
 
-    fun remove(url: HttpUrl) {
+    fun remove(url: HttpUrl, cookieNames: List<String>? = null, maxAge: Int = -1) {
         val urlString = url.toString()
         val cookies = manager.getCookie(urlString) ?: return
 
+        fun List<String>.filterNames(): List<String> {
+            return if (cookieNames != null) {
+                this.filter { it in cookieNames }
+            } else {
+                this
+            }
+        }
+
         cookies.split(";")
             .map { it.substringBefore("=") }
-            .onEach { manager.setCookie(urlString, "$it=;Max-Age=-1") }
+            .filterNames()
+            .onEach { manager.setCookie(urlString, "$it=;Max-Age=$maxAge") }
     }
 
     fun removeAll() {
         manager.removeAllCookies {}
     }
-
 }

+ 26 - 37
app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt

@@ -5,13 +5,15 @@ import android.content.Context
 import android.os.Build
 import android.os.Handler
 import android.os.Looper
-import android.webkit.WebResourceResponse
 import android.webkit.WebSettings
 import android.webkit.WebView
 import eu.kanade.tachiyomi.util.WebViewClientCompat
+import okhttp3.Cookie
 import okhttp3.Interceptor
 import okhttp3.Request
 import okhttp3.Response
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import uy.kohesive.injekt.injectLazy
 import java.io.IOException
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
@@ -22,6 +24,8 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
 
     private val handler = Handler(Looper.getMainLooper())
 
+    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
@@ -35,14 +39,21 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
     override fun intercept(chain: Interceptor.Chain): Response {
         initWebView
 
-        val response = chain.proceed(chain.request())
+        val originalRequest = chain.request()
+        val response = chain.proceed(originalRequest)
 
         // Check if Cloudflare anti-bot is on
         if (response.code == 503 && response.header("Server") in serverCheck) {
             try {
                 response.close()
-                val solutionRequest = resolveWithWebView(chain.request())
-                return chain.proceed(solutionRequest)
+                networkHelper.cookieManager.remove(originalRequest.url, listOf("__cfduid", "cf_clearance"), 0)
+                val oldCookie = networkHelper.cookieManager.get(originalRequest.url)
+                        .firstOrNull { it.name == "cf_clearance" }
+                return if (resolveWithWebView(originalRequest, oldCookie)) {
+                    chain.proceed(originalRequest)
+                } else {
+                    throw IOException("Failed to bypass Cloudflare!")
+                }
             } catch (e: Exception) {
                 // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
                 // we don't crash the entire app
@@ -53,19 +64,15 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
         return response
     }
 
-    private fun isChallengeSolutionUrl(url: String): Boolean {
-        return "chk_jschl" in url
-    }
-
     @SuppressLint("SetJavaScriptEnabled")
-    private fun resolveWithWebView(request: Request): Request {
+    private fun resolveWithWebView(request: Request, oldCookie: Cookie?): Boolean {
         // 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)
 
         var webView: WebView? = null
-        var solutionUrl: String? = null
         var challengeFound = false
+        var cloudflareBypassed = false
 
         val origRequestUrl = request.url.toString()
         val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
@@ -77,26 +84,17 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
             view.settings.userAgentString = request.header("User-Agent")
             view.webViewClient = object : WebViewClientCompat() {
 
-                override fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean {
-                    if (isChallengeSolutionUrl(url)) {
-                        solutionUrl = url
-                        latch.countDown()
+                override fun onPageFinished(view: WebView, url: String) {
+                    fun isCloudFlareBypassed(): Boolean {
+                        return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl())
+                                .firstOrNull { it.name == "cf_clearance" }
+                                .let { it != null && it != oldCookie }
                     }
-                    return solutionUrl != null
-                }
 
-                override fun shouldInterceptRequestCompat(
-                        view: WebView,
-                        url: String
-                ): WebResourceResponse? {
-                    if (solutionUrl != null) {
-                        // Intercept any request when we have the solution.
-                        return WebResourceResponse("text/plain", "UTF-8", null)
+                    if (isCloudFlareBypassed()) {
+                        cloudflareBypassed = true
+                        latch.countDown()
                     }
-                    return null
-                }
-
-                override fun onPageFinished(view: WebView, url: String) {
                     // Http error codes are only received since M
                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                         url == origRequestUrl && !challengeFound
@@ -135,16 +133,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
             webView?.stopLoading()
             webView?.destroy()
         }
-
-        val solution = solutionUrl ?: throw Exception("Challenge not found")
-
-        return Request.Builder().get()
-            .url(solution)
-            .headers(request.headers)
-            .addHeader("Referer", origRequestUrl)
-            .addHeader("Accept", "text/html,application/xhtml+xml,application/xml")
-            .addHeader("Accept-Language", "en")
-            .build()
+        return cloudflareBypassed
     }
 
 }