Updates to cloudflare / useragent from upstream

Co-Authored-By: arkon <4098258+arkon@users.noreply.github.com>
This commit is contained in:
Jays2Kings 2021-04-14 18:30:00 -04:00
parent 9e3aaab95f
commit 331d2fcd9e
5 changed files with 68 additions and 33 deletions

View File

@ -10,7 +10,10 @@ import android.widget.Toast
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.system.WebViewClientCompat 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.isOutdated
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.setDefaultSettings
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import okhttp3.Cookie import okhttp3.Cookie
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@ -39,9 +42,17 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
@Synchronized @Synchronized
override fun intercept(chain: Interceptor.Chain): Response { 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 initWebView
val originalRequest = chain.request()
val response = chain.proceed(originalRequest) val response = chain.proceed(originalRequest)
// Check if Cloudflare anti-bot is on // Check if Cloudflare anti-bot is on
@ -77,16 +88,17 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
var isWebViewOutdated = false var isWebViewOutdated = 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) ?: "" }.toMutableMap()
headers["X-Requested-With"] = WebViewUtil.REQUESTED_WITH
handler.post { handler.post {
val webview = WebView(context) val webview = WebView(context)
webView = webview webView = webview
webview.settings.javaScriptEnabled = true webview.setDefaultSettings()
// Avoid set empty User-Agent, Chromium WebView will reset to default if empty // Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
webview.settings.userAgentString = webview.settings.userAgentString = request.header("User-Agent")
request.header("User-Agent") ?: HttpSource.DEFAULT_USERAGENT ?: HttpSource.DEFAULT_USER_AGENT
webview.webViewClient = object : WebViewClientCompat() { webview.webViewClient = object : WebViewClientCompat() {
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
@ -101,7 +113,6 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
latch.countDown() latch.countDown()
} }
// HTTP error codes are only received since M
if (url == origRequestUrl && !challengeFound) { if (url == origRequestUrl && !challengeFound) {
// The first request didn't return the challenge, abort. // The first request didn't return the challenge, abort.
latch.countDown() latch.countDown()
@ -117,7 +128,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
) { ) {
if (isMainFrame) { if (isMainFrame) {
if (errorCode == 503) { if (errorCode == 503) {
// Found the cloudflare challenge page. // Found the Cloudflare challenge page.
challengeFound = true challengeFound = true
} else { } else {
// Unlock thread, the challenge wasn't found. // Unlock thread, the challenge wasn't found.
@ -156,6 +167,6 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
companion object { companion object {
private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare") private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
private val COOKIE_NAMES = listOf("__cfduid", "cf_clearance") private val COOKIE_NAMES = listOf("cf_clearance")
} }
} }

View File

@ -12,7 +12,7 @@ class UserAgentInterceptor : Interceptor {
val newRequest = originalRequest val newRequest = originalRequest
.newBuilder() .newBuilder()
.removeHeader("User-Agent") .removeHeader("User-Agent")
.addHeader("User-Agent", HttpSource.DEFAULT_USERAGENT) .addHeader("User-Agent", HttpSource.DEFAULT_USER_AGENT)
.build() .build()
chain.proceed(newRequest) chain.proceed(newRequest)
} else { } else {

View File

@ -74,7 +74,7 @@ abstract class HttpSource : CatalogueSource {
* Headers builder for requests. Implementations can override this method for custom headers. * Headers builder for requests. Implementations can override this method for custom headers.
*/ */
protected open fun headersBuilder() = Headers.Builder().apply { 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() override fun getFilterList() = FilterList()
companion object { 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"
} }
} }

View File

@ -1,8 +1,37 @@
package eu.kanade.tachiyomi.util.system 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.WebSettings
import android.webkit.WebView 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() { fun WebView.setDefaultSettings() {
with(settings) { with(settings) {
javaScriptEnabled = true javaScriptEnabled = true
@ -15,32 +44,25 @@ fun WebView.setDefaultSettings() {
} }
} }
private val WEBVIEW_UA_VERSION_REGEX by lazy { private fun WebView.getWebViewMajorVersion(): Int {
Regex(""".*Chrome/(\d+)\..*""") val uaRegexMatch = """.*Chrome/(\d+)\..*""".toRegex().matchEntire(getDefaultUserAgentString())
} return if (uaRegexMatch != null && uaRegexMatch.groupValues.size > 1) {
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) {
uaRegexMatch.groupValues[1].toInt() uaRegexMatch.groupValues[1].toInt()
} else { } else {
0 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 // Revert to original UA string
webview.settings.userAgentString = originalUA settings.userAgentString = originalUA
return webViewVersion return defaultUserAgentString
} }

View File

@ -788,6 +788,8 @@
<!-- Webview --> <!-- Webview -->
<string name="failed_to_bypass_cloudflare">Failed to bypass Cloudflare</string> <string name="failed_to_bypass_cloudflare">Failed to bypass Cloudflare</string>
<string name="please_update_webview">Please update the WebView app for better compatibility</string> <string name="please_update_webview">Please update the WebView app for better compatibility</string>
<!-- Do not translate "WebView" -->
<string name="webview_is_required">WebView is required for Tachiyomi</string>
<!-- Miscellaneous --> <!-- Miscellaneous -->
<string name="add">Add</string> <string name="add">Add</string>