Reviewers: Jasvir,

Description:
This change gets rid of the _d_attributes map and uses the
data-caja-* attribute namespace instead.  TemplateCompiler now renames
attributes rather than stripping them out, and the *Attribute()
methods in Domado prefix attributes if they're not otherwise
whitelisted.

Please review this at http://codereview.appspot.com/5672083/

Affected files:
  M     src/com/google/caja/lang/html/HtmlSchema.java
  M     src/com/google/caja/plugin/domado.js
  M     src/com/google/caja/plugin/templates/TemplateCompiler.java
  M     src/com/google/caja/plugin/templates/TemplateSanitizer.java
  M     tests/com/google/caja/plugin/templates/TemplateSanitizerTest.java
  M     tests/com/google/caja/service/HtmlHandlerTest.java


Index: tests/com/google/caja/plugin/templates/TemplateSanitizerTest.java
===================================================================
--- tests/com/google/caja/plugin/templates/TemplateSanitizerTest.java (revision 4782) +++ tests/com/google/caja/plugin/templates/TemplateSanitizerTest.java (working copy)
@@ -41,8 +41,7 @@
   public final void testUnknownAttribute() throws Exception {
     assertValid(
         htmlFragment(fromString("<b unknown=\"bogus\">Hello</b>")),
-        "<b>Hello</b>",
-        "WARNING: removing unknown attribute unknown on b");
+        "<b data-caja-unknown=\"bogus\">Hello</b>");
   }
   public final void testKnownAttribute() throws Exception {
     assertValid(htmlFragment(fromString("<b id=\"bold\">Hello</b>")),
@@ -59,8 +58,9 @@
     assertValid(
         htmlFragment(fromString("<bogus unknown=\"bogus\">Hello</bogus>")),
         "Hello",
-        "WARNING: removing unknown tag bogus"
-        // No need to warn about unknown since we have warned about tag
+        "WARNING: removing unknown tag bogus",
+        "WARNING: removing attribute data-caja-unknown " +
+            "when folding bogus into parent"
         );
   }
   public final void testDisallowedScriptElement() throws Exception {
@@ -251,12 +251,12 @@
             + "<x>Four</x>"
             + "</body>"
             + "</html>")),
-        "<p>Foo</p><p>One</p><p>Two</p>ThreeFour",
+        "<p>Foo</p><p>One</p><p data-caja-styleo=\"color: red\">Two" +
+            "</p>ThreeFour",
         "WARNING: folding element html into parent",
         "WARNING: folding element head into parent",
         "WARNING: removing disallowed tag title",
         "WARNING: folding element body into parent",
