Skip to content

Instantly share code, notes, and snippets.

@luttje
Created August 26, 2021 22:07
Show Gist options
  • Save luttje/8266f5e972fa70dafc8cd2d79286cdbe to your computer and use it in GitHub Desktop.
Save luttje/8266f5e972fa70dafc8cd2d79286cdbe to your computer and use it in GitHub Desktop.
Jint Allowlist to help decide which .NET members can be accessed from JavaScript (this is untested code, I copied this in part from a larger codebase)
[System.AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, Inherited = true)]
public class ScriptAllowedAttribute : Attribute
{
public ScriptAllowedAttribute() { }
}
[System.AttributeUsage(AttributeTargets.Class)]
public class ScriptAllowlistEnabledAttribute : Attribute
{
public ScriptAllowlistEnabledAttribute() { }
}
public class ScriptManager
{
private Engine engine;
public ScriptManager()
{
PrepareEngine();
engine.Evaluate("UI.createWindow() != null"); // Should be true
engine.Evaluate("UI.someOtherMethod()"); // Should fail
}
private void PrepareEngine()
{
engine = new Engine((_, opts) =>
{
opts.SetMemberAccessor(GetObjectMember);
});
var uiSystem = new UI();
engine.SetValue("UI", uiSystem);
}
private static JsValue GetObjectMember(Engine engine, object target, string memberName)
{
var type = target.GetType();
// Only classes marked with [ScriptAllowlistEnabled] attribute shall be filtered
if (type.GetCustomAttribute<ScriptAllowlistEnabledAttribute>() == null)
return null;
// Convert lowerCamelCase to UpperCamelCase
var builder = new StringBuilder();
builder.Append(char.ToUpper(memberName[0]));
if (memberName.Length > 1)
builder.Append(memberName.Substring(1));
var upperCamelCaseMemberName = builder.ToString();
var members = type.GetMember(upperCamelCaseMemberName);
MemberInfo member = null;
// Find member with [ScriptAllowed] attribute
for (var i = 0; i < members.Length; i++)
{
if (members[i].GetCustomAttribute<ScriptAllowedAttribute>() != null)
if (member == null)
member = members[i];
else
// TODO: Implement checking overloaded method somehow
throw new NotImplementedException($"Members name ambigous. Multiple members with name `{upperCamelCaseMemberName}` are marked [ScriptAccessible].");
}
// If no members exist marked [ScriptAllowed], do not allow accessing them
if (member == null)
return JsValue.Undefined;
// Return the value from the member
switch (member.MemberType)
{
case MemberTypes.Field:
return JsValue.FromObject(engine, ((FieldInfo)member).GetValue(target));
case MemberTypes.Property:
return JsValue.FromObject(engine, ((PropertyInfo)member).GetValue(target));
case MemberTypes.Method:
case MemberTypes.Constructor:
var method = (MethodBase)member;
var methodParameters = method.GetParameters();
// TODO: This seems hacky?
return new ClrFunctionInstance(engine, memberName, (thisObject, arguments) =>
{
return CallMethod(engine, method, methodParameters, (ObjectWrapper)thisObject, arguments);
}, methodParameters.Length, PropertyFlag.Configurable); // Why PropertyFlag.Configurable?? No idea.
default:
throw new NotImplementedException("Can't access this type of member from JavaScript yet!");
}
}
// Based on Jint\Runtime\Interop\MethodDescriptor.cs:105 in Jint
public static JsValue CallMethod(Engine engine, MethodBase member, ParameterInfo[] methodParameters, ObjectWrapper instance, JsValue[] arguments)
{
var parameters = new object[arguments.Length];
for (var i = 0; i < arguments.Length; i++)
{
var parameterType = methodParameters[i].ParameterType;
if (typeof(JsValue).IsAssignableFrom(parameterType))
{
parameters[i] = arguments[i];
}
else
{
parameters[i] = engine.ClrTypeConverter.Convert(
arguments[i].ToObject(),
parameterType,
System.Globalization.CultureInfo.InvariantCulture);
}
}
if (member is MethodInfo m)
{
var retVal = m.Invoke(instance.Target, parameters);
return JsValue.FromObject(engine, retVal);
}
else if (member is ConstructorInfo c)
{
var retVal = c.Invoke(parameters);
return JsValue.FromObject(engine, retVal);
}
else
{
throw new Exception("Method is unknown type");
}
}
}
[ScriptAllowlistEnabled]
public class UI
{
public UI()
{
}
// This method can be accessed from JavaScript (through 'createWindow' on a UI object)
[ScriptAllowed]
public Window CreateWindow()
{
return OtherUserInterfaceClass.CreateWindow();
}
// This method can't be accessed
public int SomeOtherMethod() => 9001;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment