commit 37becec35047bee5e4493cd03ea26fbac3263492
parent 3edd6ed72336ca8f4f360405a85d17973881bcfd
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Mon, 25 Dec 2023 12:10:29 +0100

feat(scripting): permissions
- Java Interfaces binding

Diffstat:
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/scripting/JSModule.kt | 25++++++++++++++++++++-----
Acommon/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/JavaInterfaces.kt | 46++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/scripting/ktx/RhinoKtx.kt | 9+++++++--
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/scripting/type/ModuleInfo.kt | 20++++++++++++++++++--
Acommon/src/main/kotlin/me/rhunk/snapenhance/common/scripting/type/Permissions.kt | 8++++++++
5 files changed, 99 insertions(+), 9 deletions(-)

diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/JSModule.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/JSModule.kt @@ -4,11 +4,13 @@ import android.os.Handler import android.widget.Toast import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding import me.rhunk.snapenhance.common.scripting.bindings.BindingsContext +import me.rhunk.snapenhance.common.scripting.impl.JavaInterfaces import me.rhunk.snapenhance.common.scripting.ktx.contextScope import me.rhunk.snapenhance.common.scripting.ktx.putFunction import me.rhunk.snapenhance.common.scripting.ktx.scriptable import me.rhunk.snapenhance.common.scripting.ktx.scriptableObject import me.rhunk.snapenhance.common.scripting.type.ModuleInfo +import me.rhunk.snapenhance.common.scripting.type.Permissions import org.mozilla.javascript.Function import org.mozilla.javascript.NativeJavaObject import org.mozilla.javascript.ScriptableObject @@ -49,6 +51,10 @@ class JSModule( }) }) + registerBindings( + JavaInterfaces(), + ) + moduleObject.putFunction("setField") { args -> val obj = args?.get(0) as? NativeJavaObject ?: return@putFunction Undefined.instance val name = args[1].toString() @@ -74,8 +80,12 @@ class JSModule( moduleObject.putFunction("findClass") { val className = it?.get(0).toString() + val useModClassLoader = it?.getOrNull(1) as? Boolean ?: false + if (useModClassLoader) moduleInfo.ensurePermissionGranted(Permissions.UNSAFE_CLASSLOADER) + runCatching { - classLoader.loadClass(className) + if (useModClassLoader) this::class.java.classLoader?.loadClass(className) + else classLoader.loadClass(className) }.onFailure { throwable -> scriptRuntime.logger.error("Failed to load class $className", throwable) }.getOrNull() @@ -83,7 +93,12 @@ class JSModule( moduleObject.putFunction("type") { args -> val className = args?.get(0).toString() - val clazz = runCatching { classLoader.loadClass(className) }.getOrNull() ?: return@putFunction Undefined.instance + val useModClassLoader = args?.getOrNull(1) as? Boolean ?: false + if (useModClassLoader) moduleInfo.ensurePermissionGranted(Permissions.UNSAFE_CLASSLOADER) + + val clazz = runCatching { + if (useModClassLoader) this::class.java.classLoader?.loadClass(className) else classLoader.loadClass(className) + }.getOrNull() ?: return@putFunction Undefined.instance scriptableObject("JavaClassWrapper") { putFunction("newInstance") newInstance@{ args -> @@ -116,7 +131,7 @@ class JSModule( } moduleObject.putFunction("logError") { args -> - scriptRuntime.logger.error(argsToString(arrayOf(args?.get(0))), args?.get(1) as? Throwable ?: Throwable()) + scriptRuntime.logger.error(argsToString(arrayOf(args?.get(0))), args?.getOrNull(1) as? Throwable ?: Throwable()) Undefined.instance } @@ -179,8 +194,8 @@ class JSModule( contextScope { name.split(".").also { split -> val function = split.dropLast(1).fold(moduleObject) { obj, key -> - obj.get(key, obj) as? ScriptableObject ?: return@contextScope - }.get(split.last(), moduleObject) as? Function ?: return@contextScope + obj.get(key, obj) as? ScriptableObject ?: return@contextScope Unit + }.get(split.last(), moduleObject) as? Function ?: return@contextScope Unit runCatching { function.call(this, moduleObject, moduleObject, args) diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/JavaInterfaces.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/JavaInterfaces.kt @@ -0,0 +1,45 @@ +package me.rhunk.snapenhance.common.scripting.impl + +import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding +import me.rhunk.snapenhance.common.scripting.bindings.BindingSide +import me.rhunk.snapenhance.common.scripting.ktx.contextScope +import me.rhunk.snapenhance.common.scripting.ktx.putFunction +import me.rhunk.snapenhance.common.scripting.ktx.scriptableObject +import java.lang.reflect.Proxy + +class JavaInterfaces : AbstractBinding("java-interfaces", BindingSide.COMMON) { + override fun getObject() = scriptableObject { + putFunction("runnable") { + val function = it?.get(0) as? org.mozilla.javascript.Function ?: return@putFunction null + Runnable { + contextScope { + function.call( + this, + this@scriptableObject, + this@scriptableObject, + emptyArray() + ) + } + } + } + + putFunction("newProxy") { arguments -> + val javaInterface = arguments?.get(0) as? Class<*> ?: return@putFunction null + val function = arguments[1] as? org.mozilla.javascript.Function ?: return@putFunction null + + Proxy.newProxyInstance( + javaInterface.classLoader, + arrayOf(javaInterface) + ) { instance, method, args -> + contextScope { + function.call( + this, + this@scriptableObject, + this@scriptableObject, + arrayOf(instance, method.name, (args ?: emptyArray<Any>()).toList()) + ) + } + } + } + } +}+ \ No newline at end of file diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/ktx/RhinoKtx.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/ktx/RhinoKtx.kt @@ -4,12 +4,17 @@ import org.mozilla.javascript.Context import org.mozilla.javascript.Function import org.mozilla.javascript.Scriptable import org.mozilla.javascript.ScriptableObject +import org.mozilla.javascript.Wrapper -fun contextScope(f: Context.() -> Unit) { +fun contextScope(f: Context.() -> Any?): Any? { val context = Context.enter() context.optimizationLevel = -1 try { - context.f() + return context.f().let { + if (it is Wrapper) { + it.unwrap() + } else it + } } finally { Context.exit() } diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/type/ModuleInfo.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/type/ModuleInfo.kt @@ -9,4 +9,20 @@ data class ModuleInfo( val minSnapchatVersion: Long? = null, val minSEVersion: Long? = null, val grantedPermissions: List<String>, -)- \ No newline at end of file +) { + override fun equals(other: Any?): Boolean { + if (other !is ModuleInfo) return false + if (other === this) return true + return name == other.name && + version == other.version && + displayName == other.displayName && + description == other.description && + author == other.author + } + + fun ensurePermissionGranted(permission: Permissions) { + if (!grantedPermissions.contains(permission.key)) { + throw AssertionError("Permission $permission is not granted") + } + } +}+ \ No newline at end of file diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/type/Permissions.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/type/Permissions.kt @@ -0,0 +1,7 @@ +package me.rhunk.snapenhance.common.scripting.type + +enum class Permissions( + val key: String, +) { + UNSAFE_CLASSLOADER("unsafe-classloader"), +}+ \ No newline at end of file