|
@@ -4,9 +4,6 @@ import android.annotation.SuppressLint
|
|
|
import android.util.Base64
|
|
import android.util.Base64
|
|
|
import androidx.annotation.Keep
|
|
import androidx.annotation.Keep
|
|
|
import androidx.media3.common.MediaItem
|
|
import androidx.media3.common.MediaItem
|
|
|
-import cn.hutool.core.codec.PercentCodec
|
|
|
|
|
-import cn.hutool.core.net.RFC3986
|
|
|
|
|
-import cn.hutool.core.util.HexUtil
|
|
|
|
|
import com.bumptech.glide.load.model.GlideUrl
|
|
import com.bumptech.glide.load.model.GlideUrl
|
|
|
import com.script.buildScriptBindings
|
|
import com.script.buildScriptBindings
|
|
|
import com.script.rhino.RhinoScriptEngine
|
|
import com.script.rhino.RhinoScriptEngine
|
|
@@ -21,6 +18,7 @@ import io.legado.app.help.CacheManager
|
|
|
import io.legado.app.help.ConcurrentRateLimiter
|
|
import io.legado.app.help.ConcurrentRateLimiter
|
|
|
import io.legado.app.help.JsExtensions
|
|
import io.legado.app.help.JsExtensions
|
|
|
import io.legado.app.help.config.AppConfig
|
|
import io.legado.app.help.config.AppConfig
|
|
|
|
|
+import io.legado.app.help.crypto.toHexString
|
|
|
import io.legado.app.help.exoplayer.ExoPlayerHelper
|
|
import io.legado.app.help.exoplayer.ExoPlayerHelper
|
|
|
import io.legado.app.help.glide.GlideHeaders
|
|
import io.legado.app.help.glide.GlideHeaders
|
|
|
import io.legado.app.help.http.BackstageWebView
|
|
import io.legado.app.help.http.BackstageWebView
|
|
@@ -65,7 +63,6 @@ import java.net.URLEncoder
|
|
|
import java.nio.charset.Charset
|
|
import java.nio.charset.Charset
|
|
|
import java.util.concurrent.ConcurrentHashMap
|
|
import java.util.concurrent.ConcurrentHashMap
|
|
|
import java.util.concurrent.TimeUnit
|
|
import java.util.concurrent.TimeUnit
|
|
|
-import java.util.regex.Pattern
|
|
|
|
|
import kotlin.coroutines.ContinuationInterceptor
|
|
import kotlin.coroutines.ContinuationInterceptor
|
|
|
import kotlin.coroutines.CoroutineContext
|
|
import kotlin.coroutines.CoroutineContext
|
|
|
import kotlin.coroutines.EmptyCoroutineContext
|
|
import kotlin.coroutines.EmptyCoroutineContext
|
|
@@ -128,8 +125,8 @@ class AnalyzeUrl(
|
|
|
|
|
|
|
|
init {
|
|
init {
|
|
|
coroutineContext = coroutineContext.minusKey(ContinuationInterceptor)
|
|
coroutineContext = coroutineContext.minusKey(ContinuationInterceptor)
|
|
|
- val urlMatcher = paramPattern.matcher(baseUrl)
|
|
|
|
|
- if (urlMatcher.find()) baseUrl = baseUrl.substring(0, urlMatcher.start())
|
|
|
|
|
|
|
+ val urlMatch = paramPattern.find(baseUrl)
|
|
|
|
|
+ if (urlMatch != null) baseUrl = baseUrl.substring(0, urlMatch.range.first)
|
|
|
(headerMapF ?: runScriptWithContext(coroutineContext) {
|
|
(headerMapF ?: runScriptWithContext(coroutineContext) {
|
|
|
source?.getHeaderMap(AppConfig.userAgent, hasLoginHeader)
|
|
source?.getHeaderMap(AppConfig.userAgent, hasLoginHeader)
|
|
|
})?.let {
|
|
})?.let {
|
|
@@ -161,18 +158,17 @@ class AnalyzeUrl(
|
|
|
*/
|
|
*/
|
|
|
private fun analyzeJs() {
|
|
private fun analyzeJs() {
|
|
|
var start = 0
|
|
var start = 0
|
|
|
- val jsMatcher = AppPattern.JS_PATTERN.matcher(ruleUrl)
|
|
|
|
|
var result = ruleUrl
|
|
var result = ruleUrl
|
|
|
- while (jsMatcher.find()) {
|
|
|
|
|
- if (jsMatcher.start() > start) {
|
|
|
|
|
- ruleUrl.substring(start, jsMatcher.start()).trim().let {
|
|
|
|
|
|
|
+ for (m in AppPattern.JS_PATTERN.findAll(ruleUrl)) {
|
|
|
|
|
+ if (m.range.first > start) {
|
|
|
|
|
+ ruleUrl.substring(start, m.range.first).trim().let {
|
|
|
if (it.isNotEmpty()) {
|
|
if (it.isNotEmpty()) {
|
|
|
result = it.replace("@result", result)
|
|
result = it.replace("@result", result)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- result = evalJS(jsMatcher.group(2) ?: jsMatcher.group(1), result).toString()
|
|
|
|
|
- start = jsMatcher.end()
|
|
|
|
|
|
|
+ result = evalJS(m.groupValues[2].ifEmpty { m.groupValues[1] }, result).toString()
|
|
|
|
|
+ start = m.range.last + 1
|
|
|
}
|
|
}
|
|
|
if (ruleUrl.length > start) {
|
|
if (ruleUrl.length > start) {
|
|
|
ruleUrl.substring(start).trim().let {
|
|
ruleUrl.substring(start).trim().let {
|
|
@@ -204,13 +200,12 @@ class AnalyzeUrl(
|
|
|
}
|
|
}
|
|
|
//page
|
|
//page
|
|
|
page?.let {
|
|
page?.let {
|
|
|
- val matcher = pagePattern.matcher(ruleUrl)
|
|
|
|
|
- while (matcher.find()) {
|
|
|
|
|
- val pages = matcher.group(1)!!.split(",")
|
|
|
|
|
|
|
+ for (m in pagePattern.findAll(ruleUrl)) {
|
|
|
|
|
+ val pages = m.groupValues[1].split(",")
|
|
|
ruleUrl = if (page < pages.size) { //pages[pages.size - 1]等同于pages.last()
|
|
ruleUrl = if (page < pages.size) { //pages[pages.size - 1]等同于pages.last()
|
|
|
- ruleUrl.replace(matcher.group(), pages[page - 1].trim { it <= ' ' })
|
|
|
|
|
|
|
+ ruleUrl.replace(m.value, pages[page - 1].trim { it <= ' ' })
|
|
|
} else {
|
|
} else {
|
|
|
- ruleUrl.replace(matcher.group(), pages.last().trim { it <= ' ' })
|
|
|
|
|
|
|
+ ruleUrl.replace(m.value, pages.last().trim { it <= ' ' })
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -221,15 +216,15 @@ class AnalyzeUrl(
|
|
|
*/
|
|
*/
|
|
|
private fun analyzeUrl() {
|
|
private fun analyzeUrl() {
|
|
|
//replaceKeyPageJs已经替换掉额外内容,此处url是基础形式,可以直接切首个‘,’之前字符串。
|
|
//replaceKeyPageJs已经替换掉额外内容,此处url是基础形式,可以直接切首个‘,’之前字符串。
|
|
|
- val urlMatcher = paramPattern.matcher(ruleUrl)
|
|
|
|
|
|
|
+ val urlMatch = paramPattern.find(ruleUrl)
|
|
|
val urlNoOption =
|
|
val urlNoOption =
|
|
|
- if (urlMatcher.find()) ruleUrl.substring(0, urlMatcher.start()) else ruleUrl
|
|
|
|
|
|
|
+ if (urlMatch != null) ruleUrl.substring(0, urlMatch.range.first) else ruleUrl
|
|
|
url = NetworkUtils.getAbsoluteURL(baseUrl, urlNoOption)
|
|
url = NetworkUtils.getAbsoluteURL(baseUrl, urlNoOption)
|
|
|
NetworkUtils.getBaseUrl(url)?.let {
|
|
NetworkUtils.getBaseUrl(url)?.let {
|
|
|
baseUrl = it
|
|
baseUrl = it
|
|
|
}
|
|
}
|
|
|
- if (urlNoOption.length != ruleUrl.length) {
|
|
|
|
|
- val urlOptionStr = ruleUrl.substring(urlMatcher.end())
|
|
|
|
|
|
|
+ if (urlMatch != null) {
|
|
|
|
|
+ val urlOptionStr = ruleUrl.substring(urlMatch.range.last + 1)
|
|
|
var urlOption = GSONStrict.fromJsonObject<UrlOption>(urlOptionStr).getOrNull()
|
|
var urlOption = GSONStrict.fromJsonObject<UrlOption>(urlOptionStr).getOrNull()
|
|
|
if (urlOption == null) {
|
|
if (urlOption == null) {
|
|
|
urlOption = GSON.fromJsonObject<UrlOption>(urlOptionStr).getOrNull()
|
|
urlOption = GSON.fromJsonObject<UrlOption>(urlOptionStr).getOrNull()
|
|
@@ -312,7 +307,7 @@ class AnalyzeUrl(
|
|
|
if (NetworkUtils.encodedQuery(params)) {
|
|
if (NetworkUtils.encodedQuery(params)) {
|
|
|
return params
|
|
return params
|
|
|
}
|
|
}
|
|
|
- return queryEncoder.encode(params, charset)
|
|
|
|
|
|
|
+ return encodeQueryParams(params, charset)
|
|
|
}
|
|
}
|
|
|
val len = params.length
|
|
val len = params.length
|
|
|
val sb = StringBuilder()
|
|
val sb = StringBuilder()
|
|
@@ -421,7 +416,7 @@ class AnalyzeUrl(
|
|
|
skipRateLimit: Boolean = false
|
|
skipRateLimit: Boolean = false
|
|
|
): StrResponse {
|
|
): StrResponse {
|
|
|
if (type != null) {
|
|
if (type != null) {
|
|
|
- return StrResponse(url, HexUtil.encodeHexStr(getByteArrayAwait()))
|
|
|
|
|
|
|
+ return StrResponse(url, getByteArrayAwait().toHexString())
|
|
|
}
|
|
}
|
|
|
if (skipRateLimit) {
|
|
if (skipRateLimit) {
|
|
|
return executeStrRequest(jsStr, sourceRegex, useWebView, isTest)
|
|
return executeStrRequest(jsStr, sourceRegex, useWebView, isTest)
|
|
@@ -776,10 +771,33 @@ class AnalyzeUrl(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
companion object {
|
|
companion object {
|
|
|
- val paramPattern: Pattern = Pattern.compile("\\s*,\\s*(?=\\{)")
|
|
|
|
|
- private val pagePattern = Pattern.compile("<(.*?)>")
|
|
|
|
|
- private val queryEncoder =
|
|
|
|
|
- RFC3986.UNRESERVED.orNew(PercentCodec.of("!$%&()*+,/:;=?@[\\]^`{|}"))
|
|
|
|
|
|
|
+ val paramPattern: Regex = Regex("\\s*,\\s*(?=\\{)")
|
|
|
|
|
+ private val pagePattern = Regex("<(.*?)>")
|
|
|
|
|
+ private const val QUERY_HEX_UPPER = "0123456789ABCDEF"
|
|
|
|
|
+ private val queryEncoderSafeChars: BooleanArray = BooleanArray(128).apply {
|
|
|
|
|
+ for (c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-~!$%&()*+,/:;=?@[\\]^`{|}") {
|
|
|
|
|
+ this[c.code] = true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun encodeQueryParams(str: String, charset: Charset): String {
|
|
|
|
|
+ val builder = StringBuilder(str.length)
|
|
|
|
|
+ for (c in str) {
|
|
|
|
|
+ if (c.code < 128 && queryEncoderSafeChars[c.code]) {
|
|
|
|
|
+ builder.append(c)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ val bytes = c.toString().toByteArray(charset)
|
|
|
|
|
+ for (b in bytes) {
|
|
|
|
|
+ val v = b.toInt() and 0xFF
|
|
|
|
|
+ builder.append('%')
|
|
|
|
|
+ builder.append(QUERY_HEX_UPPER[v ushr 4])
|
|
|
|
|
+ builder.append(QUERY_HEX_UPPER[v and 0x0F])
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return builder.toString()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
val customIp by lazy { ConcurrentHashMap<String, String>() }
|
|
val customIp by lazy { ConcurrentHashMap<String, String>() }
|
|
|
fun AnalyzeUrl.getMediaItem(): MediaItem {
|
|
fun AnalyzeUrl.getMediaItem(): MediaItem {
|
|
|
setCookie()
|
|
setCookie()
|