Exit early in CloudflareInterceptor when cloudflare challenge is interactive (#3842)
This commit is contained in:
+34
-17
@@ -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) {
|
||||
// The first request didn't return the challenge, abort.
|
||||
latch.countDown()
|
||||
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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user