/// <summary>Get the allowed attributes defined in the provided tag <see cref="XmlElement"/>.</summary> /// <param name="tagNode">The node to retrieve the values from.</param> /// <param name="tagName">The name of the tag which has attributes defined.</param> /// <param name="parseContext">The parse context.</param> /// <returns>A dictionary with the allowed attributes.</returns> private static Dictionary <string, Attribute> GetTagAllowedAttributes(XmlElement tagNode, string tagName, ParseContext parseContext) { var allowedAttributes = new Dictionary <string, Attribute>(); foreach (XmlElement attributeNode in PolicyParserUtil.GetChildrenByTagName(tagNode, "attribute")) { string attributeName = XmlUtil.GetAttributeValue(attributeNode, "name"); if (!attributeNode.HasChildNodes) { /* All they provided was the name, so they must want a common attribute. */ Attribute attribute = parseContext.commonAttributes.GetValueOrTypeDefault(attributeName.ToLowerInvariant()); if (attribute != null) { /* If they provide onInvalid/description values here they will override the common values. */ string onInvalid = XmlUtil.GetAttributeValue(attributeNode, "onInvalid"); string description = XmlUtil.GetAttributeValue(attributeNode, "description"); if (!string.IsNullOrEmpty(onInvalid)) { attribute.OnInvalid = onInvalid; } if (!string.IsNullOrEmpty(description)) { attribute.Description = description; } allowedAttributes.Add(attributeName, attribute.Clone() as Attribute); } else { throw new PolicyException($"Attribute '{XmlUtil.GetAttributeValue(attributeNode, "name")}' was referenced as a common attribute in definition of '{tagName}', but does not exist in <common-attributes>"); } } else { /* Custom attribute for this tag */ var attribute = new Attribute(XmlUtil.GetAttributeValue(attributeNode, "name")) { AllowedValues = PolicyParserUtil.GetAttributeOrValueFromGrandchildren(attributeNode, "literal-list", "literal", "value"), AllowedRegExp = GetAllowedRegexpsForRules(attributeNode, tagName, parseContext), Description = XmlUtil.GetAttributeValue(attributeNode, "description"), OnInvalid = XmlUtil.GetAttributeValue(attributeNode, "onInvalid") }; allowedAttributes.Add(attributeName, attribute); } } return(allowedAttributes); }
/// <summary>A method for returning one of the dynamic <common-attribute> entries by name.</summary> /// <param name="name">The name of the dynamic common-attribute we want to look up.</param> /// <returns>An <see cref="Attribute"/> associated with the common-attribute lookup name specified, or null if not found.</returns> internal Attribute GetDynamicAttributeByName(string name) { Attribute dynamicAttribute = null; string nameLowercase = name.ToLowerInvariant(); foreach (KeyValuePair <string, Attribute> attributeEntry in dynamicAttributes) { if (nameLowercase.StartsWith(attributeEntry.Key)) { dynamicAttribute = attributeEntry.Value; break; } } return(dynamicAttribute); }
/// <summary> Go through <global-tag-attributes> section of the policy file.</summary> /// <param name="globalAttributeListNode">Top level of <global-tag-attributes></param> /// <param name="parseContext">The <see cref="ParseContext"/> containing the global attributes dictionary to fill.</param> private static void ParseGlobalAttributes(XmlNode globalAttributeListNode, ParseContext parseContext) { foreach (XmlElement node in PolicyParserUtil.GetChildrenByTagName(globalAttributeListNode, "attribute")) { string name = XmlUtil.GetAttributeValue(node, "name"); Attribute toAdd = parseContext.commonAttributes.GetValueOrTypeDefault(name.ToLowerInvariant()); if (toAdd != null) { parseContext.globalAttributes.Add(name.ToLowerInvariant(), toAdd); } else { throw new PolicyException($"Global attribute '{name}' was not defined in <common-attributes>"); } } }
/// <summary> Go through <dynamic-tag-attributes> section of the policy file.</summary> /// <param name="dynamicAttributeListNode">Top level of <dynamic-tag-attributes></param> /// <param name="parseContext">The <see cref="ParseContext"/> containing the dynamic attributes dictionary to fill.</param> private static void ParseDynamicAttributes(XmlNode dynamicAttributeListNode, ParseContext parseContext) { foreach (XmlElement node in PolicyParserUtil.GetChildrenByTagName(dynamicAttributeListNode, "attribute")) { string name = XmlUtil.GetAttributeValue(node, "name"); Attribute toAdd = parseContext.commonAttributes.GetValueOrTypeDefault(name.ToLowerInvariant()); if (toAdd != null) { string attributeName = name.ToLowerInvariant().Substring(0, name.Length - 1); parseContext.dynamicAttributes.Add(attributeName, toAdd); } else { throw new PolicyException($"Dynamic attribute '{name}' was not defined in <common-attributes>"); } } }
/// <summary> Go through the <common-attributes> section of the policy file.</summary> /// <param name="commonAttributeListNode">Top level of <common-attributes>.</param> /// <param name="parseContext">The <see cref="ParseContext"/> containing the common attributes dictionary to fill.</param> private static void ParseCommonAttributes(XmlNode commonAttributeListNode, ParseContext parseContext) { foreach (XmlElement node in PolicyParserUtil.GetChildrenByTagName(commonAttributeListNode, "attribute")) { // TODO: Throw exception if onInvalid is defined but is not an expected option? string onInvalid = XmlUtil.GetAttributeValue(node, "onInvalid"); string name = XmlUtil.GetAttributeValue(node, "name"); var attribute = new Attribute(name) { AllowedRegExp = GetAllowedRegexpsForCommonAttributes(node, parseContext), AllowedValues = PolicyParserUtil.GetAttributeOrValueFromGrandchildren(node, "literal-list", "literal", "value"), Description = XmlUtil.GetAttributeValue(node, "description"), OnInvalid = string.IsNullOrEmpty(onInvalid) ? Constants.DEFAULT_ONINVALID : onInvalid, }; parseContext.commonAttributes.Add(name.ToLowerInvariant(), attribute); } }
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 ValidateTag(HtmlNode node, HtmlNode parentNode, string tagName, Tag tag, bool isMasqueradingParam) { // If doing <param> as <embed>, now is the time to convert it. string nameAttributeValue = null; if (isMasqueradingParam) { nameAttributeValue = node.Attributes["name"]?.Value; if (!string.IsNullOrEmpty(nameAttributeValue)) { string valueAttributeValue = node.Attributes["value"]?.Value; node.SetAttributeValue(nameAttributeValue, valueAttributeValue); node.SetAttributeValue("name", null); node.SetAttributeValue("value", null); tag = Policy.GetTagByName("embed"); } } /* * Check to see if it's a <style> tag. We have to special case this * tag so we can hand it off to the custom style sheet validating parser. */ if (tagName.ToLowerInvariant() == "style" && Policy.GetTagByName("style") != null && !ProcessStyleTag(node, parentNode)) { return; } /* * Go through the attributes in the tainted tag and validate them against the values we have for them. * If we don't have a rule for the attribute we remove the attribute. */ if (!ProcessAttributes(node, tag)) { return; } if (Policy.AddNofollowInAnchors && tagName.ToLowerInvariant() == "a") { node.SetAttributeValue("rel", "nofollow"); } if (tagName.ToLowerInvariant() == "a") { bool addNofollow = Policy.AddNofollowInAnchors; bool addNoopenerAndNoreferrer = false; if (Policy.AddNoopenerAndNoreferrerInAnchors) { string targetAttribute = node.GetAttributeValue("target", null); if (targetAttribute != null && targetAttribute.ToLowerInvariant() == "_blank") { addNoopenerAndNoreferrer = true; } } string relAttribute = node.GetAttributeValue("rel", null); string relValue = Attribute.MergeRelValuesInAnchor(addNofollow, addNoopenerAndNoreferrer, relAttribute ?? string.Empty); if (!string.IsNullOrEmpty(relValue)) { node.SetAttributeValue("rel", relValue.Trim()); } } ProcessChildren(node); // If we have been dealing with a <param> that has been converted to an <embed>, convert it back. if (isMasqueradingParam && !string.IsNullOrEmpty(nameAttributeValue)) { string valueAttributeValue = node.Attributes[nameAttributeValue]?.Value; node.SetAttributeValue("name", nameAttributeValue); node.SetAttributeValue("value", string.IsNullOrEmpty(valueAttributeValue) ? string.Empty : valueAttributeValue); // Original attribute may have been removed already by the validation if (node.Attributes[nameAttributeValue] != null) { node.Attributes.Remove(node.Attributes[nameAttributeValue]); } } }