pull/45/head
parent
5e0ae03a6d
commit
4e25c675a4
@ -0,0 +1,350 @@ |
|||||||
|
package io.legado.app.help.http |
||||||
|
|
||||||
|
import android.annotation.SuppressLint |
||||||
|
import android.content.Context |
||||||
|
import android.net.http.SslError |
||||||
|
import android.os.Build |
||||||
|
import android.os.Handler |
||||||
|
import android.os.Looper |
||||||
|
import android.os.Message |
||||||
|
import android.text.TextUtils |
||||||
|
import android.webkit.* |
||||||
|
import java.lang.ref.WeakReference |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
|
||||||
|
class AjaxWebView { |
||||||
|
var callback: Callback? = null |
||||||
|
private var mHandler: AjaxHandler |
||||||
|
|
||||||
|
init { |
||||||
|
mHandler = 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 String) |
||||||
|
destroyWebView() |
||||||
|
} |
||||||
|
MSG_ERROR -> { |
||||||
|
ajaxWebView.callback?.onError(msg.obj as Throwable) |
||||||
|
destroyWebView() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled", "JavascriptInterface") |
||||||
|
fun createAjaxWebView(params: AjaxParams, handler: Handler): WebView { |
||||||
|
val webView = WebView(params.context.applicationContext) |
||||||
|
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) |
||||||
|
webView.addJavascriptInterface(JavaInjectMethod(handler), "OUTHTML") |
||||||
|
} |
||||||
|
when (params.requestMethod) { |
||||||
|
RequestMethod.POST -> webView.postUrl(params.url, params.postData) |
||||||
|
RequestMethod.GET, RequestMethod.DEFAULT -> webView.loadUrl( |
||||||
|
params.url, |
||||||
|
params.headerMap |
||||||
|
) |
||||||
|
} |
||||||
|
return webView |
||||||
|
} |
||||||
|
|
||||||
|
private fun destroyWebView() { |
||||||
|
mWebView?.destroy() |
||||||
|
mWebView = null |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
fun ajax(params: AjaxParams) { |
||||||
|
mHandler.obtainMessage(MSG_AJAX_START, params) |
||||||
|
.sendToTarget() |
||||||
|
} |
||||||
|
|
||||||
|
fun sniff(params: AjaxParams) { |
||||||
|
mHandler.obtainMessage(MSG_SNIFF_START, params) |
||||||
|
.sendToTarget() |
||||||
|
} |
||||||
|
|
||||||
|
fun destroyWebView() { |
||||||
|
mHandler.obtainMessage(DESTROY_WEB_VIEW) |
||||||
|
} |
||||||
|
|
||||||
|
class JavaInjectMethod(private val handler: Handler) { |
||||||
|
|
||||||
|
@JavascriptInterface |
||||||
|
fun processHTML(html: String) { |
||||||
|
handler.obtainMessage(MSG_SUCCESS, html) |
||||||
|
.sendToTarget() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
class AjaxParams(val context: Context, private val tag: String) { |
||||||
|
var requestMethod: RequestMethod? = null |
||||||
|
get() { |
||||||
|
return field ?: RequestMethod.DEFAULT |
||||||
|
} |
||||||
|
var url: String? = null |
||||||
|
var postData: ByteArray? = null |
||||||
|
var headerMap: Map<String, String>? = null |
||||||
|
var cookieStore: CookieStore? = null |
||||||
|
var audioSuffix: String? = null |
||||||
|
var javaScript: String? = null |
||||||
|
private var audioSuffixList: List<String>? = null |
||||||
|
|
||||||
|
val userAgent: String? |
||||||
|
get() = if (this.headerMap != null) { |
||||||
|
this.headerMap!!.get("User-Agent") |
||||||
|
} else null |
||||||
|
|
||||||
|
val isSniff: Boolean |
||||||
|
get() = !TextUtils.isEmpty(audioSuffix) |
||||||
|
|
||||||
|
fun requestMethod(method: RequestMethod): AjaxParams { |
||||||
|
this.requestMethod = method |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun url(url: String): AjaxParams { |
||||||
|
this.url = url |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun postData(postData: ByteArray): AjaxParams { |
||||||
|
this.postData = postData |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun headerMap(headerMap: Map<String, String>): AjaxParams { |
||||||
|
this.headerMap = headerMap |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun cookieStore(cookieStore: CookieStore): AjaxParams { |
||||||
|
this.cookieStore = cookieStore |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun suffix(suffix: String): AjaxParams { |
||||||
|
this.audioSuffix = suffix |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun javaScript(javaScript: String): AjaxParams { |
||||||
|
this.javaScript = javaScript |
||||||
|
return this |
||||||
|
} |
||||||
|
|
||||||
|
fun setCookie(url: String) { |
||||||
|
if (cookieStore != null) { |
||||||
|
val cookie = CookieManager.getInstance().getCookie(url) |
||||||
|
cookieStore?.setCookie(tag, cookie) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun hasJavaScript(): Boolean { |
||||||
|
return !TextUtils.isEmpty(javaScript) |
||||||
|
} |
||||||
|
|
||||||
|
fun clearJavaScript() { |
||||||
|
javaScript = null |
||||||
|
} |
||||||
|
|
||||||
|
fun getAudioSuffixList(): List<String>? { |
||||||
|
if (audioSuffixList == null) { |
||||||
|
audioSuffixList = if (isSniff) { |
||||||
|
audioSuffix?.split("\\|\\|".toRegex()) |
||||||
|
} else { |
||||||
|
Collections.emptyList() |
||||||
|
} |
||||||
|
} |
||||||
|
return audioSuffixList |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class HtmlWebViewClient( |
||||||
|
private val params: AjaxParams, |
||||||
|
private val handler: Handler |
||||||
|
) : WebViewClient() { |
||||||
|
|
||||||
|
|
||||||
|
override fun onPageFinished(view: WebView, url: String) { |
||||||
|
params.setCookie(url) |
||||||
|
evaluateJavascript(view) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
override fun onLoadResource(view: WebView, url: String) { |
||||||
|
super.onLoadResource(view, url) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onReceivedError( |
||||||
|
view: WebView, |
||||||
|
errorCode: Int, |
||||||
|
description: String, |
||||||
|
failingUrl: String |
||||||
|
) { |
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { |
||||||
|
handler.obtainMessage(MSG_ERROR, Exception(description)) |
||||||
|
.sendToTarget() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onReceivedError( |
||||||
|
view: WebView, |
||||||
|
request: WebResourceRequest, |
||||||
|
error: WebResourceError |
||||||
|
) { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||||
|
handler.obtainMessage( |
||||||
|
MSG_ERROR, |
||||||
|
Exception(error.description.toString()) |
||||||
|
) |
||||||
|
.sendToTarget() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { |
||||||
|
handler.proceed() |
||||||
|
} |
||||||
|
|
||||||
|
private fun evaluateJavascript(webView: WebView) { |
||||||
|
val runnable = ScriptRunnable(webView, OUTER_HTML) |
||||||
|
handler.postDelayed(runnable, 1000L) |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
const val OUTER_HTML = |
||||||
|
"window.OUTHTML.processHTML('<head>'+document.getElementsByTagName('html')[0].innerHTML+'</head>');" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class SnifferWebClient( |
||||||
|
private val params: AjaxParams, |
||||||
|
private val handler: Handler |
||||||
|
) : WebViewClient() { |
||||||
|
|
||||||
|
override fun onLoadResource(view: WebView, url: String) { |
||||||
|
val suffixList = params.getAudioSuffixList() |
||||||
|
for (suffix in suffixList!!) { |
||||||
|
if (!TextUtils.isEmpty(suffix) && url.contains(suffix)) { |
||||||
|
handler.obtainMessage(MSG_SUCCESS, url) |
||||||
|
.sendToTarget() |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onReceivedError( |
||||||
|
view: WebView, |
||||||
|
errorCode: Int, |
||||||
|
description: String, |
||||||
|
failingUrl: String |
||||||
|
) { |
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { |
||||||
|
handler.obtainMessage(MSG_ERROR, Exception(description)) |
||||||
|
.sendToTarget() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onReceivedError( |
||||||
|
view: WebView, |
||||||
|
request: WebResourceRequest, |
||||||
|
error: WebResourceError |
||||||
|
) { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||||
|
handler.obtainMessage( |
||||||
|
MSG_ERROR, |
||||||
|
Exception(error.description.toString()) |
||||||
|
) |
||||||
|
.sendToTarget() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { |
||||||
|
handler.proceed() |
||||||
|
} |
||||||
|
|
||||||
|
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 = ScriptRunnable(webView, javaScript) |
||||||
|
handler.postDelayed(runnable, 1000L) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ScriptRunnable( |
||||||
|
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 |
||||||
|
} |
||||||
|
|
||||||
|
enum class RequestMethod { |
||||||
|
GET, POST, DEFAULT |
||||||
|
} |
||||||
|
|
||||||
|
interface CookieStore { |
||||||
|
fun setCookie(url: String, cookie: String) |
||||||
|
|
||||||
|
fun replaceCookie(url: String, cookie: String) |
||||||
|
|
||||||
|
fun getCookie(url: String): String |
||||||
|
|
||||||
|
fun removeCookie(url: String) |
||||||
|
|
||||||
|
fun clearCookies() |
||||||
|
} |
||||||
|
|
||||||
|
abstract class Callback { |
||||||
|
|
||||||
|
abstract fun onResult(result: String) |
||||||
|
|
||||||
|
abstract fun onError(error: Throwable) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue