public Empty Visit(DekiScriptDomComment expr, DekiScriptDomEvaluationState state) { var context = state.Context; var env = state.Env; var parent = state.Parent; try { DekiScriptLiteral value = expr.Value.VisitWith(DekiScriptExpressionEvaluation.Instance, env); if (!value.IsNil) { XmlNode result = context.CreateComment(value.AsString()); parent.AppendChild(result); } } catch (Exception e) { EmbedExceptionMessage(expr, env, context, e, parent); } return(Empty.Value); }
private void EvaluateNamespaceDefinitionAttribute(DekiScriptDomElement.Attribute expr, DekiScriptEvalContext context, DekiScriptEnv env) { if (expr.IsNamespaceDefinition) { DekiScriptLiteral name = expr.Name.VisitWith(DekiScriptExpressionEvaluation.Instance, env); DekiScriptLiteral value = expr.Value.VisitWith(DekiScriptExpressionEvaluation.Instance, env); if (!value.IsNil) { if (string.IsNullOrEmpty(expr.Prefix)) { context.Namespaces.AddNamespace(string.Empty, value.AsString()); } else { context.Namespaces.AddNamespace(XmlConvert.EncodeLocalName(name.AsString() ?? string.Empty), value.AsString()); } } } }
public DekiScriptExpression Visit(DekiScriptMapConstructor expr, DekiScriptExpressionEvaluationState state) { // TODO (steveb): need to figure out how to optimize maps with generators if (expr.Generator != null) { return(expr); } // optimize each item in the map var fields = new DekiScriptMapConstructor.FieldConstructor[expr.Fields.Length]; bool isLiteral = true; for (int i = 0; i < expr.Fields.Length; i++) { var current = expr.Fields[i]; DekiScriptExpression key = current.Key.VisitWith(this, state); DekiScriptExpression value = current.Value.VisitWith(this, state); fields[i] = new DekiScriptMapConstructor.FieldConstructor(current.Location, key, value); isLiteral = isLiteral && (key is DekiScriptLiteral) && (value is DekiScriptLiteral); } if (!isLiteral) { return(DekiScriptExpression.Map(expr.Location, fields)); } // convert expression to a map DekiScriptMap result = new DekiScriptMap(); foreach (var field in fields) { DekiScriptLiteral key = (DekiScriptLiteral)field.Key; // check that key is a simple type string text = key.AsString(); if (text != null) { result.Add(text, (DekiScriptLiteral)field.Value); } } return(result); }
public DekiScriptOutputBuffer.Range Visit(DekiScriptMapConstructor expr, DekiScriptExpressionEvaluationState state) { DekiScriptMap result = new DekiScriptMap(); if (expr.Generator == null) { foreach (DekiScriptMapConstructor.FieldConstructor field in expr.Fields) { DekiScriptLiteral key = state.Pop(field.Key.VisitWith(this, state)); // check that key is a simple type string text = key.AsString(); if (text != null) { DekiScriptLiteral value = state.Pop(field.Value.VisitWith(this, state)); result.Add(text, value); } } } else { DekiScriptGeneratorEvaluation.Generate(expr.Generator, delegate(DekiScriptEnv subEnv) { foreach (DekiScriptMapConstructor.FieldConstructor field in expr.Fields) { DekiScriptLiteral key = state.Pop(field.Key.VisitWith(this, state.With(subEnv))); // check that key is a simple type string text = key.AsString(); if (text != null) { DekiScriptLiteral value = state.Pop(field.Value.VisitWith(this, state.With(subEnv))); result.Add(text, value); } } }, state); } return(state.Push(result)); }
//--- 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); }
internal DekiScriptLiteral Evaluate(DekiScriptAccess expr, DekiScriptExpressionEvaluationState state, bool evaluateProperties) { DekiScriptLiteral prefix = state.Pop(expr.Prefix.VisitWith(this, state)); DekiScriptLiteral index = state.Pop(expr.Index.VisitWith(this, state)); switch (prefix.ScriptType) { case DekiScriptType.MAP: { DekiScriptLiteral result = ((DekiScriptMap)prefix)[index]; if (evaluateProperties) { result = state.Runtime.EvaluateProperty(expr.Location, result, state.Env); } return(result); } case DekiScriptType.LIST: { DekiScriptLiteral value = DekiScriptExpression.Constant(index.AsNumber()); DekiScriptLiteral result = ((DekiScriptList)prefix)[value]; if (evaluateProperties) { result = state.Runtime.EvaluateProperty(expr.Location, result, state.Env); } return(result); } case DekiScriptType.URI: { DekiScriptUri uri = (DekiScriptUri)prefix; // coerce the index type to STR index = DekiScriptExpression.Constant(index.AsString()); if (index.ScriptType != DekiScriptType.STR) { throw new DekiScriptBadTypeException(expr.Location, index.ScriptType, new[] { DekiScriptType.STR }); } // curry the argument DekiScriptList args; if (!uri.Arguments.IsNil) { // the uri already has curried parameters, make sure they are in LIST format; otherwise fail if (uri.Arguments.ScriptType != DekiScriptType.LIST) { throw new DekiScriptBadTypeException(expr.Location, uri.Arguments.ScriptType, new[] { DekiScriptType.NIL, DekiScriptType.LIST }); } args = new DekiScriptList((DekiScriptList)uri.Arguments); } else { args = new DekiScriptList(); } args.Add(index); return(new DekiScriptUri(uri.Value, args)); } case DekiScriptType.STR: { DekiScriptString text = (DekiScriptString)prefix; // coerce the index type to NUM double?value = index.AsNumber(); if (value == null) { throw new DekiScriptBadTypeException(expr.Location, index.ScriptType, new[] { DekiScriptType.NUM }); } // retrieve character at given index position int position = (int)value; if ((position < 0) || (position >= text.Value.Length)) { // index is out of bounds, return nil return(DekiScriptNil.Value); } return(DekiScriptExpression.Constant(text.Value[position].ToString())); } case DekiScriptType.XML: { string path = index.AsString(); if (path == null) { throw new DekiScriptBadTypeException(expr.Location, index.ScriptType, new[] { DekiScriptType.STR }); } XDoc doc = ((DekiScriptXml)prefix).Value[path]; if (doc.HasName("html")) { doc = DekiScriptLibrary.CleanseHtmlDocument(doc); } return(new DekiScriptXml(doc)); } case DekiScriptType.NIL: return(DekiScriptNil.Value); } throw new DekiScriptBadTypeException(expr.Location, prefix.ScriptType, new[] { DekiScriptType.MAP, DekiScriptType.LIST, DekiScriptType.XML, DekiScriptType.STR, DekiScriptType.URI }); }
public DekiScriptOutputBuffer.Range Visit(DekiScriptBinary expr, DekiScriptExpressionEvaluationState state) { DekiScriptExpression Left = expr.Left; DekiScriptExpression Right = expr.Right; switch (expr.OpCode) { case DekiScriptBinary.Op.LeftValue: return(Left.VisitWith(this, state)); case DekiScriptBinary.Op.IdentityEqual: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); return(state.Push(DekiScriptBinary.IdentityEqual(left, right))); } case DekiScriptBinary.Op.IdentityNotEqual: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); return(state.Push(DekiScriptBinary.IdentityNotEqual(left, right))); } case DekiScriptBinary.Op.IsType: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); string type = ((DekiScriptString)Right).Value; return(state.Push(DekiScriptExpression.Constant(type.EqualsInvariantIgnoreCase("any") || left.ScriptTypeName.EqualsInvariantIgnoreCase(type)))); } case DekiScriptBinary.Op.Equal: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); return(state.Push(DekiScriptBinary.Equal(left, right))); } case DekiScriptBinary.Op.NotEqual: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); return(state.Push(DekiScriptBinary.NotEqual(left, right))); } case DekiScriptBinary.Op.GreaterOrEqual: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); if (DekiScriptLiteral.CoerceValuesToSameType(ref left, ref right)) { switch (left.ScriptType) { case DekiScriptType.BOOL: case DekiScriptType.NUM: return(state.Push(DekiScriptExpression.Constant(left.AsNumber() >= right.AsNumber()))); case DekiScriptType.STR: return(state.Push(DekiScriptExpression.Constant(left.AsString().CompareInvariant(right.AsString()) >= 0))); default: return(DekiScriptOutputBuffer.Range.Empty); } } else { return(DekiScriptOutputBuffer.Range.Empty); } } case DekiScriptBinary.Op.GreaterThan: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); if (DekiScriptLiteral.CoerceValuesToSameType(ref left, ref right)) { switch (left.ScriptType) { case DekiScriptType.BOOL: case DekiScriptType.NUM: return(state.Push(DekiScriptExpression.Constant(left.AsNumber() > right.AsNumber()))); case DekiScriptType.STR: return(state.Push(DekiScriptExpression.Constant(left.AsString().CompareInvariant(right.AsString()) > 0))); default: return(DekiScriptOutputBuffer.Range.Empty); } } else { return(DekiScriptOutputBuffer.Range.Empty); } } case DekiScriptBinary.Op.LessOrEqual: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); if (DekiScriptLiteral.CoerceValuesToSameType(ref left, ref right)) { switch (left.ScriptType) { case DekiScriptType.BOOL: case DekiScriptType.NUM: return(state.Push(DekiScriptExpression.Constant(left.AsNumber() <= right.AsNumber()))); case DekiScriptType.STR: return(state.Push(DekiScriptExpression.Constant(left.AsString().CompareInvariant(right.AsString()) <= 0))); default: return(DekiScriptOutputBuffer.Range.Empty); } } else { return(DekiScriptOutputBuffer.Range.Empty); } } case DekiScriptBinary.Op.LessThan: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); if (DekiScriptLiteral.CoerceValuesToSameType(ref left, ref right)) { switch (left.ScriptType) { case DekiScriptType.BOOL: case DekiScriptType.NUM: return(state.Push(DekiScriptExpression.Constant(left.AsNumber() < right.AsNumber()))); case DekiScriptType.STR: return(state.Push(DekiScriptExpression.Constant(left.AsString().CompareInvariant(right.AsString()) < 0))); default: return(DekiScriptOutputBuffer.Range.Empty); } } else { return(DekiScriptOutputBuffer.Range.Empty); } } case DekiScriptBinary.Op.LogicalAnd: { DekiScriptLiteral result = state.Pop(Left.VisitWith(this, state)); if (!result.IsNilFalseZero) { return(Right.VisitWith(this, state)); } return(state.Push(result)); } case DekiScriptBinary.Op.LogicalOr: { DekiScriptLiteral result = state.Pop(Left.VisitWith(this, state)); if (result.IsNilFalseZero) { return(Right.VisitWith(this, state)); } return(state.Push(result)); } case DekiScriptBinary.Op.Addition: return(state.Push(DekiScriptExpression.Constant(state.Pop(Left.VisitWith(this, state)).AsNumber() + state.Pop(Right.VisitWith(this, state)).AsNumber()))); case DekiScriptBinary.Op.Division: return(state.Push(DekiScriptExpression.Constant(state.Pop(Left.VisitWith(this, state)).AsNumber() / state.Pop(Right.VisitWith(this, state)).AsNumber()))); case DekiScriptBinary.Op.Modulo: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); if ((left is DekiScriptString) && ((right is DekiScriptMap) || (right is DekiScriptList))) { // NOTE (steveb): string.format shorthand notation: "abc = $abc" % { abc: 123 } -OR- "0 = $0" % [ 123 ] return(state.Push(DekiScriptLiteral.FromNativeValue(DekiScriptLibrary.StringFormat(((DekiScriptString)left).Value, right.NativeValue)))); } else { return(state.Push(DekiScriptExpression.Constant(left.AsNumber() % right.AsNumber()))); } } case DekiScriptBinary.Op.Multiplication: return(state.Push(DekiScriptExpression.Constant(state.Pop(Left.VisitWith(this, state)).AsNumber() * state.Pop(Right.VisitWith(this, state)).AsNumber()))); case DekiScriptBinary.Op.Subtraction: return(state.Push(DekiScriptExpression.Constant(state.Pop(Left.VisitWith(this, state)).AsNumber() - state.Pop(Right.VisitWith(this, state)).AsNumber()))); case DekiScriptBinary.Op.NullCoalesce: { DekiScriptLiteral result = state.Pop(Left.VisitWith(this, state)); if (result.IsNil) { return(Right.VisitWith(this, state)); } return(state.Push(result)); } case DekiScriptBinary.Op.Concat: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); if (left is DekiScriptNil) { return(state.Push(right)); } else if (right is DekiScriptNil) { return(state.Push(left)); } else if ((left is DekiScriptMap) && (right is DekiScriptMap)) { // left and right expressions are maps, merge them DekiScriptMap result = new DekiScriptMap(); result.AddRange((DekiScriptMap)left); result.AddRange((DekiScriptMap)right); return(state.Push(result)); } else if ((left is DekiScriptList) && (right is DekiScriptList)) { // left and right expressions are lists, concatenate them DekiScriptList result = new DekiScriptList(); result.AddRange((DekiScriptList)left); result.AddRange((DekiScriptList)right); return(state.Push(result)); } else { // treat left and right expressions as strings string leftText = left.AsString(); string rightText = right.AsString(); if ((leftText != null) && (rightText != null)) { return(state.Push(DekiScriptExpression.Constant(leftText + rightText))); } else if (leftText != null) { return(state.Push(DekiScriptExpression.Constant(leftText))); } else if (rightText != null) { return(state.Push(DekiScriptExpression.Constant(rightText))); } else { return(DekiScriptOutputBuffer.Range.Empty); } } } case DekiScriptBinary.Op.UriAppend: { // TODO (steveb): we should throw an exception when the LHS is not a valid string or uri XUri left = XUri.TryParse(state.Pop(Left.VisitWith(this, state)).AsString()); string result = null; if (left != null) { DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); if (right is DekiScriptString) { result = DekiScriptLibrary.UriBuild(left, right.AsString(), null); } else if (right is DekiScriptMap) { result = DekiScriptLibrary.UriBuild(left, null, (Hashtable)right.NativeValue); } else { result = left.ToString(); } } return(state.Push(DekiScriptLiteral.FromNativeValue(result))); } case DekiScriptBinary.Op.InCollection: { DekiScriptLiteral left = state.Pop(Left.VisitWith(this, state)); DekiScriptLiteral right = state.Pop(Right.VisitWith(this, state)); if (right is DekiScriptList) { foreach (DekiScriptLiteral item in ((DekiScriptList)right).Value) { if (!DekiScriptBinary.Equal(left, item).IsNilFalseZero) { return(state.Push(DekiScriptBool.True)); } } return(state.Push(DekiScriptBool.False)); } else if (right is DekiScriptMap) { foreach (DekiScriptLiteral item in ((DekiScriptMap)right).Value.Values) { if (!DekiScriptBinary.Equal(left, item).IsNilFalseZero) { return(state.Push(DekiScriptBool.True)); } } return(state.Push(DekiScriptBool.False)); } else { return(state.Push(DekiScriptBool.False)); } } } throw new InvalidOperationException("invalid op code:" + expr.OpCode); }
internal void InsertValueBeforeNode(XmlNode parent, XmlNode reference, DekiScriptLiteral value) { if ((value is DekiScriptXml) || (value is DekiScriptUri)) { XDoc xml = value.AsEmbeddableXml(Mode == DekiScriptEvalMode.EvaluateSafeMode); if (xml.HasName("html")) { // TODO (steveb): merge XML namespaces // merge <head> and <tail> sections AddHeadElements(xml); AddTailElements(xml); // loop over body elements in response foreach (XDoc body in xml["body"]) { string target = body["@target"].AsText; string conflict = body["@conflict"].AsText ?? "ignore"; // check if the main body is targeted or something else if (string.IsNullOrEmpty(target)) { // append body nodes foreach (XmlNode node in body.AsXmlNode.ChildNodes) { parent.InsertBefore(parent.OwnerDocument.ImportNode(node, true), reference); } } else { // check if the targeted body already exists if (Bodies.ContainsKey(target) && !StringUtil.EqualsInvariant(conflict, "replace")) { if (StringUtil.EqualsInvariant(conflict, "append")) { // append nodes to existing body Bodies[target].Add(body.AsXmlNode); } } else { // create a new body element List <XmlNode> list = new List <XmlNode>(); list.Add(body.AsXmlNode); Bodies[target] = list; } } } } else if (!xml.IsEmpty) { // replace the current node with the entire document parent.InsertBefore(parent.OwnerDocument.ImportNode(xml.AsXmlNode, true), reference); } } else if (value is DekiScriptComplexLiteral) { // append text respresentation of value parent.InsertBefore(CreateTextNode(value.ToString()), reference); } else { // append value cast to text string text = value.AsString(); if (!string.IsNullOrEmpty(text)) { parent.InsertBefore(CreateTextNode(text), reference); } } }