Exit early in CloudflareInterceptor when cloudflare challenge is interactive (#3842)

This commit is contained in:
tigeryu8900
2026-08-23 03:50:31 -07:00
committed by GitHub
parent e3813028bd
commit ac249e3668
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.network.interceptor
import android.annotation.SuppressLint
import android.content.Context
import android.webkit.JavascriptInterface
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
@@ -32,18 +33,9 @@ class CloudflareInterceptor(
override fun shouldIntercept(response: Response): Boolean {
// Check if Cloudflare anti-bot is on
return if (response.code in ERROR_CODES && response.header("Server") in SERVER_CHECK) {
val document = Jsoup.parse(
response.peekBody(Long.MAX_VALUE).string(),
response.request.url.toString(),
)
// solve with webview only on captcha, not on geo block
document.getElementById("challenge-error-title") != null ||
document.getElementById("challenge-error-text") != null
} else {
false
}
// Checking the cf-mitigated header is the official way to detect a Cloudflare challenge:
// https://developers.cloudflare.com/cloudflare-challenges/challenge-types/challenge-pages/detect-response/
return response.header("cf-mitigated") == "challenge" && response.header("Server") in SERVER_CHECK
}
override fun intercept(
@@ -87,6 +79,18 @@ class CloudflareInterceptor(
executor.execute {
webview = createWebView(originalRequest)
webview.addJavascriptInterface(
object {
@Suppress("unused")
@JavascriptInterface
fun interactiveDetected() {
// The challenge cannot be solved non-interactively, abort.
latch.countDown()
}
},
"mihon",
)
webview.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
fun isCloudFlareBypassed(): Boolean {
@@ -100,9 +104,23 @@ class CloudflareInterceptor(
latch.countDown()
}
if (url == origRequestUrl && !challengeFound) {
if (url == origRequestUrl) {
if (!challengeFound) {
// The first request didn't return the challenge, abort.
latch.countDown()
} else {
// Listen for an interactiveBegin event
view.evaluateJavascript(
"""
addEventListener("message", ({data}) => {
if (data?.source === "cloudflare-challenge" && data?.event === "interactiveBegin") {
mihon.interactiveDetected();
}
})
""".trimIndent(),
null,
)
}
}
}
@@ -112,7 +130,7 @@ class CloudflareInterceptor(
errorResponse: WebResourceResponse?,
) {
if (request?.isForMainFrame == true) {
if (errorResponse?.statusCode in ERROR_CODES) {
if (errorResponse?.responseHeaders["cf-mitigated"] == "challenge") {
// Found the Cloudflare challenge page.
challengeFound = true
} else {
@@ -151,7 +169,6 @@ class CloudflareInterceptor(
}
}
private val ERROR_CODES = listOf(403, 503)
private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
private val COOKIE_NAMES = listOf("cf_clearance")