Repository: flavorjones/loofah (Ruby)

Issue:
Empty data attributes are stripped
Is there a reason why empty data attributes are stripped?

```ruby
Loofah.fragment('<div data-foo></div>').scrub!(:prune).to_s
 #=> "<div></div>"
Loofah.fragment('<div data-foo="bar"></div>').scrub!(:prune).to_s
 #=> "<div data-foo=\"bar\"></div>"
```

From what I can tell, empty data attributes are valid in HTML 5.


Discussion:
Hi @ryanb, thanks for reporting this. I agree, empty `data-*` attributes are valid and I think this behavior is just an oversight. Support for `data-*` attributes was added in #59 and expanded in #84 but at no point were empty attributes tested.

I think I can get to this in the next day or two, hang tight!
PR is #216 going through CI now. If it goes green, I'll ship it in v2.12.0 shortly thereafter.
v2.12.0 has been released with a fix: [Release 2.12.0 / 2021-08-11 · flavorjones/loofah](https://github.com/flavorjones/loofah/releases/tag/v2.12.0)
Wow thanks for the fast turnaround! I really appreciate the work you put into loofah.
Ah, thanks for the kind words.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44db4c3..d88fae2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## next / unreleased
+
+### Features
+
+* Support empty HTML5 data attributes. [[#215](https://github.com/flavorjones/loofah/issues/215)]
+
+
 ## 2.11.0 / 2021-07-31
 
 ### Features
diff --git a/lib/loofah/html5/scrub.rb b/lib/loofah/html5/scrub.rb
index c541903..3924c47 100644
--- a/lib/loofah/html5/scrub.rb
+++ b/lib/loofah/html5/scrub.rb
@@ -10,6 +10,7 @@ module Scrub
       CRASS_SEMICOLON = { node: :semicolon, raw: ";" }
       CSS_IMPORTANT = '!important'
       CSS_PROPERTY_STRING_WITHOUT_EMBEDDED_QUOTES = /\A(["'])?[^"']+\1\z/
+      DATA_ATTRIBUTE_NAME = /\Adata-[\w-]+\z/
 
       class << self
         def allowed_element?(element_name)
@@ -25,7 +26,7 @@ def scrub_attributes(node)
               attr_node.node_name
             end
 
-            if attr_name =~ /\Adata-[\w-]+\z/
+            if attr_name =~ DATA_ATTRIBUTE_NAME
               next
             end
 
@@ -62,7 +63,9 @@ def scrub_attributes(node)
           scrub_css_attribute(node)
 
           node.attribute_nodes.each do |attr_node|
-            node.remove_attribute(attr_node.name) if attr_node.value !~ /[^[:space:]]/
+            if attr_node.value !~ /[^[:space:]]/ && attr_node.name !~ DATA_ATTRIBUTE_NAME
+              node.remove_attribute(attr_node.name)
+            end
           end
 
           force_correct_attribute_escaping!(node)


Test patch:
diff --git a/test/html5/test_sanitizer.rb b/test/html5/test_sanitizer.rb
index bacf267..c4e7f82 100755
--- a/test/html5/test_sanitizer.rb
+++ b/test/html5/test_sanitizer.rb
@@ -102,18 +102,23 @@ def assert_completes_in_reasonable_time(&block)
 
   def test_should_allow_data_attributes
     input = "<p data-foo='foo'>foo <bad>bar</bad> baz</p>"
-
     output = "<p data-foo='foo'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>"
-    htmloutput = "<p data-foo='foo'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>"
 
-    check_sanitization(input, htmloutput, output, output)
+    check_sanitization(input, output, output, output)
   end
 
   def test_should_allow_multi_word_data_attributes
     input = "<p data-foo-bar-id='11'>foo <bad>bar</bad> baz</p>"
-    output = htmloutput = "<p data-foo-bar-id='11'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>"
+    output = "<p data-foo-bar-id='11'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>"
 
-    check_sanitization(input, htmloutput, output, output)
+    check_sanitization(input, output, output, output)
+  end
+
+  def test_should_allow_empty_data_attributes
+    input = "<p data-foo data-bar="">foo <bad>bar</bad> baz</p>"
+    output = "<p data-foo data-bar=''>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>"
+
+    check_sanitization(input, output, output, output)
   end
 
   def test_should_allow_contenteditable