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:
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