diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt
index a8fd4a2e18..59d77cf2ae 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt
@@ -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")
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt
index c63e079bd8..898a1260e1 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt
@@ -15,7 +15,10 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
-import eu.kanade.tachiyomi.util.system.*
+import eu.kanade.tachiyomi.util.system.WebViewClientCompat
+import eu.kanade.tachiyomi.util.system.getResourceColor
+import eu.kanade.tachiyomi.util.system.openInBrowser
+import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.invisible
import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.webview_activity.*
@@ -53,8 +56,6 @@ class WebViewActivity : BaseActivity() {
val url = intent.extras!!.getString(URL_KEY) ?: return
val headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
- webview.checkVersion()
-
webview.settings.javaScriptEnabled = true
webview.settings.userAgentString = source.headers["User-Agent"]
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/WebviewUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebviewUtil.kt
index 1d9da89963..83e864a05a 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/util/system/WebviewUtil.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebviewUtil.kt
@@ -1,19 +1,15 @@
package eu.kanade.tachiyomi.util.system
import android.webkit.WebView
-import android.widget.Toast
-import eu.kanade.tachiyomi.R
private val WEBVIEW_UA_VERSION_REGEX by lazy {
Regex(""".*Chrome/(\d+)\..*""")
}
-private const val MINIMUM_WEBVIEW_VERSION = 72
+private const val MINIMUM_WEBVIEW_VERSION = 70
-fun WebView.checkVersion() {
- if (getWebviewMajorVersion(this) < MINIMUM_WEBVIEW_VERSION) {
- this.context.toast(R.string.information_webview_outdated, Toast.LENGTH_LONG)
- }
+fun WebView.isOutdated(): Boolean {
+ return getWebviewMajorVersion(this) < MINIMUM_WEBVIEW_VERSION
}
// Based on https://stackoverflow.com/a/29218966
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f033e5e590..f9c73b03ba 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -521,6 +521,7 @@
No recently read manga
Your library is empty, add series to your library from the catalogues.
You have no categories. Hit the plus button to create one for organizing your library.
+ Failed to bypass Cloudflare
Please update the WebView app for better compatibility