Bug 784315 - fix CSP parser to handle single-token hosts via regex correctly. r=geekboy
authorMarshall Moutenot <mmoutenot@mozilla.com>
Mon, 27 Aug 2012 08:51:02 -0700
changeset 105607 5184abd63490cdd4dc3cee35de666cb324efa86a
parent 105606 f0c8020ccf949836456fb23aa46feb5887019f27
child 105608 c47ec3f2e7771182cd7d34d9501047d64d9e3f30
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersgeekboy
bugs784315
milestone17.0a1
Bug 784315 - fix CSP parser to handle single-token hosts via regex correctly. r=geekboy
content/base/src/CSPUtils.jsm
content/base/test/unit/test_csputils.js
--- a/content/base/src/CSPUtils.jsm
+++ b/content/base/src/CSPUtils.jsm
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * Content Security Policy Utilities
- * 
+ *
  * Overview
  * This contains a set of classes and utilities for CSP.  It is in this
  * separate file for testing purposes.
  */
 
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -40,17 +40,17 @@ const R_GETSCHEME  = new RegExp ("^" + R
 // scheme-source   = scheme ":"
 const R_SCHEMESRC  = new RegExp ("^" + R_SCHEME.source + "\\:$", 'i');
 
 // host-char       = ALPHA / DIGIT / "-"
 const R_HOSTCHAR   = new RegExp ("[a-zA-Z0-9\\-]", 'i');
 
 // host            = "*" / [ "*." ] 1*host-char *( "." 1*host-char )
 const R_HOST       = new RegExp ("\\*|(((\\*\\.)?" + R_HOSTCHAR.source +
-                                      "+)(\\." + R_HOSTCHAR.source +"+)+)",'i');
+                                      "+)(\\." + R_HOSTCHAR.source +"+)*)",'i');
 // port            = ":" ( 1*DIGIT / "*" )
 const R_PORT       = new RegExp ("(\\:([0-9]+|\\*))", 'i');
 
 // host-source     = [ scheme "://" ] host [ port ]
 const R_HOSTSRC    = new RegExp ("^((" + R_SCHEME.source + "\\:\\/\\/)?("
                                        +   R_HOST.source + ")"
                                        +   R_PORT.source + "?)$", 'i');
 
@@ -178,17 +178,17 @@ CSPPolicyURIListener.prototype = {
       this._csp.refinePolicy("allow 'none'", this._docURI, this._docRequest);
       this._csp.refinePolicy("default-src 'none'", this._docURI, this._docRequest);
     }
     // resume the parent document request
     this._docRequest.resume();
   }
 };
 
