Revision: 5460
Author:   [email protected]
Date:     Mon Jun 24 15:05:32 2013
Log:      Issue 1491: Add gradient support to CSS schema
https://codereview.appspot.com/10438046

From https://code.google.com/p/google-caja/issues/detail?id=1491

Run `sanitizeStylesheet(".test{background-image: linear-gradient(to bottom right, red, rgba(255,0,0,0));}", "prefix", function(u){return(u)})`

The expected output is ".prefix .test{background-image: linear-gradient(to bottom right, red, rgba(255,0,0,0));}"

However I receive ".prefix .test{}""

R=felixz

http://code.google.com/p/google-caja/source/detail?r=5460

Modified:
 /trunk/src/com/google/caja/lang/css/CssPropertyPatterns.java
 /trunk/src/com/google/caja/lang/css/css-extensions-defs.json
 /trunk/src/com/google/caja/lang/css/css21-defs.json
 /trunk/src/com/google/caja/plugin/sanitizecss.js
 /trunk/tests/com/google/caja/plugin/CssRewriterTest.java
 /trunk/tests/com/google/caja/plugin/sanitizecss_test.js

=======================================
--- /trunk/src/com/google/caja/lang/css/CssPropertyPatterns.java Wed Feb 13 14:19:54 2013 +++ /trunk/src/com/google/caja/lang/css/CssPropertyPatterns.java Mon Jun 24 15:05:32 2013
@@ -644,7 +644,8 @@
                 "<" + commonSymbol + ">");
       JSREBuilder p = inspector.symbolToPattern(false, sig);
       if (p != null) {
- String commonSubstring = withoutSpacesOrZero(p.p.optimize()).toString();
+        JSRE pOpt = withoutSpacesOrZero(p.p.optimize());
+        String commonSubstring = pOpt != null ? pOpt.toString() : "";
         if (commonSubstring.length() != 0) {
           commonSubstrings.add(commonSubstring);
         }
=======================================
--- /trunk/src/com/google/caja/lang/css/css-extensions-defs.json Wed May 22 11:36:57 2013 +++ /trunk/src/com/google/caja/lang/css/css-extensions-defs.json Mon Jun 24 15:05:32 2013
@@ -442,11 +442,6 @@
       "source": 
"http://www.w3.org/TR/2009/CR-css3-background-20091217/#ltbg-imagegt";
     },

