pull/1352/head
gedoor 3 years ago
parent 1a5f94e43a
commit 995c9098db
  1. 11
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  2. 236
      app/src/main/java/io/legado/app/help/http/AjaxWebView.kt
  3. 208
      app/src/main/java/io/legado/app/help/http/BackstageWebView.kt
  4. 34
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  5. 34
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.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
}
}
/**

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

@ -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<String, OkHttpClient> 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)
}
suspend fun getWebViewSrc(
url: String? = null,
html: String? = null,
encode: String? = null,
tag: String? = null,
headerMap: Map<String, String>? = null,
sourceRegex: String? = null,
javaScript: String? = null,
): StrResponse {
return BackstageWebView(url, html, encode, tag, headerMap, sourceRegex, javaScript)
.getStrResponse()
}

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

Loading…
Cancel
Save