private DekiScriptExpressionEvaluationState(DekiScriptEnv env, DekiScriptRuntime runtime, XmlNamespaceManager namespaces, DekiScriptOutputBuffer buffer, SharedState sharedState) {
     this.Env = env;
     this.Namespaces = namespaces;
     this.Buffer = buffer;
     this.Runtime = runtime;
     _sharedState = sharedState;
 }
Example #2
0
        protected override Yield Start(XDoc config, Result result) {
            yield return Coroutine.Invoke(base.Start, config, new Result());

            // init environment for script execution
            _runtime = new DekiScriptRuntime();
            _env = _runtime.CreateEnv();
            result.Return();
        }
 public DekiScriptExpressionEvaluationState(DekiScriptEvalMode mode, DekiScriptEnv env, DekiScriptRuntime runtime, XmlNamespaceManager namespaces, DekiScriptOutputBuffer buffer) {
     this.Mode = mode;
     this.Env = env;
     this.Namespaces = namespaces;
     this.Buffer = buffer;
     this.Runtime = runtime;
     _safe = (mode == DekiScriptEvalMode.EvaluateSafeMode);
 }
 //--- Constructor ---
 public DekiScriptExpressionEvaluationState(DekiScriptEvalMode mode, DekiScriptEnv env, DekiScriptRuntime runtime) {
     this.Mode = mode;
     this.Env = env;
     this.Namespaces = new XmlNamespaceManager(XDoc.XmlNameTable);
     this.Buffer = new DekiScriptOutputBuffer();
     this.Runtime = runtime;
     _safe = (mode == DekiScriptEvalMode.EvaluateSafeMode);
 }
 //--- Constructor ---
 public DekiScriptExpressionEvaluationState(DekiScriptEvalMode mode, DekiScriptEnv env, DekiScriptRuntime runtime, TimeSpan evaluationTimeout, int maxOutputBufferSize) {
     this.Env = env;
     this.Namespaces = new XmlNamespaceManager(XDoc.XmlNameTable);
     this.Buffer = new DekiScriptOutputBuffer(maxOutputBufferSize);
     this.Runtime = runtime;
     _sharedState = new SharedState();
     _sharedState.Safe = (mode == DekiScriptEvalMode.EvaluateSafeMode);
     if(evaluationTimeout == TimeSpan.MaxValue) {
         return;
     }
     _sharedState.EvaluationTimeout = evaluationTimeout;
     _sharedState.EvaluationTimer = Stopwatch.StartNew();
 }
Example #6
0
 //--- Constructors ---
 public DekiScriptScriptFunctionInvocationTarget(DreamAccess access, DekiScriptParameter[] parameters, DekiScriptExpression expr, DekiScriptEnv env, DekiScriptType returnType) {
     if(parameters == null) {
         throw new ArgumentNullException("parameters");
     }
     if(expr == null) {
         throw new ArgumentNullException("expr");
     }
     this.Access = access;
     this.Parameters = parameters;
     this.Expression = expr;
     _env = env;
     _returnType = returnType;
 }
Example #7
0
        //--- Class Methods ---
        public static string Parse(string code, string id, DekiScriptEnv env, DekiScriptRuntime runtime) {
            StringBuilder result = new StringBuilder();

            // check if code is attached to an id'ed element
            if(!string.IsNullOrEmpty(id)) {
                result.AppendLine("$(\"#" + id + "\").each(function() {");
                result.AppendLine("var $this = $(this);");
            }

            // convert JEM code into regular javascript
            int i = 0;
            result.Append(ParseExpression(code, id, ParseMode.ALL, false, env, runtime, new Dictionary<string, string>(), ref i));

            // check if code is attached to an id'ed element
            if(!string.IsNullOrEmpty(id)) {
                result.AppendLine("});");
            }
            return result.ToString();
        }
Example #8
0
        private void LoadScript() {
            _manifestPath = null;
            _resourcesPath = null;
            _manifestUri = null;
            _resourcesUri = null;
            _manifest = null;

            // read manifest
            _manifestPath = Config["manifest"].AsText;
            if(string.IsNullOrEmpty(_manifestPath)) {
                throw new ArgumentNullException("manifest");
            }
            _manifestUri = XUri.TryParse(_manifestPath);
            if(_manifestUri != null) {
                _manifestPath = null;
                _manifest = Plug.New(_manifestUri).Get().ToDocument();
                _resourcesUri = Config["resources"].AsUri ?? _manifestUri.WithoutLastSegment();
            } else {
                _manifest = XDocFactory.LoadFrom(_manifestPath, MimeType.XML);
                _resourcesPath = Config["resources"].AsText ?? Path.GetDirectoryName(_manifestPath);
            }
            if(!_manifest.HasName("extension")) {
                throw new ArgumentException("invalid extension manifest");
            }

            // initilize runtime
            _runtime = new DekiScriptRuntime();

            // read manifest settings
            _title = _manifest["title"].AsText;
            _label = _manifest["label"].AsText;
            _copyright = _manifest["copyright"].AsText;
            _description = _manifest["description"].AsText;
            _help = _manifest["uri.help"].AsText;
            _logo = _manifest["uri.logo"].AsText;
            _namespace = _manifest["namespace"].AsText;

            // initialize evaluation environment
            _commonEnv = _runtime.CreateEnv();

            // read functions
            _functions = new Dictionary<XUri, DekiScriptInvocationTargetDescriptor>();
            foreach(var function in _manifest["function"]) {
                var descriptor = ConvertFunction(function);
                if(descriptor != null) {
                    var uri = Self.At(descriptor.SystemName);
                    DekiScriptInvocationTargetDescriptor old;
                    if(_functions.TryGetValue(uri, out old)) {
                        _log.WarnFormat("duplicate function name {0} in script {1}", descriptor.Name, _manifestUri);
                    }
                    _functions[uri] = descriptor;
                }
            }
            _runtime.RegisterExtensionFunctions(_functions);

            // add extension functions to env
            foreach(var function in _functions) {
                _commonEnv.Vars.AddNativeValueAt(function.Value.Name.ToLowerInvariant(), function.Key);
            }

            // add configuration settings
            DreamContext context = DreamContext.Current;
            DekiScriptMap scriptConfig = new DekiScriptMap();
            foreach(KeyValuePair<string, string> entry in Config.ToKeyValuePairs()) {
                XUri local;
                if(XUri.TryParse(entry.Value, out local)) {
                    local = context.AsPublicUri(local);
                    scriptConfig.AddAt(entry.Key.Split('/'), DekiScriptExpression.Constant(local.ToString()));
                } else {
                    scriptConfig.AddAt(entry.Key.Split('/'), DekiScriptExpression.Constant(entry.Value));
                }
            }
            _commonEnv.Vars.Add("config", scriptConfig);
        }
