/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Content Security Policy Data Structures testing code. * * The Initial Developer of the Original Code is * Mozilla Corporation * * Contributor(s): * Sid Stamm * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ //load('CSPUtils.jsm'); Components.utils.import('resource://gre/modules/CSPUtils.jsm'); // load the HTTP server do_load_httpd_js(); var httpServer = new nsHttpServer(); const POLICY_FROM_URI = "allow 'self'; img-src *"; const POLICY_PORT = 9000; const POLICY_URI = "http://localhost:" + POLICY_PORT + "/policy"; // helper to assert that an object or array must have a given key function do_check_has_key(foo, key, stack) { if (!stack) stack = Components.stack.caller; var keys = []; for(let k in keys) { keys.push(k); } var text = key + " in [" + keys.join(",") + "]"; for(var x in foo) { if(x == key) { //succeed ++_passedChecks; dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " + stack.lineNumber + "] " + text + "\n"); return; } } do_throw(text, stack); } // helper to use .equals on stuff function do_check_equivalent(foo, bar, 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"); return; } do_throw(text, stack); } var tests = []; function test(fcn) { tests.push(fcn); } test( function test_CSPHost_fromstring() { var h; h = CSPHost.fromString("*"); do_check_neq(null, h); // "* lone wildcard should work" h = CSPHost.fromString("foo.bar"); do_check_neq(null, h); // "standard tuple failed" h = CSPHost.fromString("*.bar"); do_check_neq(null, h); // "wildcard failed" h = CSPHost.fromString("foo.*.bar"); do_check_eq(null, h); // "wildcard in wrong place worked" h = CSPHost.fromString("com"); do_check_neq(null, h); // "lone symbol should not fail" h = CSPHost.fromString("f00b4r.com"); do_check_neq(null, h); // "Numbers in hosts should work" h = CSPHost.fromString("foo-bar.com"); do_check_neq(null, h); // "dashes in hosts should work" h = CSPHost.fromString("foo!bar.com"); do_check_eq(null, h); // "special chars in hosts should fail" }); test( function test_CSPHost_clone() { h = CSPHost.fromString("*.a.b.c"); h2 = h.clone(); for(var i in h._segments) { // "cloned segments should match" do_check_eq(h._segments[i], h2._segments[i]); } }); test( function test_CSPHost_permits() { var h = CSPHost.fromString("*.b.c"); var h2 = CSPHost.fromString("a.b.c"); do_check_true( h.permits(h2)); //"CSPHost *.b.c should allow CSPHost a.b.c" do_check_true( h.permits("a.b.c")); //"CSPHost *.b.c should allow string a.b.c" do_check_false(h.permits("b.c")); //"CSPHost *.b.c should not allow string b.c" do_check_false(h.permits("a.a.c")); //"CSPHost *.b.c should not allow string a.a.c" do_check_false(h2.permits(h)); //"CSPHost a.b.c should not allow CSPHost *.b.c" do_check_false(h2.permits("b.c")); //"CSPHost a.b.c should not allow string b.c" do_check_true( h2.permits("a.b.c")); //"CSPHost a.b.c should allow string a.b.c" }); test( function test_CSPHost_intersectWith() { var h = CSPHost.fromString("*.b.c"); //"*.a.b.c ^ *.b.c should be *.a.b.c" do_check_eq("*.a.b.c", h.intersectWith(CSPHost.fromString("*.a.b.c")).toString()); //"*.b.c ^ *.d.e should not work (null)" do_check_eq(null, h.intersectWith(CSPHost.fromString("*.d.e"))); }); ///////////////////// Test the Source object ////////////////////// test( function test_CSPSource_fromString() { // can't do these tests because "self" is not defined. //"basic source should not be null."); do_check_neq(null, CSPSource.fromString("a.com")); //"ldh characters should all work for host."); do_check_neq(null, CSPSource.fromString("a2-c.com")); //"wildcard should work in first token for host."); do_check_neq(null, CSPSource.fromString("*.a.com")); //print(" --- Ignore the following two errors if they print ---"); //"wildcard should not work in non-first token for host."); do_check_eq(null, CSPSource.fromString("x.*.a.com")); //"funny characters (#) should not work for host."); do_check_eq(null, CSPSource.fromString("a#2-c.com")); //print(" --- Stop ignoring errors that print ---\n"); //"failed to parse host with port."); do_check_neq(null, CSPSource.create("a.com:23")); //"failed to parse host with scheme."); do_check_neq(null, CSPSource.create("https://a.com")); //"failed to parse host with scheme and port."); do_check_neq(null, CSPSource.create("https://a.com:200")); }); test( function test_CSPSource_fromString_withSelf() { 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" do_check_false(src.permits("https://a.com")); }); ///////////////////// Test the source list ////////////////////// test( function test_CSPSourceList_fromString() { var sd = CSPSourceList.fromString("'none'"); //"'none' -- should parse" do_check_neq(null,sd); // "'none' should be a zero-length list" do_check_eq(0, sd._sources.length); do_check_true(sd.isNone()); sd = CSPSourceList.fromString("*"); //"'*' should be a zero-length list" do_check_eq(0, sd._sources.length); //print(" --- Ignore the following three errors if they print ---"); //"funny char in host" do_check_true(CSPSourceList.fromString("f!oo.bar").isNone()); //"funny char in scheme" do_check_true(CSPSourceList.fromString("ht!ps://f-oo.bar").isNone()); //"funny char in port" do_check_true(CSPSourceList.fromString("https://f-oo.bar:3f").isNone()); //print(" --- Stop ignoring errors that print ---\n"); }); test( function test_CSPSourceList_fromString_twohost() { var str = "foo.bar:21 https://ras.bar"; var parsed = "foo.bar:21 https://ras.bar"; var sd = CSPSourceList.fromString(str, "http://self.com:80"); //"two-host list should parse" do_check_neq(null,sd); //"two-host list should parse to two hosts" do_check_eq(2, sd._sources.length); //"two-host list should contain original data" do_check_eq(parsed, sd.toString()); }); test( function test_CSPSourceList_permits() { var nullSourceList = CSPSourceList.fromString("'none'"); var simpleSourceList = CSPSourceList.fromString("a.com", "http://self.com"); var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88", "http://self.com:88"); var allSourceList = CSPSourceList.fromString("*"); //'none' should permit none." do_check_false( nullSourceList.permits("http://a.com")); //a.com should permit a.com" do_check_true( simpleSourceList.permits("http://a.com")); //wrong host" do_check_false( simpleSourceList.permits("http://b.com")); //double list permits http://bar.com:88" do_check_true( doubleSourceList.permits("http://bar.com:88")); //double list permits https://bar.com:88" do_check_false( doubleSourceList.permits("https://bar.com:88")); //double list does not permit http://bar.com:443" do_check_false( doubleSourceList.permits("http://bar.com:443")); //"double list permits https://foo.com:88" (should not inherit port) do_check_false( doubleSourceList.permits("https://foo.com:88")); //"double list does not permit foo.com on http" do_check_false( doubleSourceList.permits("http://foo.com")); //"* does not permit specific host" do_check_true( allSourceList.permits("http://x.com:23")); //"* does not permit a long host with no port" do_check_true( allSourceList.permits("http://a.b.c.d.e.f.g.h.i.j.k.l.x.com")); }); test( function test_CSPSourceList_intersect() { // for this test, 'self' values are irrelevant // policy a /\ policy b intersects policies, not context (where 'self' // values come into play) var nullSourceList = CSPSourceList.fromString("'none'"); var simpleSourceList = CSPSourceList.fromString("a.com"); var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88"); var singleFooSourceList = CSPSourceList.fromString("https://foo.com"); var allSourceList = CSPSourceList.fromString("*"); //"Intersection of one source with 'none' source list should be none."); do_check_true(nullSourceList.intersectWith(simpleSourceList).isNone()); //"Intersection of two sources with 'none' source list should be none."); do_check_true(nullSourceList.intersectWith(doubleSourceList).isNone()); //"Intersection of '*' with 'none' source list should be none."); do_check_true(nullSourceList.intersectWith(allSourceList).isNone()); //"Intersection of one source with '*' source list should be one source."); do_check_equivalent(allSourceList.intersectWith(simpleSourceList), simpleSourceList); //"Intersection of two sources with '*' source list should be two sources."); do_check_equivalent(allSourceList.intersectWith(doubleSourceList), doubleSourceList); //"Non-overlapping source lists should intersect to 'none'"); do_check_true(simpleSourceList.intersectWith(doubleSourceList).isNone()); //"subset and superset should intersect to subset."); do_check_equivalent(singleFooSourceList, doubleSourceList.intersectWith(singleFooSourceList)); //TODO: write more tests? }); ///////////////////// Test the Whole CSP rep object ////////////////////// test( function test_CSPRep_fromString() { // check default init //ASSERT(!(new CSPRep())._isInitialized, "Uninitialized rep thinks it is.") var cspr; var cspr_allowval; // check default policy "allow *" cspr = CSPRep.fromString("allow *", "http://self.com:80"); //"ALLOW directive is missing when specified in fromString" do_check_has_key(cspr._directives, CSPRep.SRC_DIRECTIVES.ALLOW); // ... and check that the other directives were auto-filled with the // ALLOW one. var SD = CSPRep.SRC_DIRECTIVES; cspr_allowval = cspr._directives[SD.ALLOW]; for(var d in CSPRep.SRC_DIRECTIVES) { //"Missing key " + d do_check_has_key(cspr._directives, SD[d]); //"Implicit directive " + d + " has non-allow value." do_check_eq(cspr._directives[SD[d]].toString(), cspr_allowval.toString()); } }); 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_ANCESTORS, SD.FRAME_SRC]; // check one-directive policies cspr = CSPRep.fromString("allow bar.com; script-src https://foo.com", "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." do_check_false(cspr.permits("https://foo.com:400", DEFAULTS[x])); //DEFAULTS[x] + " does not use default rule." do_check_false(cspr.permits("https://foo.com", DEFAULTS[x])); } //"script-src false positive in policy. do_check_false(cspr.permits("http://bar.com:22", SD.SCRIPT_SRC)); //"script-src false negative in policy. do_check_true(cspr.permits("https://foo.com:443", SD.SCRIPT_SRC)); }); test( function test_CSPRep_fromString_twodir() { var cspr; var SD = CSPRep.SRC_DIRECTIVES; var DEFAULTS = [SD.STYLE_SRC, SD.MEDIA_SRC, SD.FRAME_ANCESTORS, SD.FRAME_SRC]; // check two-directive policies var polstr = "allow allow.com; " + "script-src https://foo.com; " + "img-src bar.com:*"; cspr = CSPRep.fromString(polstr, "http://self.com"); for(var x in DEFAULTS) { do_check_true(cspr.permits("http://allow.com", DEFAULTS[x])); //DEFAULTS[x] + " does not use default rule. do_check_false(cspr.permits("https://foo.com:400", DEFAULTS[x])); //DEFAULTS[x] + " does not use default rule. do_check_false(cspr.permits("http://bar.com:400", DEFAULTS[x])); //DEFAULTS[x] + " does not use default rule. } //"img-src does not use default rule. do_check_false(cspr.permits("http://allow.com:22", SD.IMG_SRC)); //"img-src does not use default rule. do_check_false(cspr.permits("https://foo.com:400", SD.IMG_SRC)); //"img-src does not use default rule. do_check_true(cspr.permits("http://bar.com:88", SD.IMG_SRC)); //"script-src does not use default rule. do_check_false(cspr.permits("http://allow.com:22", SD.SCRIPT_SRC)); //"script-src does not use default rule. do_check_true(cspr.permits("https://foo.com:443", SD.SCRIPT_SRC)); //"script-src does not use default rule. do_check_false(cspr.permits("http://bar.com:400", SD.SCRIPT_SRC)); }); test(function test_CSPRep_fromString_withself() { var cspr; var SD = CSPRep.SRC_DIRECTIVES; var self = "https://self.com:34"; // check one-directive policies cspr = CSPRep.fromString("allow 'self'; script-src 'self' https://*:*", self); //"img-src does not enforce default rule, 'self'. do_check_false(cspr.permits("https://foo.com:400", SD.IMG_SRC)); //"img-src does not allow self CSPdebug(cspr); do_check_true(cspr.permits(self, SD.IMG_SRC)); //"script-src is too relaxed do_check_false(cspr.permits("http://evil.com", SD.SCRIPT_SRC)); //"script-src should allow self do_check_true(cspr.permits(self, SD.SCRIPT_SRC)); //"script-src is too strict on host/port do_check_true(cspr.permits("https://evil.com:100", SD.SCRIPT_SRC)); }); ///////////////////// TEST POLICY_URI ////////////////////// test(function test_CSPRep_fromPolicyURI() { var cspr; var SD = CSPRep.SRC_DIRECTIVES; var self = "http://localhost:" + POLICY_PORT; cspr = CSPRep.fromString("policy-uri " + POLICY_URI, self); cspr_static = CSPRep.fromString(POLICY_FROM_URI, self); //"policy-uri failed to load" do_check_neq(null,cspr); // other directives inherit self for(var i in SD) { //SD[i] + " parsed wrong from policy uri" do_check_equivalent(cspr._directives[SD[i]], cspr_static._directives[SD[i]]); } }); /* test(function test_CSPRep_fromPolicyURI_failswhenmixed() { var cspr; var self = "http://localhost:" + POLICY_PORT; var closed_policy = CSPRep.fromString("allow 'none'"); var my_uri_policy = "policy-uri " + POLICY_URI; //print(" --- Ignore the following two errors if they print ---"); cspr = CSPRep.fromString("allow *; " + my_uri_policy, self); //"Parsing should fail when 'policy-uri' is mixed with allow directive" do_check_equivalent(cspr, closed_policy); cspr = CSPRep.fromString("img-src 'self'; " + my_uri_policy, self); //"Parsing should fail when 'policy-uri' is mixed with other directives" do_check_equivalent(cspr, closed_policy); //print(" --- Stop ignoring errors that print ---\n"); }); */ // TODO: test reporting // TODO: test refinements (?) // TODO: test 'eval' and 'inline' keywords function run_test() { function policyresponder(request,response) { response.setStatusLine(request.httpVersion, 200, "OK"); response.setHeader("Content-Type", "text/csp", false); response.bodyOutputStream.write(POLICY_FROM_URI, POLICY_FROM_URI.length); } //server.registerDirectory("/", nsILocalFileForBasePath); httpServer.registerPathHandler("/policy", policyresponder); httpServer.start(POLICY_PORT); for(let i in tests) { tests[i](); } //teardown httpServer.stop(function() { }); do_test_finished(); }