-//:::::::::::::::::::::::: CLASSES ::::::::::::::::::::::::::// 
+//:::::::::::::::::::::::: CLASSES :::::::::::::::::::::::::://
 
 /**
  * Class that represents a parsed policy structure.
  */
 function CSPRep() {
   // this gets set to true when the policy is done parsing, or when a
   // URI-borne policy has finished loading.
   this._isInitialized = false;
@@ -474,17 +474,17 @@ CSPRep.prototype = {
     }
     return dirs.join("; ");
   },
 
   /**
    * Determines if this policy accepts a URI.
    * @param aContext
    *        one of the SRC_DIRECTIVES defined above
-   * @returns 
+   * @returns
    *        true if the policy permits the URI in given context.
    */
   permits:
   function csp_permits(aURI, aContext) {
     if (!aURI) return false;
 
     // GLOBALLY ALLOW "about:" SCHEME
     if (aURI instanceof String && aURI.substring(0,6) === "about:")
@@ -497,17 +497,17 @@ CSPRep.prototype = {
       if (CSPRep.SRC_DIRECTIVES[i] === aContext) {
         return this._directives[aContext].permits(aURI);
       }
     }
     return false;
   },
 
   /**
-   * Intersects with another CSPRep, deciding the subset policy 
+   * Intersects with another CSPRep, deciding the subset policy
    * that should be enforced, and returning a new instance.
    * @param aCSPRep
    *        a CSPRep instance to use as "other" CSP
    * @returns
    *        a new CSPRep instance of the intersection
    */
   intersectWith:
   function cspsd_intersectWith(aCSPRep) {
@@ -534,17 +534,17 @@ CSPRep.prototype = {
     else if (aCSPRep._directives[reportURIDir]) {
       // blank concat makes a copy of the string.
       newRep._directives[reportURIDir] = aCSPRep._directives[reportURIDir].concat();
     }
 
     newRep._allowEval =          this.allowsEvalInScripts
                            && aCSPRep.allowsEvalInScripts;
 
-    newRep._allowInlineScripts = this.allowsInlineScripts 
+    newRep._allowInlineScripts = this.allowsInlineScripts
                            && aCSPRep.allowsInlineScripts;
 
     return newRep;
   },
 
   /**
    * Copies default source list to each unspecified directive.
    * @returns
@@ -590,17 +590,17 @@ CSPRep.prototype = {
    */
   get allowsInlineScripts () {
     return this._allowInlineScripts;
   },
 };
 
 //////////////////////////////////////////////////////////////////////
 /**
- * Class to represent a list of sources 
+ * Class to represent a list of sources
  */
 function CSPSourceList() {
   this._sources = [];
   this._permitAllSources = false;
 
   // Set to true when this list is created using "makeExplicit()"
   // It's useful to know this when reporting the directive that was violated.
   this._isImplicit = false;
@@ -612,17 +612,17 @@ function CSPSourceList() {
  * @param aStr
  *        string rep of a CSP Source List
  * @param self (optional)
  *        URI or CSPSource representing the "self" source
  * @param enforceSelfChecks (optional)
  *        if present, and "true", will check to be sure "self" has the
  *        appropriate values to inherit when they are omitted from the source.
  * @returns
- *        an instance of CSPSourceList 
+ *        an instance of CSPSourceList
  */
 CSPSourceList.fromString = function(aStr, self, enforceSelfChecks) {
   // source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ]
   //             / *WSP "'none'" *WSP
 
   /* If self parameter is passed, convert to CSPSource,
      unless it is already a CSPSource. */
   if(self && !(self instanceof CSPSource)) {
@@ -663,17 +663,17 @@ CSPSourceList.fromString = function(aStr
 };
 
 CSPSourceList.prototype = {
   /**
    * Compares one CSPSourceList to another.
    *
    * @param that
    *        another CSPSourceList
-   * @returns 
+   * @returns
    *        true if they have the same data
    */
   equals:
   function(that) {
     // special case to default-src * and 'none' to look different
     // (both have a ._sources.length of 0).
     if (that._permitAllSources != this._permitAllSources) {
       return false;
@@ -760,17 +760,17 @@ CSPSourceList.prototype = {
       }
     }
     return false;
   },
 
   /**
    * Intersects with another CSPSourceList, deciding the subset directive
    * that should be enforced, and returning a new instance.
-   * @param that 
+   * @param that
    *        the other CSPSourceList to intersect "this" with
    * @returns
    *        a new instance of a CSPSourceList representing the intersection
    */
   intersectWith:
   function cspsd_intersectWith(that) {
 
     var newCSPSrcList = null;
@@ -836,20 +836,20 @@ function CSPSource() {
 }
 
 /**
  * General factory method to create a new source from one of the following
  * types:
  *  - nsURI
  *  - string
  *  - CSPSource (clone)
- * @param aData 
+ * @param aData
  *        string, nsURI, or CSPSource
  * @param self (optional)
- *	  if present, string, URI, or CSPSource representing the "self" resource 
+ *	  if present, string, URI, or CSPSource representing the "self" resource
  * @param enforceSelfChecks (optional)
  *	  if present, and "true", will check to be sure "self" has the
  *        appropriate values to inherit when they are omitted from the source.
  * @returns
  *        an instance of CSPSource
  */
 CSPSource.create = function(aData, self, enforceSelfChecks) {
   if (typeof aData === 'string')
@@ -875,17 +875,17 @@ CSPSource.create = function(aData, self,
  * @param aURI
  *        nsIURI rep of a URI
  * @param self (optional)
  *        string or CSPSource representing the "self" source
  * @param enforceSelfChecks (optional)
  *        if present, and "true", will check to be sure "self" has the
  *        appropriate values to inherit when they are omitted from aURI.
  * @returns
- *        an instance of CSPSource 
+ *        an instance of CSPSource
  */
 CSPSource.fromURI = function(aURI, self, enforceSelfChecks) {
   if (!(aURI instanceof Components.interfaces.nsIURI)){
     CSPError(CSPLocalizer.getStr("cspSourceNotURI"));
     return null;
   }
 
   if (!self && enforceSelfChecks) {
@@ -918,18 +918,18 @@ CSPSource.fromURI = function(aURI, self,
     // (NS_ERROR_FAILURE)
     sObj._host = CSPHost.fromString(aURI.host);
   } catch(e) {
     sObj._host = undefined;
   }
 
   // grab port (if there is one)
   // creating a source from an nsURI is limited in that one cannot specify "*"
-  // for port.  In fact, there's no way to represent "*" differently than 
-  // a blank port in an nsURI, since "*" turns into -1, and so does an 
+  // for port.  In fact, there's no way to represent "*" differently than
+  // a blank port in an nsURI, since "*" turns into -1, and so does an
   // absence of port declaration.
 
   // port is never inherited from self -- this gets too confusing.
   // Instead, whatever scheme is used (an explicit one or the inherited
   // one) dictates the port if no port is explicitly stated.
   // Set it to undefined here and the default port will be resolved in the
   // getter for .port.
   sObj._port = undefined;
@@ -952,17 +952,17 @@ CSPSource.fromURI = function(aURI, self,
  * @param aStr
  *        string rep of a CSP Source
  * @param self (optional)
  *        string, URI, or CSPSource representing the "self" source
  * @param enforceSelfChecks (optional)
  *        if present, and "true", will check to be sure "self" has the
  *        appropriate values to inherit when they are omitted from aURI.
  * @returns
- *        an instance of CSPSource 
+ *        an instance of CSPSource
  */
 CSPSource.fromString = function(aStr, self, enforceSelfChecks) {
   if (!aStr)
     return null;
 
   if (!(typeof aStr === 'string')) {
     CSPError(CSPLocalizer.getStr("argumentIsNotString"));
     return null;
@@ -991,29 +991,36 @@ CSPSource.fromString = function(aStr, se
     var schemeSrcMatch = R_GETSCHEME.exec(aStr);
     sObj._scheme = schemeSrcMatch[0];
     if (!sObj._host) sObj._host = CSPHost.fromString("*");
     if (!sObj._port) sObj._port = "*";
     return sObj;
   }
 
   // check for host-source or ext-host-source match
-  if (R_HOSTSRC.test(aStr) || R_EXTHOSTSRC.test(aStr)){
+  if (R_HOSTSRC.test(aStr) || R_EXTHOSTSRC.test(aStr)) {
     var schemeMatch = R_GETSCHEME.exec(aStr);
-    if (!schemeMatch)
+    // check that the scheme isn't accidentally matching the host. There should
+    // be '://' if there is a valid scheme in an (EXT)HOSTSRC
+    if (!schemeMatch || aStr.indexOf("://") == -1) {
       sObj._scheme = self.scheme;
-    else {
+      schemeMatch = null;
+    } else {
       sObj._scheme = schemeMatch[0];
     }
 
+    // get array of matches to the R_HOST regular expression
     var hostMatch = R_HOST.exec(aStr);
-    if (!hostMatch) {
+    if (!hostMatch){
       CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidSource", [aStr]));
       return null;
     }
+    // host regex gets scheme, so remove scheme from aStr. Add 3 for '://'
+    if (schemeMatch)
+      hostMatch = R_HOST.exec(aStr.substring(schemeMatch[0].length + 3));
     sObj._host = CSPHost.fromString(hostMatch[0]);
     var portMatch = R_PORT.exec(aStr);
     if (!portMatch) {
       // gets the default port for the given scheme
       defPort = Services.io.getProtocolHandler(sObj._scheme).defaultPort;
       if (!defPort) {
         CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidSource", [aStr]));
         return null;
@@ -1041,17 +1048,17 @@ CSPSource.fromString = function(aStr, se
   return null;
 };
 
 CSPSource.validSchemeName = function(aStr) {
   // <scheme-name>       ::= <alpha><scheme-suffix>
   // <scheme-suffix>     ::= <scheme-chr>
   //                      | <scheme-suffix><scheme-chr>
   // <scheme-chr>        ::= <letter> | <digit> | "+" | "." | "-"
-  
+
   return aStr.match(/^[a-zA-Z][a-zA-Z0-9+.-]*$/);
 };
 
 CSPSource.prototype = {
 
   get scheme () {
     if (this._isSelf && this._self)
       return this._self.scheme;
@@ -1096,17 +1103,17 @@ CSPSource.prototype = {
     return undefined;
   },
 
   /**
    * Generates canonical string representation of the Source.
    */
   toString:
   function() {
-    if (this._isSelf) 
+    if (this._isSelf)
       return this._self.toString();
 
     var s = "";
     if (this.scheme)
       s = s + this.scheme + "://";
     if (this._host)
       s = s + this._host;
     if (this.port)
@@ -1144,49 +1151,49 @@ CSPSource.prototype = {
     if (!(aSource instanceof CSPSource))
       return this.permits(CSPSource.create(aSource));
 
     // verify scheme
     if (this.scheme != aSource.scheme)
       return false;
 
     // port is defined in 'this' (undefined means it may not be relevant
-    // to the scheme) AND this port (implicit or explicit) matches 
+    // to the scheme) AND this port (implicit or explicit) matches
     // aSource's port
     if (this.port && this.port !== "*" && this.port != aSource.port)
       return false;
 
     // host is defined in 'this' (undefined means it may not be relevant
-    // to the scheme) AND this host (implicit or explicit) permits 
+    // to the scheme) AND this host (implicit or explicit) permits
     // aSource's host.
     if (this.host && !this.host.permits(aSource.host))
       return false;
 
     // all scheme, host and port matched!
     return true;
   },
 
   /**
    * Determines the intersection of two sources.
    * Returns a null object if intersection generates no
    * hosts that satisfy it.
-   * @param that 
+   * @param that
    *        the other CSPSource to intersect "this" with
    * @returns
    *        a new instance of a CSPSource representing the intersection
    */
   intersectWith:
   function(that) {
     var newSource = new CSPSource();
 
     // 'self' is not part of the intersection.  Intersect the raw values from
     // the source, self must be set by someone creating this source.
     // When intersecting, we take the more specific of the two: if one scheme,
     // host or port is undefined, the other is taken.  (This is contrary to
-    // when "permits" is called -- there, the value of 'self' is looked at 
+    // when "permits" is called -- there, the value of 'self' is looked at
     // when a scheme, host or port is undefined.)
 
     // port
     if (!this.port)
       newSource._port = that.port;
     else if (!that.port)
       newSource._port = this.port;
     else if (this.port === "*")
@@ -1241,20 +1248,20 @@ CSPSource.prototype = {
     return newSource;
   },
 
   /**
    * Compares one CSPSource to another.
    *
    * @param that
    *        another CSPSource
-   * @param resolveSelf (optional) 
+   * @param resolveSelf (optional)
    *        if present, and 'true', implied values are obtained from 'self'
    *        instead of assumed to be "anything"
-   * @returns 
+   * @returns
    *        true if they have the same data
    */
   equals:
   function(that, resolveSelf) {
     // 1. schemes match
     // 2. ports match
     // 3. either both hosts are undefined, or one equals the other.
     if (resolveSelf)
@@ -1279,17 +1286,17 @@ CSPSource.prototype = {
 function CSPHost() {
   this._segments = [];
 }
 
 /**
  * Factory to create a new CSPHost, parsed from a string.
  *
  * @param aStr
- *        string rep of a CSP Host 
+ *        string rep of a CSP Host
  * @returns
  *        an instance of CSPHost
  */
 CSPHost.fromString = function(aStr) {
   if (!aStr) return null;
 
   // host string must be LDH with dots and stars.
   var invalidChar = aStr.match(/[^a-zA-Z0-9\-\.\*]/);
@@ -1307,17 +1314,17 @@ CSPHost.fromString = function(aStr) {
   for (var i in hObj._segments) {
     var seg = hObj._segments[i];
     if (seg == "*") {
       if (i > 0) {
         // Wildcard must be FIRST
         CSPdebug("Wildcard char located at invalid position in '" + aStr + "'");
         return null;
       }
-    } 
+    }
     else if (seg.match(/[^a-zA-Z0-9\-]/)) {
       // Non-wildcard segment must be LDH string
       CSPdebug("Invalid segment '" + seg + "' in host value");
       return null;
     }
   }
   return hObj;
 };
@@ -1358,58 +1365,58 @@ CSPHost.prototype = {
 
     if (!(aHost instanceof CSPHost)) {
       // -- compare CSPHost to String
       return this.permits(CSPHost.fromString(aHost));
     }
     var thislen = this._segments.length;
     var thatlen = aHost._segments.length;
 
-    // don't accept a less specific host: 
+    // don't accept a less specific host:
     //   \--> *.b.a doesn't accept b.a.
     if (thatlen < thislen) { return false; }
 
     // check for more specific host (and wildcard):
     //   \--> *.b.a accepts d.c.b.a.
     //   \--> c.b.a doesn't accept d.c.b.a.
     if ((thatlen > thislen) && this._segments[0] != "*") {
       return false;
     }
 
-    // Given the wildcard condition (from above), 
+    // Given the wildcard condition (from above),
     // only necessary to compare elements that are present
-    // in this host.  Extra tokens in aHost are ok. 
+    // in this host.  Extra tokens in aHost are ok.
     // * Compare from right to left.
     for (var i=1; i <= thislen; i++) {
-      if (this._segments[thislen-i] != "*" && 
+      if (this._segments[thislen-i] != "*" &&
           (this._segments[thislen-i] != aHost._segments[thatlen-i])) {
         return false;
       }
     }
 
     // at this point, all conditions are met, so the host is allowed
     return true;
   },
 
   /**
    * Determines the intersection of two Hosts.
    * Basically, they must be the same, or one must have a wildcard.
-   * @param that 
+   * @param that
    *        the other CSPHost to intersect "this" with
    * @returns
    *        a new instance of a CSPHost representing the intersection
    *        (or null, if they can't be intersected)
    */
   intersectWith:
   function(that) {
     if (!(this.permits(that) || that.permits(this))) {
       // host definitions cannot co-exist without a more general host
       // ... one must be a subset of the other, or intersection makes no sense.
       return null;
-    } 
+    }
 
     // pick the more specific one, if both are same length.
     if (this._segments.length == that._segments.length) {
       // *.a vs b.a : b.a
       return (this._segments[0] === "*") ? that.clone() : this.clone();
     }
 
     // different lengths...
@@ -1419,17 +1426,17 @@ CSPHost.prototype = {
             this.clone() : that.clone();
   },
 
   /**
    * Compares one CSPHost to another.
    *
    * @param that
    *        another CSPHost
-   * @returns 
+   * @returns
    *        true if they have the same data
    */
   equals:
   function(that) {
     if (this._segments.length != that._segments.length)
       return false;
 
     for (var i=0; i<this._segments.length; i++) {
--- a/content/base/test/unit/test_csputils.js
+++ b/content/base/test/unit/test_csputils.js
@@ -46,17 +46,17 @@ function do_check_in_array(arr, val, sta
       return;
     }
   }
   do_throw(text, stack);
 }
 
 // helper to assert that an object or array must have a given key
 function do_check_has_key(foo, key, stack) {
-  if (!stack) 
+  if (!stack)
     stack = Components.stack.caller;
 
   var keys = [];
   for (let k in foo) { keys.push(k); }
   var text = key + " in [" + keys.join(",") + "]";
 
   for (var x in foo) {
     if (x == key) {
@@ -67,17 +67,17 @@ function do_check_has_key(foo, key, stac
       return;
     }
   }
   do_throw(text, stack);
 }
 
 // helper to use .equals on stuff
 function do_check_equivalent(foo, bar, stack) {
-  if (!stack) 
+  if (!stack)
     stack = Components.stack.caller;
 
   var text = foo + ".equals(" + bar + ")";
 
   if(foo.equals && foo.equals(bar)) {
     ++_passedChecks;
       dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
            stack.lineNumber + "] " + text + "\n");
@@ -195,26 +195,26 @@ test(
       var src;
       src = CSPSource.create("a.com", "https://foobar.com:443");
       //"src should inherit port *
       do_check_true(src.permits("https://a.com:443"));
       //"src should inherit and require https scheme
       do_check_false(src.permits("http://a.com"));
       //"src should inherit scheme 'https'"
       do_check_true(src.permits("https://a.com"));
-      
+
       src = CSPSource.create("http://a.com", "https://foobar.com:443");
       //"src should inherit and require http scheme"
       do_check_false(src.permits("https://a.com"));
       //"src should inherit scheme 'http'"
       do_check_true(src.permits("http://a.com"));
       //"src should inherit port and scheme from parent"
       //"src should inherit default port for 'http'"
       do_check_true(src.permits("http://a.com:80"));
-      
+
       src = CSPSource.create("'self'", "https://foobar.com:443");
       //"src should inherit port *
       do_check_true(src.permits("https://foobar.com:443"));
       //"src should inherit and require https scheme
       do_check_false(src.permits("http://foobar.com"));
       //"src should inherit scheme 'https'"
       do_check_true(src.permits("https://foobar.com"));
       //"src should reject other hosts"
@@ -403,17 +403,17 @@ test(
 test(
     function test_CSPRep_fromString_oneDir() {
 
       var cspr;
       var SD = CSPRep.SRC_DIRECTIVES;
       var DEFAULTS = [SD.STYLE_SRC, SD.MEDIA_SRC, SD.IMG_SRC, SD.FRAME_SRC];
 
       // check one-directive policies
-      cspr = CSPRep.fromString("allow bar.com; script-src https://foo.com", 
+      cspr = CSPRep.fromString("allow bar.com; script-src https://foo.com",
                                URI("http://self.com"));
 
       for(var x in DEFAULTS) {
         //DEFAULTS[x] + " does not use default rule."
         do_check_false(cspr.permits("http://bar.com:22", DEFAULTS[x]));
         //DEFAULTS[x] + " does not use default rule."
         do_check_true(cspr.permits("http://bar.com:80", DEFAULTS[x]));
         //DEFAULTS[x] + " does not use default rule."
@@ -503,16 +503,38 @@ test(function test_FrameAncestor_default
       //"frame-ancestors should only allow self"
       do_check_true(cspr.permits("http://self.com:34", SD.FRAME_ANCESTORS));
       do_check_false(cspr.permits("https://foo.com:400", SD.FRAME_ANCESTORS));
       do_check_false(cspr.permits("https://self.com:34", SD.FRAME_ANCESTORS));
       do_check_false(cspr.permits("http://self.com", SD.FRAME_ANCESTORS));
       do_check_false(cspr.permits("http://subd.self.com:34", SD.FRAME_ANCESTORS));
      });
 
+test(function test_FrameAncestor_TLD_defaultPorts() {
+      var cspr;
+      var SD = CSPRep.SRC_DIRECTIVES;
+      var self = "http://self"; //TLD only, no .com or anything.
+
+      cspr = CSPRep.fromString("allow 'self'; frame-ancestors 'self' http://foo:80 bar:80 http://three", URI(self));
+
+      //"frame-ancestors should default to * not 'allow' value"
+      do_check_true(cspr.permits("http://self", SD.FRAME_ANCESTORS));
+      do_check_true(cspr.permits("http://self:80", SD.FRAME_ANCESTORS));
+      do_check_true(cspr.permits("http://foo", SD.FRAME_ANCESTORS));
+      do_check_true(cspr.permits("http://foo:80", SD.FRAME_ANCESTORS));
+      do_check_true(cspr.permits("http://bar", SD.FRAME_ANCESTORS));
+      do_check_true(cspr.permits("http://three:80", SD.FRAME_ANCESTORS));
+
+      do_check_false(cspr.permits("https://foo:400", SD.FRAME_ANCESTORS));
+      do_check_false(cspr.permits("https://self:34", SD.FRAME_ANCESTORS));
+      do_check_false(cspr.permits("https://bar", SD.FRAME_ANCESTORS));
+      do_check_false(cspr.permits("http://three:81", SD.FRAME_ANCESTORS));
+      do_check_false(cspr.permits("https://three:81", SD.FRAME_ANCESTORS));
+     });
+
 test(function test_CSP_ReportURI_parsing() {
       var cspr;
       var SD = CSPRep.SRC_DIRECTIVES;
       var self = "http://self.com:34";
       var parsedURIs = [];
 
       var uri_valid_absolute = self + "/report.py";
       var uri_invalid_host_absolute = "http://foo.org:34/report.py";
@@ -585,27 +607,27 @@ test(
        */
 
       var src;
       src = CSPSource.create("a.com", "https://foobar.com:4443");
       //"src should inherit and require https scheme
       do_check_false(src.permits("http://a.com"));
       //"src should inherit scheme 'https'"
       do_check_true(src.permits("https://a.com"));
-      //"src should get default port 
+      //"src should get default port
       do_check_true(src.permits("https://a.com:443"));
-      
+
       src = CSPSource.create("http://a.com", "https://foobar.com:4443");
       //"src should require http scheme"
       do_check_false(src.permits("https://a.com"));
       //"src should keep scheme 'http'"
       do_check_true(src.permits("http://a.com"));
       //"src should inherit default port for 'http'"
       do_check_true(src.permits("http://a.com:80"));
-      
+
       src = CSPSource.create("'self'", "https://foobar.com:4443");
       //"src should inherit nonstandard port from self
       do_check_true(src.permits("https://foobar.com:4443"));
       do_check_false(src.permits("https://foobar.com"));
       do_check_false(src.permits("https://foobar.com:443"));
 
       //"src should inherit and require https scheme from self
       do_check_false(src.permits("http://foobar.com:4443"));