Example #9
0
 protected override Yield Stop(Result result) {
     _publicDigitalSignature = null;
     _manifestPath = null;
     _resourcesPath = null;
     _manifestUri = null;
     _resourcesUri = null;
     _manifest = null;
     _debug = false;
     _runtime = null;
     _commonEnv = null;
     _copyright = null;
     _description = null;
     _functions = null;
     _help = null;
     _label = null;
     _logo = null;
     _namespace = null;
     _title = null;
     yield return Coroutine.Invoke(base.Stop, new Result());
     result.Return();
 }
        //--- Class Constructor ---
        static DekiScriptRuntime() {

            // register built-in functions
            _commonFunctions = new Dictionary<XUri, DekiScriptInvocationTargetDescriptor>();
            foreach(MethodInfo method in typeof(DekiScriptLibrary).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)) {

                // check if it has the DekiScriptFunction attribute
                DekiScriptFunctionAttribute functionAttribute = (DekiScriptFunctionAttribute)Attribute.GetCustomAttribute(method, typeof(DekiScriptFunctionAttribute));
                if(functionAttribute != null) {
                    var parameters = from param in method.GetParameters()
                                     let attr = (DekiScriptParamAttribute[])param.GetCustomAttributes(typeof(DekiScriptParamAttribute), false)
                                     select ((attr != null) && (attr.Length > 0)) ? new DekiScriptNativeInvocationTarget.Parameter(attr[0].Hint, attr[0].Optional) : null;
                    var target = new DekiScriptNativeInvocationTarget(null, method, parameters.ToArray());
                    var function = new DekiScriptInvocationTargetDescriptor(target.Access, functionAttribute.IsProperty, functionAttribute.IsIdempotent, functionAttribute.Name ?? method.Name, target.Parameters, target.ReturnType, functionAttribute.Description, functionAttribute.Transform, target);
                    _commonFunctions[new XUri("native:///").At(function.SystemName)] = function;
                }
            }

            // build common env
            DekiScriptMap common = new DekiScriptMap();

            // add global constants
            common.AddNativeValueAt("num.e", Math.E);
            common.AddNativeValueAt("num.pi", Math.PI);
            common.AddNativeValueAt("num.epsilon", double.Epsilon);
            common.AddNativeValueAt("num.positiveinfinity", double.PositiveInfinity);
            common.AddNativeValueAt("num.negativeinfinity", double.NegativeInfinity);
            common.AddNativeValueAt("num.nan", double.NaN);

            // add global functions & properties
            foreach(var function in _commonFunctions) {
                common.AddNativeValueAt(function.Value.Name, function.Key);
            }
            _commonEnv = new DekiScriptEnv(common);
        }
        public virtual DekiScriptLiteral Invoke(XUri uri, DekiScriptLiteral args, DekiScriptEnv env) {
            DekiScriptInvocationTargetDescriptor descriptor;
            var target = _functions.TryGetValue(uri, out descriptor) ? descriptor.Target : FindTarget(uri);

            // invoke function directly
            try {
                return target.Invoke(this, args);
            } catch(Exception e) {
                throw UnwrapAsyncException(uri, e).Rethrow();
            }
        }
 public virtual DekiScriptLiteral EvaluateProperty(Location location, DekiScriptLiteral value, DekiScriptEnv env) {
     if(IsProperty(value)) {
         DekiScriptUri uri = (DekiScriptUri)value;
         try {
             value = Invoke(uri.Value, uri.Arguments.IsNil ? _emptyList : uri.Arguments, env);
         } catch(DekiScriptFatalException) {
             throw;
         } catch(Exception e) {
             var descriptor = ResolveRegisteredFunctionUri(uri.Value);
             throw new DekiScriptInvokeException(location, uri.Value, (descriptor != null) ? descriptor.Name : uri.Value.ToString(), e);
         }
     }
     return value;
 }
 public virtual DekiScriptLiteral Evaluate(DekiScriptExpression expr, DekiScriptEvalMode mode, DekiScriptEnv env) {
     DekiScriptExpressionEvaluationState state = new DekiScriptExpressionEvaluationState(mode, env, this);
     try {
         return state.Pop(expr.VisitWith(DekiScriptExpressionEvaluation.Instance, state));
     } catch(DekiScriptReturnException e) {
         state.Push(e.Value);
         return state.PopAll();
     }
 }
 //--- Extension Methods ---
 public static DekiScriptExpression Optimize(this DekiScriptRuntime runtime, DekiScriptExpression expr, DekiScriptEvalMode mode, DekiScriptEnv env) {
     return expr.VisitWith(DekiScriptExpressionOptimizer.Instance, new DekiScriptExpressionEvaluationState(mode, env, runtime));
 }
Example #15
0
        public void EvaluateContent(XDoc script, DekiScriptEnv env, DekiScriptEvalMode mode, out bool scripted) {
            scripted = false;
            switch(mode) {
            case DekiScriptEvalMode.None:
                break;
            case DekiScriptEvalMode.Evaluate:
            case DekiScriptEvalMode.EvaluateSafeMode: {
                var expr = DekiScriptParser.Parse(script);
                try {
                    expr = DekiContext.Current.Instance.ScriptRuntime.Evaluate(expr, mode, env);
                } catch(Exception e) {
                    var error = DekiScriptLibrary.MakeErrorObject(e, env);
                    DekiContext.Current.Instance.ScriptRuntime.LogExceptionInOutput(error);
                    expr = DekiScriptExpression.Constant(DekiScriptLibrary.WebShowError((Hashtable)error.NativeValue));
                }

                // check if outcome is an XML document
                var xml = expr as DekiScriptXml;
                if(xml != null) {

                    // check if outcome is the expected content document
                    if(xml.Value.HasName("content")) {
                        script.Replace(((DekiScriptXml)expr).Value);
                    } else {

                        // remove all contents from existing document and append new document
                        script.RemoveNodes();
                        script.Start("body").Add(xml.Value).End();
                    }
                } else if(expr is DekiScriptString) {   

                    // remove all contents from existing document and append new document
                    script.RemoveNodes();
                    script.Start("body").Value(((DekiScriptString)expr).Value).End();
                } else {

                    // remove all contents from existing document and append new document
                    script.RemoveNodes();
                    script.Start("body").Value(expr.ToString()).End();
                }
            }
                break;
            case DekiScriptEvalMode.EvaluateEditOnly:
            case DekiScriptEvalMode.EvaluateSaveOnly:
            case DekiScriptEvalMode.Verify: {
                DekiScriptEvalContext context = new DekiScriptEvalContext(script, mode, false, DekiContext.Current.Instance.ScriptRuntime.GetMaxOutputSize(mode));

                // add <head> and <tail> sections
                context.AddHeadElements(script);
                context.AddTailElements(script);
                script["head"].RemoveNodes();
                script["tail"].RemoveNodes();

                // evaluate the script
                bool error = false;
                DekiScriptInterpreter.Evaluate(script, (XmlElement)script.Root.AsXmlNode, context, env, DekiContext.Current.Instance.ScriptRuntime, out scripted, ref error);
                if((mode == DekiScriptEvalMode.Verify) || !error) {
                    context.MergeContextIntoDocument(script.AsXmlNode.OwnerDocument);
                }
            }
                break;
            default:
                throw new InvalidOperationException(string.Format("unrecognized evaluation mode '{0}'", mode));
            }
        }