-    { "key": "<image>",
-      "signature": "<uri>",
-      "source": 
"http://www.w3.org/TR/2009/CR-css3-background-20091217/#ltimagegt";
-    },
-
     { "key": "<bg-position>",
"signature": "[ [ [ <percentage> | <length> | left | center | right ] ] [ [ <percentage> | <length> | top | center | bottom ] ]? | [ [ center | [ left | right ] [ <percentage> | <length> ]? ] || [ center | [ top | bottom ] [ <percentage> | <length> ]? ] ] ]",
       "source": 
"http://www.w3.org/TR/2009/CR-css3-background-20091217/#ltbg-positiongt";
@@ -460,6 +455,76 @@
     { "key": "<attachment>",
       "signature": "scroll | fixed | local",
       "source": 
"http://www.w3.org/TR/2009/CR-css3-background-20091217/#ltattachmentgt";
-    }
+    },
+
+    { "key": "<image>",
+      "signature": "<uri> | <image-list> | <gradient>",
+      "source": "http://dev.w3.org/csswg/css-images-3/#image-values";
+    },
+
+    { "key": "<image-list>",
+ "signature": "image( [ <image-decl> , ]* [ <image-decl> | <color> ] )",
+      "source": "http://dev.w3.org/csswg/css-images-3/#image-notation";
+    },
+
+    { "key": "<image-decl>",
+      "signature": "<uri>",
+      "source": "http://dev.w3.org/csswg/css-images-3/#image-notation";
+    },
+
+    { "key": "<gradient>",
+ "signature": "[ <linear-gradient> | <radial-gradient> | <repeating-linear-gradient> | <repeating-radial-gradient> ]",
+      "source": "http://dev.w3.org/csswg/css-images-3/#linear-gradient-type";
+    },
+
+    { "key": "<linear-gradient>",
+ "signature": "linear-gradient ( [ [ <angle> | to <side-or-corner> ] ,]? <color-stop> [ , <color-stop>]+ )",
+      "source": "http://dev.w3.org/csswg/css-images-3/#linear-gradient-type";
+    },
+
+    { "key": "<radial-gradient>",
+ "signature": "radial-gradient ( [ [ <ending-shape> || <size> ] [ at <position> ]? , | at <position> , ]? <color-stop> [ , <color-stop> ]+ )",
+      "source": "http://dev.w3.org/csswg/css-images-3/#radial-gradients";
+    },
+
+    { "key": "<repeating-linear-gradient>",
+ "signature": "repeating-linear-gradient ( [ [ <angle> | to <side-or-corner> ] ,]? <color-stop>[, <color-stop>]+ )",
+      "source": "http://dev.w3.org/csswg/css-images-3/#repeating-gradients";
+    },
+
+    { "key": "<repeating-radial-gradient>",
+ "signature": "repeating-radial-gradient ( [ [ <ending-shape> || <size> ] [ at <position> ]? , | at <position> , ]? <color-stop> [ , <color-stop> ]+ )",
+      "source": "http://dev.w3.org/csswg/css-images-3/#repeating-gradients";
+    },
+
+    { "key": "<side-or-corner>",
+      "signature": "[left | right] || [top | bottom]",
+      "source": "http://dev.w3.org/csswg/css-images-3/#linear-gradient-type";
+    },
+
+    { "key": "<size>",
+ "signature": "[ closest-side | farthest-side | closest-corner | farthest-corner | <length> | [<length> | <percentage>] [<length> | <percentage>] ]",
+      "default": "farthest-corner",
+      "source": "http://dev.w3.org/csswg/css-images-3/#size";
+    },
+
+    { "key": "<color-stop>",
+      "signature": "<color> [ <percentage> | <length> ]?",
+      "source": "http://dev.w3.org/csswg/css-images-3/#color-stop-syntax";
+    },
+
+    { "key": "<ending-shape>",
+      "signature": "circle | ellipse",
+      "default": "circle",
+      "source": "http://dev.w3.org/csswg/css-images-3/#shape";
+    },
+
+    { "key": "<position>",
+      "default": "center",
+ "signature": "[ <percentage> | <length> | [[ left | right ] [ <percentage> | <length> ]? [ center | [ top | bottom ] [ <percentage> | <length> ]? ]? ] | [ [ top | bottom ] [ <percentage> | <length> ]? [ center | [ left | right ] [ <percentage> | <length> ]? ]? ] | [ center [ center | [ left | right | top | bottom ] [ <percentage> | <length> ]? ]? ] ]", + "comment": "the signature was derived from the source by duplicating and reducing to avoid the use of the && operator defined in http://www.w3.org/TR/CSS21/about.html#property-defs which is not supported by the CSS whitelist parser.",
+      "source": "http://dev.w3.org/csswg/css-backgrounds/#position";,
+    },
+
   ]
 }
=======================================
--- /trunk/src/com/google/caja/lang/css/css21-defs.json Tue Mar 20 17:18:52 2012 +++ /trunk/src/com/google/caja/lang/css/css21-defs.json Mon Jun 24 15:05:32 2013
@@ -34,7 +34,7 @@
       "dom2property": "backgroundColor" },

     { "key": "background-image",
-      "signature": "<uri> | none | inherit",
+      "signature": "<image> | none | inherit",
       "default": "none",
       "appliesTo": "*",
       "inherited": false,
@@ -407,7 +407,7 @@
       "dom2property": "lineHeight" },

     { "key": "list-style-image",
-      "signature": "<uri> | none | inherit",
+      "signature": "<image> | none | inherit",
       "default": "none",
       "appliesTo": { "include": ["display: list-item"] },
       "inherited": true,
@@ -906,6 +906,10 @@
                  " the keywords with the same names."]
     },

