mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 16:18:55 +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