diff --git a/app/build.gradle b/app/build.gradle index 0d14e1a8a..02ee0fe0e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 35 - versionCode = 675 - versionName = '7.6.2' + versionCode = 676 + versionName = '7.6.3' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { @@ -82,7 +82,7 @@ afterEvaluate { } } dependencies { - implementation('com.github.KotatsuApp:kotatsu-parsers:6f7e1fcfb2') { + implementation('com.github.KotatsuApp:kotatsu-parsers:1ebb298cd7') { exclude group: 'org.json', module: 'json' } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareActivity.kt index b75711ae4..09738c113 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareActivity.kt @@ -32,6 +32,7 @@ import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.util.ext.configureForParser import org.koitharu.kotatsu.databinding.ActivityBrowserBinding import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.network.CloudFlareHelper import javax.inject.Inject import com.google.android.material.R as materialR @@ -175,8 +176,7 @@ class CloudFlareActivity : BaseActivity(), CloudFlareCal private suspend fun clearCfCookies(url: HttpUrl) = runInterruptible(Dispatchers.Default) { cookieJar.removeCookies(url) { cookie -> - val name = cookie.name - name.startsWith("cf_") || name.startsWith("_cf") || name.startsWith("__cf") || name == "csrftoken" + CloudFlareHelper.isCloudFlareCookie(cookie.name) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt index 7bdd0aada..e5bd0c5cb 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt @@ -2,11 +2,10 @@ package org.koitharu.kotatsu.browser.cloudflare import android.graphics.Bitmap import android.webkit.WebView -import okhttp3.HttpUrl.Companion.toHttpUrl import org.koitharu.kotatsu.browser.BrowserClient 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 class CloudFlareClient( @@ -50,8 +49,5 @@ class CloudFlareClient( } } - private fun getClearance(): String? { - return cookieJar.loadForRequest(targetUrl.toHttpUrl()) - .find { it.name == CF_CLEARANCE }?.value - } + private fun getClearance() = CloudFlareHelper.getClearanceCookie(cookieJar, targetUrl) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CloudFlareInterceptor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CloudFlareInterceptor.kt index 227a64035..5fdd13a8b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CloudFlareInterceptor.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/CloudFlareInterceptor.kt @@ -2,41 +2,43 @@ package org.koitharu.kotatsu.core.network import okhttp3.Interceptor import okhttp3.Response -import okhttp3.internal.closeQuietly -import org.jsoup.Jsoup +import okio.IOException import org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.parsers.model.MangaSource -import java.net.HttpURLConnection.HTTP_FORBIDDEN -import java.net.HttpURLConnection.HTTP_UNAVAILABLE +import org.koitharu.kotatsu.parsers.network.CloudFlareHelper class CloudFlareInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { - val response = chain.proceed(chain.request()) - if (response.code == HTTP_FORBIDDEN || response.code == HTTP_UNAVAILABLE) { - val content = response.body?.let { response.peekBody(Long.MAX_VALUE) }?.byteStream()?.use { - Jsoup.parse(it, Charsets.UTF_8.name(), response.request.url.toString()) - } ?: return response - val hasCaptcha = content.getElementById("challenge-error-title") != null - val isBlocked = content.selectFirst("h2[data-translate=\"blocked_why_headline\"]") != null - if (hasCaptcha || isBlocked) { - val request = response.request - response.closeQuietly() - if (isBlocked) { - throw CloudFlareBlockedException( - url = request.url.toString(), - source = request.tag(MangaSource::class.java), - ) - } else { - throw CloudFlareProtectedException( - url = request.url.toString(), - source = request.tag(MangaSource::class.java), - headers = request.headers, - ) - } - } + val request = chain.request() + val response = chain.proceed(request) + return when (CloudFlareHelper.checkResponseForProtection(response)) { + CloudFlareHelper.PROTECTION_BLOCKED -> response.closeThrowing( + CloudFlareBlockedException( + url = request.url.toString(), + source = request.tag(MangaSource::class.java), + ), + ) + + CloudFlareHelper.PROTECTION_CAPTCHA -> response.closeThrowing( + CloudFlareProtectedException( + url = request.url.toString(), + source = request.tag(MangaSource::class.java), + headers = request.headers, + ), + ) + + else -> response } - return response + } + + private fun Response.closeThrowing(error: IOException): Nothing { + try { + close() + } catch (e: Exception) { + error.addSuppressed(e) + } + throw error } }