This commit is contained in:
kunfei 2023-04-20 23:42:51 +08:00
parent cd7f139dd3
commit 41eacf72fc
13 changed files with 202 additions and 75 deletions

View File

@ -1,7 +0,0 @@
package io.legado.app.constant
import com.script.rhino.RhinoScriptEngine
val SCRIPT_ENGINE: RhinoScriptEngine by lazy {
RhinoScriptEngine()
}

View File

@ -1,16 +1,18 @@
package io.legado.app.data.entities
import cn.hutool.crypto.symmetric.AES
import com.script.SimpleBindings
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppLog
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.data.entities.rule.RowUi
import io.legado.app.help.CacheManager
import io.legado.app.help.JsExtensions
import io.legado.app.help.config.AppConfig
import io.legado.app.help.http.CookieStore
import io.legado.app.model.SharedJsScope
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import io.legado.app.rhino.putBindings
import io.legado.app.utils.*
import org.intellij.lang.annotations.Language
import org.mozilla.javascript.Scriptable
@ -227,19 +229,22 @@ interface BaseSource : JsExtensions {
* 执行JS
*/
@Throws(Exception::class)
fun evalJS(jsStr: String, bindingsConfig: SimpleBindings.() -> Unit = {}): Any? {
val bindings = SimpleBindings()
fun evalJS(jsStr: String, bindingsConfig: Bindings.() -> Unit = {}): Any? {
val bindings = Bindings()
bindings.apply(bindingsConfig)
bindings["java"] = this
bindings["source"] = this
bindings["baseUrl"] = getKey()
bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager
val scope = SCRIPT_ENGINE.getRuntimeScope(SCRIPT_ENGINE.getScriptContext(bindings))
getShareScope()?.let {
scope.prototype = it
return Rhino.use {
val scope = initStandardObjects()
scope.putBindings(bindings)
getShareScope()?.let {
scope.prototype = it
}
evaluate(scope, jsStr)
}
return SCRIPT_ENGINE.eval(jsStr, scope)
}
fun getShareScope(): Scriptable? {

View File

@ -3,7 +3,6 @@
package io.legado.app.help.book
import android.net.Uri
import com.script.SimpleBindings
import io.legado.app.constant.*
import io.legado.app.data.appDb
import io.legado.app.data.entities.BaseBook
@ -11,6 +10,9 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.putBindings
import io.legado.app.utils.*
import splitties.init.appCtx
import java.io.File
@ -234,11 +236,15 @@ fun Book.getExportFileName(suffix: String): String {
if (jsStr.isNullOrBlank()) {
return "$name 作者:${getRealAuthor()}.$suffix"
}
val bindings = SimpleBindings()
val bindings = Bindings()
bindings["name"] = name
bindings["author"] = getRealAuthor()
return kotlin.runCatching {
SCRIPT_ENGINE.eval(jsStr, bindings).toString() + "." + suffix
Rhino.use {
val scope = initStandardObjects()
scope.putBindings(bindings)
evaluateString(scope, jsStr, "name&author", 1, null)
}.toString() + "." + suffix
}.onFailure {
AppLog.put("导出书名规则错误,使用默认规则\n${it.localizedMessage}", it)
}.getOrDefault("${name} 作者:${getRealAuthor()}.$suffix")

View File

@ -1,8 +1,6 @@
package io.legado.app.model
import com.google.gson.reflect.TypeToken
import com.script.SimpleBindings
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.http.newCallStrResponse
import io.legado.app.help.http.okHttpClient
@ -33,9 +31,8 @@ object SharedJsScope {
val key = MD5Utils.md5Encode(jsLib)
var scope = scopeMap[key]?.get()
if (scope == null) {
val context = SCRIPT_ENGINE.getScriptContext(SimpleBindings())
scope = SCRIPT_ENGINE.getRuntimeScope(context)
Rhino.use {
scope = initStandardObjects()
if (jsLib.isJsonObject()) {
val jsMap: Map<String, String> = GSON.fromJson(
jsLib,

View File

@ -2,14 +2,16 @@ package io.legado.app.model.analyzeRule
import android.text.TextUtils
import androidx.annotation.Keep
import com.script.SimpleBindings
import io.legado.app.constant.AppPattern.JS_PATTERN
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.data.entities.*
import io.legado.app.help.CacheManager
import io.legado.app.help.JsExtensions
import io.legado.app.help.http.CookieStore
import io.legado.app.model.webBook.WebBook
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import io.legado.app.rhino.putBindings
import io.legado.app.utils.*
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
@ -681,7 +683,7 @@ class AnalyzeRule(
* 执行JS
*/
fun evalJS(jsStr: String, result: Any? = null): Any? {
val bindings = SimpleBindings()
val bindings = Bindings()
bindings["java"] = this
bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager
@ -693,12 +695,14 @@ class AnalyzeRule(
bindings["title"] = chapter?.title
bindings["src"] = content
bindings["nextChapterUrl"] = nextChapterUrl
val context = SCRIPT_ENGINE.getScriptContext(bindings)
val scope = SCRIPT_ENGINE.getRuntimeScope(context)
source?.getShareScope()?.let {
scope.prototype = it
return Rhino.use {
val scope = initStandardObjects()
scope.putBindings(bindings)
source?.getShareScope()?.let {
scope.prototype = it
}
evaluate(scope, jsStr)
}
return SCRIPT_ENGINE.eval(jsStr, scope)
}
override fun getSource(): BaseSource? {

View File

@ -5,12 +5,10 @@ import android.util.Base64
import androidx.annotation.Keep
import cn.hutool.core.util.HexUtil
import com.bumptech.glide.load.model.GlideUrl
import com.script.SimpleBindings
import io.legado.app.constant.AppConst.UA_NAME
import io.legado.app.constant.AppPattern
import io.legado.app.constant.AppPattern.JS_PATTERN
import io.legado.app.constant.AppPattern.dataUriRegex
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.data.entities.BaseSource
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
@ -20,6 +18,10 @@ import io.legado.app.help.JsExtensions
import io.legado.app.help.config.AppConfig
import io.legado.app.help.glide.GlideHeaders
import io.legado.app.help.http.*
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import io.legado.app.rhino.putBindings
import io.legado.app.utils.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
@ -253,7 +255,7 @@ class AnalyzeUrl(
* 执行JS
*/
fun evalJS(jsStr: String, result: Any? = null): Any? {
val bindings = SimpleBindings()
val bindings = Bindings()
bindings["java"] = this
bindings["baseUrl"] = baseUrl
bindings["cookie"] = CookieStore
@ -265,12 +267,14 @@ class AnalyzeUrl(
bindings["book"] = ruleData as? Book
bindings["source"] = source
bindings["result"] = result
val context = SCRIPT_ENGINE.getScriptContext(bindings)
val scope = SCRIPT_ENGINE.getRuntimeScope(context)
source?.getShareScope()?.let {
scope.prototype = it
return Rhino.use {
val scope = initStandardObjects()
scope.putBindings(bindings)
source?.getShareScope()?.let {
scope.prototype = it
}
evaluate(scope, jsStr)
}
return SCRIPT_ENGINE.eval(jsStr, scope)
}
fun put(key: String, value: String): String {

View File

@ -3,7 +3,6 @@ package io.legado.app.model.localBook
import android.net.Uri
import android.util.Base64
import androidx.documentfile.provider.DocumentFile
import com.script.SimpleBindings
import io.legado.app.R
import io.legado.app.constant.*
import io.legado.app.data.appDb
@ -20,6 +19,9 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.WebDavException
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import io.legado.app.rhino.putBinding
import io.legado.app.utils.*
import kotlinx.coroutines.runBlocking
import org.jsoup.nodes.Entities
@ -265,13 +267,15 @@ object LocalBook {
var author = ""
if (!AppConfig.bookImportFileName.isNullOrBlank()) {
try {
//在用户脚本后添加捕获author、name的代码只要脚本中author、name有值就会被捕获
val js =
AppConfig.bookImportFileName + "\nJSON.stringify({author:author,name:name})"
//在脚本中定义如何分解文件名成书名、作者名
val jsonStr = SCRIPT_ENGINE.eval(
//在用户脚本后添加捕获author、name的代码只要脚本中author、name有值就会被捕获
AppConfig.bookImportFileName + "\nJSON.stringify({author:author,name:name})",
//将文件名注入到脚本的src变量中
SimpleBindings().also { it["src"] = tempFileName }
).toString()
val jsonStr = Rhino.use {
val scope = initStandardObjects()
scope.putBinding("src", tempFileName)
evaluate(scope, js)
}.toString()
val bookMess = GSON.fromJsonObject<HashMap<String, String>>(jsonStr)
.getOrThrow()
name = bookMess["name"] ?: ""

View File

@ -0,0 +1,54 @@
/*
* Decompiled with CFR 0.152.
*/
package io.legado.app.rhino
class Bindings @JvmOverloads constructor(
private val map: MutableMap<String, Any?> = HashMap()
) : MutableMap<String, Any?> {
override fun put(key: String, value: Any?): Any? {
return map.put(key, value)
}
override fun putAll(from: Map<out String, Any?>) {
map.putAll(from)
}
override fun clear() {
map.clear()
}
override fun containsKey(key: String): Boolean {
return map.containsKey(key)
}
override fun containsValue(value: Any?): Boolean {
return map.containsValue(value)
}
override val entries: MutableSet<MutableMap.MutableEntry<String, Any?>>
get() = map.entries
override operator fun get(key: String): Any? {
return map[key]
}
override fun isEmpty(): Boolean {
return map.isEmpty()
}
override val keys: MutableSet<String>
get() = map.keys
override fun remove(key: String): Any? {
return map.remove(key)
}
override val size: Int
get() = map.size
override val values: MutableCollection<Any?>
get() = map.values
}

View File

@ -8,11 +8,10 @@ import org.mozilla.javascript.Wrapper
object Rhino {
inline fun use(block: Context.() -> Any?): Any? {
inline fun <T> use(block: Context.() -> T): T {
return try {
val cx = Context.enter()
val result = block.invoke(cx)
unwrapReturnValue(result)
block.invoke(cx)
} finally {
Context.exit()
}

View File

@ -7,12 +7,56 @@ import org.mozilla.javascript.Scriptable
import org.mozilla.javascript.ScriptableObject
import java.io.Reader
fun Context.evaluateString(scope: Scriptable, source: String, sourceName: String) {
evaluateString(scope, source, sourceName, 1, null)
fun Context.evaluate(
scope: Scriptable,
source: String,
sourceName: String = "<Unknown source>",
lineno: Int = 1,
securityDomain: Any? = null
): Any? {
return Rhino.unwrapReturnValue(
evaluateString(scope, source, sourceName, lineno, securityDomain)
)
}
fun Context.evaluateReader(scope: Scriptable, reader: Reader, sourceName: String) {
evaluateReader(scope, reader, sourceName, 1, null)
fun Context.evaluate(
bindings: Bindings,
source: String,
sourceName: String = "<Unknown source>",
lineno: Int = 1,
securityDomain: Any? = null
): Any? {
val scope = initStandardObjects()
scope.putBindings(bindings)
return Rhino.unwrapReturnValue(
evaluateString(scope, source, sourceName, lineno, securityDomain)
)
}
fun Context.evaluate(
scope: Scriptable,
reader: Reader,
sourceName: String = "<Unknown source>",
lineno: Int = 1,
securityDomain: Any? = null
): Any? {
return Rhino.unwrapReturnValue(
evaluateReader(scope, reader, sourceName, lineno, securityDomain)
)
}
fun Context.evaluate(
bindings: Bindings,
reader: Reader,
sourceName: String = "<Unknown source>",
lineno: Int = 1,
securityDomain: Any? = null
): Any? {
val scope = initStandardObjects()
scope.putBindings(bindings)
return Rhino.unwrapReturnValue(
evaluateReader(scope, reader, sourceName, lineno, securityDomain)
)
}
fun Scriptable.putBinding(key: String, value: Any?) {
@ -20,7 +64,7 @@ fun Scriptable.putBinding(key: String, value: Any?) {
ScriptableObject.putProperty(this, key, wrappedOut)
}
fun Scriptable.putBindings(bindings: Map<String, Any?>) {
fun Scriptable.putBindings(bindings: Bindings) {
bindings.forEach { (t, u) ->
putBinding(t, u)
}

View File

@ -1,20 +1,23 @@
package io.legado.app.utils
import com.script.SimpleBindings
import io.legado.app.constant.AppPattern.EXP_PATTERN
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
object JsUtils {
fun evalJs(js: String, bindingsFun: ((SimpleBindings) -> Unit)? = null): String {
val bindings = SimpleBindings()
fun evalJs(js: String, bindingsFun: ((Bindings) -> Unit)? = null): String {
val bindings = Bindings()
bindingsFun?.invoke(bindings)
if (js.contains("{{") && js.contains("}}")) {
val sb = StringBuffer()
val expMatcher = EXP_PATTERN.matcher(js)
while (expMatcher.find()) {
val result = expMatcher.group(1)?.let {
SCRIPT_ENGINE.eval(it, bindings)
Rhino.use {
evaluate(bindings, it)
}
} ?: ""
if (result is String) {
expMatcher.appendReplacement(sb, result)
@ -27,7 +30,9 @@ object JsUtils {
expMatcher.appendTail(sb)
return sb.toString()
}
return SCRIPT_ENGINE.eval(js, bindings).toString()
return Rhino.use {
evaluate(bindings, js)
}.toString()
}

View File

@ -1,11 +1,12 @@
package io.legado.app.utils
import androidx.core.os.postDelayed
import com.script.SimpleBindings
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.exception.RegexTimeoutException
import io.legado.app.help.CrashHandler
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import splitties.init.appCtx
@ -30,10 +31,11 @@ fun CharSequence.replace(regex: Regex, replacement: String, timeout: Long): Stri
val stringBuffer = StringBuffer()
while (matcher.find()) {
if (isJs) {
val bindings = SimpleBindings()
val bindings = Bindings()
bindings["result"] = matcher.group()
val jsResult =
SCRIPT_ENGINE.eval(replacement1, bindings).toString()
val jsResult = Rhino.use {
evaluate(bindings, replacement1)
}.toString()
matcher.appendReplacement(stringBuffer, jsResult)
} else {
matcher.appendReplacement(stringBuffer, replacement1)

View File

@ -1,14 +1,13 @@
package io.legado.app
import com.script.SimpleBindings
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.data.entities.BookChapter
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import io.legado.app.rhino.putBinding
import org.intellij.lang.annotations.Language
import org.junit.Assert
import org.junit.Test
import org.mozilla.javascript.Scriptable
class JsTest {
@ -56,7 +55,7 @@ class JsTest {
val scope = initStandardObjects()
evaluateString(scope, printJs, "print", 1, null)
scope
} as Scriptable
}
@Language("js")
val jsFor = """
@ -85,7 +84,9 @@ class JsTest {
@Test
fun testReturnNull() {
val result = SCRIPT_ENGINE.eval("null")
val result = Rhino.use {
evaluate(initStandardObjects(), "null")
}
Assert.assertEquals(null, result)
}
@ -101,9 +102,11 @@ class JsTest {
.replace(/\;/g,"")
.replace(/\:/g,"")
""".trimIndent()
val bindings = SimpleBindings()
bindings["result"] = ",.!?…;:"
val result = SCRIPT_ENGINE.eval(js, bindings).toString()
val result = Rhino.use {
val scope = initStandardObjects()
scope.putBinding("result", ",.!?…;:")
evaluate(scope, js)
}
Assert.assertEquals(result, ",。!?……;:")
}
@ -115,22 +118,29 @@ class JsTest {
bindings["chapter"] = chapter
@Language("js")
val js = "chapter.title"
val result = SCRIPT_ENGINE.eval(js, bindings)
val result = Rhino.use {
val scope = initStandardObjects()
scope.putBinding("chapter", chapter)
evaluate(scope, js)
}
Assert.assertEquals(result, "xxxyyy")
}
@Test
fun javaListForEach() {
val list = arrayListOf(1, 2, 3)
val bindings = SimpleBindings()
bindings["list"] = list
@Language("js")
val js = """
var result = 0
list.forEach(item => {result = result + item})
result
""".trimIndent()
val result = SCRIPT_ENGINE.eval(js, bindings)
val result = Rhino.use {
val scope = initStandardObjects()
scope.putBinding("list", list)
evaluate(scope, js)
}
Assert.assertEquals(result, 6.0)
}