CloudflareInterceptor update (#2537) dcd3c709 Mike <51273546+SnakeDoc83@users.noreply.github.com> Jan 25, 2020 at 16:37

This commit is contained in:
Mike 2020-01-25 16:37:59 -05:00 committed by arkon
parent 0fd00331e1
commit 72920130c0
2 changed files with 37 additions and 43 deletions

View file

@ -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 urlString = url.toString()
val cookies = manager.getCookie(urlString) ?: return 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(";") cookies.split(";")
.map { it.substringBefore("=") } .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) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
syncManager.sync() syncManager.sync()
@ -67,5 +76,4 @@ class AndroidCookieJar(context: Context) : CookieJar {
syncManager.sync() syncManager.sync()
} }
} }
} }

View file

@ -5,13 +5,11 @@ import android.content.Context
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.webkit.WebResourceResponse
import android.webkit.WebSettings import android.webkit.WebSettings
import android.webkit.WebView import android.webkit.WebView
import eu.kanade.tachiyomi.util.WebViewClientCompat import eu.kanade.tachiyomi.util.WebViewClientCompat
import okhttp3.Interceptor import okhttp3.*
import okhttp3.Request import uy.kohesive.injekt.injectLazy
import okhttp3.Response
import java.io.IOException import java.io.IOException
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -22,6 +20,8 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
private val handler = Handler(Looper.getMainLooper()) 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 * 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 * 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 { override fun intercept(chain: Interceptor.Chain): Response {
initWebView initWebView
val response = chain.proceed(chain.request()) val originalRequest = chain.request()
val response = chain.proceed(originalRequest)
// Check if Cloudflare anti-bot is on // Check if Cloudflare anti-bot is on
if (response.code() == 503 && response.header("Server") in serverCheck) { if (response.code() == 503 && response.header("Server") in serverCheck) {
try { try {
response.close() response.close()
val solutionRequest = resolveWithWebView(chain.request()) networkHelper.cookieManager.remove(originalRequest.url(), listOf("__cfduid", "cf_clearance"), 0)
return chain.proceed(solutionRequest) 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) { } catch (e: Exception) {
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
// we don't crash the entire app // we don't crash the entire app
@ -57,19 +64,15 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
return response return response
} }
private fun isChallengeSolutionUrl(url: String): Boolean {
return "chk_jschl" in url
}
@SuppressLint("SetJavaScriptEnabled") @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 // We need to lock this thread until the WebView finds the challenge solution url, because
// OkHttp doesn't support asynchronous interceptors. // OkHttp doesn't support asynchronous interceptors.
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
var webView: WebView? = null var webView: WebView? = null
var solutionUrl: String? = null
var challengeFound = false var challengeFound = false
var cloudflareBypassed = false
val origRequestUrl = request.url().toString() val origRequestUrl = request.url().toString()
val headers = request.headers().toMultimap().mapValues { it.value.getOrNull(0) ?: "" } 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.settings.userAgentString = request.header("User-Agent")
view.webViewClient = object : WebViewClientCompat() { view.webViewClient = object : WebViewClientCompat() {
override fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean { override fun onPageFinished(view: WebView, url: String) {
if (isChallengeSolutionUrl(url)) { fun isCloudFlareBypassed(): Boolean {
solutionUrl = url return networkHelper.cookieManager.get(HttpUrl.parse(origRequestUrl)!!)
.firstOrNull { it.name() == "cf_clearance" }
.let { it != null && it != oldCookie }
}
if (isCloudFlareBypassed()) {
cloudflareBypassed = true
latch.countDown() 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 // Http error codes are only received since M
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
url == origRequestUrl && !challengeFound url == origRequestUrl && !challengeFound
@ -140,15 +134,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
webView?.destroy() webView?.destroy()
} }
val solution = solutionUrl ?: throw Exception("Challenge not found") return cloudflareBypassed
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()
} }
} }