From 4e5469022999665c57f1570188ca66a980e22469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thiago=20Fran=C3=A7a=20da=20Silva?= Date: Mon, 9 Mar 2020 15:10:10 -0300 Subject: [PATCH] Add an UserAgent Interceptor to Cloudflare Client --- .../network/CloudflareInterceptor.kt | 28 ++++++------------- .../kanade/tachiyomi/network/NetworkHelper.kt | 1 + .../tachiyomi/network/UserAgentInterceptor.kt | 22 +++++++++++++++ .../tachiyomi/source/online/HttpSource.kt | 24 ++++++++-------- 4 files changed, 44 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/network/UserAgentInterceptor.kt 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 5f57390e9b..1aeb1c4fe9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt @@ -8,6 +8,7 @@ import android.webkit.WebSettings import android.webkit.WebView 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.isOutdated import eu.kanade.tachiyomi.util.system.toast @@ -55,18 +56,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { .firstOrNull { it.name == "cf_clearance" } resolveWithWebView(originalRequest, oldCookie) - // Avoid use empty User-Agent - return if (originalRequest.header("User-Agent").isNullOrEmpty()) { - val newRequest = originalRequest - .newBuilder() - .removeHeader("User-Agent") - .addHeader("User-Agent", - DEFAULT_USERAGENT) - .build() - chain.proceed(newRequest) - } else { - chain.proceed(originalRequest) - } + 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 @@ -84,11 +74,10 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { var challengeFound = false var cloudflareBypassed = false - var isWebviewOutdated = false + var isWebViewOutdated = false val origRequestUrl = request.url.toString() val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" } - val withUserAgent = request.header("User-Agent").isNullOrEmpty() handler.post { val webview = WebView(context) @@ -96,15 +85,15 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { webview.settings.javaScriptEnabled = true // Avoid set empty User-Agent, Chromium WebView will reset to default if empty - webview.settings.userAgentString = request.header("User-Agent") - ?: DEFAULT_USERAGENT + webview.settings.userAgentString = + request.header("User-Agent") ?: HttpSource.DEFAULT_USERAGENT webview.webViewClient = object : WebViewClientCompat() { override fun onPageFinished(view: WebView, url: String) { fun isCloudFlareBypassed(): Boolean { return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl()) .firstOrNull { it.name == "cf_clearance" } - .let { it != null && (it != oldCookie || withUserAgent) } + .let { it != null && it != oldCookie } } if (isCloudFlareBypassed()) { @@ -147,7 +136,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { handler.post { if (!cloudflareBypassed) { - isWebviewOutdated = webView?.isOutdated() == true + isWebViewOutdated = webView?.isOutdated() == true } webView?.stopLoading() @@ -157,7 +146,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { // Throw exception if we failed to bypass Cloudflare if (!cloudflareBypassed) { // Prompt user to update WebView if it seems too outdated - if (isWebviewOutdated) { + if (isWebViewOutdated) { context.toast(R.string.please_update_webview, Toast.LENGTH_LONG) } @@ -168,6 +157,5 @@ 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 const val DEFAULT_USERAGENT = "Mozilla/5.0 (Windows NT 6.3; WOW64)" } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index b789774461..b190d99c27 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -19,6 +19,7 @@ class NetworkHelper(context: Context) { .build() val cloudflareClient = client.newBuilder() + .addInterceptor(UserAgentInterceptor()) .addInterceptor(CloudflareInterceptor(context)) .build() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/UserAgentInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/UserAgentInterceptor.kt new file mode 100644 index 0000000000..52d37a77aa --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/network/UserAgentInterceptor.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.network + +import eu.kanade.tachiyomi.source.online.HttpSource +import okhttp3.Interceptor +import okhttp3.Response + +class UserAgentInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + + return if (originalRequest.header("User-Agent").isNullOrEmpty()) { + val newRequest = originalRequest + .newBuilder() + .removeHeader("User-Agent") + .addHeader("User-Agent", HttpSource.DEFAULT_USERAGENT) + .build() + chain.proceed(newRequest) + } else { + chain.proceed(originalRequest) + } + } +} 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 1a0e5b49f7..1c89199d61 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", "Mozilla/5.0 (Windows NT 6.3; WOW64)") + add("User-Agent", DEFAULT_USERAGENT) } /** @@ -207,14 +207,14 @@ abstract class HttpSource : CatalogueSource { * @param manga the manga to look for chapters. */ override fun fetchChapterList(manga: SManga): Observable> { - if (manga.status != SManga.LICENSED) { - return client.newCall(chapterListRequest(manga)) + return if (manga.status != SManga.LICENSED) { + client.newCall(chapterListRequest(manga)) .asObservableSuccess() .map { response -> chapterListParse(response) } } else { - return Observable.error(Exception("Licensed - No chapters to show")) + Observable.error(Exception("Licensed - No chapters to show")) } } @@ -340,16 +340,14 @@ abstract class HttpSource : CatalogueSource { * @param orig the full url. */ private fun getUrlWithoutDomain(orig: String): String { - try { + return try { val uri = URI(orig) var out = uri.path - if (uri.query != null) - out += "?" + uri.query - if (uri.fragment != null) - out += "#" + uri.fragment - return out + if (uri.query != null) out += "?" + uri.query + if (uri.fragment != null) out += "#" + uri.fragment + out } catch (e: URISyntaxException) { - return orig + orig } } @@ -367,4 +365,8 @@ abstract class HttpSource : CatalogueSource { * Returns the list of filters for the source. */ override fun getFilterList() = FilterList() + + companion object { + const val DEFAULT_USERAGENT = "Mozilla/5.0 (Windows NT 6.3; WOW64)" + } }