From 995c9098db7ab76545f7698c27241c8da79b24d4 Mon Sep 17 00:00:00 2001 From: gedoor Date: Sun, 26 Sep 2021 10:29:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/help/JsExtensions.kt | 11 +- .../io/legado/app/help/http/AjaxWebView.kt | 236 ------------------ .../legado/app/help/http/BackstageWebView.kt | 208 +++++++++++++++ .../io/legado/app/help/http/HttpHelper.kt | 34 +-- .../app/model/analyzeRule/AnalyzeUrl.kt | 34 ++- 5 files changed, 254 insertions(+), 269 deletions(-) delete mode 100644 app/src/main/java/io/legado/app/help/http/AjaxWebView.kt create mode 100644 app/src/main/java/io/legado/app/help/http/BackstageWebView.kt diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index 285131cc9..bfc2cf0b5 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -108,9 +108,14 @@ interface JsExtensions { * @param js 用来取返回值的js语句, 没有就返回整个源代码 * @return 返回js获取的内容 */ - fun webView(html: String?, url: String?, js: String?): String { - //TODO - return "" + fun webView(html: String?, url: String?, js: String?): String? { + return runBlocking { + BackstageWebView( + url = url, + html = html, + javaScript = js + ).getStrResponse().body + } } /** diff --git a/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt b/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt deleted file mode 100644 index 8bf30ba8d..000000000 --- a/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt +++ /dev/null @@ -1,236 +0,0 @@ -package io.legado.app.help.http - -import android.annotation.SuppressLint -import android.os.Handler -import android.os.Looper -import android.os.Message -import android.text.TextUtils -import android.webkit.CookieManager -import android.webkit.WebSettings -import android.webkit.WebView -import android.webkit.WebViewClient -import io.legado.app.constant.AppConst -import org.apache.commons.text.StringEscapeUtils -import splitties.init.appCtx -import java.lang.ref.WeakReference - - -class AjaxWebView { - var callback: Callback? = null - private var mHandler: AjaxHandler = AjaxHandler(this) - - class AjaxHandler(private val ajaxWebView: AjaxWebView) : Handler(Looper.getMainLooper()) { - - private var mWebView: WebView? = null - - override fun handleMessage(msg: Message) { - val params: AjaxParams - when (msg.what) { - MSG_AJAX_START -> { - params = msg.obj as AjaxParams - mWebView = createAjaxWebView(params, this) - } - MSG_SNIFF_START -> { - params = msg.obj as AjaxParams - mWebView = createAjaxWebView(params, this) - } - MSG_SUCCESS -> { - ajaxWebView.callback?.onResult(msg.obj as StrResponse) - destroyWebView() - } - MSG_ERROR -> { - ajaxWebView.callback?.onError(msg.obj as Throwable) - destroyWebView() - } - } - } - - @SuppressLint("SetJavaScriptEnabled", "JavascriptInterface") - fun createAjaxWebView(params: AjaxParams, handler: Handler): WebView { - val webView = WebView(appCtx) - val settings = webView.settings - settings.javaScriptEnabled = true - settings.domStorageEnabled = true - settings.blockNetworkImage = true - settings.userAgentString = params.userAgent - settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW - if (params.isSniff) { - webView.webViewClient = SnifferWebClient(params, handler) - } else { - webView.webViewClient = HtmlWebViewClient(params, handler) - } - when (params.requestMethod) { - RequestMethod.POST -> params.postData?.let { - webView.postUrl(params.url, it) - } - RequestMethod.GET -> params.headerMap?.let { - webView.loadUrl(params.url, it) - } - } - return webView - } - - private fun destroyWebView() { - mWebView?.destroy() - mWebView = null - } - } - - fun load(params: AjaxParams) { - if (params.sourceRegex != "") { - mHandler.obtainMessage(MSG_SNIFF_START, params) - .sendToTarget() - } else { - mHandler.obtainMessage(MSG_AJAX_START, params) - .sendToTarget() - } - } - - fun destroyWebView() { - mHandler.obtainMessage(DESTROY_WEB_VIEW) - } - - class AjaxParams(val url: String) { - var tag: String? = null - var requestMethod = RequestMethod.GET - var postData: ByteArray? = null - var headerMap: Map? = null - var sourceRegex: String? = null - var javaScript: String? = null - - fun getJs(): String { - javaScript?.let { - if (it.isNotEmpty()) { - return it - } - } - return JS - } - - val userAgent: String? - get() = this.headerMap?.get(AppConst.UA_NAME) - - val isSniff: Boolean - get() = !TextUtils.isEmpty(sourceRegex) - - fun setCookie(url: String) { - tag?.let { - val cookie = CookieManager.getInstance().getCookie(url) - CookieStore.setCookie(it, cookie) - } - } - - fun hasJavaScript(): Boolean { - return !TextUtils.isEmpty(javaScript) - } - - fun clearJavaScript() { - javaScript = null - } - - } - - private class HtmlWebViewClient( - private val params: AjaxParams, - private val handler: Handler - ) : WebViewClient() { - - override fun onPageFinished(view: WebView, url: String) { - params.setCookie(url) - val runnable = EvalJsRunnable(view, url, params.getJs(), handler) - handler.postDelayed(runnable, 1000) - } - - } - - private class EvalJsRunnable( - webView: WebView, - private val url: String, - private val mJavaScript: String, - private val handler: Handler - ) : Runnable { - var retry = 0 - private val mWebView: WeakReference = WeakReference(webView) - override fun run() { - mWebView.get()?.evaluateJavascript(mJavaScript) { - if (it.isNotEmpty() && it != "null") { - val content = StringEscapeUtils.unescapeJson(it) - .replace("^\"|\"$".toRegex(), "") - try { - val response = StrResponse(url, content) - handler.obtainMessage(MSG_SUCCESS, response).sendToTarget() - } catch (e: Exception) { - handler.obtainMessage(MSG_ERROR, e).sendToTarget() - } - handler.removeCallbacks(this) - return@evaluateJavascript - } - if (retry > 30) { - handler.obtainMessage(MSG_ERROR, Exception("js执行超时")) - .sendToTarget() - handler.removeCallbacks(this) - return@evaluateJavascript - } - retry++ - handler.removeCallbacks(this) - handler.postDelayed(this, 1000) - } - } - } - - private class SnifferWebClient( - private val params: AjaxParams, - private val handler: Handler - ) : WebViewClient() { - - override fun onLoadResource(view: WebView, url: String) { - params.sourceRegex?.let { - if (url.matches(it.toRegex())) { - try { - val response = StrResponse(params.url, url) - handler.obtainMessage(MSG_SUCCESS, response).sendToTarget() - } catch (e: Exception) { - handler.obtainMessage(MSG_ERROR, e).sendToTarget() - } - } - } - } - - override fun onPageFinished(view: WebView, url: String) { - params.setCookie(url) - if (params.hasJavaScript()) { - evaluateJavascript(view, params.javaScript) - params.clearJavaScript() - } - } - - private fun evaluateJavascript(webView: WebView, javaScript: String?) { - val runnable = LoadJsRunnable(webView, javaScript) - handler.postDelayed(runnable, 1000L) - } - } - - private class LoadJsRunnable( - webView: WebView, - private val mJavaScript: String? - ) : Runnable { - private val mWebView: WeakReference = WeakReference(webView) - override fun run() { - mWebView.get()?.loadUrl("javascript:${mJavaScript ?: ""}") - } - } - - companion object { - const val MSG_AJAX_START = 0 - const val MSG_SNIFF_START = 1 - const val MSG_SUCCESS = 2 - const val MSG_ERROR = 3 - const val DESTROY_WEB_VIEW = 4 - const val JS = "document.documentElement.outerHTML" - } - - abstract class Callback { - abstract fun onResult(response: StrResponse) - abstract fun onError(error: Throwable) - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/BackstageWebView.kt b/app/src/main/java/io/legado/app/help/http/BackstageWebView.kt new file mode 100644 index 000000000..9cde863d9 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/BackstageWebView.kt @@ -0,0 +1,208 @@ +package io.legado.app.help.http + +import android.annotation.SuppressLint +import android.os.Handler +import android.os.Looper +import android.webkit.CookieManager +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import io.legado.app.constant.AppConst +import io.legado.app.model.NoStackTraceException +import io.legado.app.utils.runOnUI +import kotlinx.coroutines.* +import org.apache.commons.text.StringEscapeUtils +import splitties.init.appCtx +import java.lang.ref.WeakReference +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.resume + + +class BackstageWebView( + private val url: String? = null, + private val html: String? = null, + private val encode: String? = null, + private val tag: String? = null, + private val headerMap: Map? = null, + private val sourceRegex: String? = null, + private val javaScript: String? = null, +) : CoroutineScope { + private lateinit var job: Job + private val mHandler = Handler(Looper.getMainLooper()) + private var callback: Callback? = null + private var mWebView: WebView? = null + + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + job + + suspend fun getStrResponse(): StrResponse = suspendCancellableCoroutine { block -> + job = Job() + block.invokeOnCancellation { + destroy() + } + callback = object : BackstageWebView.Callback() { + override fun onResult(response: StrResponse) { + if (!block.isCompleted) + block.resume(response) + } + + override fun onError(error: Throwable) { + if (!block.isCompleted) + block.cancel(error) + } + } + runOnUI { + load() + } + } + + private fun getEncoding(): String { + return encode ?: "utf-8" + } + + private fun load() { + mWebView = createWebView().apply { + when { + !html.isNullOrEmpty() -> if (url.isNullOrEmpty()) { + loadData(html, "text/html", getEncoding()) + } else { + loadDataWithBaseURL(url, html, "text/html", getEncoding(), url) + } + else -> if (headerMap == null) { + loadUrl(url!!) + } else { + loadUrl(url!!, headerMap) + } + } + } + } + + @SuppressLint("SetJavaScriptEnabled", "JavascriptInterface") + private fun createWebView(): WebView { + val webView = WebView(appCtx) + val settings = webView.settings + settings.javaScriptEnabled = true + settings.domStorageEnabled = true + settings.blockNetworkImage = true + settings.userAgentString = headerMap?.get(AppConst.UA_NAME) + settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW + if (sourceRegex.isNullOrEmpty()) { + webView.webViewClient = HtmlWebViewClient() + } else { + webView.webViewClient = SnifferWebClient() + } + return webView + } + + private fun destroy() { + launch { + mWebView?.destroy() + mWebView = null + } + job.cancel() + } + + private fun getJs(): String { + javaScript?.let { + if (it.isNotEmpty()) { + return it + } + } + return JS + } + + private fun setCookie(url: String) { + tag?.let { + val cookie = CookieManager.getInstance().getCookie(url) + CookieStore.setCookie(it, cookie) + } + } + + private inner class HtmlWebViewClient : WebViewClient() { + + override fun onPageFinished(view: WebView, url: String) { + setCookie(url) + val runnable = EvalJsRunnable(view, url, getJs()) + mHandler.postDelayed(runnable, 1000) + } + + } + + private inner class EvalJsRunnable( + webView: WebView, + private val url: String, + private val mJavaScript: String + ) : Runnable { + var retry = 0 + private val mWebView: WeakReference = WeakReference(webView) + override fun run() { + mWebView.get()?.evaluateJavascript(mJavaScript) { + if (it.isNotEmpty() && it != "null") { + val content = StringEscapeUtils.unescapeJson(it) + .replace("^\"|\"$".toRegex(), "") + try { + val response = StrResponse(url, content) + callback?.onResult(response) + } catch (e: Exception) { + callback?.onError(e) + } + mHandler.removeCallbacks(this) + return@evaluateJavascript + } + if (retry > 30) { + callback?.onError(NoStackTraceException("js执行超时")) + mHandler.removeCallbacks(this) + return@evaluateJavascript + } + retry++ + mHandler.removeCallbacks(this) + mHandler.postDelayed(this, 1000) + } + } + } + + private inner class SnifferWebClient : WebViewClient() { + + override fun onLoadResource(view: WebView, resUrl: String) { + sourceRegex?.let { + if (resUrl.matches(it.toRegex())) { + try { + val response = StrResponse(url!!, resUrl) + callback?.onResult(response) + } catch (e: Exception) { + callback?.onError(e) + } + } + } + } + + override fun onPageFinished(webView: WebView, url: String) { + setCookie(url) + val js = javaScript + if (!js.isNullOrEmpty()) { + val runnable = LoadJsRunnable(webView, javaScript) + mHandler.postDelayed(runnable, 1000L) + } + } + + } + + private class LoadJsRunnable( + webView: WebView, + private val mJavaScript: String? + ) : Runnable { + private val mWebView: WeakReference = WeakReference(webView) + override fun run() { + mWebView.get()?.loadUrl("javascript:${mJavaScript ?: ""}") + } + } + + companion object { + const val JS = "document.documentElement.outerHTML" + } + + abstract class Callback { + abstract fun onResult(response: StrResponse) + abstract fun onError(error: Throwable) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt index 63113a251..15f94e8bb 100644 --- a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt +++ b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt @@ -3,7 +3,6 @@ package io.legado.app.help.http import io.legado.app.help.AppConfig import io.legado.app.help.http.cronet.CronetInterceptor import io.legado.app.help.http.cronet.CronetLoader -import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.ConnectionSpec import okhttp3.Credentials import okhttp3.Interceptor @@ -12,7 +11,6 @@ import java.net.InetSocketAddress import java.net.Proxy import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit -import kotlin.coroutines.resume private val proxyClientCache: ConcurrentHashMap by lazy { ConcurrentHashMap() @@ -96,23 +94,15 @@ fun getProxyClient(proxy: String? = null): OkHttpClient { return okHttpClient } -suspend fun getWebViewSrc(params: AjaxWebView.AjaxParams): StrResponse = - suspendCancellableCoroutine { block -> - val webView = AjaxWebView() - block.invokeOnCancellation { - webView.destroyWebView() - } - webView.callback = object : AjaxWebView.Callback() { - override fun onResult(response: StrResponse) { - - if (!block.isCompleted) - block.resume(response) - } - - override fun onError(error: Throwable) { - if (!block.isCompleted) - block.cancel(error) - } - } - webView.load(params) - } \ No newline at end of file +suspend fun getWebViewSrc( + url: String? = null, + html: String? = null, + encode: String? = null, + tag: String? = null, + headerMap: Map? = null, + sourceRegex: String? = null, + javaScript: String? = null, +): StrResponse { + return BackstageWebView(url, html, encode, tag, headerMap, sourceRegex, javaScript) + .getStrResponse() +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt index 601b967c7..0387f89db 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt @@ -330,14 +330,32 @@ class AnalyzeUrl( judgmentConcurrent() setCookie(source?.getKey()) if (useWebView) { - val params = AjaxWebView.AjaxParams(url) - params.headerMap = headerMap - params.requestMethod = method - params.javaScript = webJs ?: jsStr - params.sourceRegex = sourceRegex - params.postData = body?.toByteArray() - params.tag = source?.getKey() - return getWebViewSrc(params) + return when (method) { + RequestMethod.POST -> { + val body = getProxyClient(proxy).newCallStrResponse(retry) { + addHeaders(headerMap) + url(urlNoQuery) + if (fieldMap.isNotEmpty() || body.isNullOrBlank()) { + postForm(fieldMap, true) + } else { + postJson(body) + } + }.body + BackstageWebView( + url = url, + html = body, + tag = source?.getKey(), + javaScript = webJs ?: jsStr, + sourceRegex = sourceRegex + ).getStrResponse() + } + else -> BackstageWebView( + url = url, + tag = source?.getKey(), + javaScript = webJs ?: jsStr, + sourceRegex = sourceRegex + ).getStrResponse() + } } return getProxyClient(proxy).newCallStrResponse(retry) { addHeaders(headerMap)