+    { "key": "<image>",
+      "see": 
"http://www.w3.org/TR/2009/CR-css3-background-20091217/#ltimagegt";,
+      "signature": "<uri>" },
+
     { "key": "<relative-size>",
       "see": "http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size";,
       "signature": "smaller|larger" },
=======================================
--- /trunk/src/com/google/caja/plugin/sanitizecss.js Sat May 11 15:02:28 2013 +++ /trunk/src/com/google/caja/plugin/sanitizecss.js Mon Jun 24 15:05:32 2013
@@ -139,11 +139,26 @@
     function normalizeFunctionCall(tokens, start) {
       var parenDepth = 1, end = start + 1, n = tokens.length;
       while (end < n && parenDepth) {
-        // TODO: Can URLs appear in functions?
         var token = tokens[end++];
-        parenDepth += (token === '(' ? 1 : token === ')' ? -1 : 0);
+        // Decrement if we see a close parenthesis, and increment if we
+        // see a function.  Since url(...) are whole tokens, they will not
+        // affect the token scanning.
+        parenDepth += (token === ')' ? -1 : /^[^"']*\($/.test(token));
       }
-      return end;
+ // Allow error-recovery from unclosed functions by ignoring the call and
+      // so allowing resumption at the next ';'.
+      return parenDepth ? start+1 : end;
+    }
+
+    /** Put spaces between tokens, but don't duplicate existing spaces. */
+    function respace(tokens) {
+      var i = 0, j = 0, n = tokens.length, tok;
+      while (i < n) {
+        tok = tokens[i++];
+        if (tok !== ' ') { tokens[j++] = tok; }
+      }
+      tokens.length = j;
+      return tokens.join(' ');
     }

     // Used as map value to avoid hasOwnProperty checks.
@@ -241,7 +256,7 @@
// splices tokens to where i now is the index of the whole call:
                 //   ['x', ' ', 'rgb( 255 , 0 , 0 )', ' ', 'y']
                 tokens.splice(i, end - i,
-                              token = tokens.slice(i, end).join(' '))),
+                              token = respace(tokens.slice(i, end)))),
             litGroup = propertySchema.cssLitGroup,
             litMap = (litGroup
                       ? (propertySchema.cssLitMap
@@ -303,8 +318,8 @@
     var HISTORY_NON_SENSITIVE_PSEUDO_SELECTOR_WHITELIST =
       /^(active|after|before|first-child|first-letter|focus|hover)$/;

- // TODO: This should be removed now as modern browsers no longer require this
-    // special handling
+ // TODO: This should be removed now as modern browsers no longer require
+    // this special handling
     var HISTORY_SENSITIVE_PSEUDO_SELECTOR_WHITELIST =
       /^(link|visited)$/;

@@ -532,15 +547,15 @@
       // but supported by Chrome
       var url3 = /^\s*url\s*[(]([^)]*)[)]\s*$/;
       var match;
-      if (match = string1.exec(candidate)) {
+      if ((match = string1.exec(candidate))) {
         return match[1];
-      } else if (match = string2.exec(candidate)) {
+      } else if ((match = string2.exec(candidate))) {
         return match[1];
-      } else if (match = url1.exec(candidate)) {
+      } else if ((match = url1.exec(candidate))) {
         return match[1];
-      } else if (match = url2.exec(candidate)) {
+      } else if ((match = url2.exec(candidate))) {
         return match[1];
-      } else if (match = url3.exec(candidate)) {
+      } else if ((match = url3.exec(candidate))) {
         return match[1];
       }
       return null;
@@ -562,10 +577,11 @@
      *    file in sanitized CSS.
      * @param {function(string, Array.<string>): ?Array.<string>} tagPolicy
      *     As in html-sanitizer, used for rewriting element names.
- * @param {undefined|function(string, boolean)} continuation callback from external - * css urls. The callback is called with a string, the CSS contents - * and a boolean, which is true if the external url itself contained
-     *     other external urls.
+ * @param {undefined|function(string, boolean)} continuation callback from
+     *     external CSS URLs.
+ * The callback is called with a string, the CSS contents and a boolean, + * which is true if the external url itself contained other external
+     *     URLs.
      */
     function sanitizeStylesheetInternal(baseUri, cssText, suffix,
       naiveUriRewriter, naiveUriFetcher, tagPolicy,
=======================================
--- /trunk/tests/com/google/caja/plugin/CssRewriterTest.java Tue Jan 22 20:45:33 2013 +++ /trunk/tests/com/google/caja/plugin/CssRewriterTest.java Mon Jun 24 15:05:32 2013
@@ -181,10 +181,42 @@
   }

   public final void testGradients() throws Exception {
-    runTest("p { background-image:gradient(" +
-        "linear, left top, left bottom)}",
-        "", false);
-       assertNoErrors();
+ runTest("p { background-image:gradient(linear, left top, left bottom)}",
+            "", false);
+    assertNoErrors();
+
+    String pre = ".namespace__ p {\n  ";
+
+    // A gradient on 45deg axis starting blue and finishing red
+    runTest(
+        "p {   background-image: linear-gradient(45deg, blue, red) }",
+        pre + "background-image: linear-gradient(45deg, blue, red)\n}");
+    assertNoErrors();
+
+ // A gradient going from the bottom right to the top left starting blue and
+    // finishing red
+    runTest(
+ "p { background-image:linear-gradient( to left top, blue, red); }", + pre + "background-image: linear-gradient(to left top, blue, red)\n}");
+    assertNoErrors();
+
+    // A gradient going from the bottom to top, starting blue, being green
+    // after 40%  and finishing red
+    runTest(
+ "p { background-image:linear-gradient( 0deg, blue, green 40%, red ); }", + pre + "background-image: linear-gradient(0deg, blue, green 40%, red)\n}"
+        );
+
+    runTest(
+       ""
+       + "li {"
+       + "  list-style-image:repeating-radial-gradient("
+       + "    circle closest-side at 20px 30px,"
+       + "    red, yellow, green 100%, yellow 150%, red 200%"
+       + "  );"
+       + "}",
+       "");
+
   }

   public final void testSubstitutions() throws Exception {
@@ -254,7 +286,7 @@

public final void testUrisCalledWithProperPropertyPart() throws Exception {
     // The CssRewriter needs to rewrite URIs.
-    // When it does so it passes
+    // When it does so it passes a context string.
     assertCallsUriRewriterWithPropertyPart(
         "background: 'foo.png'",
         "background::bg-image::image");
=======================================
--- /trunk/tests/com/google/caja/plugin/sanitizecss_test.js Wed Mar 20 10:43:44 2013 +++ /trunk/tests/com/google/caja/plugin/sanitizecss_test.js Mon Jun 24 15:05:32 2013
@@ -255,3 +255,14 @@
   assertSelector("#foo:bogus", "sfx", [".sfx #foo-sfx", []]);
   jsunit.pass();
 });
+
+jsunitRegister('testGradients',
+               function testGradients() {
+  var source = 'linear-gradient(to bottom right, red, rgb(255,0,0))';
+ var expect = 'linear-gradient( to bottom right , red , rgb( 255 , 0 , 0 ) )';
+  var tokens = lexCss(source);
+  sanitizeCssProperty(
+      'background-image', cssSchema['background-image'], tokens);
+  assertEquals(expect, tokens.join(''));
+  jsunit.pass();
+});

--

--- You received this message because you are subscribed to the Google Groups "Google Caja Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to