Repository: shapesecurity/salvation
Pull request #152 (2016-10-09 22:38:44)

Added Missing Allows Checks to Policy
Policy.java now includes allowXFromSource() checks, where X is one of the following:

- font (font-src)
- object (object-src)
- media (media-src)
- manifest (manifest-src)

This covers the remainder of the source directives that did not have checks matching the existing functionality. Include additions to PolicyQueryingTest.java.

Fixes shapesecurity/salvation#151

kingthorin: Build failed, but does not seem to be related to the submitted code.
kingthorin: CLA PR is here: https://github.com/shapesecurity/CLA/pull/21

Let me know what I need to do once that is accepted.
kingthorin: That fixup is done and the CA pull request has been merged, how do we get Travis to rebuild/retest?
michaelficarra: Hmm, it should re-run when a new commit is pushed. I've manually restarted it now.
michaelficarra: @kingthorin Can you add tests, similar to those for the other `allowsXFromSource` tests in [`PolicyQueryingTest.java`](https://github.com/shapesecurity/salvation/blob/master/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java)?
kingthorin: Sure, will tackle that

Patch:
diff --git a/src/main/java/com/shapesecurity/salvation/Constants.java b/src/main/java/com/shapesecurity/salvation/Constants.java
index 4ea2ab3..371c04e 100644
--- a/src/main/java/com/shapesecurity/salvation/Constants.java
+++ b/src/main/java/com/shapesecurity/salvation/Constants.java
@@ -14,7 +14,7 @@ import java.util.regex.Pattern;
     public static final Pattern requireSriForEnumeratedTokenPattern = Pattern
             .compile("^(?:script|style)$", Pattern.CASE_INSENSITIVE);
     public static final Pattern mediaTypePattern = Pattern.compile("^(?<type>[^/]+)/(?<subtype>[^/]+)$");
-    public static final Pattern unquotedKeywordPattern = Pattern.compile("^(?:self|unsafe-inline|unsafe-eval|unsafe-redirect|none)$");
+    public static final Pattern unquotedKeywordPattern = Pattern.compile("^(?:self|unsafe-inline|unsafe-eval|unsafe-redirect|none|strict-dynamic)$");
     // port-part constants
     public static final int WILDCARD_PORT = -200;
     public static final int EMPTY_PORT = -1;
diff --git a/src/main/java/com/shapesecurity/salvation/Parser.java b/src/main/java/com/shapesecurity/salvation/Parser.java
index 674c74c..3b9bf1f 100644
--- a/src/main/java/com/shapesecurity/salvation/Parser.java
+++ b/src/main/java/com/shapesecurity/salvation/Parser.java
@@ -50,7 +50,8 @@ public class Parser {
         new DirectiveParseException("Non-empty directive-value list");
 
     private static final String unsafeInlineWarningMessage = "The 'unsafe-inline' keyword-source has no effect in source lists that contain hash-source or nonce-source.";
-    private enum SeenStates {SEEN_HASH, SEEN_NONE, SEEN_NONCE, SEEN_UNSAFE_INLINE};
+    private static final String strictDynamicWarningMessage = "The 'strict-dynamic' keyword-source makes host-source and scheme-source expressions, as well as the \"'unsafe-inline'\" and \"'self' keyword-sources unnecessary in CSP3 and later.";
+    private enum SeenStates {SEEN_HASH, SEEN_HOST_OR_SCHEME_SOURCE, SEEN_NONE, SEEN_NONCE, SEEN_SELF, SEEN_STRICT_DYNAMIC, SEEN_UNSAFE_EVAL, SEEN_UNSAFE_INLINE};
     @Nonnull protected final Token[] tokens;
     @Nonnull private final Origin origin;
     protected int index = 0;
@@ -367,12 +368,20 @@ public class Parser {
                 SourceExpression se = this.parseSourceExpression(seenStates, !sourceExpressions.isEmpty());
                 if (se == None.INSTANCE) {
                     seenStates.add(SeenStates.SEEN_NONE);
+                } else if (se == KeywordSource.UnsafeEval) {
+                    seenStates.add(SeenStates.SEEN_UNSAFE_EVAL);
+                } else if (se == KeywordSource.Self) {
+                    seenStates.add(SeenStates.SEEN_SELF);
                 } else if (se == KeywordSource.UnsafeInline) {
                     seenStates.add(SeenStates.SEEN_UNSAFE_INLINE);
                 } else if (se instanceof HashSource) {
                     seenStates.add(SeenStates.SEEN_HASH);
                 } else if (se instanceof NonceSource) {
                     seenStates.add(SeenStates.SEEN_NONCE);
+                } else if (se == KeywordSource.StrictDynamic) {
+                    seenStates.add(SeenStates.SEEN_STRICT_DYNAMIC);
+                } else if (se instanceof HostSource || se instanceof SchemeSource) {
+                    seenStates.add(SeenStates.SEEN_HOST_OR_SCHEME_SOURCE);
                 }
                 sourceExpressions.add(se);
             } catch (DirectiveValueParseException e) {
@@ -396,11 +405,22 @@ public class Parser {
             case "'none'":
                 return None.INSTANCE;
             case "'self'":
+                if (seenStates.contains(SeenStates.SEEN_STRICT_DYNAMIC)) {
+                    this.warn(token, strictDynamicWarningMessage);
+                }
                 return KeywordSource.Self;
+            case "'strict-dynamic'":
+                if (seenStates.contains(SeenStates.SEEN_UNSAFE_INLINE) || seenStates.contains(SeenStates.SEEN_HOST_OR_SCHEME_SOURCE) || seenStates.contains(SeenStates.SEEN_SELF))  {
+                    this.warn(token, strictDynamicWarningMessage);
+                }
+                return KeywordSource.StrictDynamic;
             case "'unsafe-inline'":
                 if (seenStates.contains(SeenStates.SEEN_HASH) || seenStates.contains(SeenStates.SEEN_NONCE))  {
                     this.warn(token, unsafeInlineWarningMessage);
                 }
+                if (seenStates.contains(SeenStates.SEEN_STRICT_DYNAMIC)) {
+                    this.warn(token, strictDynamicWarningMessage);
+                }
                 return KeywordSource.UnsafeInline;
             case "'unsafe-eval'":
                 return KeywordSource.UnsafeEval;
@@ -460,6 +480,9 @@ public class Parser {
                     }
                     return hashSource;
                 } else if (token.value.matches("^" + Constants.schemePart + ":$")) {
+                    if (seenStates.contains(SeenStates.SEEN_STRICT_DYNAMIC)) {
+                        this.warn(token, strictDynamicWarningMessage);
+                    }
                     return new SchemeSource(token.value.substring(0, token.value.length() - 1));
                 } else {
                     Matcher matcher = Constants.hostSourcePattern.matcher(token.value);
@@ -474,6 +497,9 @@ public class Parser {
                         } else {
                             port = portString.equals(":*") ? Constants.WILDCARD_PORT : Integer.parseInt(portString.substring(1));
                         }
+                        if (seenStates.contains(SeenStates.SEEN_STRICT_DYNAMIC)) {
+                            this.warn(token, strictDynamicWarningMessage);
+                        }
                         String host = matcher.group("host");
                         String path = matcher.group("path");
                         return new HostSource(scheme, host, port, path);
diff --git a/src/main/java/com/shapesecurity/salvation/data/Policy.java b/src/main/java/com/shapesecurity/salvation/data/Policy.java
index 99d80bf..0580e8d 100644
--- a/src/main/java/com/shapesecurity/salvation/data/Policy.java
+++ b/src/main/java/com/shapesecurity/salvation/data/Policy.java
@@ -564,7 +564,71 @@ public class Policy implements Show {
         }
         return frameAncestorsDirective.matchesSource(this.origin, source);
     }
+    
+    public boolean allowsFontFromSource(@Nonnull URI source) {
+    	FontSrcDirective fontSrcDirective = this.getDirectiveByType(FontSrcDirective.class);
+        if (fontSrcDirective == null) {
+            return this.defaultsAllowSource(source);
+        }
+        return fontSrcDirective.matchesSource(this.origin, source);
+    }
+
+    public boolean allowsFontFromSource(@Nonnull GUID source) {
+        FontSrcDirective fontSrcDirective = this.getDirectiveByType(FontSrcDirective.class);
+        if (fontSrcDirective == null) {
+            return this.defaultsAllowSource(source);
+        }
+        return fontSrcDirective.matchesSource(this.origin, source);
+    }
+    
+    public boolean allowsObjectFromSource(@Nonnull URI source) {
+    	ObjectSrcDirective objectSrcDirective = this.getDirectiveByType(ObjectSrcDirective.class);
+        if (objectSrcDirective == null) {
+            return this.defaultsAllowSource(source);
+        }
+        return objectSrcDirective.matchesSource(this.origin, source);
+    }
+
+    public boolean allowsObjectFromSource(@Nonnull GUID source) {
+        ObjectSrcDirective objectSrcDirective = this.getDirectiveByType(ObjectSrcDirective.class);
+        if (objectSrcDirective == null) {
+            return this.defaultsAllowSource(source);
+        }
+        return objectSrcDirective.matchesSource(this.origin, source);
+    }
 
+    public boolean allowsMediaFromSource(@Nonnull URI source) {
+    	MediaSrcDirective mediaSrcDirective = this.getDirectiveByType(MediaSrcDirective.class);
+        if (mediaSrcDirective == null) {
+            return this.defaultsAllowSource(source);
+        }
+        return mediaSrcDirective.matchesSource(this.origin, source);
+    }
+
+    public boolean allowsMediaFromSource(@Nonnull GUID source) {
+        MediaSrcDirective mediaSrcDirective = this.getDirectiveByType(MediaSrcDirective.class);
+        if (mediaSrcDirective == null) {
+            return this.defaultsAllowSource(source);
+        }
+        return mediaSrcDirective.matchesSource(this.origin, source);
+    }
+    
+    public boolean allowsManifestFromSource(@Nonnull URI source) {
+    	ManifestSrcDirective manifestSrcDirective = this.getDirectiveByType(ManifestSrcDirective.class);
+        if (manifestSrcDirective == null) {
+            return this.defaultsAllowSource(source);
+        }
+        return manifestSrcDirective.matchesSource(this.origin, source);
+    }
+
+    public boolean allowsManifestFromSource(@Nonnull GUID source) {
+        ManifestSrcDirective manifestSrcDirective = this.getDirectiveByType(ManifestSrcDirective.class);
+        if (manifestSrcDirective == null) {
+            return this.defaultsAllowSource(source);
+        }
+        return manifestSrcDirective.matchesSource(this.origin, source);
+    }
+    
     public boolean hasSomeEffect() {
         for (Map.Entry<Class<?>, Directive<? extends DirectiveValue>> entry : this.directives.entrySet()) {
             Directive<? extends DirectiveValue> directive = entry.getValue();
diff --git a/src/main/java/com/shapesecurity/salvation/directiveValues/KeywordSource.java b/src/main/java/com/shapesecurity/salvation/directiveValues/KeywordSource.java
index 2eb3f3e..6f02b50 100644
--- a/src/main/java/com/shapesecurity/salvation/directiveValues/KeywordSource.java
+++ b/src/main/java/com/shapesecurity/salvation/directiveValues/KeywordSource.java
@@ -13,6 +13,7 @@ public class KeywordSource implements SourceExpression, AncestorSource, MatchesS
     @Nonnull public static final KeywordSource UnsafeInline = new KeywordSource("unsafe-inline");
     @Nonnull public static final KeywordSource UnsafeEval = new KeywordSource("unsafe-eval");
     @Nonnull public static final KeywordSource UnsafeRedirect = new KeywordSource("unsafe-redirect");
+    @Nonnull public static final KeywordSource StrictDynamic = new KeywordSource("strict-dynamic");
     @Nonnull private final String value;
 
     private KeywordSource(@Nonnull String value) {


Test patch:
diff --git a/src/test/java/com/shapesecurity/salvation/ParserTest.java b/src/test/java/com/shapesecurity/salvation/ParserTest.java
index dc3ca23..4544294 100644
--- a/src/test/java/com/shapesecurity/salvation/ParserTest.java
+++ b/src/test/java/com/shapesecurity/salvation/ParserTest.java
@@ -574,6 +574,8 @@ public class ParserTest extends CSPTest {
             parse("img-src example.com 'unsafe-eval'").getDirectiveByType(ImgSrcDirective.class).show());
         assertEquals("directive-name, directive-value", "img-src example.com 'unsafe-redirect'",
             parse("img-src example.com 'unsafe-redirect'").getDirectiveByType(ImgSrcDirective.class).show());
+        assertEquals("directive-name, directive-value", "img-src 'nonce-123' 'strict-dynamic'",
+            parse("img-src 'nonce-123' 'strict-dynamic'").getDirectiveByType(ImgSrcDirective.class).show());
     }
 
     @Test public void testDirectNameSpacing() {
@@ -800,8 +802,64 @@ public class ParserTest extends CSPTest {
         assertEquals("The require-sri-for directive should contain only \"script\", \"style\" tokens.", notices.get(0).message);
         assertEquals("The require-sri-for directive contains duplicate token: \"script\".", notices.get(1).message);
         assertEquals("require-sri-for script bla", p.show());
+    }
 
+    @Test public void testStrictDynamic() {
+        Policy p;
+        ArrayList<Notice> notices = new ArrayList<>();
+        p = parseWithNotices("script-src a 'strict-dynamic'", notices);
+        assertEquals(1, p.getDirectives().size());
+        assertEquals(1, notices.size());
+        assertTrue(notices.get(0).isWarning());
+        assertEquals("The 'strict-dynamic' keyword-source makes host-source and scheme-source expressions, as well as the \"'unsafe-inline'\" and \"'self' keyword-sources unnecessary in CSP3 and later.", notices.get(0).message);
+        assertEquals("script-src a 'strict-dynamic'", p.show());
 
+        notices.clear();
+        p = parseWithNotices("script-src 'strict-dynamic' a", notices);
+        assertEquals(1, p.getDirectives().size());
+        assertEquals(1, notices.size());
+        assertTrue(notices.get(0).isWarning());
+        assertEquals("The 'strict-dynamic' keyword-source makes host-source and scheme-source expressions, as well as the \"'unsafe-inline'\" and \"'self' keyword-sources unnecessary in CSP3 and later.", notices.get(0).message);
+        assertEquals("script-src 'strict-dynamic' a", p.show());
+
+        notices.clear();
+        p = parseWithNotices("script-src 'strict-dynamic' https:", notices);
+        assertEquals(1, p.getDirectives().size());
+        assertEquals(1, notices.size());
+        assertTrue(notices.get(0).isWarning());
+        assertEquals("The 'strict-dynamic' keyword-source makes host-source and scheme-source expressions, as well as the \"'unsafe-inline'\" and \"'self' keyword-sources unnecessary in CSP3 and later.", notices.get(0).message);
+        assertEquals("script-src 'strict-dynamic' https:", p.show());
+
+        notices.clear();
+        p = parseWithNotices("script-src 'strict-dynamic' 'self'", notices);
+        assertEquals(1, p.getDirectives().size());
+        assertEquals(1, notices.size());
+        assertTrue(notices.get(0).isWarning());
+        assertEquals("The 'strict-dynamic' keyword-source makes host-source and scheme-source expressions, as well as the \"'unsafe-inline'\" and \"'self' keyword-sources unnecessary in CSP3 and later.", notices.get(0).message);
+        assertEquals("script-src 'strict-dynamic' 'self'", p.show());
+
+        notices.clear();
+        p = parseWithNotices("script-src 'strict-dynamic' 'unsafe-inline'", notices);
+        assertEquals(1, p.getDirectives().size());
+        assertEquals(1, notices.size());
+        assertTrue(notices.get(0).isWarning());
+        assertEquals("The 'strict-dynamic' keyword-source makes host-source and scheme-source expressions, as well as the \"'unsafe-inline'\" and \"'self' keyword-sources unnecessary in CSP3 and later.", notices.get(0).message);
+        assertEquals("script-src 'strict-dynamic' 'unsafe-inline'", p.show());
+
+        notices.clear();
+        p = parseWithNotices("script-src 'strict-dynamic' 'unsafe-inline' 'self' a https:", notices);
+        assertEquals(1, p.getDirectives().size());
+        assertEquals(4, notices.size());
+        assertTrue(notices.get(0).isWarning());
+        assertEquals("The 'strict-dynamic' keyword-source makes host-source and scheme-source expressions, as well as the \"'unsafe-inline'\" and \"'self' keyword-sources unnecessary in CSP3 and later" +
+                ".", notices.get(0).message);
+        assertEquals("script-src 'strict-dynamic' 'unsafe-inline' 'self' a https:", p.show());
+
+        notices.clear();
+        p = parseWithNotices("script-src 'strict-dynamic' 'unsafe-eval'", notices);
+        assertEquals(1, p.getDirectives().size());
+        assertEquals(0, notices.size());
+        assertEquals("script-src 'strict-dynamic' 'unsafe-eval'", p.show());
     }
 
     @Test public void testParseMulti() {
diff --git a/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java b/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java
index 839420f..47bcce6 100644
--- a/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java
+++ b/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java
@@ -78,6 +78,11 @@ public class PolicyMergeTest extends CSPTest {
         p2 = parse("default-src a");
         p1.union(p2);
         assertEquals("default-src b a; script-src a", p1.show());
+
+        p1 = parse("default-src 'strict-dynamic' 'nonce-1234' b; script-src a");
+        p2 = parse("default-src a");
+        p1.union(p2);
+        assertEquals("default-src 'strict-dynamic' 'nonce-1234' b a; script-src a", p1.show());
     }
 
     @Test public void testIntersect() {
@@ -138,6 +143,11 @@ public class PolicyMergeTest extends CSPTest {
         p1.intersect(p2);
         assertEquals("default-src 'self'; script-src a; style-src", p1.show());
 
+        p1 = parse("default-src 'self' 'strict-dynamic'; script-src a");
+        p2 = parse("default-src *; script-src *; style-src *:80");
+        p1.intersect(p2);
+        assertEquals("default-src 'self' 'strict-dynamic'; script-src a; style-src", p1.show());
+
         p1 = ParserWithLocation.parse("script-src a", URI.parse("https://origin"));
         p2 = parse("script-src b; report-uri /x");
         try {
diff --git a/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java b/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
index a7304ac..df47ae2 100644
--- a/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
+++ b/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
@@ -109,6 +109,31 @@ public class PolicyQueryingTest extends CSPTest {
         assertFalse("resource is not allowed", p.allowsFrameFromSource(URI.parse("http://www.def.am:555")));
         assertFalse("resource is not allowed", p.allowsChildFromSource(URI.parse("https://www.def.am:555")));
         assertTrue("resource is allowed", p.allowsChildFromSource(URI.parse("http://www.def.am:555")));
+        
+        p = Parser.parse("font-src https://font.com http://font.org", URI.parse("https://abc.com"));
+        assertTrue("resource is allowed", p.allowsFontFromSource(URI.parse("https://font.com")));
+        assertFalse("resource is not allowed", p.allowsFontFromSource(URI.parse("https://font.com:555")));
+        assertFalse("resource is not allowed", p.allowsFontFromSource(URI.parse("http://www.def.am:555")));
+        assertFalse("resource is not allowed", p.allowsFontFromSource(URI.parse("https://someco.net")));
+        
+        p = Parser.parse("object-src https://object.com http://object.org", URI.parse("https://abc.com"));
+        assertTrue("resource is allowed", p.allowsObjectFromSource(URI.parse("https://object.com")));
+        assertFalse("resource is not allowed", p.allowsObjectFromSource(URI.parse("https://object.com:555")));
+        assertFalse("resource is not allowed", p.allowsObjectFromSource(URI.parse("http://www.def.am:555")));
+        assertFalse("resource is not allowed", p.allowsObjectFromSource(URI.parse("https://someco.net")));
+        
+        p = Parser.parse("media-src https://media.com http://media.org", URI.parse("https://abc.com"));
+        assertTrue("resource is allowed", p.allowsMediaFromSource(URI.parse("https://media.com")));
+        assertFalse("resource is not allowed", p.allowsMediaFromSource(URI.parse("https://media.com:555")));
+        assertFalse("resource is not allowed", p.allowsMediaFromSource(URI.parse("http://www.def.am:555")));
+        assertFalse("resource is not allowed", p.allowsMediaFromSource(URI.parse("https://someco.net")));
+        
+        p = Parser.parse("manifest-src https://manifest.com http://manifest.org", URI.parse("https://abc.com"));
+        assertTrue("resource is allowed", p.allowsManifestFromSource(URI.parse("https://manifest.com")));
+        assertFalse("resource is not allowed", p.allowsManifestFromSource(URI.parse("https://manifest.com:555")));
+        assertFalse("resource is not allowed", p.allowsManifestFromSource(URI.parse("http://www.def.am:555")));
+        assertFalse("resource is not allowed", p.allowsManifestFromSource(URI.parse("https://someco.net")));
+        
     }
 
     @Test public void testAllowsUnsafeInline() {
@@ -484,6 +509,155 @@ public class PolicyQueryingTest extends CSPTest {
         assertFalse(p.allowsScriptFromSource(URI.parse("wss://example.com/PATH")));
         assertFalse(p.allowsScriptFromSource(new GUID("data:")));
         assertFalse(p.allowsScriptFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("font-src *", "http://example.com");
+        assertTrue(p.allowsFontFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsFontFromSource(URI.parse("https://example.com")));
+        assertTrue(p.allowsFontFromSource(URI.parse("http://example.com:81")));
+        assertTrue(p.allowsFontFromSource(URI.parse("ftp://example.com")));
+        assertTrue(p.allowsFontFromSource(URI.parse("ftp://example.com:80")));
+        assertTrue(p.allowsFontFromSource(URI.parse("http://example.com/path")));
+        assertTrue(p.allowsFontFromSource(URI.parse("http://example.com/PATH")));
+        assertTrue(p.allowsFontFromSource(URI.parse("ws://example.com/PATH")));
+        assertTrue(p.allowsFontFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsFontFromSource(new GUID("data:")));
+        assertFalse(p.allowsFontFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("font-src http://*", "http://example.com");
+        assertTrue(p.allowsFontFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsFontFromSource(URI.parse("https://example.com")));
+        assertFalse(p.allowsFontFromSource(URI.parse("http://example.com:81")));
+        assertFalse(p.allowsFontFromSource(URI.parse("ftp://example.com")));
+        assertFalse(p.allowsFontFromSource(URI.parse("ftp://example.com:80")));
+        assertTrue(p.allowsFontFromSource(URI.parse("http://example.com/path")));
+        assertTrue(p.allowsFontFromSource(URI.parse("http://example.com/PATH")));
+        assertFalse(p.allowsFontFromSource(URI.parse("ws://example.com/PATH")));
+        assertFalse(p.allowsFontFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsFontFromSource(new GUID("data:")));
+        assertFalse(p.allowsFontFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("font-src *.example.com", "http://example.com");
+        assertTrue(p.allowsFontFromSource(URI.parse("http://a.b.example.com/c/d")));
+        assertTrue(p.allowsFontFromSource(URI.parse("http://a.b.example.com")));
+        assertTrue(p.allowsFontFromSource(URI.parse("http://www.example.com")));
+        assertFalse(p.allowsFontFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsFontFromSource(URI.parse("http://com")));
+        assertFalse(p.allowsFontFromSource(URI.parse("ws://example.com/PATH")));
+        assertFalse(p.allowsFontFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsFontFromSource(new GUID("data:")));
+        assertFalse(p.allowsFontFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("object-src *", "http://example.com");
+        assertTrue(p.allowsObjectFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("https://example.com")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("http://example.com:81")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("ftp://example.com")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("ftp://example.com:80")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("http://example.com/path")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("http://example.com/PATH")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("ws://example.com/PATH")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsObjectFromSource(new GUID("data:")));
+        assertFalse(p.allowsObjectFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("object-src http://*", "http://example.com");
+        assertTrue(p.allowsObjectFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsObjectFromSource(URI.parse("https://example.com")));
+        assertFalse(p.allowsObjectFromSource(URI.parse("http://example.com:81")));
+        assertFalse(p.allowsObjectFromSource(URI.parse("ftp://example.com")));
+        assertFalse(p.allowsObjectFromSource(URI.parse("ftp://example.com:80")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("http://example.com/path")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("http://example.com/PATH")));
+        assertFalse(p.allowsObjectFromSource(URI.parse("ws://example.com/PATH")));
+        assertFalse(p.allowsObjectFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsObjectFromSource(new GUID("data:")));
+        assertFalse(p.allowsObjectFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("object-src *.example.com", "http://example.com");
+        assertTrue(p.allowsObjectFromSource(URI.parse("http://a.b.example.com/c/d")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("http://a.b.example.com")));
+        assertTrue(p.allowsObjectFromSource(URI.parse("http://www.example.com")));
+        assertFalse(p.allowsObjectFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsObjectFromSource(URI.parse("http://com")));
+        assertFalse(p.allowsObjectFromSource(URI.parse("ws://example.com/PATH")));
+        assertFalse(p.allowsObjectFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsObjectFromSource(new GUID("data:")));
+        assertFalse(p.allowsObjectFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("media-src *", "http://example.com");
+        assertTrue(p.allowsMediaFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("https://example.com")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("http://example.com:81")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("ftp://example.com")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("ftp://example.com:80")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("http://example.com/path")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("http://example.com/PATH")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("ws://example.com/PATH")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsMediaFromSource(new GUID("data:")));
+        assertFalse(p.allowsMediaFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("media-src http://*", "http://example.com");
+        assertTrue(p.allowsMediaFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsMediaFromSource(URI.parse("https://example.com")));
+        assertFalse(p.allowsMediaFromSource(URI.parse("http://example.com:81")));
+        assertFalse(p.allowsMediaFromSource(URI.parse("ftp://example.com")));
+        assertFalse(p.allowsMediaFromSource(URI.parse("ftp://example.com:80")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("http://example.com/path")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("http://example.com/PATH")));
+        assertFalse(p.allowsMediaFromSource(URI.parse("ws://example.com/PATH")));
+        assertFalse(p.allowsMediaFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsMediaFromSource(new GUID("data:")));
+        assertFalse(p.allowsMediaFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("media-src *.example.com", "http://example.com");
+        assertTrue(p.allowsMediaFromSource(URI.parse("http://a.b.example.com/c/d")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("http://a.b.example.com")));
+        assertTrue(p.allowsMediaFromSource(URI.parse("http://www.example.com")));
+        assertFalse(p.allowsMediaFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsMediaFromSource(URI.parse("http://com")));
+        assertFalse(p.allowsMediaFromSource(URI.parse("ws://example.com/PATH")));
+        assertFalse(p.allowsMediaFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsMediaFromSource(new GUID("data:")));
+        assertFalse(p.allowsMediaFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("manifest-src *", "http://example.com");
+        assertTrue(p.allowsManifestFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("https://example.com")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("http://example.com:81")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("ftp://example.com")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("ftp://example.com:80")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("http://example.com/path")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("http://example.com/PATH")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("ws://example.com/PATH")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsManifestFromSource(new GUID("data:")));
+        assertFalse(p.allowsManifestFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("manifest-src http://*", "http://example.com");
+        assertTrue(p.allowsManifestFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsManifestFromSource(URI.parse("https://example.com")));
+        assertFalse(p.allowsManifestFromSource(URI.parse("http://example.com:81")));
+        assertFalse(p.allowsManifestFromSource(URI.parse("ftp://example.com")));
+        assertFalse(p.allowsManifestFromSource(URI.parse("ftp://example.com:80")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("http://example.com/path")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("http://example.com/PATH")));
+        assertFalse(p.allowsManifestFromSource(URI.parse("ws://example.com/PATH")));
+        assertFalse(p.allowsManifestFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsManifestFromSource(new GUID("data:")));
+        assertFalse(p.allowsManifestFromSource(new GUID("custom.scheme:")));
+        
+        p = Parser.parse("manifest-src *.example.com", "http://example.com");
+        assertTrue(p.allowsManifestFromSource(URI.parse("http://a.b.example.com/c/d")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("http://a.b.example.com")));
+        assertTrue(p.allowsManifestFromSource(URI.parse("http://www.example.com")));
+        assertFalse(p.allowsManifestFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsManifestFromSource(URI.parse("http://com")));
+        assertFalse(p.allowsManifestFromSource(URI.parse("ws://example.com/PATH")));
+        assertFalse(p.allowsManifestFromSource(URI.parse("wss://example.com/PATH")));
+        assertFalse(p.allowsManifestFromSource(new GUID("data:")));
+        assertFalse(p.allowsManifestFromSource(new GUID("custom.scheme:")));
+        
     }
 
     @Test public void testEmptyPolicy() {