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 1aeb1c4fe9..2fe60bb098 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt
@@ -10,7 +10,10 @@ import android.widget.Toast
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
+import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.isOutdated
+import eu.kanade.tachiyomi.util.system.launchUI
+import eu.kanade.tachiyomi.util.system.setDefaultSettings
import eu.kanade.tachiyomi.util.system.toast
import okhttp3.Cookie
import okhttp3.HttpUrl.Companion.toHttpUrl
@@ -39,9 +42,17 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
@Synchronized
override fun intercept(chain: Interceptor.Chain): Response {
+ val originalRequest = chain.request()
+
+ if (!WebViewUtil.supportsWebView(context)) {
+ launchUI {
+ context.toast(R.string.webview_is_required, Toast.LENGTH_LONG)
+ }
+ return chain.proceed(originalRequest)
+ }
+
initWebView
- val originalRequest = chain.request()
val response = chain.proceed(originalRequest)
// Check if Cloudflare anti-bot is on
@@ -77,16 +88,17 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
var isWebViewOutdated = false
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) ?: "" }.toMutableMap()
+ headers["X-Requested-With"] = WebViewUtil.REQUESTED_WITH
handler.post {
val webview = WebView(context)
webView = webview
- webview.settings.javaScriptEnabled = true
+ webview.setDefaultSettings()
- // Avoid set empty User-Agent, Chromium WebView will reset to default if empty
- webview.settings.userAgentString =
- request.header("User-Agent") ?: HttpSource.DEFAULT_USERAGENT
+ // Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
+ webview.settings.userAgentString = request.header("User-Agent")
+ ?: HttpSource.DEFAULT_USER_AGENT
webview.webViewClient = object : WebViewClientCompat() {
override fun onPageFinished(view: WebView, url: String) {
@@ -101,7 +113,6 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
latch.countDown()
}
- // HTTP error codes are only received since M
if (url == origRequestUrl && !challengeFound) {
// The first request didn't return the challenge, abort.
latch.countDown()
@@ -117,7 +128,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
) {
if (isMainFrame) {
if (errorCode == 503) {
- // Found the cloudflare challenge page.
+ // Found the Cloudflare challenge page.
challengeFound = true
} else {
// Unlock thread, the challenge wasn't found.
@@ -156,6 +167,6 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
companion object {
private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
- private val COOKIE_NAMES = listOf("__cfduid", "cf_clearance")
+ private val COOKIE_NAMES = listOf("cf_clearance")
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/UserAgentInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/UserAgentInterceptor.kt
index a5aa3e4f51..e1669472de 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/network/UserAgentInterceptor.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/network/UserAgentInterceptor.kt
@@ -12,7 +12,7 @@ class UserAgentInterceptor : Interceptor {
val newRequest = originalRequest
.newBuilder()
.removeHeader("User-Agent")
- .addHeader("User-Agent", HttpSource.DEFAULT_USERAGENT)
+ .addHeader("User-Agent", HttpSource.DEFAULT_USER_AGENT)
.build()
chain.proceed(newRequest)
} else {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
index 9d0784056b..1ef533f810 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
@@ -74,7 +74,7 @@ abstract class HttpSource : CatalogueSource {
* Headers builder for requests. Implementations can override this method for custom headers.
*/
protected open fun headersBuilder() = Headers.Builder().apply {
- add("User-Agent", DEFAULT_USERAGENT)
+ add("User-Agent", DEFAULT_USER_AGENT)
}
/**
@@ -371,6 +371,6 @@ abstract class HttpSource : CatalogueSource {
override fun getFilterList() = FilterList()
companion object {
- const val DEFAULT_USERAGENT = "Mozilla/5.0 (Windows NT 6.3; WOW64)"
+ const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63"
}
}
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 ed6cdbdbe9..9c5cc349e0 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,8 +1,37 @@
package eu.kanade.tachiyomi.util.system
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.pm.PackageManager
+import android.webkit.CookieManager
import android.webkit.WebSettings
import android.webkit.WebView
+import timber.log.Timber
+object WebViewUtil {
+ const val REQUESTED_WITH = "com.android.browser"
+
+ const val MINIMUM_WEBVIEW_VERSION = 88
+
+ fun supportsWebView(context: Context): Boolean {
+ try {
+ // May throw android.webkit.WebViewFactory$MissingWebViewPackageException if WebView
+ // is not installed
+ CookieManager.getInstance()
+ } catch (e: Throwable) {
+ Timber.e(e)
+ return false
+ }
+
+ return context.packageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)
+ }
+}
+
+fun WebView.isOutdated(): Boolean {
+ return getWebViewMajorVersion() < WebViewUtil.MINIMUM_WEBVIEW_VERSION
+}
+
+@SuppressLint("SetJavaScriptEnabled")
fun WebView.setDefaultSettings() {
with(settings) {
javaScriptEnabled = true
@@ -15,32 +44,25 @@ fun WebView.setDefaultSettings() {
}
}
-private val WEBVIEW_UA_VERSION_REGEX by lazy {
- Regex(""".*Chrome/(\d+)\..*""")
-}
-
-private const val MINIMUM_WEBVIEW_VERSION = 86
-
-fun WebView.isOutdated(): Boolean {
- return getWebviewMajorVersion(this) < MINIMUM_WEBVIEW_VERSION
-}
-
-// Based on https://stackoverflow.com/a/29218966
-private fun getWebviewMajorVersion(webview: WebView): Int {
- val originalUA: String = webview.settings.userAgentString
-
- // Next call to getUserAgentString() will get us the default
- webview.settings.userAgentString = null
-
- val uaRegexMatch = WEBVIEW_UA_VERSION_REGEX.matchEntire(webview.settings.userAgentString)
- val webViewVersion: Int = if (uaRegexMatch != null && uaRegexMatch.groupValues.size > 1) {
+private fun WebView.getWebViewMajorVersion(): Int {
+ val uaRegexMatch = """.*Chrome/(\d+)\..*""".toRegex().matchEntire(getDefaultUserAgentString())
+ return if (uaRegexMatch != null && uaRegexMatch.groupValues.size > 1) {
uaRegexMatch.groupValues[1].toInt()
} else {
0
}
+}
+
+// Based on https://stackoverflow.com/a/29218966
+private fun WebView.getDefaultUserAgentString(): String {
+ val originalUA: String = settings.userAgentString
+
+ // Next call to getUserAgentString() will get us the default
+ settings.userAgentString = null
+ val defaultUserAgentString = settings.userAgentString
// Revert to original UA string
- webview.settings.userAgentString = originalUA
+ settings.userAgentString = originalUA
- return webViewVersion
+ return defaultUserAgentString
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fcfdb5c326..275d84dde3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -788,6 +788,8 @@
Failed to bypass Cloudflare
Please update the WebView app for better compatibility
+
+ WebView is required for Tachiyomi
Add