diff --git a/app/src/main/java/io/legado/app/constant/ScriptEngine.kt b/app/src/main/java/io/legado/app/constant/ScriptEngine.kt deleted file mode 100644 index ee37d2e3f..000000000 --- a/app/src/main/java/io/legado/app/constant/ScriptEngine.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.legado.app.constant - -import com.script.rhino.RhinoScriptEngine - -val SCRIPT_ENGINE: RhinoScriptEngine by lazy { - RhinoScriptEngine() -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/BaseSource.kt b/app/src/main/java/io/legado/app/data/entities/BaseSource.kt index c09fb4cfd..9f87a2591 100644 --- a/app/src/main/java/io/legado/app/data/entities/BaseSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/BaseSource.kt @@ -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? { diff --git a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt index dbf722bf3..55dbe3dde 100644 --- a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt +++ b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt @@ -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") diff --git a/app/src/main/java/io/legado/app/model/SharedJsScope.kt b/app/src/main/java/io/legado/app/model/SharedJsScope.kt index e7b8a7613..10d320404 100644 --- a/app/src/main/java/io/legado/app/model/SharedJsScope.kt +++ b/app/src/main/java/io/legado/app/model/SharedJsScope.kt @@ -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 = GSON.fromJson( jsLib, diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt index 9e6aba827..f1c75422e 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt @@ -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? { diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt index 3c2cda490..91042723b 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt @@ -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 { diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index ef692a49e..1c3f5a52d 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -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>(jsonStr) .getOrThrow() name = bookMess["name"] ?: "" diff --git a/app/src/main/java/io/legado/app/rhino/Bindings.kt b/app/src/main/java/io/legado/app/rhino/Bindings.kt new file mode 100644 index 000000000..048acb742 --- /dev/null +++ b/app/src/main/java/io/legado/app/rhino/Bindings.kt @@ -0,0 +1,54 @@ +/* + * Decompiled with CFR 0.152. + */ +package io.legado.app.rhino + +class Bindings @JvmOverloads constructor( + private val map: MutableMap = HashMap() +) : MutableMap { + + override fun put(key: String, value: Any?): Any? { + return map.put(key, value) + } + + override fun putAll(from: Map) { + 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> + get() = map.entries + + override operator fun get(key: String): Any? { + return map[key] + } + + override fun isEmpty(): Boolean { + return map.isEmpty() + } + + override val keys: MutableSet + get() = map.keys + + override fun remove(key: String): Any? { + return map.remove(key) + } + + override val size: Int + get() = map.size + + override val values: MutableCollection + get() = map.values + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/rhino/Rhino.kt b/app/src/main/java/io/legado/app/rhino/Rhino.kt index 7e6eacb97..0a69a5b2c 100644 --- a/app/src/main/java/io/legado/app/rhino/Rhino.kt +++ b/app/src/main/java/io/legado/app/rhino/Rhino.kt @@ -8,11 +8,10 @@ import org.mozilla.javascript.Wrapper object Rhino { - inline fun use(block: Context.() -> Any?): Any? { + inline fun use(block: Context.() -> T): T { return try { val cx = Context.enter() - val result = block.invoke(cx) - unwrapReturnValue(result) + block.invoke(cx) } finally { Context.exit() } diff --git a/app/src/main/java/io/legado/app/rhino/RhinoExtensions.kt b/app/src/main/java/io/legado/app/rhino/RhinoExtensions.kt index 18173d241..c1589a125 100644 --- a/app/src/main/java/io/legado/app/rhino/RhinoExtensions.kt +++ b/app/src/main/java/io/legado/app/rhino/RhinoExtensions.kt @@ -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 = "", + 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 = "", + 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 = "", + 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 = "", + 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) { +fun Scriptable.putBindings(bindings: Bindings) { bindings.forEach { (t, u) -> putBinding(t, u) } diff --git a/app/src/main/java/io/legado/app/utils/JsUtils.kt b/app/src/main/java/io/legado/app/utils/JsUtils.kt index a9c85cc5c..e23346fe9 100644 --- a/app/src/main/java/io/legado/app/utils/JsUtils.kt +++ b/app/src/main/java/io/legado/app/utils/JsUtils.kt @@ -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() } diff --git a/app/src/main/java/io/legado/app/utils/RegexExtensions.kt b/app/src/main/java/io/legado/app/utils/RegexExtensions.kt index 247009117..b931e36d8 100644 --- a/app/src/main/java/io/legado/app/utils/RegexExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/RegexExtensions.kt @@ -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) diff --git a/app/src/test/java/io/legado/app/JsTest.kt b/app/src/test/java/io/legado/app/JsTest.kt index 4538b316e..4c8cb7ec6 100644 --- a/app/src/test/java/io/legado/app/JsTest.kt +++ b/app/src/test/java/io/legado/app/JsTest.kt @@ -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) }