Refine Cloudflare challenges detection

This commit is contained in:
FooIbar authored and tarsin committed 2023-08-04 05:46:51 +08:00
1 parent 5422c04c53
commit d98292da1d
3 files changed
+22 -19

No files matched your search

@@ -111,7 +111,6 @@ private fun rethrowExactly(code: Int, headers: Headers, body: String, e: Throwab
// Check bad response code
if (code >= 400) {
Log.d(TAG, "Code: $code\nHeaders: $headers\nBody: $body")
throw StatusCodeException(code)
}
@@ -0,0 +1,3 @@
package com.hippo.ehviewer.client.exception
class CloudflareBypassException : EhException("Failed to bypass Cloudflare")
@@ -1,14 +1,13 @@
package eu.kanade.tachiyomi.network.interceptor
import android.content.Context
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.content.ContextCompat
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.exception.EhException
import eu.kanade.tachiyomi.util.system.logcat
import com.hippo.ehviewer.client.exception.CloudflareBypassException
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.Request
@@ -20,7 +19,7 @@ class CloudflareInterceptor(context: Context) : WebViewInterceptor(context) {
private val executor = ContextCompat.getMainExecutor(context)
override fun shouldIntercept(response: Response): Boolean {
return response.header("cf-mitigated") == "challenge"
return response.header(HEADER_NAME) == HEADER_VALUE
}
override fun intercept(
@@ -31,13 +30,9 @@ class CloudflareInterceptor(context: Context) : WebViewInterceptor(context) {
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
// we don't crash the entire app
return runCatching {
// TODO: Remove logging when it's stable
logcat { "Response code: ${response.code}" }
logcat { "Response body: ${response.body.string()}" }
response.close()
EhCookieStore.deleteCookie(request.url, COOKIE_NAME)
resolveWithWebView(request)
chain.proceed(request)
}.getOrElse { throw IOException(it) }
}
@@ -76,17 +71,19 @@ class CloudflareInterceptor(context: Context) : WebViewInterceptor(context) {
}
}
override fun onReceivedError(
override fun onReceivedHttpError(
view: WebView,
request: WebResourceRequest,
error: WebResourceError,
errorResponse: WebResourceResponse,
) {
if (request.isForMainFrame && error.errorCode in ERROR_CODES) {
// Found the Cloudflare challenge page.
challengeFound = true
} else {
// Unlock thread, the challenge wasn't found.
latch.countDown()
if (request.isForMainFrame) {
if (errorResponse.responseHeaders[HEADER_NAME] == HEADER_VALUE) {
// Found the Cloudflare challenge page.
challengeFound = true
} else {
// Unlock thread, the challenge wasn't found.
latch.countDown()
}
}
}
}
@@ -104,9 +101,13 @@ class CloudflareInterceptor(context: Context) : WebViewInterceptor(context) {
}
// Throw exception if we failed to bypass Cloudflare
if (!cloudflareBypassed) throw EhException("Failed to bypass Cloudflare")
if (!cloudflareBypassed) {
// TODO: Prompt user to open url in WebView and finish the challenge manually
throw CloudflareBypassException()
}
}
}
private val ERROR_CODES = intArrayOf(403, 503)
private const val COOKIE_NAME = "cf_clearance"
private const val HEADER_NAME = "cf-mitigated"
private const val HEADER_VALUE = "challenge"