/// <summary>Invokes the CSS parser on the element.</summary> /// <param name="node">The <see cref="HtmlNode"/> to scan.</param> /// <param name="parentNode">The parent of the node.</param> /// <returns><see langword="true"/> if processing ended with no exceptions.</returns> private bool ProcessStyleTag(HtmlNode node, HtmlNode parentNode) { var styleScanner = new CssScanner(Policy); try { CleanResults cleanStyleSheet = styleScanner.ScanStyleSheet(node.FirstChild.InnerHtml); errorMessages.AddRange(cleanStyleSheet.GetErrorMessages()); /* * If IE gets an empty style tag, i.e. <style/> it will break all CSS on the page. I wish I * was kidding. So, if after validation no CSS properties are left, we would normally be left * with an empty style tag and break all CSS. To prevent that, we have this check. */ string cleanHtml = cleanStyleSheet.GetCleanHtml(); node.FirstChild.InnerHtml = string.IsNullOrEmpty(cleanHtml) ? EMPTY_CSS_COMMENT : cleanHtml; } catch (Exception exc) { if (exc is ScanException || exc is ParseException) { AddError(Constants.ERROR_CSS_TAG_MALFORMED, HtmlEntityEncoder.HtmlEntityEncode(node.FirstChild.InnerHtml)); parentNode.RemoveChild(node); return(false); } else { throw; } } return(true); }
internal ErrorReporter(CssScanner aScanner, CssStyleSheet aSheet, CssLoader aLoader, Uri aUri) { mScanner = aScanner; mSheet = aSheet; mLoader = aLoader; mUri = aUri; }
private static void AssertNextToken(CssScanner lex, CssTokenType type, Func <CssToken, bool> condition) { var token = new CssToken(); Assert.IsTrue(lex.Next(token, true), "Unexpected EOF"); Assert.AreEqual(type, token.mType); Assert.IsTrue(condition(token), "Condition for token {0} failed".Fmt(token.mType)); }
public void ParseSimple() { var lex = new CssScanner("h1 { color: #123; }", 0); AssertNextToken(lex, CssTokenType.Ident, t => t.mIdent.ToString() == "h1"); AssertNextToken(lex, CssTokenType.Symbol, t => t.mSymbol == '{'); AssertNextToken(lex, CssTokenType.Ident, t => t.mIdent.ToString() == "color"); AssertNextToken(lex, CssTokenType.Symbol, t => t.mSymbol == ':'); AssertNextToken(lex, CssTokenType.Hash, t => t.mIdent.ToString() == "123"); AssertNextToken(lex, CssTokenType.Symbol, t => t.mSymbol == ';'); AssertNextToken(lex, CssTokenType.Symbol, t => t.mSymbol == '}'); AssertNextTokenEnd(lex); }
private static void AssertNextTokenEnd(CssScanner lex) { var token = new CssToken(); Assert.IsFalse(lex.Next(token, true), "Expected EOF"); }
public void Create() { // ReSharper disable once UnusedVariable var lex = new CssScanner("", 0); }
private bool ProcessAttributes(HtmlNode node, Tag tag) { string tagName = tag.Name; int currentAttributeIndex = 0; while (currentAttributeIndex < node.Attributes.Count) { HtmlAttribute htmlAttribute = node.Attributes[currentAttributeIndex]; string name = htmlAttribute.Name; string value = htmlAttribute.Value; Attribute attribute = tag.GetAttributeByName(name); if (attribute == null) { attribute = Policy.GetGlobalAttributeByName(name); // Not a global attribute, perhaps it is a dynamic attribute, if allowed. if (attribute == null && Policy.AllowsDynamicAttributes) { attribute = Policy.GetDynamicAttributeByName(name); } } if (name.ToLowerInvariant() == "style" && attribute != null) { var styleScanner = new CssScanner(Policy); try { CleanResults cleanInlineStyle = styleScanner.ScanInlineStyle(value, tagName); htmlAttribute.Value = cleanInlineStyle.GetCleanHtml(); errorMessages.AddRange(cleanInlineStyle.GetErrorMessages()); } catch (Exception exc) { if (exc is ScanException || exc is ParseException) { AddError(Constants.ERROR_CSS_ATTRIBUTE_MALFORMED, HtmlEntityEncoder.HtmlEntityEncode(value), HtmlEntityEncoder.HtmlEntityEncode(tagName)); node.Attributes.Remove(name); currentAttributeIndex--; } else { throw; } } } else { if (attribute != null) { value = HtmlEntity.DeEntitize(value); string lowerCaseValue = value.ToLowerInvariant(); bool isAttributeValid = attribute.AllowedValues.Any(v => v != null && v.ToLowerInvariant() == lowerCaseValue) || attribute.AllowedRegExp.Any(r => r != null && Regex.IsMatch(value, "^" + r + "$")); if (!isAttributeValid) { string onInvalidAction = attribute.OnInvalid; if (onInvalidAction == "removeTag") { RemoveNode(node); AddError(Constants.ERROR_ATTRIBUTE_INVALID_REMOVED, HtmlEntityEncoder.HtmlEntityEncode(tagName), HtmlEntityEncoder.HtmlEntityEncode(name), HtmlEntityEncoder.HtmlEntityEncode(value)); } else if (onInvalidAction == "filterTag") { // Remove the node and move up the rest that was inside the tag after processing ProcessChildren(node); PromoteChildren(node); AddError(Constants.ERROR_ATTRIBUTE_CAUSE_FILTER, HtmlEntityEncoder.HtmlEntityEncode(tagName), HtmlEntityEncoder.HtmlEntityEncode(name), HtmlEntityEncoder.HtmlEntityEncode(value)); } else if (onInvalidAction == "encodeTag") { // Encode the node and move up the rest that was inside the tag after processing ProcessChildren(node); EncodeAndPromoteChildren(node); AddError(Constants.ERROR_ATTRIBUTE_CAUSE_ENCODE, HtmlEntityEncoder.HtmlEntityEncode(tagName), HtmlEntityEncoder.HtmlEntityEncode(name), HtmlEntityEncoder.HtmlEntityEncode(value)); } else { // Just remove the attribute node.Attributes.Remove(attribute.Name); currentAttributeIndex--; AddError(Constants.ERROR_ATTRIBUTE_INVALID, HtmlEntityEncoder.HtmlEntityEncode(tagName), HtmlEntityEncoder.HtmlEntityEncode(name), HtmlEntityEncoder.HtmlEntityEncode(value)); } if (new string[] { "removeTag", "filterTag", "encodeTag" }.Contains(onInvalidAction)) { return(false); // Can't process any more if we remove/filter/encode the tag } } } else { AddError(Constants.ERROR_ATTRIBUTE_NOT_IN_POLICY, HtmlEntityEncoder.HtmlEntityEncode(tagName), HtmlEntityEncoder.HtmlEntityEncode(name), HtmlEntityEncoder.HtmlEntityEncode(value)); node.Attributes.Remove(name); currentAttributeIndex--; } } currentAttributeIndex++; } return(true); }
private void recursiveValidateTag(HtmlNode node) { int maxinputsize = int.Parse(policy.getDirective("maxInputSize")); num++; HtmlNode parentNode = node.ParentNode; HtmlNode tmp = null; string tagName = node.Name; //check this out //might not be robust enough if (tagName.ToLower().Equals("#text")) // || tagName.ToLower().Equals("#comment")) { return; } Tag tag = policy.getTagByName(tagName.ToLower()); if (tag == null || "filter".Equals(tag.Action)) { StringBuilder errBuff = new StringBuilder(); if (tagName == null || tagName.Trim().Equals("")) { errBuff.Append("An unprocessable "); } else { errBuff.Append("The <b>" + HTMLEntityEncoder.htmlEntityEncode(tagName.ToLower()) + "</b> "); } errBuff.Append("tag has been filtered for security reasons. The contents of the tag will "); errBuff.Append("remain in place."); errorMessages.Add(errBuff.ToString()); for (int i = 0; i < node.ChildNodes.Count; i++) { tmp = node.ChildNodes[i]; recursiveValidateTag(tmp); if (tmp.ParentNode == null) { i--; } } promoteChildren(node); return; } else if ("validate".Equals(tag.Action)) { if ("style".Equals(tagName.ToLower()) && policy.getTagByName("style") != null) { CssScanner styleScanner = new CssScanner(policy); try { CleanResults cr = styleScanner.scanStyleSheet(node.FirstChild.InnerHtml, maxinputsize); foreach (string msg in cr.getErrorMessages()) { errorMessages.Add(msg.ToString()); } /* * If IE gets an empty style tag, i.e. <style/> * it will break all CSS on the page. I wish I * was kidding. So, if after validation no CSS * properties are left, we would normally be left * with an empty style tag and break all CSS. To * prevent that, we have this check. */ if (cr.getCleanHTML() == null || cr.getCleanHTML().Equals("")) { //node.getFirstChild().setNodeValue("/* */"); node.FirstChild.InnerHtml = "/* */"; } else { //node.getFirstChild().setNodeValue(cr.getCleanHTML()); node.FirstChild.InnerHtml = cr.getCleanHTML(); } } // catch (DomException e) // { // addError(ErrorMessageUtil.ERROR_CSS_TAG_MALFORMED, new Object[] { HTMLEntityEncoder.htmlEntityEncode(node.getFirstChild().getNodeValue()) }); // parentNode.removeChild(node); // } catch (ScanException e) { Console.WriteLine("Scan Exception: " + e.Message); //addError(ErrorMessageUtil.ERROR_CSS_TAG_MALFORMED, new Object[] { HTMLEntityEncoder.htmlEntityEncode(node.getFirstChild().getNodeValue()) }); parentNode.RemoveChild(node); } } HtmlAttribute attribute = null; for (int currentAttributeIndex = 0; currentAttributeIndex < node.Attributes.Count; currentAttributeIndex++) { attribute = node.Attributes[currentAttributeIndex]; string name = attribute.Name; string _value = attribute.Value; Attribute attr = tag.getAttributeByName(name); if (attr == null) { attr = policy.getGlobalAttributeByName(name); } bool isAttributeValid = false; if ("style".Equals(name.ToLower()) && attr != null) { CssScanner styleScanner = new CssScanner(policy); try { CleanResults cr = styleScanner.scanInlineStyle(_value, tagName, maxinputsize); //attribute.setNodeValue(cr.getCleanHTML()); attribute.Value = cr.getCleanHTML(); ArrayList cssScanErrorMessages = cr.getErrorMessages(); foreach (string msg in cr.getErrorMessages()) { errorMessages.Add(msg.ToString()); } } /* * catch (DOMException e) * { * * addError(ErrorMessageUtil.ERROR_CSS_ATTRIBUTE_MALFORMED, new Object[] { tagName, HTMLEntityEncoder.htmlEntityEncode(node.getNodeValue()) }); * * ele.removeAttribute(name); * currentAttributeIndex--; * * } */ catch (ScanException ex) { Console.WriteLine(ex.Message); //addError(ErrorMessageUtil.ERROR_CSS_ATTRIBUTE_MALFORMED, new Object[] { tagName, HTMLEntityEncoder.htmlEntityEncode(node.getNodeValue()) }); //ele.removeAttribute(name); currentAttributeIndex--; } } else { if (attr != null) { //try to find out how robust this is - do I need to do this in a loop? _value = HtmlEntity.DeEntitize(_value); foreach (string allowedValue in attr.AllowedValues) { if (isAttributeValid) { break; } if (allowedValue != null && allowedValue.ToLower().Equals(_value.ToLower())) { isAttributeValid = true; } } foreach (string ptn in attr.AllowedRegExp) { if (isAttributeValid) { break; } string pattern = "^" + ptn + "$"; Match m = Regex.Match(_value, pattern); if (m.Success) { isAttributeValid = true; } } if (!isAttributeValid) { string onInvalidAction = attr.OnInvalid; StringBuilder errBuff = new StringBuilder(); errBuff.Append("The <b>" + HTMLEntityEncoder.htmlEntityEncode(tagName) + "</b> tag contained an attribute that we couldn't process. "); errBuff.Append("The <b>" + HTMLEntityEncoder.htmlEntityEncode(name) + "</b> attribute had a value of <u>" + HTMLEntityEncoder.htmlEntityEncode(_value) + "</u>. "); errBuff.Append("This value could not be accepted for security reasons. We have chosen to "); //Console.WriteLine(policy); if ("removeTag".Equals(onInvalidAction)) { parentNode.RemoveChild(node); errBuff.Append("remove the <b>" + HTMLEntityEncoder.htmlEntityEncode(tagName) + "</b> tag and its contents in order to process this input. "); } else if ("filterTag".Equals(onInvalidAction)) { for (int i = 0; i < node.ChildNodes.Count; i++) { tmp = node.ChildNodes[i]; recursiveValidateTag(tmp); if (tmp.ParentNode == null) { i--; } } promoteChildren(node); errBuff.Append("filter the <b>" + HTMLEntityEncoder.htmlEntityEncode(tagName) + "</b> tag and leave its contents in place so that we could process this input."); } else { node.Attributes.Remove(attr.Name); currentAttributeIndex--; errBuff.Append("remove the <b>" + HTMLEntityEncoder.htmlEntityEncode(name) + "</b> attribute from the tag and leave everything else in place so that we could process this input."); } errorMessages.Add(errBuff.ToString()); if ("removeTag".Equals(onInvalidAction) || "filterTag".Equals(onInvalidAction)) { return; // can't process any more if we remove/filter the tag } } } else { StringBuilder errBuff = new StringBuilder(); errBuff.Append("The <b>" + HTMLEntityEncoder.htmlEntityEncode(name)); errBuff.Append("</b> attribute of the <b>" + HTMLEntityEncoder.htmlEntityEncode(tagName) + "</b> tag has been removed for security reasons. "); errBuff.Append("This removal should not affect the display of the HTML submitted."); errorMessages.Add(errBuff.ToString()); node.Attributes.Remove(name); currentAttributeIndex--; } // end if attribute is or is not found in policy file } // end if style.equals("name") } // end while loop through attributes for (int i = 0; i < node.ChildNodes.Count; i++) { tmp = node.ChildNodes[i]; recursiveValidateTag(tmp); if (tmp.ParentNode == null) { i--; } } } else if ("truncate".Equals(tag.Action)) { Console.WriteLine("truncate"); HtmlAttributeCollection nnmap = node.Attributes; while (nnmap.Count > 0) { StringBuilder errBuff = new StringBuilder(); errBuff.Append("The <b>" + HTMLEntityEncoder.htmlEntityEncode(nnmap[0].Name)); errBuff.Append("</b> attribute of the <b>" + HTMLEntityEncoder.htmlEntityEncode(tagName) + "</b> tag has been removed for security reasons. "); errBuff.Append("This removal should not affect the display of the HTML submitted."); node.Attributes.Remove(nnmap[0].Name); errorMessages.Add(errBuff.ToString()); } HtmlNodeCollection cList = node.ChildNodes; int i = 0; int j = 0; int length = cList.Count; while (i < length) { HtmlNode nodeToRemove = cList[j]; if (nodeToRemove.NodeType != HtmlNodeType.Text && nodeToRemove.NodeType != HtmlNodeType.Comment) { node.RemoveChild(nodeToRemove); } else { j++; } i++; } } else { errorMessages.Add("The <b>" + HTMLEntityEncoder.htmlEntityEncode(tagName) + "</b> tag has been removed for security reasons."); parentNode.RemoveChild(node); } }