diff --git a/src/all/myreadingmanga/build.gradle b/src/all/myreadingmanga/build.gradle index c13f5f942..6d5fe2409 100644 --- a/src/all/myreadingmanga/build.gradle +++ b/src/all/myreadingmanga/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'MyReadingManga' pkgNameSuffix = 'all.myreadingmanga' extClass = '.MyReadingMangaFactory' - extVersionCode = 41 + extVersionCode = 42 libVersion = '1.2' containsNsfw = true } diff --git a/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/CloudflareWafInterceptor.kt b/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/CloudflareWafInterceptor.kt new file mode 100644 index 000000000..566207353 --- /dev/null +++ b/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/CloudflareWafInterceptor.kt @@ -0,0 +1,108 @@ +package eu.kanade.tachiyomi.extension.all.myreadingmanga + +import android.annotation.SuppressLint +import android.app.Application +import android.os.Handler +import android.os.Looper +import android.webkit.CookieManager +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.io.IOException +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class CloudflareWafInterceptor(private val cookieDomain: String) : Interceptor { + private val context = Injekt.get() + private val handler = Handler(Looper.getMainLooper()) + private val cookieManager = CookieManager.getInstance() + + private val initWebView by lazy { + WebSettings.getDefaultUserAgent(context) + } + + @Synchronized + override fun intercept(chain: Interceptor.Chain): Response { + initWebView + + val originalRequest = chain.request() + val request = originalRequest.newBuilder() + .header("User-Agent", initWebView) + .build() + val response = chain.proceed(request) + + if (response.code != 403 || response.header("Server") !in SERVER_CHECK) { + return response + } + + try { + response.close() + + // Remove all cookies and retry the Cloudflare WAF in a webview. + // This is the key. + cookieManager.setCookie(request.url.toString(), "__cf_bm=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=$cookieDomain") + cookieManager.flush() + resolveWithWebView(request) + + return chain.proceed(request) + } catch (e: Exception) { + throw IOException(e) + } + } + + @SuppressLint("SetJavaScriptEnabled") + private fun resolveWithWebView(request: Request) { + val latch = CountDownLatch(1) + + var webView: WebView? = null + + val origRequestUrl = request.url.toString() + val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap() + headers.remove("cookie") + + handler.post { + val webview = WebView(context) + webView = webview + with(webview.settings) { + javaScriptEnabled = true + domStorageEnabled = true + databaseEnabled = true + useWideViewPort = false + loadWithOverviewMode = false + userAgentString = request.header("User-Agent") + } + + webview.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView, url: String) { + latch.countDown() + } + + override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) { + latch.countDown() + } + } + + webView?.loadUrl(origRequestUrl, headers) + } + + latch.await(TIMEOUT_SEC, TimeUnit.SECONDS) + + handler.post { + webView?.stopLoading() + webView?.destroy() + webView = null + } + } + + companion object { + private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare") + const val TIMEOUT_SEC: Long = 10 + } +} diff --git a/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt b/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt index ff3ef8510..0c959c724 100644 --- a/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt +++ b/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt @@ -14,7 +14,6 @@ import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup import okhttp3.CacheControl -import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -31,6 +30,7 @@ open class MyReadingManga(override val lang: String, private val siteLang: Strin override val name = "MyReadingManga" final override val baseUrl = "https://myreadingmanga.info" override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .addInterceptor(CloudflareWafInterceptor(".myreadingmanga.info")) .connectTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES) .retryOnConnectionFailure(true) @@ -38,9 +38,6 @@ open class MyReadingManga(override val lang: String, private val siteLang: Strin .build()!! override val supportsLatest = true - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Referer", baseUrl) - // Popular - Random override fun popularMangaRequest(page: Int): Request { return GET("$baseUrl/search/?wpsolr_sort=sort_by_random&wpsolr_page=$page&wpsolr_fq[0]=lang_str:$siteLang", headers) // Random Manga as returned by search