mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +01:00 
			
		
		
		
	CloudflareInterceptor update (#2537)	dcd3c709	Mike <51273546+SnakeDoc83@users.noreply.github.com>	Jan 25, 2020 at 16:37
				
					
				
			This commit is contained in:
		| @@ -46,13 +46,22 @@ class AndroidCookieJar(context: Context) : 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") } | ||||
|  | ||||
|         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { | ||||
|             syncManager.sync() | ||||
| @@ -67,5 +76,4 @@ class AndroidCookieJar(context: Context) : CookieJar { | ||||
|             syncManager.sync() | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -5,13 +5,11 @@ 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.Interceptor | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import okhttp3.* | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import java.io.IOException | ||||
| import java.util.concurrent.CountDownLatch | ||||
| import java.util.concurrent.TimeUnit | ||||
| @@ -22,6 +20,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 | ||||
| @@ -39,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 | ||||
| @@ -57,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) ?: "" } | ||||
| @@ -81,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 | ||||
|                 override fun onPageFinished(view: WebView, url: String) { | ||||
|                     fun isCloudFlareBypassed(): Boolean { | ||||
|                         return networkHelper.cookieManager.get(HttpUrl.parse(origRequestUrl)!!) | ||||
|                                 .firstOrNull { it.name() == "cf_clearance" } | ||||
|                                 .let { it != null && it != oldCookie } | ||||
|                     } | ||||
|  | ||||
|                     if (isCloudFlareBypassed()) { | ||||
|                         cloudflareBypassed = true | ||||
|                         latch.countDown() | ||||
|                     } | ||||
|                     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) | ||||
|                     } | ||||
|                     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 | ||||
| @@ -140,15 +134,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { | ||||
|             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 | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user