Example #16
0
        internal void InsertExceptionMessageBeforeNode(DekiScriptEnv env, XmlNode parent, XmlNode reference, Location location, Exception exception) {
            if(Fallthrough) {
                throw exception;
            }
            if(exception is DekiScriptDocumentTooLargeException) {
                DekiScriptDocumentTooLargeException e = (DekiScriptDocumentTooLargeException)exception;

                // check if an error message was already embedded
                if(e.Handled) {
                    throw exception;
                }
                e.Handled = true;
            }

            // check if environment has a __callstack variable
            DekiScriptList callstack = null;
            DekiScriptLiteral callstackVar;
            if(env.Vars.TryGetValue(DekiScriptEnv.CALLSTACK, out callstackVar)) {
                callstack = callstackVar as DekiScriptList;
            }

            XDoc warning = DekiScriptRuntime.CreateWarningFromException(callstack, location, exception);
            parent.InsertBefore(parent.OwnerDocument.ImportNode(warning.AsXmlNode, true), reference);
            if(exception is DekiScriptDocumentTooLargeException) {
                throw exception;
            }
        }
Example #17
0
        private void InitScriptRuntimeAndEnv() {
            if(_runtime != null) {
                return;
            }

            // Note (arnec): have to lazily init the runtime and env, because they require instance settings and they currently have
            // a requirement of DekiContext.Current.Instance being set, i.e. a race condition in the ctor
            lock(SettingsSyncRoot) {
                if(_runtime != null) {
                    return;
                }
                _runtime = new ExtensionRuntime(new DekiInstanceSettings());
                _scriptEnv = _deki.InitializeRuntimeAndEnvironment(_runtime);
            }
        }
Example #18
0
        private static void BuildWhenStatement(string expr, string id, string body, StringBuilder head, DekiScriptEnv env, Dictionary<string, string> channels) {

            // remove the optional outer () and {}; the expression is already scoped, so we don't need them anymore
            if(expr.StartsWith("(") && expr.EndsWith(")")) {
                expr = expr.Substring(1, expr.Length - 2).Trim();
            }

            // gather all events from 'when()' condition expression into a map { sink1: [ event1, event2, ... ], sink2: [ event1, event2, ... ], ... }
            Dictionary<string, Dictionary<string, string>> sinks = new Dictionary<string, Dictionary<string, string>>();
            string condition = EVENT_PATTERN.Replace(expr, delegate(Match match) {

                // check if a tail element was matched, which disqualifies this match
                Group tail = match.Groups["tail"];
                if(!tail.Success) {

                    // check for event match
                    Group group = match.Groups["event"];
                    if(group.Success) {
                        string sink = match.Groups["sink"].Value;
                        Dictionary<string, string> events;
                        if(!sinks.TryGetValue(sink, out events)) {
                            events = new Dictionary<string, string>();
                            sinks[sink] = events;
                        }
                        events[group.Value] = group.Value;

                        // TODO (steveb): we should also check that the event source is what we expect it to be

                        return string.Format("($event.type == {0})", StringUtil.QuoteString(group.Value));
                    }
                } else {
                    string sink = match.Groups["sink"].Value;
                    if(sink.StartsWith("#")) {
                        return string.Format("$(\"{0}\").{1}{2}", sink, match.Groups["event"].Value, tail.Value);
                    }
                }
                return match.Value;
            });

            // create stub function; $event is only set when invoked for events, not for messages
            string function = "_" + StringUtil.CreateAlphaNumericKey(8);
            if(sinks.Count > 0) {
                head.Append("var " + function + " = function($event) { $event = $event||{}; ");
            } else {
                head.Append("var " + function + " = function() { ");
            }

            // read channel state for all channels that a read from; including 'when()' condition and body
            if(channels.Count > 0) {
                bool first = true;
                head.Append("var $channels = {");
                foreach(KeyValuePair<string, string> channel in channels) {
                    if(!first) {
                        head.Append(", ");
                    }
                    first = false;
                    head.AppendFormat("{0}: Deki.query({1})||{{}}", channel.Key.QuoteString(), channel.Value);
                }
                head.Append(" }; ");
            }

            // add optional condition
            if(!string.IsNullOrEmpty(condition)) {
                head.Append("if(" + condition + ") ");
            }

            // append body of 'when()' statement and close the function
            if(string.IsNullOrEmpty(body)) {
                head.Append(";");
            } else {
                head.Append(body);
            }
            head.AppendLine(" };");

            // register function for event handlers
            if(sinks.Count > 0) {
                foreach(KeyValuePair<string, Dictionary<string, string>> events in sinks) {
                    string bind = string.Join(" ", new List<string>(events.Value.Values).ToArray()).QuoteString();
                    if(events.Key.EqualsInvariant("$this")) {
                        head.AppendLine("$this.bind(" + bind + ", " + function + ");");
                    } else {
                        head.AppendLine("$(" + events.Key.QuoteString() + ").bind(" + bind + ", " + function + ");");
                    }
                }
            }

            // register function for message handlers
            foreach(KeyValuePair<string, string> channel in channels) {
                head.AppendLine("Deki.subscribe(" + channel.Value + ", " + (string.IsNullOrEmpty(id) ? "null" : "this") + ", " + function + ");");
            }
        }
