mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
Manually handle HTTP 103 through WebView (#7907)
* Manually handle HTTP 103 through WebView * Review Changes
This commit is contained in:
parent
c39a1b7867
commit
81f49f34ef
2 changed files with 159 additions and 0 deletions
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.network
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
|
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.Http103Interceptor
|
||||||
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
|
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
@ -21,6 +22,8 @@ class NetworkHelper(context: Context) {
|
||||||
|
|
||||||
val cookieManager = AndroidCookieJar()
|
val cookieManager = AndroidCookieJar()
|
||||||
|
|
||||||
|
private val http103Interceptor = Http103Interceptor(context)
|
||||||
|
|
||||||
private val baseClientBuilder: OkHttpClient.Builder
|
private val baseClientBuilder: OkHttpClient.Builder
|
||||||
get() {
|
get() {
|
||||||
val builder = OkHttpClient.Builder()
|
val builder = OkHttpClient.Builder()
|
||||||
|
@ -30,6 +33,7 @@ class NetworkHelper(context: Context) {
|
||||||
.callTimeout(2, TimeUnit.MINUTES)
|
.callTimeout(2, TimeUnit.MINUTES)
|
||||||
// .fastFallback(true) // TODO: re-enable when OkHttp 5 is stabler
|
// .fastFallback(true) // TODO: re-enable when OkHttp 5 is stabler
|
||||||
.addInterceptor(UserAgentInterceptor())
|
.addInterceptor(UserAgentInterceptor())
|
||||||
|
.addNetworkInterceptor(http103Interceptor)
|
||||||
|
|
||||||
if (preferences.verboseLogging()) {
|
if (preferences.verboseLogging()) {
|
||||||
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
package eu.kanade.tachiyomi.network.interceptor
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.webkit.JavascriptInterface
|
||||||
|
import android.webkit.WebSettings
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
||||||
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.Protocol
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
// TODO: Remove when OkHttp can handle http 103 responses
|
||||||
|
class Http103Interceptor(private val context: Context) : Interceptor {
|
||||||
|
|
||||||
|
private val executor = ContextCompat.getMainExecutor(context)
|
||||||
|
|
||||||
|
private val networkHelper: NetworkHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When this is called, it initializes the WebView if it wasn't already. We use this to avoid
|
||||||
|
* blocking the main thread too much. If used too often we could consider moving it to the
|
||||||
|
* Application class.
|
||||||
|
*/
|
||||||
|
private val initWebView by lazy {
|
||||||
|
// Crashes on some devices. We skip this in some cases since the only impact is slower
|
||||||
|
// WebView init in those rare cases.
|
||||||
|
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1279562
|
||||||
|
if (DeviceUtil.isMiui || Build.VERSION.SDK_INT == Build.VERSION_CODES.S && DeviceUtil.isSamsung) {
|
||||||
|
return@lazy
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSettings.getDefaultUserAgent(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
val response = chain.proceed(request)
|
||||||
|
if (response.code != 103) return response
|
||||||
|
if (!WebViewUtil.supportsWebView(context)) {
|
||||||
|
launchUI {
|
||||||
|
context.toast(R.string.information_webview_required, Toast.LENGTH_LONG)
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
initWebView
|
||||||
|
|
||||||
|
logcat { "Proceeding with WebView for request $request" }
|
||||||
|
try {
|
||||||
|
return proceedWithWebView(request, response)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IOException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class JsInterface(private val latch: CountDownLatch, var payload: String? = null) {
|
||||||
|
@JavascriptInterface
|
||||||
|
fun passPayload(passedPayload: String) {
|
||||||
|
payload = passedPayload
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val jsScript = "window.android.passPayload(document.querySelector('html').outerHTML)"
|
||||||
|
|
||||||
|
val htmlMediaType = "text/html".toMediaType()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
|
||||||
|
private fun proceedWithWebView(ogRequest: Request, ogResponse: Response): Response {
|
||||||
|
// We need to lock this thread until the WebView finds the challenge solution url, because
|
||||||
|
// OkHttp doesn't support asynchronous interceptors.
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
|
||||||
|
val jsInterface = JsInterface(latch)
|
||||||
|
|
||||||
|
var outerWebView: WebView? = null
|
||||||
|
|
||||||
|
var exception: Exception? = null
|
||||||
|
|
||||||
|
val requestUrl = ogRequest.url.toString()
|
||||||
|
val headers = ogRequest.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||||
|
|
||||||
|
executor.execute {
|
||||||
|
val webview = WebView(context).also { outerWebView = it }
|
||||||
|
with(webview.settings) {
|
||||||
|
javaScriptEnabled = true
|
||||||
|
userAgentString = ogRequest.header("User-Agent") ?: networkHelper.defaultUserAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
webview.addJavascriptInterface(jsInterface, "android")
|
||||||
|
|
||||||
|
webview.webViewClient = object : WebViewClientCompat() {
|
||||||
|
override fun onPageFinished(view: WebView, url: String) {
|
||||||
|
view.evaluateJavascript(jsScript) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceivedErrorCompat(
|
||||||
|
view: WebView,
|
||||||
|
errorCode: Int,
|
||||||
|
description: String?,
|
||||||
|
failingUrl: String,
|
||||||
|
isMainFrame: Boolean,
|
||||||
|
) {
|
||||||
|
if (isMainFrame) {
|
||||||
|
exception = Exception("Error $errorCode - $description")
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webview.loadUrl(requestUrl, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
latch.await(10, TimeUnit.SECONDS)
|
||||||
|
|
||||||
|
executor.execute {
|
||||||
|
outerWebView?.run {
|
||||||
|
stopLoading()
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
outerWebView = null
|
||||||
|
}
|
||||||
|
|
||||||
|
exception?.let { throw it }
|
||||||
|
|
||||||
|
val payload = jsInterface.payload ?: throw Exception("Couldn't fetch site through webview")
|
||||||
|
|
||||||
|
return ogResponse.newBuilder()
|
||||||
|
.code(200)
|
||||||
|
.protocol(Protocol.HTTP_1_1)
|
||||||
|
.message("OK")
|
||||||
|
.body(payload.toResponseBody(htmlMediaType))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue