Revision: 5468
Author:   ihab.awad
Date:     Tue Jul  2 11:57:39 2013
Log:      Support literal Unicode in string literals in ES5
https://codereview.appspot.com/10205043

Test incoming guest source for embedded Unicode characters. If it does
have these, we force a re-rendering from a parse tree, which turns any
such characters inside string literals into escaped strings. If any such
characters remain, we go ahead and fail as before

[email protected],[email protected]

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

Modified:
 /trunk/src/com/google/caja/ses/atLeastFreeVarNames.js
 /trunk/src/com/google/caja/ses/hookupSES.js
 /trunk/src/com/google/caja/ses/hookupSESPlus.js
 /trunk/src/com/google/caja/ses/mitigateGotchas.js
 /trunk/src/com/google/caja/ses/startSES.js
 /trunk/tests/com/google/caja/plugin/es53-test-cajajs-invocation.js

=======================================
--- /trunk/src/com/google/caja/ses/atLeastFreeVarNames.js Wed Mar 6 20:18:30 2013 +++ /trunk/src/com/google/caja/ses/atLeastFreeVarNames.js Tue Jul 2 11:57:39 2013
@@ -21,6 +21,7 @@
  * anticipated ES6.
  *
  * // provides ses.atLeastFreeVarNames
+ * // provides ses.limitSrcCharset
  * @author Mark S. Miller
  * @requires StringMap
  * @overrides ses, atLeastFreeVarNamesModule
@@ -96,21 +97,20 @@
    * We use this to limit the input text to ascii only text.  All other
    * characters are encoded using backslash-u escapes.
    */
-  function LIMIT_SRC(programSrc) {
+  ses.limitSrcCharset = function(programSrc) {
     if (OTHER_WHITESPACE.test(programSrc)) {
-      throw new EvalError(
-        'Disallowing unusual unicode whitespace characters');
+ return { error: 'Disallowing unusual unicode whitespace characters' };
     }
     programSrc = programSrc.replace(/([\u0080-\u009f\u00a1-\uffff])/g,
       function(_, u) {
         return '\\u' + ('0000' + u.charCodeAt(0).toString(16)).slice(-4);
       });
-    return programSrc;
+    return { programSrc: programSrc };
   }

   /**
    * Return a regexp that can be used repeatedly to scan for the next
-   * identifier. It works correctly in concert with LIMIT_SRC above.
+ * identifier. It works correctly in concert with ses.limitSrcCharset above.
    *
    * If this regexp is changed compileExprLater.js should be checked for
    * correct escaping of freeNames.
@@ -125,7 +125,12 @@

   ses.atLeastFreeVarNames = function atLeastFreeVarNames(programSrc) {
     programSrc = ''+programSrc;
-    programSrc = LIMIT_SRC(programSrc);
+    var result = ses.limitSrcCharset(programSrc);
+    if (!('programSrc' in result)) {
+      throw new EvalError(result.error);
+    } else {
+      programSrc = result.programSrc;
+    }
     // Now that we've temporarily limited our attention to ascii...
     var regexp = SHOULD_MATCH_IDENTIFIER();
     // Once we decide this file can depends on ES5, the following line
=======================================
--- /trunk/src/com/google/caja/ses/hookupSES.js Thu Feb 14 14:31:30 2013
+++ /trunk/src/com/google/caja/ses/hookupSES.js Tue Jul  2 11:57:39 2013
@@ -34,6 +34,7 @@

     ses.startSES(global,
                  ses.whitelist,
+                 ses.limitSrcCharset,
                  ses.atLeastFreeVarNames,
                  function () { return {}; });
   } catch (err) {
=======================================
--- /trunk/src/com/google/caja/ses/hookupSESPlus.js     Thu Feb 14 14:31:30 2013
+++ /trunk/src/com/google/caja/ses/hookupSESPlus.js     Tue Jul  2 11:57:39 2013
@@ -34,6 +34,7 @@

     ses.startSES(global,
                  ses.whitelist,
+                 ses.limitSrcCharset,
                  ses.atLeastFreeVarNames,
                  ses.ejectorsGuardsTrademarks);
   } catch (err) {
=======================================
--- /trunk/src/com/google/caja/ses/mitigateGotchas.js Wed Jun 5 20:35:14 2013 +++ /trunk/src/com/google/caja/ses/mitigateGotchas.js Tue Jul 2 11:57:39 2013
@@ -219,43 +219,42 @@
       var path = [];
       var scopeLevel = 0;
       var ast = ses.rewriter_.parse(programSrc);
-      if (!needsRewriting(options)) {
-        return programSrc;
-      }
-      ses.rewriter_.traverse(ast, {
-        enter: function enter(node) {
-            var parent = path[path.length - 1];
-            path.push(node);
+      if (needsRewriting(options)) {
+        ses.rewriter_.traverse(ast, {
+          enter: function enter(node) {
+              var parent = path[path.length - 1];
+              path.push(node);

-            if (options.rewriteTopLevelFuncs &&
-                isFunctionDecl(node) && scopeLevel === 0) {
-              rewriteFuncDecl(node, parent);
-              dirty = true;
-            } else if (options.rewriteTypeOf &&
-                isTypeOf(node) && isId(node.argument)) {
-              rewriteTypeOf(node);
-              dirty = true;
-            } else if (options.rewriteTopLevelVars &&
-                       isVariableDecl(node) && scopeLevel === 0) {
-              rewriteVars(node, parent);
-              dirty = true;
-            }
+              if (options.rewriteTopLevelFuncs &&
+                  isFunctionDecl(node) && scopeLevel === 0) {
+                rewriteFuncDecl(node, parent);
+                dirty = true;
+              } else if (options.rewriteTypeOf &&
+                  isTypeOf(node) && isId(node.argument)) {
+                rewriteTypeOf(node);
+                dirty = true;
+              } else if (options.rewriteTopLevelVars &&
+                         isVariableDecl(node) && scopeLevel === 0) {
+                rewriteVars(node, parent);
+                dirty = true;
+              }

-            if (introducesVarScope(node)) {
-              scopeLevel++;
-            }
-        },
-        leave: function leave(node) {
-            var last = path.pop();
-            if (node !== last) {
-              throw new Error('Internal error traversing the AST');
-            }
-            if (introducesVarScope(node)) {
-              scopeLevel--;
-            }
-        }
-      });
-      if (dirty) {
+              if (introducesVarScope(node)) {
+                scopeLevel++;
+              }
+          },
+          leave: function leave(node) {
+              var last = path.pop();
+              if (node !== last) {
+                throw new Error('Internal error traversing the AST');
+              }
+              if (introducesVarScope(node)) {
+                scopeLevel--;
+              }
+          }
+        });
+      }
+      if (dirty || options.forceParseAndRender) {
         return "\n"
             + "/*\n"
             + " * Program rewritten to mitigate differences between\n"
=======================================
--- /trunk/src/com/google/caja/ses/startSES.js  Wed Jun  5 20:35:14 2013
+++ /trunk/src/com/google/caja/ses/startSES.js  Tue Jul  2 11:57:39 2013
@@ -176,6 +176,11 @@
  *        skip this last freezing step. With confined-ES5, each frame
  *        is considered a separate protection domain rather that each
  *        individual object.
+ * @param limitSrcCharset ::F([string])
+ * Given the sourceText for a strict Program, return a record with an
+ *        'error' field if it is not in the limited character set that SES
+ * should process; otherwise, return a record with a 'programSrc' field
+ *        containing the original program text with Unicode escapes.
  * @param atLeastFreeVarNames ::F([string], Record(true))
  *        Given the sourceText for a strict Program,
  *        atLeastFreeVarNames(sourceText) returns a Record whose
@@ -203,6 +208,7 @@
  */
 ses.startSES = function(global,
                         whitelist,
+                        limitSrcCharset,
                         atLeastFreeVarNames,
                         extensions) {
   "use strict";
@@ -292,6 +298,7 @@
       options.rewriteTopLevelFuncs = true;
       options.rewriteFunctionCalls = true;
       options.rewriteTypeOf = false;
+      options.forceParseAndRender = false;
     } else {
       options.maskReferenceError = resolve('maskReferenceError', true);

@@ -308,6 +315,7 @@
       options.rewriteFunctionCalls = resolve('rewriteFunctionCalls', true);
       options.rewriteTypeOf = resolve('rewriteTypeOf',
                                       !options.maskReferenceError);
+      options.forceParseAndRender = resolve('forceParseAndRender', false);
     }
     return options;
   }
