|
@@ -7,8 +7,11 @@ import android.os.Handler
|
|
|
import android.os.Looper
|
|
|
import android.webkit.WebSettings
|
|
|
import android.webkit.WebView
|
|
|
+import android.widget.Toast
|
|
|
+import eu.kanade.tachiyomi.R
|
|
|
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
|
|
-import eu.kanade.tachiyomi.util.system.checkVersion
|
|
|
+import eu.kanade.tachiyomi.util.system.isOutdated
|
|
|
+import eu.kanade.tachiyomi.util.system.toast
|
|
|
import okhttp3.Cookie
|
|
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
|
import okhttp3.Interceptor
|
|
@@ -21,8 +24,6 @@ import java.util.concurrent.TimeUnit
|
|
|
|
|
|
class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|
|
|
|
|
- private val serverCheck = arrayOf("cloudflare-nginx", "cloudflare")
|
|
|
-
|
|
|
private val handler = Handler(Looper.getMainLooper())
|
|
|
|
|
|
private val networkHelper: NetworkHelper by injectLazy()
|
|
@@ -44,36 +45,35 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|
|
val response = chain.proceed(originalRequest)
|
|
|
|
|
|
// Check if Cloudflare anti-bot is on
|
|
|
- if (response.code == 503 && response.header("Server") in serverCheck) {
|
|
|
- try {
|
|
|
- response.close()
|
|
|
- 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
|
|
|
- throw IOException(e)
|
|
|
- }
|
|
|
+ if (response.code != 503 || response.header("Server") !in SERVER_CHECK) {
|
|
|
+ return response
|
|
|
}
|
|
|
|
|
|
- return response
|
|
|
+ try {
|
|
|
+ response.close()
|
|
|
+ networkHelper.cookieManager.remove(originalRequest.url, COOKIE_NAMES, 0)
|
|
|
+ val oldCookie = networkHelper.cookieManager.get(originalRequest.url)
|
|
|
+ .firstOrNull { it.name == "cf_clearance" }
|
|
|
+ resolveWithWebView(originalRequest, oldCookie)
|
|
|
+ return chain.proceed(originalRequest)
|
|
|
+ } catch (e: Exception) {
|
|
|
+ // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
|
|
|
+ // we don't crash the entire app
|
|
|
+ throw IOException(e)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@SuppressLint("SetJavaScriptEnabled")
|
|
|
- private fun resolveWithWebView(request: Request, oldCookie: Cookie?): Boolean {
|
|
|
+ private fun resolveWithWebView(request: 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)
|
|
|
|
|
|
var webView: WebView? = null
|
|
|
+
|
|
|
var challengeFound = false
|
|
|
var cloudflareBypassed = false
|
|
|
+ var isWebviewOutdated = false
|
|
|
|
|
|
val origRequestUrl = request.url.toString()
|
|
|
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
|
|
@@ -82,8 +82,6 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|
|
val webview = WebView(context)
|
|
|
webView = webview
|
|
|
|
|
|
- webview.checkVersion()
|
|
|
-
|
|
|
webview.settings.javaScriptEnabled = true
|
|
|
webview.settings.userAgentString = request.header("User-Agent")
|
|
|
|
|
@@ -135,10 +133,28 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|
|
latch.await(12, TimeUnit.SECONDS)
|
|
|
|
|
|
handler.post {
|
|
|
+ if (!cloudflareBypassed) {
|
|
|
+ isWebviewOutdated = webView?.isOutdated() == true
|
|
|
+ }
|
|
|
+
|
|
|
webView?.stopLoading()
|
|
|
webView?.destroy()
|
|
|
}
|
|
|
- return cloudflareBypassed
|
|
|
+
|
|
|
+ // Throw exception if we failed to bypass Cloudflare
|
|
|
+ if (!cloudflareBypassed) {
|
|
|
+ // Prompt user to update WebView if it seems too outdated
|
|
|
+ if (isWebviewOutdated) {
|
|
|
+ context.toast(R.string.information_webview_outdated, Toast.LENGTH_LONG)
|
|
|
+ }
|
|
|
+
|
|
|
+ throw Exception(context.getString(R.string.information_cloudflare_bypass_failure))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ companion object {
|
|
|
+ private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
|
|
|
+ private val COOKIE_NAMES = listOf("__cfduid", "cf_clearance")
|
|
|
}
|
|
|
|
|
|
}
|