From 53dea647154a5ddcc0f15525ebc4d4dc91b58c51 Mon Sep 17 00:00:00 2001 From: Horis <8674809+821938089@users.noreply.github.com> Date: Thu, 22 May 2025 19:39:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/App.kt | 27 ++++++++++- .../io/legado/app/data/entities/BaseSource.kt | 8 ++-- .../app/help/crypto/SymmetricCryptoAndroid.kt | 12 +++++ .../legado/app/help/rhino/NativeBaseSource.kt | 46 +++++++++++++++++++ .../app/help/source/BaseSourceExtensions.kt | 16 ------- .../app/model/analyzeRule/AnalyzeRule.kt | 22 ++------- .../app/model/analyzeRule/AnalyzeUrl.kt | 5 +- .../legado/app/ui/rss/read/RssJsExtensions.kt | 2 +- .../legado/app/utils/BookChapterExtensions.kt | 7 --- .../legado/app/utils/RssArticleExtensions.kt | 10 ---- .../io/legado/app/utils/StringExtensions.kt | 6 +++ .../com/script/rhino/JavaObjectWrapFactory.kt | 9 ++++ .../com/script/rhino/ReadOnlyJavaObject.kt | 43 +++++++++++++++++ .../java/com/script/rhino/RhinoWrapFactory.kt | 19 +++++++- 14 files changed, 171 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/io/legado/app/help/rhino/NativeBaseSource.kt delete mode 100644 app/src/main/java/io/legado/app/utils/RssArticleExtensions.kt create mode 100644 modules/rhino/src/main/java/com/script/rhino/JavaObjectWrapFactory.kt create mode 100644 modules/rhino/src/main/java/com/script/rhino/ReadOnlyJavaObject.kt diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index 30c445c2a..504360076 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -12,13 +12,24 @@ import android.os.Build import com.github.liuyueyi.quick.transfer.constants.TransType import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.logger.DefaultLogger +import com.script.rhino.ReadOnlyJavaObject import com.script.rhino.RhinoScriptEngine +import com.script.rhino.RhinoWrapFactory import io.legado.app.base.AppContextWrapper import io.legado.app.constant.AppConst.channelIdDownload import io.legado.app.constant.AppConst.channelIdReadAloud import io.legado.app.constant.AppConst.channelIdWeb import io.legado.app.constant.PreferKey import io.legado.app.data.appDb +import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookChapter +import io.legado.app.data.entities.BookSource +import io.legado.app.data.entities.HttpTTS +import io.legado.app.data.entities.RssSource +import io.legado.app.data.entities.rule.BookInfoRule +import io.legado.app.data.entities.rule.ContentRule +import io.legado.app.data.entities.rule.ExploreRule +import io.legado.app.data.entities.rule.SearchRule import io.legado.app.help.AppFreezeMonitor import io.legado.app.help.AppWebDav import io.legado.app.help.CrashHandler @@ -33,6 +44,7 @@ import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.http.Cronet import io.legado.app.help.http.ObsoleteUrlFactory import io.legado.app.help.http.okHttpClient +import io.legado.app.help.rhino.NativeBaseSource import io.legado.app.help.source.SourceHelp import io.legado.app.help.storage.Backup import io.legado.app.model.BookCover @@ -79,7 +91,7 @@ class App : Application() { AppFreezeMonitor.init(this@App) URL.setURLStreamHandlerFactory(ObsoleteUrlFactory(okHttpClient)) launch { installGmsTlsProvider(appCtx) } - RhinoScriptEngine + initRhino() //初始化封面 BookCover.toString() //清除过期数据 @@ -200,6 +212,19 @@ class App : Application() { ) } + private fun initRhino() { + RhinoScriptEngine + RhinoWrapFactory.register(BookSource::class.java, NativeBaseSource.factory) + RhinoWrapFactory.register(RssSource::class.java, NativeBaseSource.factory) + RhinoWrapFactory.register(HttpTTS::class.java, NativeBaseSource.factory) + RhinoWrapFactory.register(ExploreRule::class.java, ReadOnlyJavaObject.factory) + RhinoWrapFactory.register(SearchRule::class.java, ReadOnlyJavaObject.factory) + RhinoWrapFactory.register(BookInfoRule::class.java, ReadOnlyJavaObject.factory) + RhinoWrapFactory.register(ContentRule::class.java, ReadOnlyJavaObject.factory) + RhinoWrapFactory.register(BookChapter::class.java, ReadOnlyJavaObject.factory) + RhinoWrapFactory.register(Book.ReadConfig::class.java, ReadOnlyJavaObject.factory) + } + class EventLogger : DefaultLogger() { override fun log(level: Level, msg: String) { 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 fa010b507..63101f23c 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 @@ -12,7 +12,6 @@ import io.legado.app.help.JsExtensions import io.legado.app.help.config.AppConfig import io.legado.app.help.crypto.SymmetricCryptoAndroid import io.legado.app.help.http.CookieStore -import io.legado.app.help.source.copy import io.legado.app.help.source.getShareScope import io.legado.app.model.Debug import io.legado.app.utils.GSON @@ -63,7 +62,7 @@ interface BaseSource : JsExtensions { fun getKey(): String override fun getSource(): BaseSource? { - return copy() + return this } fun loginUi(): List? { @@ -240,10 +239,9 @@ interface BaseSource : JsExtensions { */ @Throws(Exception::class) fun evalJS(jsStr: String, bindingsConfig: ScriptBindings.() -> Unit = {}): Any? { - val sourceCopy = copy() val bindings = buildScriptBindings { bindings -> - bindings["java"] = sourceCopy - bindings["source"] = sourceCopy + bindings["java"] = this + bindings["source"] = this bindings["baseUrl"] = getKey() bindings["cookie"] = CookieStore bindings["cache"] = CacheManager diff --git a/app/src/main/java/io/legado/app/help/crypto/SymmetricCryptoAndroid.kt b/app/src/main/java/io/legado/app/help/crypto/SymmetricCryptoAndroid.kt index 7dbd0c6a6..c98eb68d4 100644 --- a/app/src/main/java/io/legado/app/help/crypto/SymmetricCryptoAndroid.kt +++ b/app/src/main/java/io/legado/app/help/crypto/SymmetricCryptoAndroid.kt @@ -1,8 +1,11 @@ package io.legado.app.help.crypto import androidx.annotation.Keep +import cn.hutool.core.codec.Base64 +import cn.hutool.core.util.HexUtil import cn.hutool.crypto.symmetric.SymmetricCrypto import io.legado.app.utils.EncoderUtils +import io.legado.app.utils.isHex import java.io.InputStream import java.nio.charset.Charset @@ -32,4 +35,13 @@ class SymmetricCryptoAndroid( return EncoderUtils.base64Encode(encrypt(data)) } + override fun decrypt(data: String): ByteArray { + val bytes = if (data.isHex()) { + HexUtil.decodeHex(data) + } else { + Base64.decode(data) + } + return decrypt(bytes) + } + } diff --git a/app/src/main/java/io/legado/app/help/rhino/NativeBaseSource.kt b/app/src/main/java/io/legado/app/help/rhino/NativeBaseSource.kt new file mode 100644 index 000000000..ed50b4d88 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/rhino/NativeBaseSource.kt @@ -0,0 +1,46 @@ +package io.legado.app.help.rhino + +import com.script.rhino.JavaObjectWrapFactory +import org.mozilla.javascript.NativeJavaObject +import org.mozilla.javascript.Scriptable + +class NativeBaseSource(scope: Scriptable?, javaObject: Any, staticType: Class<*>?) : + NativeJavaObject(scope, javaObject, staticType) { + + override fun has(name: String, start: Scriptable): Boolean { + if (name != "setVariable" && name.length > 3 && name.startsWith("set")) { + val name = name.substring(3).replaceFirstChar { it.lowercase() } + if (super.has(name, start)) { + return false + } + } + return super.has(name, start) + } + + override fun get(name: String, start: Scriptable): Any? { + if (name != "setVariable" && name.length > 3 && name.startsWith("set")) { + val name = name.substring(3).replaceFirstChar { it.lowercase() } + if (super.has(name, start)) { + return NOT_FOUND + } + } + return super.get(name, start) + } + + override fun put( + name: String, + start: Scriptable, + value: Any? + ) { + if (name == "variable") { + super.put(name, start, value) + } + } + + companion object { + val factory = JavaObjectWrapFactory { scope, javaObject, staticType -> + NativeBaseSource(scope, javaObject, staticType) + } + } + +} diff --git a/app/src/main/java/io/legado/app/help/source/BaseSourceExtensions.kt b/app/src/main/java/io/legado/app/help/source/BaseSourceExtensions.kt index e51428e42..5c64a1b66 100644 --- a/app/src/main/java/io/legado/app/help/source/BaseSourceExtensions.kt +++ b/app/src/main/java/io/legado/app/help/source/BaseSourceExtensions.kt @@ -3,7 +3,6 @@ package io.legado.app.help.source import io.legado.app.constant.SourceType import io.legado.app.data.entities.BaseSource import io.legado.app.data.entities.BookSource -import io.legado.app.data.entities.HttpTTS import io.legado.app.data.entities.RssSource import io.legado.app.model.SharedJsScope import org.mozilla.javascript.Scriptable @@ -20,18 +19,3 @@ fun BaseSource.getSourceType(): Int { else -> error("unknown source type: ${this::class.simpleName}.") } } - -fun BaseSource.copy(): BaseSource { - return when (this) { - is BookSource -> copy( - ruleExplore = ruleExplore?.copy(), - ruleSearch = ruleSearch?.copy(), - ruleBookInfo = ruleBookInfo?.copy(), - ruleToc = ruleToc?.copy() - ) - - is RssSource -> copy() - is HttpTTS -> copy() - else -> error("unknown copy source type: ${this::class.simpleName}.") - } -} 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 e372614ab..4005798c6 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 @@ -16,7 +16,6 @@ import io.legado.app.exception.NoStackTraceException import io.legado.app.help.CacheManager import io.legado.app.help.JsExtensions import io.legado.app.help.http.CookieStore -import io.legado.app.help.source.copy import io.legado.app.help.source.getShareScope import io.legado.app.model.Debug import io.legado.app.model.webBook.WebBook @@ -30,7 +29,6 @@ import io.legado.app.utils.isJson import io.legado.app.utils.printOnDebug import io.legado.app.utils.splitNotBlank import io.legado.app.utils.stackTraceStr -import io.legado.app.utils.updateVariableTo import kotlinx.coroutines.ensureActive import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout @@ -777,15 +775,15 @@ class AnalyzeRule( bindings["java"] = this bindings["cookie"] = CookieStore bindings["cache"] = CacheManager - bindings["source"] = source?.copy() + bindings["source"] = source bindings["book"] = book bindings["result"] = result bindings["baseUrl"] = baseUrl - bindings["chapter"] = chapterCopy - bindings["title"] = chapterCopy?.title + bindings["chapter"] = chapter + bindings["title"] = chapter?.title bindings["src"] = content bindings["nextChapterUrl"] = nextChapterUrl - bindings["rssArticle"] = rssArticleCopy + bindings["rssArticle"] = rssArticle } val topScope = source?.getShareScope(coroutineContext) ?: topScopeRef?.get() val scope = if (topScope == null) { @@ -801,19 +799,9 @@ class AnalyzeRule( } val script = compileScriptCache(jsStr) val result = script.eval(scope, coroutineContext) - updateVariable(chapterCopy, rssArticleCopy) return result } - private fun updateVariable(chapterCopy: BookChapter?, rssArticleCopy: RssArticle?) { - chapter?.let { - chapterCopy?.updateVariableTo(it) - } - rssArticle?.let { - rssArticleCopy?.updateVariableTo(it) - } - } - private fun compileScriptCache(jsStr: String): CompiledScript { return scriptCache.getOrPutLimit(jsStr, 16) { RhinoScriptEngine.compile(jsStr) @@ -821,7 +809,7 @@ class AnalyzeRule( } override fun getSource(): BaseSource? { - return source?.copy() + return source } /** 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 a529a7e94..3987bb6ae 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 @@ -38,7 +38,6 @@ import io.legado.app.help.http.newCallStrResponse import io.legado.app.help.http.postForm import io.legado.app.help.http.postJson import io.legado.app.help.http.postMultipart -import io.legado.app.help.source.copy import io.legado.app.help.source.getShareScope import io.legado.app.model.Debug import io.legado.app.utils.EncoderUtils @@ -357,7 +356,7 @@ class AnalyzeUrl( bindings["speakText"] = speakText bindings["speakSpeed"] = speakSpeed bindings["book"] = ruleData as? Book - bindings["source"] = source?.copy() + bindings["source"] = source bindings["result"] = result } val sharedScope = source?.getShareScope(coroutineContext) @@ -660,7 +659,7 @@ class AnalyzeUrl( } override fun getSource(): BaseSource? { - return source?.copy() + return source } companion object { diff --git a/app/src/main/java/io/legado/app/ui/rss/read/RssJsExtensions.kt b/app/src/main/java/io/legado/app/ui/rss/read/RssJsExtensions.kt index edcac9a9e..5df98b9b7 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/RssJsExtensions.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/RssJsExtensions.kt @@ -10,7 +10,7 @@ import io.legado.app.utils.showDialogFragment class RssJsExtensions(private val activity: ReadRssActivity) : JsExtensions { override fun getSource(): BaseSource? { - return activity.getSource()?.copy() + return activity.getSource() } fun searchBook(key: String) { diff --git a/app/src/main/java/io/legado/app/utils/BookChapterExtensions.kt b/app/src/main/java/io/legado/app/utils/BookChapterExtensions.kt index 53539308c..890062f6a 100644 --- a/app/src/main/java/io/legado/app/utils/BookChapterExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/BookChapterExtensions.kt @@ -6,10 +6,3 @@ fun BookChapter.internString() { title = title.intern() bookUrl = bookUrl.intern() } - -fun BookChapter.updateVariableTo(chapter: BookChapter) { - if (variable != chapter.variable) { - chapter.variableMap.clear() - chapter.variableMap.putAll(variableMap) - } -} diff --git a/app/src/main/java/io/legado/app/utils/RssArticleExtensions.kt b/app/src/main/java/io/legado/app/utils/RssArticleExtensions.kt deleted file mode 100644 index 3f0ef6355..000000000 --- a/app/src/main/java/io/legado/app/utils/RssArticleExtensions.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.legado.app.utils - -import io.legado.app.data.entities.RssArticle - -fun RssArticle.updateVariableTo(rssArticle: RssArticle) { - if (variable != rssArticle.variable) { - rssArticle.variableMap.clear() - rssArticle.variableMap.putAll(variableMap) - } -} diff --git a/app/src/main/java/io/legado/app/utils/StringExtensions.kt b/app/src/main/java/io/legado/app/utils/StringExtensions.kt index 0bb75668f..483ef96e3 100644 --- a/app/src/main/java/io/legado/app/utils/StringExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/StringExtensions.kt @@ -78,6 +78,12 @@ fun String?.isTrue(nullIsTrue: Boolean = false): Boolean { return !this.trim().matches("(?i)^(false|no|not|0)$".toRegex()) } +fun String.isHex(): Boolean { + return all {c -> + c in '0'..'9' || c in 'A'..'F' || c in 'a'..'f' + } +} + fun String.splitNotBlank(vararg delimiter: String, limit: Int = 0): Array = run { this.split(*delimiter, limit = limit).map { it.trim() }.filterNot { it.isBlank() } .toTypedArray() diff --git a/modules/rhino/src/main/java/com/script/rhino/JavaObjectWrapFactory.kt b/modules/rhino/src/main/java/com/script/rhino/JavaObjectWrapFactory.kt new file mode 100644 index 000000000..e9520cec0 --- /dev/null +++ b/modules/rhino/src/main/java/com/script/rhino/JavaObjectWrapFactory.kt @@ -0,0 +1,9 @@ +package com.script.rhino + +import org.mozilla.javascript.Scriptable + +fun interface JavaObjectWrapFactory { + + fun wrap(scope: Scriptable?, javaObject: Any, staticType: Class<*>?): Scriptable + +} diff --git a/modules/rhino/src/main/java/com/script/rhino/ReadOnlyJavaObject.kt b/modules/rhino/src/main/java/com/script/rhino/ReadOnlyJavaObject.kt new file mode 100644 index 000000000..15477e212 --- /dev/null +++ b/modules/rhino/src/main/java/com/script/rhino/ReadOnlyJavaObject.kt @@ -0,0 +1,43 @@ +package com.script.rhino + +import org.mozilla.javascript.NativeJavaObject +import org.mozilla.javascript.Scriptable + +class ReadOnlyJavaObject(scope: Scriptable?, javaObject: Any, staticType: Class<*>?) : + NativeJavaObject(scope, javaObject, staticType) { + + override fun has(name: String, start: Scriptable): Boolean { + if (name.length > 3 && name.startsWith("set")) { + val name = name.substring(3).replaceFirstChar { it.lowercase() } + if (super.has(name, start)) { + return false + } + } + return super.has(name, start) + } + + override fun get(name: String, start: Scriptable): Any? { + if (name.length > 3 && name.startsWith("set")) { + val name = name.substring(3).replaceFirstChar { it.lowercase() } + if (super.has(name, start)) { + return NOT_FOUND + } + } + return super.get(name, start) + } + + override fun put( + name: String?, + start: Scriptable?, + value: Any? + ) { + // do nothing + } + + companion object { + val factory = JavaObjectWrapFactory { scope, javaObject, staticType -> + ReadOnlyJavaObject(scope, javaObject, staticType) + } + } + +} diff --git a/modules/rhino/src/main/java/com/script/rhino/RhinoWrapFactory.kt b/modules/rhino/src/main/java/com/script/rhino/RhinoWrapFactory.kt index f5a1989b3..8576047fa 100644 --- a/modules/rhino/src/main/java/com/script/rhino/RhinoWrapFactory.kt +++ b/modules/rhino/src/main/java/com/script/rhino/RhinoWrapFactory.kt @@ -45,6 +45,8 @@ import org.mozilla.javascript.WrapFactory */ object RhinoWrapFactory : WrapFactory() { + private val factories = hashMapOf, JavaObjectWrapFactory>() + override fun wrapAsJavaObject( cx: Context, scope: Scriptable?, @@ -54,7 +56,8 @@ object RhinoWrapFactory : WrapFactory() { if (!RhinoClassShutter.visibleToScripts(javaObject)) { return null } - return super.wrapAsJavaObject(cx, scope, javaObject, staticType) + return wrapOrNull(scope, javaObject, staticType) + ?: super.wrapAsJavaObject(cx, scope, javaObject, staticType) } override fun wrapJavaClass( @@ -71,4 +74,18 @@ object RhinoWrapFactory : WrapFactory() { return RhinoClassShutter.wrapJavaClass(scope, javaClass) } + private fun wrapOrNull( + scope: Scriptable?, + javaObject: Any, + staticType: Class<*>? + ): Scriptable? { + return factories[javaObject.javaClass]?.wrap(scope, javaObject, staticType) + } + + fun register(clazz: Class<*>, factory: JavaObjectWrapFactory) { + if (!factories.contains(clazz)) { + factories.put(clazz, factory) + } + } + } \ No newline at end of file