@@ -923,6 +931,9 @@
// Note the EOL after modSrc to prevent trailing line comment in modSrc
       // eliding the rest of the wrapper.
       var options = resolveOptions(opt_mitigateOpts);
+      if (!('programSrc' in limitSrcCharset(modSrc))) {
+        options.forceParseAndRender = true;
+      }
       var exprSrc =
           '(function() {' +
           mitigateSrcGotchas(modSrc, options) +
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-cajajs-invocation.js Fri Jun 21 14:57:00 2013 +++ /trunk/tests/com/google/caja/plugin/es53-test-cajajs-invocation.js Tue Jul 2 11:57:39 2013
@@ -219,8 +219,10 @@
     });
   });

-  if (inES5Mode)
-  jsunitRegister('testBuilderApiXhr', function testBuilderApiXhr() {
+  jsunitRegisterIf(
+      inES5Mode,
+      'testBuilderApiXhr',
+      function testBuilderApiXhr() {
     var div = createDiv();
     caja.load(div, xhrUriPolicy, function (frame) {
       frame.code('es53-test-guest.html', 'text/html')
@@ -231,6 +233,54 @@
     });
   });

+  jsunitRegisterIf(
+      inES5Mode,
+      'testUnicodeInCodeFails',
+      function testUnicodeInCodeFails() {
+    var code =
+      '<script> var x = 42; </script>' +
+      '<script> x++; var te\ufeffst = 1; </script>' + // should not run
+      '<script> record(x); </script>';
+    var xValue = 0;
+    var div = createDiv();
+    caja.load(div, xhrUriPolicy, jsunitCallback(function(frame) {
+      frame.code('http://example.com/', 'text/html', code)
+           .api({
+             record: frame.tame(frame.markFunction(function(x) {
+               xValue = x;
+             }))
+           })
+           .run(jsunitCallback(function(result) {
+             assertEquals(42, xValue);
+             jsunitPass('testUnicodeInCodeFails');
+           }));
+    }));
+  });
+
+  jsunitRegisterIf(
+      inES5Mode,
+      'testUnicodeInStringOk',
+      function testUnicodeInStringOk() {
+    var code =
+      '<script> var x = 42; </script>' +
+      '<script> x = \'te\ufeffst\'; </script>' + // should run
+      '<script> record(x); </script>';
+    var xValue = 0;
+    var div = createDiv();
+    caja.load(div, xhrUriPolicy, jsunitCallback(function(frame) {
+      frame.code('http://example.com/', 'text/html', code)
+           .api({
+             record: frame.tame(frame.markFunction(function(x) {
+               xValue = x;
+             }))
+           })
+           .run(jsunitCallback(function(result) {
+             assertEquals('te\ufeffst', xValue);
+             jsunitPass('testUnicodeInStringOk');
+           }));
+    }));
+  });
+
jsunitRegister('testBuilderApiJsNoDom', function testBuilderApiJsNoDom() {
     caja.load(undefined, uriPolicy, function (frame) {
       var extraImports = { x: 4, y: 3 };

--

--- 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