Example #19
0
        private static bool TryParseDekiScriptExpression(string ctor, DekiScriptEnv env, DekiScriptRuntime runtime, ref int i, out string value) {
            string source = ParseExpression(ctor, null, ParseMode.EXPRESSION, false, env, runtime, null, ref i);
            if((i >= ctor.Length) || (ctor[i] != '}')) {
                value = null;
                return false;
            }

            // try to parse and execute the dekiscript fragment
            try {
                source = source.Substring(1, source.Length - 2);
                DekiScriptExpression dekiscript = DekiScriptParser.Parse(new Location("jem"),  source);
                DekiScriptLiteral result = runtime.Evaluate(dekiscript, DekiScriptEvalMode.EvaluateSafeMode, env);
                value = DekiScriptLibrary.JsonEmit(result.NativeValue);
            } catch(Exception e) {

                // execution failed; convert exception into a javascript comment
                value = "alert(\"ERROR in DekiScript expression:\\n---------------------------\\n\\n\" + " + e.GetCoroutineStackTrace().QuoteString() + ")";
            }
            return true;
        }
Example #20
0
        private static string ParseExpression(string code, string id, ParseMode mode, bool whenCondition, DekiScriptEnv env, DekiScriptRuntime runtime, Dictionary<string, string> channels, ref int i) {
            StringBuilder result = new StringBuilder();
            int nesting = 0;
            for(; i < code.Length; ++i) {
                int start;
                switch(code[i]) {
                case '"':
                case '\'':

                    // process strings
                    start = i;
                    ScanString(code, code[i], ref i);
                    result.Append(code, start, i - start);
                    --i;
                    break;
                case '/':

                    // check if / denotes the beginning of a comment, if so process it
                    start = i;
                    if(TryScanComment(code, ref i)) {

                        // NOTE: remove comments in when-condition

                        if(!whenCondition) {
                            result.Append(code, start, i - start);
                            result.Append("\n");
                        }
                        --i;
                    } else {
                        result.Append(code[i]);
                    }
                    break;
                case '\\':

                    // backslash (\) always appends the next character
                    result.Append(code[i++]);
                    if(i < code.Length) {
                        result.Append(code[i]);
                    }
                    break;
                case '(':

                    // increase nesting level
                    result.Append(code[i]);
                    ++nesting;
                    break;
                case '{':

                    // check if this is the beginning of a dekiscript block {{ }}
                    if(((i + 1) < code.Length) && (code[i + 1] == '{')) {
                        ++i;
                        string value;
                        start = i;
                        if(TryParseDekiScriptExpression(code, env, runtime, ref i, out value)) {
                            result.Append(value);
                        } else {
                            ++nesting;
                            result.Append('{');
                            result.Append(code, start, i - start);
                            --i;
                        }
                    } else {
                        ++nesting;
                        result.Append(code[i]);
                    }
                    break;
                case ')':
                case '}':

                    // decrease nesting level and check if this is the end of the sougth expression
                    result.Append(code[i]);
                    --nesting;

                    // NOTE: only exit if
                    // 1) we don't have to read all of the code
                    // 2) there are no open parentheses or cruly braces
                    // 3) we don't on a complete statement or the current characteris a closing curly brace

                    if((mode != ParseMode.ALL) && (nesting <= 0) && ((mode != ParseMode.STATEMENT) || (code[i] == '}'))) {

                        // found the end of the expression
                        ++i;
                        return result.ToString();
                    }
                    break;
                case ';':

                    // check if the statement is the end of the sougth expression
                    result.Append(code[i]);

                    // NOTE: only exit if
                    // 1) we don't have to read all of the code
                    // 2) there are no open parentheses or cruly braces
                    // 3) we stop on a complete statement

                    if((nesting <= 0) && (mode == ParseMode.STATEMENT)) {

                        // found the end of the expression
                        ++i;
                        return result.ToString();
                    }
                    break;
                case '@':

                    // channel name
                    if(channels != null) {
                        ++i;
                        start = i;
                        string channel;
                        string name;
                        if((i < code.Length) && ((code[i] == '"') || (code[i] == '\''))) {

                            // process: @"channel_name" or @'channel_name'
                            ScanString(code, code[i], ref i);
                            channel = code.Substring(start, i - start);
                            name = channel.Substring(1, channel.Length - 2).UnescapeString();
                        } else {

                            // process: @channel_magic_id
                            ScanId(code, ref i);
                            name = code.Substring(start, i - start);
                            if(!channels.TryGetValue(name, out channel)) {
                                channel = env.GetMagicId(name).ToString();
                            }
                        }
                        start = i;
                        ScanWhitespace(code, ref i);
                        if((i < code.Length) && (code[i] == '(')) {

                            // process: @channel ( ... )
                            string message = ParseExpression(code, id, ParseMode.EXPRESSION, false, env, runtime, channels, ref i);
                            message = message.Substring(1, message.Length - 2).Trim();
                            if(message.Length == 0) {
                                result.AppendFormat("Deki.publish({0})", channel);
                            } else {
                                result.AppendFormat("Deki.publish({0}, {1})", channel, message);
                            }
                        } else {

                            // channel is used for reading; add it to the channel set to read on activation
                            channels[name] = channel;

                            // convert channel name and add whitespace
                            result.AppendFormat("$channels[{0}]", name.QuoteString());
                            result.Append(code, start, i - start);
                        }
                        --i;
                    } else {
                        result.Append(code[i]);
                    }
                    break;
                case '#':

                    // NOTE: don't process #id in the when-condition

                    // element name
                    if(!whenCondition && (channels != null)) {
                        ++i;
                        start = i;

                        // process: #id
                        ScanId(code, ref i);
                        string name = code.Substring(start, i - start);
                        result.Append("$(\"#" + name + "\")");
                        --i;
                    } else {
                        result.Append(code[i]);
                    }
                    break;
                default:

                    // NOTE: don't process when() in the when-condition

                    // check if this is the beginning of an identifier
                    if(!whenCondition && IsAlpha(code[i])) {
                        start = i;
                        ScanId(code, ref i);
                        int j = i;
                        ScanWhitespace(code, ref j);

                        // check if scanned identifier is the keyword 'when'
                        if(((i - start) == WHEN.Length) && (string.Compare(code, start, WHEN, 0, WHEN.Length, StringComparison.Ordinal) == 0) && (j < code.Length) && (code[j] == '(')) {
                            i = j;
                            Dictionary<string, string> subChannels = new Dictionary<string, string>();

                            // parse the condition of the 'when()' statement
                            string condition = ParseExpression(code, id, ParseMode.EXPRESSION, true, env, runtime, subChannels, ref i);

                            // parse the body of the 'when()' expression
                            string body = ParseExpression(code, id, ParseMode.STATEMENT, false, env, runtime, subChannels, ref i);
                            BuildWhenStatement(condition.Trim(), id, body.Trim(), result, env, subChannels);
                        } else {
                            result.Append(code, start, i - start);
                        }
                        --i;
                    } else {
                        result.Append(code[i]);
                    }
                    break;
                }
            }
            return result.ToString();
        }
        //--- Class Methods ---
        public static XmlNode Evaluate(
            XDoc script,
            XmlElement node,
            DekiScriptEvalContext context,
            DekiScriptEnv env,
            DekiScriptRuntime runtime,
            out bool scripted,
            ref bool error
        ) {
            if((context.Mode != DekiScriptEvalMode.Verify) && (context.Mode != DekiScriptEvalMode.EvaluateEditOnly) && (context.Mode != DekiScriptEvalMode.EvaluateSaveOnly)) {
                throw new InvalidOperationException("DekiScript interpreter can only used for save, edit, or verify evaluations.");
            }
            scripted = false;
            XmlNode next = node.NextSibling;
            try {

                // check if element needs to be evaluated
                if(StringUtil.EqualsInvariant(node.NamespaceURI, XDekiScript.ScriptNS)) {
                    scripted = true;

                    // NOTE (steveb): <eval:xyz> nodes are not processed by the interpreter anymore

                } else {
                    XDoc current = script[node];
                    bool hasScriptClass = StringUtil.EqualsInvariant(node.GetAttribute("class"), "script");

                    #region <elem class="script" init="..." if="..." foreach="..." where="..." block="...">
                    // check if element has form <elem class="script" init="..." if="..." foreach="..." where="..." block="...">
                    scripted = node.HasAttribute("init") || node.HasAttribute("if") || node.HasAttribute("foreach") || node.HasAttribute("block");
                    if(context.Mode == DekiScriptEvalMode.Verify) {

                        // check if "block" is present
                        string blockAttr = node.GetAttribute("block");
                        if(!string.IsNullOrEmpty(blockAttr)) {

                            // TODO (steveb): validate script expression

                        }

                        // check if "foreach" is present
                        string foreachAttr = node.GetAttribute("foreach");
                        if(!string.IsNullOrEmpty(foreachAttr)) {

                            // TODO (steveb): validate script expression

                        }

                        // check if "if" is present
                        string ifAttr = node.GetAttribute("if");
                        if(!string.IsNullOrEmpty(ifAttr)) {

                            // TODO (steveb): validate script expression

                        }

                        // check if "init" is present
                        string initAttr = node.GetAttribute("init");
                        if(!string.IsNullOrEmpty(initAttr)) {

                            // TODO (steveb): validate script expression

                        }
                    }
                    #endregion

                    // evaluate child nodes
                    EvaluateChildren(script, node, context, env, runtime, out scripted, ref error);

                    #region evaluate attributes
                    for(int i = 0; i < node.Attributes.Count; ++i) {
                        XmlAttribute attribute = node.Attributes[i];

                        // check if attribute needs to be evaluated
                        if(attribute.NamespaceURI == XDekiScript.ScriptNS) {
                            scripted = true;

                            // NOTE (steveb): eval:xyz="abc" attributes are not processed by the interpreter anymore

                        } else if(StringUtil.StartsWithInvariant(attribute.Value, "{{") && StringUtil.EndsWithInvariant(attribute.Value, "}}")) {
                            scripted = true;

                            // NOTE (steveb): key="{{value}}"
                            string code = attribute.Value.Substring(2, attribute.Value.Length - 4).Trim();

                            // check if script content is substituted
                            bool isPermanentReplacement = false;
                            if(StringUtil.StartsWithInvariantIgnoreCase(code, DekiScriptRuntime.ON_SAVE_PATTERN)) {
                                isPermanentReplacement = (context.Mode == DekiScriptEvalMode.EvaluateSaveOnly);
                                code = code.Substring(DekiScriptRuntime.ON_SAVE_PATTERN.Length);
                            } else if(StringUtil.StartsWithInvariantIgnoreCase(code, DekiScriptRuntime.ON_SUBST_PATTERN)) {
                                isPermanentReplacement = (context.Mode == DekiScriptEvalMode.EvaluateSaveOnly);
                                code = code.Substring(DekiScriptRuntime.ON_SUBST_PATTERN.Length);
                            } else if(StringUtil.StartsWithInvariantIgnoreCase(code, DekiScriptRuntime.ON_EDIT_PATTERN)) {
                                isPermanentReplacement = (context.Mode == DekiScriptEvalMode.EvaluateEditOnly);
                                code = code.Substring(DekiScriptRuntime.ON_EDIT_PATTERN.Length);
                            }

                            // parse expression
                            if((context.Mode == DekiScriptEvalMode.Verify) || isPermanentReplacement) {
                                DekiScriptExpression expression = DekiScriptParser.Parse(ComputeNodeLocation(attribute), code);
                                if(isPermanentReplacement) {
                                    DekiScriptLiteral eval = runtime.Evaluate(expression, DekiScriptEvalMode.EvaluateSafeMode, env);

                                    // determine what the outcome value is
                                    string value = eval.AsString();

                                    // check if we have a value to replace the current attribute with
                                    if((value != null) && !DekiScriptLibrary.ContainsXSSVulnerability(attribute.LocalName, value)) {
                                        attribute.Value = value;
                                    } else {
                                        node.Attributes.RemoveAt(i);
                                        --i;
                                    }
                                }
                            }
                        }
                    }
                    #endregion

                    #region evaluate <span class="script"> or <pre class="script">
                    if(hasScriptClass && (StringUtil.EqualsInvariant(node.LocalName, "pre") || StringUtil.EqualsInvariant(node.LocalName, "span")) && !node.HasAttribute("function")) {

                        // replace the non-breaking space character with space
                        string code = node.InnerText.ReplaceAll("\u00A0", " ", "\u00AD", "").Trim();

                        // check if script content is substituted
                        bool isPermanentReplacement = false;
                        if(StringUtil.StartsWithInvariantIgnoreCase(code, DekiScriptRuntime.ON_SAVE_PATTERN)) {
                            isPermanentReplacement = (context.Mode == DekiScriptEvalMode.EvaluateSaveOnly);
                            code = code.Substring(DekiScriptRuntime.ON_SAVE_PATTERN.Length);
                        } else if(StringUtil.StartsWithInvariantIgnoreCase(code, DekiScriptRuntime.ON_SUBST_PATTERN)) {
                            isPermanentReplacement = (context.Mode == DekiScriptEvalMode.EvaluateSaveOnly);
                            code = code.Substring(DekiScriptRuntime.ON_SUBST_PATTERN.Length);
                        } else if(StringUtil.StartsWithInvariantIgnoreCase(code, DekiScriptRuntime.ON_EDIT_PATTERN)) {
                            isPermanentReplacement = (context.Mode == DekiScriptEvalMode.EvaluateEditOnly);
                            code = code.Substring(DekiScriptRuntime.ON_EDIT_PATTERN.Length);
                        }

                        // parse expression
                        if((context.Mode == DekiScriptEvalMode.Verify) || isPermanentReplacement) {
                            DekiScriptExpression expression = DekiScriptParser.Parse(ComputeNodeLocation(node), code);
                            if(isPermanentReplacement) {
                                DekiScriptLiteral value = runtime.Evaluate(expression, DekiScriptEvalMode.EvaluateSafeMode, env);
                                context.ReplaceNodeWithValue(node, value);
                            }
                            if(!isPermanentReplacement) {
                                scripted = true;
                            }
                        }
                    }
                    #endregion
                }
            } catch(Exception e) {

                // only embed error in verify mode, not in save/edit modes
                if(context.Mode == DekiScriptEvalMode.Verify) {
                    context.InsertExceptionMessageBeforeNode(env, node.ParentNode, node, ComputeNodeLocation(node), e);
                    node.ParentNode.RemoveChild(node);
                }
                error |= true;
            }
            return next;
        }
 //--- Methods ---
 public DekiScriptExpressionEvaluationState With(DekiScriptEnv env) {
     return new DekiScriptExpressionEvaluationState(env, Runtime, Namespaces, Buffer, _sharedState);
 }
        private static void EvaluateChildren(
            XDoc script,
            XmlElement node,
            DekiScriptEvalContext context,
            DekiScriptEnv env,
            DekiScriptRuntime runtime,
            out bool scripted,
            ref bool error
        ) {
            scripted = false;

            // recurse first to evaluate nested script content
            XmlNode child = node.FirstChild;
            while(child != null) {
                XmlNode next = child.NextSibling;
                if(child.NodeType == XmlNodeType.Element) {
                    bool childScripted;
                    next = Evaluate(script, (XmlElement)child, context, env, runtime, out childScripted, ref error);
                    scripted = scripted || childScripted;
                }
                child = next;
            }
        }
 //--- Methods ---
 public DekiScriptExpressionEvaluationState With(DekiScriptEnv env) {
     return new DekiScriptExpressionEvaluationState(Mode, env, Runtime, Namespaces, Buffer);
 }
Example #25
0
        public virtual DekiScriptLiteral Invoke(Location location, XUri uri, DekiScriptLiteral args, DekiScriptEnv env) {
            var sw = Stopwatch.StartNew();
            DekiScriptInvocationTargetDescriptor descriptor;
            var target = _functions.TryGetValue(uri, out descriptor) ? descriptor.Target : FindTarget(uri);
            try {

                // invoke function directly
                return target.Invoke(this, args);
            } catch(Exception e) {
                throw UnwrapAsyncException(uri, e).Rethrow();
            } finally {
                sw.Stop();
                bool property = (uri.LastSegment ?? string.Empty).StartsWithInvariant("$");
                env.AddFunctionProfile(location, (descriptor != null) ? (property ? "$" : "") + descriptor.Name : uri.ToString(), sw.Elapsed);
            }
        }