using System.Dynamic; using System.Globalization; using System.Reflection; using Jint.Native; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Interop; using Jint.Runtime.Debugger; using Jint.Runtime.Descriptors; using Jint.Runtime.Modules; namespace Jint { public delegate JsValue? MemberAccessorDelegate(Engine engine, object target, string member); public delegate ObjectInstance? WrapObjectDelegate(Engine engine, object target); public delegate bool ExceptionHandlerDelegate(Exception exception); public class Options { internal List> _configurations { get; } = new(); /// /// Execution constraints for the engine. /// public ConstraintOptions Constraints { get; } = new(); /// /// CLR interop related options. /// public InteropOptions Interop { get; } = new(); /// /// Debugger configuration. /// public DebuggerOptions Debugger { get; } = new(); /// /// Host options. /// internal HostOptions Host { get; } = new(); /// /// Module options /// public ModuleOptions Modules { get; } = new(); /// /// Whether the code should be always considered to be in strict mode. Can improve performance. /// public bool Strict { get; set; } /// /// The culture the engine runs on, defaults to current culture. /// public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture; /// /// The time zone the engine runs on, defaults to local. /// public TimeZoneInfo TimeZone { get; set; } = TimeZoneInfo.Local; /// /// Reference resolver allows customizing behavior for reference resolving. This can be useful in cases where /// you want to ignore long chain of property accesses that might throw if anything is null or undefined. /// An example of such is var a = obj.field.subField.value. Custom resolver could accept chain to return /// null/undefined on first occurrence. /// public IReferenceResolver ReferenceResolver { get; set; } = DefaultReferenceResolver.Instance; /// /// Called by the instance that loads this /// once it is loaded. /// internal void Apply(Engine engine) { foreach (var configuration in _configurations) { configuration?.Invoke(engine); } // add missing bits if needed if (Interop.Enabled) { engine.Realm.GlobalObject.SetProperty("System", new PropertyDescriptor(new NamespaceReference(engine, "System"), PropertyFlag.AllForbidden)); engine.Realm.GlobalObject.SetProperty("importNamespace", new PropertyDescriptor(new ClrFunctionInstance( engine, "importNamespace", (thisObj, arguments) => new NamespaceReference(engine, TypeConverter.ToString(arguments.At(0)))), PropertyFlag.AllForbidden)); } if (Interop.ExtensionMethodTypes.Count > 0) { AttachExtensionMethodsToPrototypes(engine); } if (Modules.RegisterRequire) { // Node js like loading of modules engine.Realm.GlobalObject.SetProperty("require", new PropertyDescriptor(new ClrFunctionInstance( engine, "require", (thisObj, arguments) => { var specifier = TypeConverter.ToString(arguments.At(0)); return engine.ImportModule(specifier); }), PropertyFlag.AllForbidden)); } engine.ModuleLoader = Modules.ModuleLoader; // ensure defaults engine.ClrTypeConverter ??= new DefaultTypeConverter(engine); } private static void AttachExtensionMethodsToPrototypes(Engine engine) { AttachExtensionMethodsToPrototype(engine, engine.Realm.Intrinsics.Array.PrototypeObject, typeof(Array)); AttachExtensionMethodsToPrototype(engine, engine.Realm.Intrinsics.Boolean.PrototypeObject, typeof(bool)); AttachExtensionMethodsToPrototype(engine, engine.Realm.Intrinsics.Date.PrototypeObject, typeof(DateTime)); AttachExtensionMethodsToPrototype(engine, engine.Realm.Intrinsics.Number.PrototypeObject, typeof(double)); AttachExtensionMethodsToPrototype(engine, engine.Realm.Intrinsics.Object.PrototypeObject, typeof(ExpandoObject)); AttachExtensionMethodsToPrototype(engine, engine.Realm.Intrinsics.RegExp.PrototypeObject, typeof(System.Text.RegularExpressions.Regex)); AttachExtensionMethodsToPrototype(engine, engine.Realm.Intrinsics.String.PrototypeObject, typeof(string)); } private static void AttachExtensionMethodsToPrototype(Engine engine, ObjectInstance prototype, Type objectType) { if (!engine._extensionMethods.TryGetExtensionMethods(objectType, out var methods)) { return; } foreach (var overloads in methods.GroupBy(x => x.Name)) { PropertyDescriptor CreateMethodInstancePropertyDescriptor(ClrFunctionInstance? function) { var instance = function is null ? new MethodInfoFunctionInstance(engine, MethodDescriptor.Build(overloads.ToList())) : new MethodInfoFunctionInstance(engine, MethodDescriptor.Build(overloads.ToList()), function); return new PropertyDescriptor(instance, PropertyFlag.AllForbidden); } JsValue key = overloads.Key; PropertyDescriptor? descriptorWithFallback = null; PropertyDescriptor? descriptorWithoutFallback = null; if (prototype.HasOwnProperty(key) && prototype.GetOwnProperty(key).Value is ClrFunctionInstance clrFunctionInstance) { descriptorWithFallback = CreateMethodInstancePropertyDescriptor(clrFunctionInstance); prototype.SetOwnProperty(key, descriptorWithFallback); } else { descriptorWithoutFallback = CreateMethodInstancePropertyDescriptor(null); prototype.SetOwnProperty(key, descriptorWithoutFallback); } // make sure we register both lower case and upper case if (char.IsUpper(overloads.Key[0])) { key = char.ToLower(overloads.Key[0]) + overloads.Key.Substring(1); if (prototype.HasOwnProperty(key) && prototype.GetOwnProperty(key).Value is ClrFunctionInstance lowerclrFunctionInstance) { descriptorWithFallback ??= CreateMethodInstancePropertyDescriptor(lowerclrFunctionInstance); prototype.SetOwnProperty(key, descriptorWithFallback); } else { descriptorWithoutFallback ??= CreateMethodInstancePropertyDescriptor(null); prototype.SetOwnProperty(key, descriptorWithoutFallback); } } } } } public class DebuggerOptions { /// /// Whether debugger functionality is enabled, defaults to false. /// public bool Enabled { get; set; } /// /// Configures the statement handling strategy, defaults to Ignore. /// public DebuggerStatementHandling StatementHandling { get; set; } = DebuggerStatementHandling.Ignore; /// /// Configures the step mode used when entering the script. /// public StepMode InitialStepMode { get; set; } = StepMode.None; } public class InteropOptions { /// /// Whether accessing CLR and it's types and methods is allowed from JS code, defaults to false. /// public bool Enabled { get; set; } /// /// Whether to expose which can allow bypassing allow lists and open a way to reflection. /// Defaults to false. /// public bool AllowGetType { get; set; } /// /// Whether Jint should allow wrapping objects from System.Reflection namespace. /// Defaults to false. /// public bool AllowSystemReflection { get; set; } /// /// Whether writing to CLR objects is allowed (set properties), defaults to true. /// public bool AllowWrite { get; set; } = true; /// /// Whether operator overloading resolution is allowed, defaults to false. /// public bool AllowOperatorOverloading { get; set; } /// /// Types holding extension methods that should be considered when resolving methods. /// public List ExtensionMethodTypes { get; } = new(); /// /// Object converters to try when build-in conversions. /// public List ObjectConverters { get; } = new(); /// /// Whether identity map is persisted for object wrappers in order to maintain object identity. /// Defaults to true. /// public bool TrackObjectWrapperIdentity { get; set; } = true; /// /// If no known type could be guessed, objects are by default wrapped as an /// ObjectInstance using class ObjectWrapper. This function can be used to /// change the behavior. /// public WrapObjectDelegate WrapObjectHandler { get; set; } = static (engine, target) => { // check global cache, have we already wrapped the value? if (engine._objectWrapperCache.TryGetValue(target, out var wrapped)) { return wrapped; } wrapped = new ObjectWrapper(engine, target); if (engine.Options.Interop.TrackObjectWrapperIdentity) { engine._objectWrapperCache.Add(target, wrapped); } return wrapped; }; /// /// /// public MemberAccessorDelegate MemberAccessor { get; set; } = static (engine, target, member) => null; /// /// Exceptions that thrown from CLR code are converted to JavaScript errors and /// can be used in at try/catch statement. By default these exceptions are bubbled /// to the CLR host and interrupt the script execution. If handler returns true these exceptions are converted /// to JS errors that can be caught by the script. /// public ExceptionHandlerDelegate ExceptionHandler { get; set; } = static exception => false; /// /// Assemblies to allow scripts to call CLR types directly like System.IO.File. /// public List AllowedAssemblies { get; set; } = new(); /// /// Type and member resolving strategy, which allows filtering allowed members and configuring member /// name matching comparison. /// /// /// As this object holds caching state same instance should be shared between engines, if possible. /// public TypeResolver TypeResolver { get; set; } = TypeResolver.Default; /// /// When writing values to CLR objects, how should JS values be coerced to CLR types. /// Defaults to only coercing to string values when writing to string targets. /// public ValueCoercionType ValueCoercion { get; set; } = ValueCoercionType.String; /// /// Strategy to create a CLR object to hold converted . /// public Func> CreateClrObject = _ => new ExpandoObject(); /// /// When not null, is used to serialize any CLR object in an /// passing through 'JSON.stringify'. /// public Func? SerializeToJson { get; set; } } /// /// Rules for writing values to CLR fields. /// [Flags] public enum ValueCoercionType { /// /// No coercion will be done. If there's no type converter, and error will be thrown. /// None = 0, /// /// JS coercion using boolean rules "dog" == true, "" == false, 1 == true, 3 == true, 0 == false, { "prop": 1 } == true etc. /// Boolean = 1, /// /// JS coercion to numbers, false == 0, true == 1. valueOf functions will be used when available for object instances. /// Valid against targets of type: Decimal, Double, Int32, Int64. /// Number = 2, /// /// JS coercion to strings, toString function will be used when available for objects. /// String = 4, /// /// All coercion rules enabled. /// All = Boolean | Number | String } public class ConstraintOptions { /// /// Registered constraints. /// public List Constraints { get; } = new(); /// /// Maximum recursion depth allowed, defaults to -1 (no checks). /// public int MaxRecursionDepth { get; set; } = -1; /// /// Maximum time a Regex is allowed to run, defaults to 10 seconds. /// public TimeSpan RegexTimeout { get; set; } = TimeSpan.FromSeconds(10); /// /// The maximum size for JavaScript array, defaults to . /// public uint MaxArraySize { get; set; } = uint.MaxValue; } /// /// Host related customization, still work in progress. /// public class HostOptions { internal Func Factory { get; set; } = _ => new Host(); } /// /// Module related customization /// public class ModuleOptions { /// /// Whether to register require function to engine which will delegate to module loader, defaults to false. /// public bool RegisterRequire { get; set; } /// /// Module loader implementation, by default exception will be thrown if module loading is not enabled. /// public IModuleLoader ModuleLoader { get; set; } = FailFastModuleLoader.Instance; } }