pull/1352/head
			
			
		
		
							parent
							
								
									1a5f94e43a
								
							
						
					
					
						commit
						995c9098db
					
				@ -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<String, String>? = 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<WebView> = 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<WebView> = 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) | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -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<String, String>? = 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<WebView> = 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<WebView> = 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) | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue