Fix CloudFlare protection detection (close #1129)

This commit is contained in:
Koitharu
2024-10-07 15:24:02 +03:00
parent 4faef85086
commit 9ea1122ca0
4 changed files with 37 additions and 39 deletions

View File

@@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu' applicationId 'org.koitharu.kotatsu'
minSdk = 21 minSdk = 21
targetSdk = 35 targetSdk = 35
versionCode = 675 versionCode = 676
versionName = '7.6.2' versionName = '7.6.3'
generatedDensities = [] generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp { ksp {
@@ -82,7 +82,7 @@ afterEvaluate {
} }
} }
dependencies { dependencies {
implementation('com.github.KotatsuApp:kotatsu-parsers:6f7e1fcfb2') { implementation('com.github.KotatsuApp:kotatsu-parsers:1ebb298cd7') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }

View File

@@ -32,6 +32,7 @@ import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.configureForParser import org.koitharu.kotatsu.core.util.ext.configureForParser
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
import javax.inject.Inject import javax.inject.Inject
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
@@ -175,8 +176,7 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
private suspend fun clearCfCookies(url: HttpUrl) = runInterruptible(Dispatchers.Default) { private suspend fun clearCfCookies(url: HttpUrl) = runInterruptible(Dispatchers.Default) {
cookieJar.removeCookies(url) { cookie -> cookieJar.removeCookies(url) { cookie ->
val name = cookie.name CloudFlareHelper.isCloudFlareCookie(cookie.name)
name.startsWith("cf_") || name.startsWith("_cf") || name.startsWith("__cf") || name == "csrftoken"
} }
} }

View File

@@ -2,11 +2,10 @@ package org.koitharu.kotatsu.browser.cloudflare
import android.graphics.Bitmap import android.graphics.Bitmap
import android.webkit.WebView import android.webkit.WebView
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.browser.BrowserClient import org.koitharu.kotatsu.browser.BrowserClient
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
private const val CF_CLEARANCE = "cf_clearance"
private const val LOOP_COUNTER = 3 private const val LOOP_COUNTER = 3
class CloudFlareClient( class CloudFlareClient(
@@ -50,8 +49,5 @@ class CloudFlareClient(
} }
} }
private fun getClearance(): String? { private fun getClearance() = CloudFlareHelper.getClearanceCookie(cookieJar, targetUrl)
return cookieJar.loadForRequest(targetUrl.toHttpUrl())
.find { it.name == CF_CLEARANCE }?.value
}
} }

View File

@@ -2,41 +2,43 @@ package org.koitharu.kotatsu.core.network
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import okhttp3.internal.closeQuietly import okio.IOException
import org.jsoup.Jsoup
import org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException import org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import java.net.HttpURLConnection.HTTP_FORBIDDEN import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
import java.net.HttpURLConnection.HTTP_UNAVAILABLE
class CloudFlareInterceptor : Interceptor { class CloudFlareInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request()) val request = chain.request()
if (response.code == HTTP_FORBIDDEN || response.code == HTTP_UNAVAILABLE) { val response = chain.proceed(request)
val content = response.body?.let { response.peekBody(Long.MAX_VALUE) }?.byteStream()?.use { return when (CloudFlareHelper.checkResponseForProtection(response)) {
Jsoup.parse(it, Charsets.UTF_8.name(), response.request.url.toString()) CloudFlareHelper.PROTECTION_BLOCKED -> response.closeThrowing(
} ?: return response CloudFlareBlockedException(
val hasCaptcha = content.getElementById("challenge-error-title") != null url = request.url.toString(),
val isBlocked = content.selectFirst("h2[data-translate=\"blocked_why_headline\"]") != null source = request.tag(MangaSource::class.java),
if (hasCaptcha || isBlocked) { ),
val request = response.request )
response.closeQuietly()
if (isBlocked) { CloudFlareHelper.PROTECTION_CAPTCHA -> response.closeThrowing(
throw CloudFlareBlockedException( CloudFlareProtectedException(
url = request.url.toString(), url = request.url.toString(),
source = request.tag(MangaSource::class.java), source = request.tag(MangaSource::class.java),
) headers = request.headers,
} else { ),
throw CloudFlareProtectedException( )
url = request.url.toString(),
source = request.tag(MangaSource::class.java), else -> response
headers = request.headers,
)
}
}
} }
return response }
private fun Response.closeThrowing(error: IOException): Nothing {
try {
close()
} catch (e: Exception) {
error.addSuppressed(e)
}
throw error
} }
} }