Bypass cloudflare WAF using webview in MyReadingManga (#8562)

* Bypass cloudflare WAF using webview

* MyReadingManga: remove single cookie when bypassing Cloudflare
This commit is contained in:
mountfox 2021-08-17 17:45:28 -04:00 committed by GitHub
parent 8c302fe615
commit 10c2dc0395
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 5 deletions

View File

@ -5,7 +5,7 @@ ext {
extName = 'MyReadingManga'
pkgNameSuffix = 'all.myreadingmanga'
extClass = '.MyReadingMangaFactory'
extVersionCode = 41
extVersionCode = 42
libVersion = '1.2'
containsNsfw = true
}

View File

@ -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<Application>()
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
}
}

View File

@ -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