-        "WARNING: removing unknown attribute styleo on p",
         "WARNING: removing unknown tag x");
   }
   public final void testElementFolding4() throws Exception {
Index: tests/com/google/caja/service/HtmlHandlerTest.java
===================================================================
--- tests/com/google/caja/service/HtmlHandlerTest.java  (revision 4782)
+++ tests/com/google/caja/service/HtmlHandlerTest.java  (working copy)
@@ -151,7 +151,8 @@
   private void assertHtml2Json(String inputMimeType)
       throws Exception {
     registerUri(
-        "http://foo/bar.html";, "<p>hi</p><script>42;</script><p>bye</p>",
+        "http://foo/bar.html";,
+        "<p foo align=center>hi</p><script>42;</script><p>bye</p>",
         "text/html");

     Object result = json((String) requestGet(
@@ -162,8 +163,10 @@
     JSONObject json = (JSONObject) result;

     // Check html generation is correct
-    assertEquals("<p>hi<span id=\"id_2___\"></span></p><p>bye</p>",
-                 (String) json.get("html"));
+    assertEquals(
+        "<p align=\"center\" data-caja-foo=\"foo\">" +
+        "hi<span id=\"id_2___\"></span></p><p>bye</p>",
+        (String) json.get("html"));
     assertEquals("{"
         + "___.loadModule({"
         + "'instantiate':function(___,IMPORTS___){"
Index: src/com/google/caja/lang/html/HtmlSchema.java
===================================================================
--- src/com/google/caja/lang/html/HtmlSchema.java       (revision 4782)
+++ src/com/google/caja/lang/html/HtmlSchema.java       (working copy)
@@ -229,7 +229,9 @@

   public boolean isAttributeAllowed(AttribKey k) {
     HTML.Attribute a = lookupAttribute(k);
-    return a != null && allowedAttributes.contains(a.getKey());
+ if (a != null && allowedAttributes.contains(a.getKey())) { return true; }
+    String keyName = k.localName;
+    return keyName.startsWith("data-caja-") && !keyName.endsWith("___");
   }

   public HTML.Attribute lookupAttribute(AttribKey k) {
Index: src/com/google/caja/plugin/domado.js
===================================================================
--- src/com/google/caja/plugin/domado.js        (revision 4782)
+++ src/com/google/caja/plugin/domado.js        (working copy)
@@ -33,7 +33,7 @@
  * TODO(ihab.awad): Our implementation of getAttribute (and friends)
  * is such that standard DOM attributes which we disallow for security
  * reasons (like 'form:enctype') are placed in the "virtual" attributes
- * map (node._d_attributes). They appear to be settable and gettable,
+ * map (the data-caja-* namespace). They appear to be settable and gettable,
  * but their values are ignored and do not have the expected semantics
  * per the DOM API. This is because we do not have a column in
  * html4-defs.js stating that an attribute is valid but explicitly
@@ -3089,17 +3089,17 @@
           return np(this).feral.hasFocus();
         });
       }
+      var cajaPrefix = "data-caja-";
TameElement.prototype.getAttribute = nodeMethod(function (attribName) {
         var feral = np(this).feral;
         attribName = String(attribName).toLowerCase();
+        if (/__$/.test(attribName)) {
+          throw new TypeError('Attributes may not end with __');
+        }
         var tagName = feral.tagName.toLowerCase();
         var atype = getAttributeType(tagName, attribName);
         if (atype === void 0) {
-          // Unrecognized attribute; use virtual map
-          if (feral._d_attributes) {
-            return feral._d_attributes[attribName] || null;
-          }
-          return null;
+          return feral.getAttribute(cajaPrefix + attribName);
         }
         var value = bridal.getAttribute(feral, attribName);
         if ('string' !== typeof value) { return value; }
@@ -3118,11 +3118,7 @@
         var tagName = feral.tagName.toLowerCase();
         var atype = getAttributeType(tagName, attribName);
         if (atype === void 0) {
-          // Unrecognized attribute; use virtual map
-          return !!(
-              feral._d_attributes &&
-              Object.prototype.hasOwnProperty.call(
-                  feral._d_attributes, attribName));
+          return bridal.hasAttribute(feral, cajaPrefix + attribName);
         } else {
           return bridal.hasAttribute(feral, attribName);
         }
@@ -3133,12 +3129,13 @@
         var feral = np(this).feral;
         if (!np(this).editable) { throw new Error(NOT_EDITABLE); }
         attribName = String(attribName).toLowerCase();
+        if (/__$/.test(attribName)) {
+          throw new TypeError('Attributes may not end with __');
+        }
         var tagName = feral.tagName.toLowerCase();
         var atype = getAttributeType(tagName, attribName);
         if (atype === void 0) {
-          // Unrecognized attribute; use virtual map
-          if (!feral._d_attributes) { feral._d_attributes = {}; }
-          feral._d_attributes[attribName] = String(value);
+          bridal.setAttribute(feral, cajaPrefix + attribName, value);
         } else {
           var sanitizedValue = rewriteAttribute(
               tagName, attribName, atype, value);
@@ -3152,13 +3149,13 @@
         var feral = np(this).feral;
         if (!np(this).editable) { throw new Error(NOT_EDITABLE); }
         attribName = String(attribName).toLowerCase();
+        if (/__$/.test(attribName)) {
+          throw new TypeError('Attributes may not end with __');
+        }
         var tagName = feral.tagName.toLowerCase();
         var atype = getAttributeType(tagName, attribName);
         if (atype === void 0) {
-          // Unrecognized attribute; use virtual map
-          if (feral._d_attributes) {
-            delete feral._d_attributes[attribName];
-          }
+          feral.removeAttribute(cajaPrefix + attribName);
         } else {
           feral.removeAttribute(attribName);
         }
Index: src/com/google/caja/plugin/templates/TemplateSanitizer.java
===================================================================
--- src/com/google/caja/plugin/templates/TemplateSanitizer.java (revision 4782) +++ src/com/google/caja/plugin/templates/TemplateSanitizer.java (working copy)
@@ -143,12 +143,16 @@
     HTML.Attribute a = schema.lookupAttribute(attrKey);
     if (null == a) {
       if (!Placeholder.ID_ATTR.is(attrib)) {
-        if (!ignore) {
-          mq.getMessages().add(new Message(
-              PluginMessageType.UNKNOWN_ATTRIBUTE, MessageLevel.WARNING,
-              Nodes.getFilePositionFor(attrib), attrKey, elKey));
+        if (attrKey.localName.endsWith("___")) { valid = false; }
+        else if (!attrKey.localName.startsWith("data-caja-")) {
+          // Remove this attribute and create another that
+          // starts with "data-caja-".
+          removeBadAttribute(el, attrKey);
+          el.setAttributeNS(
+              attrKey.ns.uri,
+              "data-caja-" + attrKey.localName,
+              attrib.getValue());
         }
-        valid &= removeBadAttribute(el, attrKey);
       }
     } else if ("target".equals(attrKey.localName)) {
       if (!"_self".equals(attrib.getNodeValue())) {
Index: src/com/google/caja/plugin/templates/TemplateCompiler.java
===================================================================
--- src/com/google/caja/plugin/templates/TemplateCompiler.java (revision 4782) +++ src/com/google/caja/plugin/templates/TemplateCompiler.java (working copy)
@@ -21,6 +21,7 @@
 import com.google.caja.parser.html.AttribKey;
 import com.google.caja.parser.html.ElKey;
 import com.google.caja.parser.html.Nodes;
+import com.google.caja.parser.js.StringLiteral;
 import com.google.caja.parser.js.UncajoledModule;
 import com.google.caja.plugin.JobEnvelope;
 import com.google.caja.plugin.Placeholder;
@@ -41,6 +42,7 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.DocumentFragment;
 import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.w3c.dom.Text;

@@ -236,6 +238,17 @@
         }
       }
     }
+ // Iterate over all attributes on el and allow any starting with data-caja-
+    NamedNodeMap attrMap = el.getAttributes();
+    int attrLen = attrMap.getLength();
+    for (int i = 0; i < attrLen; ++i) {
+      Attr attr = (Attr) attrMap.item(i);
+      if (attr.getLocalName().startsWith("data-caja-")) {
+        scriptsPerNode.put(attr, new StringLiteral(
+            Nodes.getFilePositionForValue(attr),
+            attr.getValue()));
+      }
+    }
     scriptsPerNode.put(el, null);
   }



Reply via email to