using FileFlows.Plugin; using Jint; using Jint.Runtime; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; namespace BasicNodes; /// /// A Javascript code executor /// internal class JavascriptExecutor { /// /// Delegate used by the executor so log messages can be passed from the javascript code into the flow runner /// /// the parameters for the logger delegate void LogDelegate(params object[] values); /// /// Executes javascript /// /// the execution arguments /// the output to be called next public static int Execute(JavascriptExecutionArgs execArgs) { if (string.IsNullOrEmpty(execArgs?.Code)) return -1; // no code, flow cannot continue doesnt know what to do var args = execArgs.Args; try { long fileSize = 0; var fileInfo = new FileInfo(args.WorkingFile); if (fileInfo.Exists) fileSize = fileInfo.Length; // replace Variables. with dictionary notation string tcode = execArgs.Code; foreach (string k in args.Variables.Keys.OrderByDescending(x => x.Length)) { // replace Variables.Key or Variables?.Key?.Subkey etc to just the variable // so Variables.file?.Orig.Name, will be replaced to Variables["file.Orig.Name"] // since its just a dictionary key value string keyRegex = @"Variables(\?)?\." + k.Replace(".", @"(\?)?\."); object? value = args.Variables[k]; if (value is JsonElement jElement) { if (jElement.ValueKind == JsonValueKind.String) value = jElement.GetString(); if (jElement.ValueKind == JsonValueKind.Number) value = jElement.GetInt64(); } tcode = Regex.Replace(tcode, keyRegex, "Variables['" + k + "']"); } var sb = new StringBuilder(); var log = new { ILog = new LogDelegate(args.Logger.ILog), DLog = new LogDelegate(args.Logger.DLog), WLog = new LogDelegate(args.Logger.WLog), ELog = new LogDelegate(args.Logger.ELog), }; var engine = new Engine(options => { options.LimitMemory(4_000_000); options.MaxStatements(500); }) .SetValue("Logger", args.Logger) .SetValue("Variables", args.Variables) .SetValue("Flow", args) .SetValue(nameof(FileInfo), new Func((string file) => new FileInfo(file))) .SetValue(nameof(DirectoryInfo), new Func((string path) => new DirectoryInfo(path))); ; foreach (var arg in execArgs.AdditionalArguments) engine.SetValue(arg.Key, arg.Value); var result = int.Parse(engine.Evaluate(tcode).ToObject().ToString()); return result; } catch(JavaScriptException ex) { // print out the code block for debugging int lineNumber = 0; var lines = execArgs.Code.Split('\n'); string pad = "D" + (lines.ToString().Length); args.Logger.DLog("Code: " + Environment.NewLine + string.Join("\n", lines.Select(x => (++lineNumber).ToString("D3") + ": " + x))); args.Logger?.ELog($"Failed executing script [{ex.LineNumber}, {ex.Column}]: {ex.Message}"); return -1; } catch (Exception ex) { args.Logger?.ELog("Failed executing function: " + ex.Message + Environment.NewLine + ex.StackTrace); return -1; } } } /// /// The arguments to pass to the javascript executor /// public class JavascriptExecutionArgs { /// /// Gets or sets the code to execute /// public string Code { get; set; } /// /// Gets or sets teh NodeParameters /// public NodeParameters Args { get; set; } /// /// Gets a collection of additional arguments to be passed to the javascript executor /// public Dictionary AdditionalArguments { get; private set; } = new Dictionary(); }