Bug 971341 - Fix infinite tab loading due to missing characters in CSP's path regexes. r=sstamm, a=lsblakk
authorGarrett Robinson <grobinson@mozilla.com>
Wed, 02 Apr 2014 10:57:35 -0700
changeset 183647 fe5d67aa5366
parent 183646 51e5b0ec21b3
child 183648 ea5b3027bb42
push id3434
push userryanvm@gmail.com
push date2014-04-07 16:57 +0000
Treeherderresults
reviewerssstamm, lsblakk
bugs971341
milestone29.0
Bug 971341 - Fix infinite tab loading due to missing characters in CSP's path regexes. r=sstamm, a=lsblakk
content/base/src/CSPUtils.jsm
content/base/test/csp/test_csp_regexp_parsing.html
content/base/test/unit/test_csp_ignores_path.js
--- a/content/base/src/CSPUtils.jsm
+++ b/content/base/src/CSPUtils.jsm
@@ -49,32 +49,31 @@ const R_HOSTCHAR   = new RegExp ("[a-zA-
 
 // host            = "*" / [ "*." ] 1*host-char *( "." 1*host-char )
 const R_HOST       = new RegExp ("\\*|(((\\*\\.)?" + R_HOSTCHAR.source +
                               "+)" + "(\\." + R_HOSTCHAR.source + "+)*)", 'i');
 
 // port            = ":" ( 1*DIGIT / "*" )
 const R_PORT       = new RegExp ("(\\:([0-9]+|\\*))", 'i');
 
-// path
-const R_PATH       = new RegExp("(\\/(([a-zA-Z0-9\\-\\_]+)\\/?)*)", 'i');
+// host-source     = [ scheme "://" ] host [ port path file ]
+const R_HOSTSRC    = new RegExp ("^((" + R_SCHEME.source + "\\:\\/\\/)?("
+                                         + R_HOST.source + ")"
+                                         + R_PORT.source + "?)$", 'i');
 
-// file
-const R_FILE       = new RegExp("(\\/([a-zA-Z0-9\\-\\_]+)\\.([a-zA-Z]+))", 'i');
-
-// host-source     = [ scheme "://" ] host [ port path file ]
-const R_HOSTSRC    = new RegExp ("^((((" + R_SCHEME.source + "\\:\\/\\/)?("
-                                         + R_HOST.source + ")"
-                                         + R_PORT.source + "?)"
-                                         + R_PATH.source + "?)"
-                                         + R_FILE.source + "?)$", 'i');
+function STRIP_INPUTDELIM(re) {
+  return re.replace(/(^\^)|(\$$)/g, "");
+}
 
 // ext-host-source = host-source "/" *( <VCHAR except ";" and ","> )
 //                 ; ext-host-source is reserved for future use.
-const R_EXTHOSTSRC = new RegExp ("^" + R_HOSTSRC.source + "\\/[:print:]+$", 'i');
+const R_VCHAR_EXCEPT = new RegExp("[!-+--:<-~]"); // ranges exclude , and ;
+const R_EXTHOSTSRC   = new RegExp ("^" + STRIP_INPUTDELIM(R_HOSTSRC.source)
+                                       + "\\/"
+                                       + R_VCHAR_EXCEPT.source + "*$", 'i');
 
 // keyword-source  = "'self'" / "'unsafe-inline'" / "'unsafe-eval'"
 const R_KEYWORDSRC = new RegExp ("^('self'|'unsafe-inline'|'unsafe-eval')$", 'i');
 
 const R_BASE64     = new RegExp ("([a-zA-Z0-9+/]+={0,2})");
 
 // nonce-source      = "'nonce-" nonce-value "'"
 // nonce-value       = 1*( ALPHA / DIGIT / "+" / "/" )
@@ -85,16 +84,17 @@ const R_NONCESRC = new RegExp ("^'nonce-
 // hash-value        = 1*( ALPHA / DIGIT / "+" / "/" / "=" )
 // Each algo must be a valid argument to nsICryptoHash.init
 const R_HASH_ALGOS = new RegExp ("(sha256|sha384|sha512)");
 const R_HASHSRC    = new RegExp ("^'" + R_HASH_ALGOS.source + "-" + R_BASE64.source + "'$");
 
 // source-exp      = scheme-source / host-source / keyword-source
 const R_SOURCEEXP  = new RegExp (R_SCHEMESRC.source + "|" +
                                    R_HOSTSRC.source + "|" +
+                                R_EXTHOSTSRC.source + "|" +
                                 R_KEYWORDSRC.source + "|" +
                                   R_NONCESRC.source + "|" +
                                    R_HASHSRC.source,  'i');
 
 const R_QUOTELESS_KEYWORDS = new RegExp ("^(self|unsafe-inline|unsafe-eval|" +
                                          "inline-script|eval-script|none)$", 'i');
 
 this.CSPPrefObserver = {
@@ -1338,42 +1338,44 @@ CSPSource.fromString = function(aStr, aC
     // be '://' if there is a valid scheme in an (EXT)HOSTSRC
     if (!schemeMatch || aStr.indexOf("://") == -1) {
       sObj._scheme = self.scheme;
       schemeMatch = null;
     } else {
       sObj._scheme = schemeMatch[0];
     }
 
-    // get array of matches to the R_HOST regular expression
+    // Bug 916054: in CSP 1.0, source-expressions that are paths should have
+    // the path after the origin ignored and only the origin enforced.
+    if (R_EXTHOSTSRC.test(aStr)) {
+      var extHostMatch = R_EXTHOSTSRC.exec(aStr);
+      aStr = extHostMatch[1];
+    }
+
     var hostMatch = R_HOSTSRC.exec(aStr);
     if (!hostMatch) {
       cspError(aCSPRep, CSPLocalizer.getFormatStr("couldntParseInvalidSource",
                                                   [aStr]));
       return null;
     }
     // Host regex gets scheme, so remove scheme from aStr. Add 3 for '://'
-    if (schemeMatch)
+    if (schemeMatch) {
       hostMatch = R_HOSTSRC.exec(aStr.substring(schemeMatch[0].length + 3));
-
-    // Bug 916054: in CSP 1.0, source-expressions that are paths should have
-    // the path after the origin ignored and only the origin enforced.
-    hostMatch[0] = hostMatch[0].replace(R_FILE, "");
-    hostMatch[0] = hostMatch[0].replace(R_PATH, "");
+    }
 
     var portMatch = R_PORT.exec(hostMatch);
-
     // Host regex also gets port, so remove the port here.
-    if (portMatch)
+    if (portMatch) {
       hostMatch = R_HOSTSRC.exec(hostMatch[0].substring(0, hostMatch[0].length - portMatch[0].length));
+    }
 
     sObj._host = CSPHost.fromString(hostMatch[0]);
     if (!portMatch) {
       // gets the default port for the given scheme
-      defPort = Services.io.getProtocolHandler(sObj._scheme).defaultPort;
+      var defPort = Services.io.getProtocolHandler(sObj._scheme).defaultPort;
       if (!defPort) {
         cspError(aCSPRep,
                  CSPLocalizer.getFormatStr("couldntParseInvalidSource",
                                            [aStr]));
         return null;
       }
       sObj._port = defPort;
     }
@@ -1386,22 +1388,24 @@ CSPSource.fromString = function(aStr, aC
     if (R_QUOTELESS_KEYWORDS.test(aStr)) {
       cspWarn(aCSPRep, CSPLocalizer.getFormatStr("hostNameMightBeKeyword",
                                                  [aStr, aStr.toLowerCase()]));
     }
     return sObj;
   }
 
   // check for a nonce-source match
-  if (R_NONCESRC.test(aStr))
+  if (R_NONCESRC.test(aStr)) {
     return CSPNonceSource.fromString(aStr, aCSPRep);
+  }
 
   // check for a hash-source match
-  if (R_HASHSRC.test(aStr))
+  if (R_HASHSRC.test(aStr)) {
     return CSPHashSource.fromString(aStr, aCSPRep);
+  }
 
   // check for 'self' (case insensitive)
   if (aStr.toLowerCase() === "'self'") {
     if (!self) {
       cspError(aCSPRep, CSPLocalizer.getStr("selfKeywordNoSelfData"));
       return null;
     }
     sObj._self = self.clone();
--- a/content/base/test/csp/test_csp_regexp_parsing.html
+++ b/content/base/test/csp/test_csp_regexp_parsing.html
@@ -22,46 +22,45 @@ var policies = [
   ["allowed", "test1.example.com/"],
   ["allowed", "test1.example.com/path-1"],
   ["allowed", "test1.example.com/path-1/"],
   ["allowed", "test1.example.com/path-1/path_2/"],
   ["allowed", "test1.example.com/path-1/path_2/file.js"],
   ["allowed", "test1.example.com/path-1/path_2/file_1.js"],
   ["allowed", "test1.example.com/path-1/path_2/file-2.js"],
   ["allowed", "test1.example.com/path-1/path_2/f.js"],
+  ["allowed", "test1.example.com/path-1/path_2/f.oo.js"],
   ["allowed", "*.example.com"],
   ["allowed", "*.example.com/"],
   ["allowed", "*.example.com/path-1"],
   ["allowed", "*.example.com/path-1/"],
   ["allowed", "*.example.com/path-1/path_2/"],
   ["allowed", "*.example.com/path-1/path_2/file.js"],
   ["allowed", "*.example.com/path-1/path_2/file_1.js"],
   ["allowed", "*.example.com/path-1/path_2/file-2.js"],
   ["allowed", "*.example.com/path-1/path_2/f.js"],
+  ["allowed", "*.example.com/path-1/path_2/f.oo.js"],
   ["allowed", "test1.example.com:80"],
   ["allowed", "test1.example.com:80/"],
   ["allowed", "test1.example.com:80/path-1"],
   ["allowed", "test1.example.com:80/path-1/"],
   ["allowed", "test1.example.com:80/path-1/path_2"],
   ["allowed", "test1.example.com:80/path-1/path_2/"],
   ["allowed", "test1.example.com:80/path-1/path_2/file.js"],
+  ["allowed", "test1.example.com:80/path-1/path_2/f.ile.js"],
   ["allowed", "test1.example.com:*"],
   ["allowed", "test1.example.com:*/"],
   ["allowed", "test1.example.com:*/path-1"],
   ["allowed", "test1.example.com:*/path-1/"],
   ["allowed", "test1.example.com:*/path-1/path_2"],
   ["allowed", "test1.example.com:*/path-1/path_2/"],
   ["allowed", "test1.example.com:*/path-1/path_2/file.js"],
+  ["allowed", "test1.example.com:*/path-1/path_2/f.ile.js"],
   // the following tests should fail
-  ["blocked", "test1.example.com/path-1//path_2"],
-  ["blocked", "test1.example.com/path-1/file.js.cpp"],
   ["blocked", "test1.example.com:88path-1/"],
-  ["blocked", "test1.example.com:80//"],
-  ["blocked", "test1.example.com:80//path-1"],
-  ["blocked", "test1.example.com:80/.js"],
   ["blocked", "test1.example.com:80.js"],
   ["blocked", "test1.example.com:*.js"],
   ["blocked", "test1.example.com:*."]
 ]
 
 var counter = 0;
 var policy;
 
--- a/content/base/test/unit/test_csp_ignores_path.js
+++ b/content/base/test/unit/test_csp_ignores_path.js
@@ -7,27 +7,54 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import('resource://gre/modules/CSPUtils.jsm');
 
 var ioService = Cc["@mozilla.org/network/io-service;1"]
                   .getService(Ci.nsIIOService);
 var self = ioService.newURI("http://test1.example.com:80", null, null);
 
+function testValidSRCsHostSourceWithSchemeAndPath() {
+  var csps = [
+    "http://test1.example.com",
+    "http://test1.example.com/",
+    "http://test1.example.com/path-1",
+    "http://test1.example.com/path-1/",
+    "http://test1.example.com/path-1/path_2/",
+    "http://test1.example.com/path-1/path_2/file.js",
+    "http://test1.example.com/path-1/path_2/file_1.js",
+    "http://test1.example.com/path-1/path_2/file-2.js",
+    "http://test1.example.com/path-1/path_2/f.js",
+    "http://test1.example.com/path-1/path_2/f.oo.js"
+  ]
+
+  var obj;
+  var expected = "http://test1.example.com:80";
+  for (let i in csps) {
+    var src = csps[i];
+    obj = CSPSourceList.fromString(src, undefined, self);
+    dump("expected: " + expected + "\n");
+    dump("got:      " + obj._sources[0] + "\n");
+    do_check_eq(1, obj._sources.length);
+    do_check_eq(obj._sources[0], expected);
+  }
+}
+
 function testValidSRCsRegularHost() {
   var csps = [
     "test1.example.com",
     "test1.example.com/",
     "test1.example.com/path-1",
     "test1.example.com/path-1/",
     "test1.example.com/path-1/path_2/",
     "test1.example.com/path-1/path_2/file.js",
     "test1.example.com/path-1/path_2/file_1.js",
     "test1.example.com/path-1/path_2/file-2.js",
-    "test1.example.com/path-1/path_2/f.js"
+    "test1.example.com/path-1/path_2/f.js",
+    "test1.example.com/path-1/path_2/f.oo.js"
   ]
 
   var obj;
   var expected = "http://test1.example.com:80";
   for (let i in csps) {
     var src = csps[i];
     obj = CSPSourceList.fromString(src, undefined, self);
     do_check_eq(1, obj._sources.length);
@@ -41,16 +68,17 @@ function testValidSRCsWildCardHost() {
     "*.example.com/",
     "*.example.com/path-1",
     "*.example.com/path-1/",
     "*.example.com/path-1/path_2/",
     "*.example.com/path-1/path_2/file.js",
     "*.example.com/path-1/path_2/file_1.js",
     "*.example.com/path-1/path_2/file-2.js",
     "*.example.com/path-1/path_2/f.js",
+    "*.example.com/path-1/path_2/f.oo.js"
   ]
 
   var obj;
   var expected = "http://*.example.com:80";
   for (let i in csps) {
     var src = csps[i];
     obj = CSPSourceList.fromString(src, undefined, self);
     do_check_eq(1, obj._sources.length);
@@ -61,17 +89,18 @@ function testValidSRCsWildCardHost() {
 function testValidSRCsRegularPort() {
   var csps = [
     "test1.example.com:80",
     "test1.example.com:80/",
     "test1.example.com:80/path-1",
     "test1.example.com:80/path-1/",
     "test1.example.com:80/path-1/path_2",
     "test1.example.com:80/path-1/path_2/",
-    "test1.example.com:80/path-1/path_2/file.js"
+    "test1.example.com:80/path-1/path_2/file.js",
+    "test1.example.com:80/path-1/path_2/f.ile.js"
   ]
 
   var obj;
   var expected = "http://test1.example.com:80";
   for (let i in csps) {
     var src = csps[i];
     obj = CSPSourceList.fromString(src, undefined, self);
     do_check_eq(1, obj._sources.length);
@@ -82,52 +111,49 @@ function testValidSRCsRegularPort() {
 function testValidSRCsWildCardPort() {
   var csps = [
     "test1.example.com:*",
     "test1.example.com:*/",
     "test1.example.com:*/path-1",
     "test1.example.com:*/path-1/",
     "test1.example.com:*/path-1/path_2",
     "test1.example.com:*/path-1/path_2/",
-    "test1.example.com:*/path-1/path_2/file.js"
+    "test1.example.com:*/path-1/path_2/file.js",
+    "test1.example.com:*/path-1/path_2/f.ile.js"
   ]
 
   var obj;
   var expected = "http://test1.example.com:*";
   for (let i in csps) {
     var src = csps[i];
     obj = CSPSourceList.fromString(src, undefined, self);
     do_check_eq(1, obj._sources.length);
     do_check_eq(obj._sources[0], expected);
   }
 }
 
 
 function testInvalidSRCs() {
   var csps = [
-    "test1.example.com/path-1//path_2",
-    "test1.example.com/path-1/file.js.cpp",
     "test1.example.com:88path-1/",
-    "test1.example.com:80//",
-    "test1.example.com:80//path-1",
-    "test1.example.com:80/.js",
     "test1.example.com:80.js",
     "test1.example.com:*.js",
     "test1.example.com:*."
   ]
 
   var obj;
   var expected = [];
   for (let i in csps) {
     var src = csps[i];
     obj = CSPSourceList.fromString(src, undefined, self);
     do_check_eq(0, obj._sources.length);
   }
 }
 
 function run_test() {
+  testValidSRCsHostSourceWithSchemeAndPath();
   testValidSRCsRegularHost();
   testValidSRCsWildCardHost();
   testValidSRCsRegularPort();
   testValidSRCsWildCardPort();
   testInvalidSRCs();
   do_test_finished();
 }