优化
This commit is contained in:
@@ -4,7 +4,8 @@ import android.webkit.WebSettings
|
||||
import androidx.annotation.Keep
|
||||
import cn.hutool.core.codec.Base64
|
||||
import cn.hutool.core.util.HexUtil
|
||||
import com.script.rhino.RhinoContext
|
||||
import com.script.rhino.rhinoContext
|
||||
import com.script.rhino.rhinoContextOrNull
|
||||
import io.legado.app.constant.AppConst
|
||||
import io.legado.app.constant.AppConst.dateFormat
|
||||
import io.legado.app.constant.AppLog
|
||||
@@ -52,7 +53,6 @@ import kotlinx.coroutines.runBlocking
|
||||
import okio.use
|
||||
import org.jsoup.Connection
|
||||
import org.jsoup.Jsoup
|
||||
import org.mozilla.javascript.Context
|
||||
import splitties.init.appCtx
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -83,9 +83,6 @@ interface JsExtensions : JsEncodeUtils {
|
||||
|
||||
fun getSource(): BaseSource?
|
||||
|
||||
private val rhinoContext: RhinoContext
|
||||
get() = Context.getCurrentContext() as RhinoContext
|
||||
|
||||
private val context: CoroutineContext
|
||||
get() = rhinoContext.coroutineContext ?: EmptyCoroutineContext
|
||||
|
||||
@@ -943,7 +940,7 @@ interface JsExtensions : JsEncodeUtils {
|
||||
* 输出调试日志
|
||||
*/
|
||||
fun log(msg: Any?): Any? {
|
||||
rhinoContext.ensureActive()
|
||||
rhinoContextOrNull?.ensureActive()
|
||||
getSource()?.let {
|
||||
Debug.log(it.getKey(), msg.toString())
|
||||
} ?: Debug.log(msg.toString())
|
||||
|
||||
@@ -130,6 +130,18 @@ fun Request.Builder.get(url: String, queryMap: Map<String, String>, encoded: Boo
|
||||
url(httpBuilder.build())
|
||||
}
|
||||
|
||||
fun Request.Builder.get(url: String, encodedQuery: String?) {
|
||||
val httpBuilder = url.toHttpUrl().newBuilder()
|
||||
httpBuilder.encodedQuery(encodedQuery)
|
||||
url(httpBuilder.build())
|
||||
}
|
||||
|
||||
private val formContentType = "application/x-www-form-urlencoded".toMediaType()
|
||||
|
||||
fun Request.Builder.postForm(encodedForm: String) {
|
||||
post(encodedForm.toRequestBody(formContentType))
|
||||
}
|
||||
|
||||
fun Request.Builder.postForm(form: Map<String, String>, encoded: Boolean = false) {
|
||||
val formBody = FormBody.Builder()
|
||||
form.forEach {
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.legado.app.utils.GSON
|
||||
import io.legado.app.utils.NetworkUtils
|
||||
import io.legado.app.utils.fromJsonObject
|
||||
import io.legado.app.utils.getOrPutLimit
|
||||
import io.legado.app.utils.isDataUrl
|
||||
import io.legado.app.utils.isJson
|
||||
import io.legado.app.utils.printOnDebug
|
||||
import io.legado.app.utils.splitNotBlank
|
||||
@@ -100,6 +101,9 @@ class AnalyzeRule(
|
||||
}
|
||||
|
||||
fun setRedirectUrl(url: String): URL? {
|
||||
if (url.isDataUrl()) {
|
||||
return redirectUrl
|
||||
}
|
||||
try {
|
||||
redirectUrl = URL(url)
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
||||
import android.util.Base64
|
||||
import androidx.annotation.Keep
|
||||
import androidx.media3.common.MediaItem
|
||||
import cn.hutool.core.net.URLEncodeUtil
|
||||
import cn.hutool.core.util.HexUtil
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.script.buildScriptBindings
|
||||
@@ -47,7 +48,6 @@ import io.legado.app.utils.isJson
|
||||
import io.legado.app.utils.isJsonArray
|
||||
import io.legado.app.utils.isJsonObject
|
||||
import io.legado.app.utils.isXml
|
||||
import io.legado.app.utils.splitNotBlank
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -56,6 +56,7 @@ import okhttp3.Response
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.Charset
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.coroutines.ContinuationInterceptor
|
||||
@@ -101,8 +102,8 @@ class AnalyzeUrl(
|
||||
private set
|
||||
val headerMap = HashMap<String, String>()
|
||||
private var urlNoQuery: String = ""
|
||||
private var queryStr: String? = null
|
||||
private val fieldMap = LinkedHashMap<String, String>()
|
||||
private var encodedForm: String? = null
|
||||
private var encodedQuery: String? = null
|
||||
private var charset: String? = null
|
||||
private var method = RequestMethod.GET
|
||||
private var proxy: String? = null
|
||||
@@ -251,7 +252,7 @@ class AnalyzeUrl(
|
||||
RequestMethod.GET -> {
|
||||
val pos = url.indexOf('?')
|
||||
if (pos != -1) {
|
||||
analyzeFields(url.substring(pos + 1))
|
||||
analyzeQuery(url.substring(pos + 1))
|
||||
urlNoQuery = url.substring(0, pos)
|
||||
}
|
||||
}
|
||||
@@ -271,23 +272,68 @@ class AnalyzeUrl(
|
||||
* name=<BASE64> eg name=bmFtZQ==
|
||||
*/
|
||||
private fun analyzeFields(fieldsTxt: String) {
|
||||
queryStr = fieldsTxt
|
||||
val queryS = fieldsTxt.splitNotBlank("&")
|
||||
for (query in queryS) {
|
||||
val queryPair = query.splitNotBlank("=", limit = 2)
|
||||
val key = queryPair[0]
|
||||
val value = queryPair.getOrNull(1) ?: ""
|
||||
if (charset.isNullOrEmpty()) {
|
||||
if (NetworkUtils.hasUrlEncoded(value)) {
|
||||
fieldMap[key] = value
|
||||
} else {
|
||||
fieldMap[key] = URLEncoder.encode(value, "UTF-8")
|
||||
}
|
||||
} else if (charset == "escape") {
|
||||
fieldMap[key] = EncoderUtils.escape(value)
|
||||
} else {
|
||||
fieldMap[key] = URLEncoder.encode(value, charset)
|
||||
encodedForm = encodeParams(fieldsTxt, charset, false)
|
||||
}
|
||||
|
||||
private fun analyzeQuery(query: String) {
|
||||
encodedQuery = encodeParams(query, charset, true)
|
||||
}
|
||||
|
||||
private fun encodeParams(params: String, charset: String?, isQuery: Boolean): String {
|
||||
val checkEncoded = charset.isNullOrEmpty()
|
||||
val charset = when {
|
||||
charset.isNullOrEmpty() -> Charsets.UTF_8
|
||||
charset == "escape" -> null
|
||||
else -> charset(charset)
|
||||
}
|
||||
val len = params.length
|
||||
val sb = StringBuilder()
|
||||
var pos = 0
|
||||
while (pos <= len) {
|
||||
if (sb.isNotEmpty()) {
|
||||
sb.append("&")
|
||||
}
|
||||
var ampOffset = params.indexOf("&", pos)
|
||||
if (ampOffset == -1) {
|
||||
ampOffset = len
|
||||
}
|
||||
val eqOffset = params.indexOf("=", pos)
|
||||
val key: String
|
||||
val value: String?
|
||||
if (eqOffset == -1 || eqOffset > ampOffset) {
|
||||
key = params.substring(pos, ampOffset)
|
||||
value = null
|
||||
} else {
|
||||
key = params.substring(pos, eqOffset)
|
||||
value = params.substring(eqOffset + 1, ampOffset)
|
||||
}
|
||||
sb.appendEncoded(key, isQuery, checkEncoded, charset)
|
||||
if (value != null) {
|
||||
sb.append("=")
|
||||
sb.appendEncoded(value, isQuery, checkEncoded, charset)
|
||||
}
|
||||
pos = ampOffset + 1
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendEncoded(
|
||||
value: String,
|
||||
isQuery: Boolean,
|
||||
checkEncoded: Boolean,
|
||||
charset: Charset?
|
||||
) {
|
||||
val needEncode = checkEncoded &&
|
||||
((isQuery && !NetworkUtils.encodedQuery(value)) ||
|
||||
(!isQuery && !NetworkUtils.encodedForm(value)))
|
||||
if (!needEncode) {
|
||||
append(value)
|
||||
} else if (charset == null) {
|
||||
append(EncoderUtils.escape(value))
|
||||
} else if (isQuery) {
|
||||
append(URLEncodeUtil.encodeQuery(value, charset))
|
||||
} else {
|
||||
append(URLEncoder.encode(value, charset))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,8 +402,8 @@ class AnalyzeUrl(
|
||||
val res = getClient().newCallStrResponse(retry) {
|
||||
addHeaders(headerMap)
|
||||
url(urlNoQuery)
|
||||
if (fieldMap.isNotEmpty() || body.isNullOrBlank()) {
|
||||
postForm(fieldMap, true)
|
||||
if (!encodedForm.isNullOrEmpty() || body.isNullOrBlank()) {
|
||||
postForm(encodedForm ?: "")
|
||||
} else {
|
||||
postJson(body)
|
||||
}
|
||||
@@ -390,8 +436,8 @@ class AnalyzeUrl(
|
||||
url(urlNoQuery)
|
||||
val contentType = headerMap["Content-Type"]
|
||||
val body = body
|
||||
if (fieldMap.isNotEmpty() || body.isNullOrBlank()) {
|
||||
postForm(fieldMap, true)
|
||||
if (!encodedForm.isNullOrEmpty() || body.isNullOrBlank()) {
|
||||
postForm(encodedForm ?: "")
|
||||
} else if (!contentType.isNullOrBlank()) {
|
||||
val requestBody = body.toRequestBody(contentType.toMediaType())
|
||||
post(requestBody)
|
||||
@@ -400,7 +446,7 @@ class AnalyzeUrl(
|
||||
}
|
||||
}
|
||||
|
||||
else -> get(urlNoQuery, fieldMap, true)
|
||||
else -> get(urlNoQuery, encodedQuery)
|
||||
}
|
||||
}.let {
|
||||
val isXml = it.raw.body?.contentType()?.toString()
|
||||
@@ -438,8 +484,8 @@ class AnalyzeUrl(
|
||||
url(urlNoQuery)
|
||||
val contentType = headerMap["Content-Type"]
|
||||
val body = body
|
||||
if (fieldMap.isNotEmpty() || body.isNullOrBlank()) {
|
||||
postForm(fieldMap, true)
|
||||
if (!encodedForm.isNullOrEmpty() || body.isNullOrBlank()) {
|
||||
postForm(encodedForm ?: "")
|
||||
} else if (!contentType.isNullOrBlank()) {
|
||||
val requestBody = body.toRequestBody(contentType.toMediaType())
|
||||
post(requestBody)
|
||||
@@ -448,7 +494,7 @@ class AnalyzeUrl(
|
||||
}
|
||||
}
|
||||
|
||||
else -> get(urlNoQuery, fieldMap, true)
|
||||
else -> get(urlNoQuery, encodedQuery)
|
||||
}
|
||||
}
|
||||
return response
|
||||
|
||||
@@ -26,6 +26,7 @@ import io.legado.app.utils.dpToPx
|
||||
import io.legado.app.utils.isAbsUrl
|
||||
import io.legado.app.utils.openUrl
|
||||
import io.legado.app.utils.printOnDebug
|
||||
import io.legado.app.utils.sendToClip
|
||||
import io.legado.app.utils.setLayout
|
||||
import io.legado.app.utils.showDialogFragment
|
||||
import io.legado.app.utils.toastOnUi
|
||||
@@ -35,6 +36,7 @@ import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import splitties.init.appCtx
|
||||
import splitties.views.onClick
|
||||
|
||||
|
||||
@@ -109,6 +111,9 @@ class SourceLoginDialog : BaseDialogFragment(R.layout.dialog_login, true) {
|
||||
setTitle(R.string.login_header)
|
||||
source.getLoginHeader()?.let { loginHeader ->
|
||||
setMessage(loginHeader)
|
||||
positiveButton(R.string.copy_text) {
|
||||
appCtx.sendToClip(loginHeader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ object NetworkUtils {
|
||||
return false
|
||||
}
|
||||
|
||||
private val notNeedEncoding: BitSet by lazy {
|
||||
private val notNeedEncodingQuery: BitSet by lazy {
|
||||
val bitSet = BitSet(256)
|
||||
for (i in 'a'.code..'z'.code) {
|
||||
bitSet.set(i)
|
||||
@@ -66,7 +66,24 @@ object NetworkUtils {
|
||||
for (i in '0'.code..'9'.code) {
|
||||
bitSet.set(i)
|
||||
}
|
||||
for (char in "+-_.~$:()!*@&#,[]") {
|
||||
for (char in "!$&()*+,-./:;=?@[\\]^_`{|}~") {
|
||||
bitSet.set(char.code)
|
||||
}
|
||||
return@lazy bitSet
|
||||
}
|
||||
|
||||
private val notNeedEncodingForm: BitSet by lazy {
|
||||
val bitSet = BitSet(256)
|
||||
for (i in 'a'.code..'z'.code) {
|
||||
bitSet.set(i)
|
||||
}
|
||||
for (i in 'A'.code..'Z'.code) {
|
||||
bitSet.set(i)
|
||||
}
|
||||
for (i in '0'.code..'9'.code) {
|
||||
bitSet.set(i)
|
||||
}
|
||||
for (char in "*-._") {
|
||||
bitSet.set(char.code)
|
||||
}
|
||||
return@lazy bitSet
|
||||
@@ -78,12 +95,38 @@ object NetworkUtils {
|
||||
* ! * ' ( ) ; : @ & = + $ , / ? # [ ] 保留
|
||||
* 其他字符转成%XX的格式,X是16进制的大写字符,范围是[0-9A-F]
|
||||
*/
|
||||
fun hasUrlEncoded(str: String): Boolean {
|
||||
fun encodedQuery(str: String): Boolean {
|
||||
var needEncode = false
|
||||
var i = 0
|
||||
while (i < str.length) {
|
||||
val c = str[i]
|
||||
if (notNeedEncoding.get(c.code)) {
|
||||
if (notNeedEncodingQuery.get(c.code)) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (c == '%' && i + 2 < str.length) {
|
||||
// 判断是否符合urlEncode规范
|
||||
val c1 = str[++i]
|
||||
val c2 = str[++i]
|
||||
if (isDigit16Char(c1) && isDigit16Char(c2)) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
}
|
||||
// 其他字符,肯定需要urlEncode
|
||||
needEncode = true
|
||||
break
|
||||
}
|
||||
|
||||
return !needEncode
|
||||
}
|
||||
|
||||
fun encodedForm(str: String): Boolean {
|
||||
var needEncode = false
|
||||
var i = 0
|
||||
while (i < str.length) {
|
||||
val c = str[i]
|
||||
if (notNeedEncodingForm.get(c.code)) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ import kotlin.coroutines.ContinuationInterceptor
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
val rhinoContext: RhinoContext
|
||||
get() = Context.getCurrentContext() as RhinoContext
|
||||
|
||||
val rhinoContextOrNull: RhinoContext?
|
||||
get() = Context.getCurrentContext() as? RhinoContext
|
||||
|
||||
@Suppress("LEAKED_IN_PLACE_LAMBDA", "WRONG_INVOCATION_KIND")
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
inline fun <T> suspendContinuation(crossinline block: suspend CoroutineScope.() -> T): T {
|
||||
|
||||
Reference in New Issue
Block a user