diff --git a/src/HtmlSanitizer/HtmlSanitizer.cs b/src/HtmlSanitizer/HtmlSanitizer.cs index bb7443e..d612581 100644 --- a/src/HtmlSanitizer/HtmlSanitizer.cs +++ b/src/HtmlSanitizer/HtmlSanitizer.cs @@ -99,6 +99,8 @@ public HtmlSanitizer(HtmlSanitizerOptions options) AllowedClasses = new HashSet(options.AllowedCssClasses, StringComparer.OrdinalIgnoreCase); AllowedCssProperties = new HashSet(options.AllowedCssProperties, StringComparer.OrdinalIgnoreCase); AllowedAtRules = new HashSet(options.AllowedAtRules); + AllowCssCustomProperties = options.AllowCssCustomProperties; + AllowDataAttributes = options.AllowDataAttributes; } /// @@ -204,6 +206,11 @@ public HtmlSanitizer(HtmlSanitizerOptions options) /// public ISet AllowedCssProperties { get; private set; } + /// + /// Allow all custom CSS properties (variables) prefixed with --. + /// + public bool AllowCssCustomProperties { get; set; } + /// /// Gets or sets a regex that must not match for legal CSS property values. /// @@ -726,6 +733,19 @@ protected void SanitizeStyle(IElement element, string baseUrl) SanitizeStyleDeclaration(element, styles, baseUrl); } + /// + /// Verify if the given CSS property name is allowed. By default this will + /// check if the property is in the set, + /// or if the property is a custom property and is true. + /// + /// The name of the CSS property. + /// True if the property is allowed or not. + protected virtual bool IsAllowedCssProperty(string propertyName) + { + return AllowedCssProperties.Contains(propertyName) + || AllowCssCustomProperties && propertyName != null && propertyName.StartsWith("--"); + } + private void SanitizeStyleDeclaration(IElement element, ICssStyleDeclaration styles, string baseUrl) { var removeStyles = new List>(); @@ -736,7 +756,7 @@ private void SanitizeStyleDeclaration(IElement element, ICssStyleDeclaration sty var key = DecodeCss(style.Name); var val = DecodeCss(style.Value); - if (!AllowedCssProperties.Contains(key)) + if (!IsAllowedCssProperty(key)) { removeStyles.Add(new Tuple(style, RemoveReason.NotAllowedStyle)); continue; diff --git a/src/HtmlSanitizer/HtmlSanitizerOptions.cs b/src/HtmlSanitizer/HtmlSanitizerOptions.cs index 05e38e1..def25da 100644 --- a/src/HtmlSanitizer/HtmlSanitizerOptions.cs +++ b/src/HtmlSanitizer/HtmlSanitizerOptions.cs @@ -43,5 +43,15 @@ public class HtmlSanitizerOptions /// Gets or sets the HTML attributes that can contain a URI such as "href". /// public ISet UriAttributes { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); + + /// + /// Allow all custom CSS properties (variables) prefixed with --. + /// + public bool AllowCssCustomProperties { get; set; } + + /// + /// Allow all HTML5 data attributes; the attributes prefixed with data-. + /// + public bool AllowDataAttributes { get; set; } } } diff --git a/test/HtmlSanitizer.Tests/Tests.cs b/test/HtmlSanitizer.Tests/Tests.cs index a8ad6d0..aec4a6e 100644 --- a/test/HtmlSanitizer.Tests/Tests.cs +++ b/test/HtmlSanitizer.Tests/Tests.cs @@ -2903,7 +2903,7 @@ public void ThreadTest() var waiting = numThreads; var methods = typeof(HtmlSanitizerTests).GetTypeInfo().GetMethods() .Where(m => m.GetCustomAttributes(typeof(Xunit.FactAttribute), false).Cast().Any(f => f.Skip == null)) - .Where(m => m.Name != "ThreadTest" && m.Name != "HexColorTest"); + .Where(m => m.Name != nameof(ThreadTest) && m.Name != nameof(HexColorTest)); var threads = Shuffle(methods, random) .Take(numThreads) .Select(m => new Thread(() => @@ -3589,4 +3589,44 @@ public void KeepChildNodesTextTest() var sanitized = sanitizer.Sanitize(input); Assert.Equal("<img>&lt;img&gt;", sanitized); } + + [Fact] + public void AllowStyleAttributeCssCustomPropertiesTest() + { + var input = "
"; + var sanitizer = new HtmlSanitizer { AllowCssCustomProperties = true }; + sanitizer.AllowedTags.Remove("iframe"); + var sanitized = sanitizer.Sanitize(input); + Assert.Equal("
", sanitized); + } + + [Fact] + public void AllowStyleTagCssCustomPropertiesTest() + { + var input = ""; + var sanitizer = new HtmlSanitizer { AllowCssCustomProperties = true }; + sanitizer.AllowedTags.Add("style"); + var sanitized = sanitizer.Sanitize(input); + Assert.Equal("", sanitized); + } + + [Fact] + public void DisallowStyleAttributeCssCustomPropertiesTest() + { + var input = "
"; + var sanitizer = new HtmlSanitizer(); + sanitizer.AllowedTags.Remove("iframe"); + var sanitized = sanitizer.Sanitize(input); + Assert.Equal("
", sanitized); + } + + [Fact] + public void DisallowStyleTagCssCustomPropertiesTest() + { + var input = ""; + var sanitizer = new HtmlSanitizer(); + sanitizer.AllowedTags.Add("style"); + var sanitized = sanitizer.Sanitize(input); + Assert.Equal("", sanitized); + } }