diff --git a/includes/common.inc b/includes/common.inc
index ad2a345..89b9aa0 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -1571,6 +1571,7 @@ function _filter_xss_attributes($attr) {
   $attrarr = array();
   $mode = 0;
   $attrname = '';
+  $skip_protocol_filtering = FALSE;
 
   while (strlen($attr) != 0) {
     // Was the last operation successful?
@@ -1582,6 +1583,22 @@ function _filter_xss_attributes($attr) {
         if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
           $attrname = strtolower($match[1]);
           $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
+
+          // Values for attributes of type URI should be filtered for
+          // potentially malicious protocols (for example, an href-attribute
+          // starting with "javascript:").
+          // However, for some non-URI attributes, performing this filtering
+          // actually causes valid and safe data to be mangled. To prevent
+          // this, we whitelist a number of non-URI attributes for which
+          // protocol filtering can be safely by-passed.
+          //
+          // @See filter_xss_bad_protocol()
+          // @See http://www.w3.org/TR/html4/index/attributes.html
+          $skip_protocol_filtering = substr($attrname, 0, 5) === 'data-' || in_array($attrname, array(
+            'title',
+            'alt',
+          ));
+
           $working = $mode = 1;
           $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
         }
@@ -1607,7 +1624,7 @@ function _filter_xss_attributes($attr) {
       case 2:
         // Attribute value, a URL after href= for instance.
         if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
-          $thisval = filter_xss_bad_protocol($match[1]);
+          $thisval = $skip_protocol_filtering ? $match[1] : filter_xss_bad_protocol($match[1]);
 
           if (!$skip) {
             $attrarr[] = "$attrname=\"$thisval\"";
@@ -1619,7 +1636,7 @@ function _filter_xss_attributes($attr) {
         }
 
         if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
-          $thisval = filter_xss_bad_protocol($match[1]);
+          $thisval = $skip_protocol_filtering ? $match[1] : filter_xss_bad_protocol($match[1]);
 
           if (!$skip) {
             $attrarr[] = "$attrname='$thisval'";
@@ -1630,7 +1647,7 @@ function _filter_xss_attributes($attr) {
         }
 
         if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
-          $thisval = filter_xss_bad_protocol($match[1]);
+          $thisval = $skip_protocol_filtering ? $match[1] : filter_xss_bad_protocol($match[1]);
 
           if (!$skip) {
             $attrarr[] = "$attrname=\"$thisval\"";
diff --git a/modules/filter/filter.test b/modules/filter/filter.test
index fe9cfc3..4962a27 100644
--- a/modules/filter/filter.test
+++ b/modules/filter/filter.test
@@ -1051,6 +1051,12 @@ class FilterUnitTestCase extends DrupalUnitTestCase {
     $f = filter_xss('<img src=javascript:alert(0)>', array('img'));
     $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- no quotes.');
 
+    $f = filter_xss('<img src="http://example.com/foo.jpg" title="Example: title" alt="Example: alt">', array('img'));
+    $this->assertIdentical($f, '<img src="http://example.com/foo.jpg" title="Example: title" alt="Example: alt">', 'No HTML scheme clearing on allowed attributes.');
+
+    $f = filter_xss('<img src="http://example.com/foo.jpg" data-caption="Example: data-caption.">', array('img'));
+    $this->assertIdentical($f, '<img src="http://example.com/foo.jpg" data-caption="Example: data-caption.">', 'No HTML scheme clearing on allowed data attributes.');
+
     // A bit like CVE-2006-0070.
     $f = filter_xss('<img src="javascript:confirm(0)">', array('img'));
     $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- no alert ;)');
