Repository: shapesecurity/salvation
Pull request #164 (2016-12-08 19:10:53)

fixes GH-163:
`hash-source` and `nonce-source` should override `unsafe-inline`


Patch:
diff --git a/README.md b/README.md
index fcbf1e8..80b76b3 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,6 @@ mvn install
 Parse a policy using one of the `Parser.parse` static methods. An `Origin` or `String` may be given as the origin. The third parameter, if given, will be populated with notices.
 
 ```java
-
 ArrayList<Notice> notices = new ArrayList<>();
 Origin origin = URI.parse("http://example.com");
 String policyText = "...";
@@ -62,6 +61,14 @@ Policy p = Parser.parse("script-src a; default-src b", "http://example.com");
 p.allowsScriptFromSource(URI.parse("http://a")); // true
 p.allowsScriptFromSource(URI.parse("http://b")); // false
 p.allowsStyleFromSource(URI.parse("http://b")); // true
+```
+Perform `source-expression`-specific queries and manipulations:
+```java
+Policy p = Parser.parse("default-src *.example.com 'unsafe-inline' 'nonce-123' 'strict-dynamic'", "http://example.com");
+p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline)); // true
+p.allowsUnsafeInlineScript(); // false
+p.getEffectiveSourceExpressions(ScriptSrcDirective.class).filter(x -> x instanceof HostSource).count(); // 1
+
 ```
 ### Manipulate Policies
 
diff --git a/salvation.iml b/salvation.iml
index 8fb5010..791b5cb 100644
--- a/salvation.iml
+++ b/salvation.iml
@@ -9,7 +9,7 @@
       <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
       <excludeFolder url="file://$MODULE_DIR$/target" />
     </content>
-    <orderEntry type="inheritedJdk" />
+    <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:2.0.1" level="project" />
     <orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.11" level="project" />
diff --git a/src/main/java/com/shapesecurity/salvation/data/Policy.java b/src/main/java/com/shapesecurity/salvation/data/Policy.java
index 2661d83..655b01a 100644
--- a/src/main/java/com/shapesecurity/salvation/data/Policy.java
+++ b/src/main/java/com/shapesecurity/salvation/data/Policy.java
@@ -1,16 +1,51 @@
 package com.shapesecurity.salvation.data;
 
+import com.shapesecurity.salvation.directiveValues.HashSource;
 import com.shapesecurity.salvation.directiveValues.HashSource.HashAlgorithm;
-import com.shapesecurity.salvation.directiveValues.*;
-import com.shapesecurity.salvation.directives.*;
+import com.shapesecurity.salvation.directiveValues.HostSource;
+import com.shapesecurity.salvation.directiveValues.KeywordSource;
+import com.shapesecurity.salvation.directiveValues.MediaType;
+import com.shapesecurity.salvation.directiveValues.NonceSource;
+import com.shapesecurity.salvation.directiveValues.None;
+import com.shapesecurity.salvation.directiveValues.SchemeSource;
+import com.shapesecurity.salvation.directiveValues.SourceExpression;
+import com.shapesecurity.salvation.directives.ChildSrcDirective;
+import com.shapesecurity.salvation.directives.ConnectSrcDirective;
+import com.shapesecurity.salvation.directives.DefaultSrcDirective;
+import com.shapesecurity.salvation.directives.Directive;
+import com.shapesecurity.salvation.directives.DirectiveValue;
+import com.shapesecurity.salvation.directives.FetchDirective;
+import com.shapesecurity.salvation.directives.FontSrcDirective;
+import com.shapesecurity.salvation.directives.FrameAncestorsDirective;
+import com.shapesecurity.salvation.directives.FrameSrcDirective;
+import com.shapesecurity.salvation.directives.ImgSrcDirective;
+import com.shapesecurity.salvation.directives.ManifestSrcDirective;
+import com.shapesecurity.salvation.directives.MediaSrcDirective;
+import com.shapesecurity.salvation.directives.ObjectSrcDirective;
+import com.shapesecurity.salvation.directives.PluginTypesDirective;
+import com.shapesecurity.salvation.directives.ReferrerDirective;
+import com.shapesecurity.salvation.directives.ReportToDirective;
+import com.shapesecurity.salvation.directives.ReportUriDirective;
+import com.shapesecurity.salvation.directives.ScriptSrcDirective;
+import com.shapesecurity.salvation.directives.SourceListDirective;
+import com.shapesecurity.salvation.directives.StyleSrcDirective;
 import com.shapesecurity.salvation.interfaces.Show;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import java.util.*;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class Policy implements Show {
 
@@ -345,7 +380,7 @@ public class Policy implements Show {
 
 
     private boolean defaultsAllowHash(@Nonnull HashAlgorithm algorithm, @Nonnull Base64Value hashValue) {
-        if (this.defaultsHaveUnsafeInline() && !this.defaultsHaveStrictDynamic())
+        if (this.defaultsHaveUnsafeInline() && !this.defaultsHaveNonceSource() && !this.defaultsHaveHashSource() && !this.defaultsHaveStrictDynamic())
             return true;
         DefaultSrcDirective defaultSrcDirective = this.getDirectiveByType(DefaultSrcDirective.class);
         if (defaultSrcDirective == null) {
@@ -355,7 +390,7 @@ public class Policy implements Show {
     }
 
     private boolean defaultsAllowNonce(@Nonnull String nonce) {
-        if (this.defaultsHaveUnsafeInline() && !this.defaultsHaveStrictDynamic())
+        if (this.defaultsHaveUnsafeInline() && !this.defaultsHaveHashSource() && !this.defaultsHaveNonceSource() && !this.defaultsHaveStrictDynamic())
             return true;
         DefaultSrcDirective defaultSrcDirective = this.getDirectiveByType(DefaultSrcDirective.class);
         if (defaultSrcDirective == null) {
@@ -392,6 +427,22 @@ public class Policy implements Show {
         return defaultSrcDirective.values().anyMatch(x -> x == KeywordSource.UnsafeInline);
     }
 
+    private boolean defaultsHaveNonceSource() {
+        DefaultSrcDirective defaultSrcDirective = this.getDirectiveByType(DefaultSrcDirective.class);
+        if (defaultSrcDirective == null) {
+            return false;
+        }
+        return defaultSrcDirective.values().anyMatch(x -> x instanceof NonceSource);
+    }
+
+    private boolean defaultsHaveHashSource() {
+        DefaultSrcDirective defaultSrcDirective = this.getDirectiveByType(DefaultSrcDirective.class);
+        if (defaultSrcDirective == null) {
+            return false;
+        }
+        return defaultSrcDirective.values().anyMatch(x -> x instanceof HashSource);
+    }
+
     private boolean defaultsHaveStrictDynamic() {
         DefaultSrcDirective defaultSrcDirective = this.getDirectiveByType(DefaultSrcDirective.class);
         if (defaultSrcDirective == null) {
@@ -493,27 +544,33 @@ public class Policy implements Show {
     }
 
     public boolean allowsUnsafeInlineScript() {
-        return this.hasUnsafeInlineScript() && !this.hasStrictDynamic();
+        return containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline) &&
+                !containsSourceExpression(ScriptSrcDirective.class, x -> x instanceof NonceSource) &&
+                !containsSourceExpression(ScriptSrcDirective.class, x -> x instanceof HashSource) &&
+                !containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic);
+
     }
 
-    public boolean hasUnsafeInlineScript() {
-        ScriptSrcDirective scriptSrcDirective = this.getDirectiveByType(ScriptSrcDirective.class);
-        if (scriptSrcDirective == null) {
-            return this.defaultsHaveUnsafeInline();
+    public <T extends SourceListDirective> boolean containsSourceExpression(Class<T> type, @Nonnull Predicate<SourceExpression> predicate) {
+        T d = this.getDirectiveByType(type);
+        if (d == null) {
+            return type != DefaultSrcDirective.class && this.containsSourceExpression(DefaultSrcDirective.class, predicate);
         }
-        return scriptSrcDirective.values().anyMatch(x -> x == KeywordSource.UnsafeInline);
+        return d.values().anyMatch(predicate);
     }
-
-    public boolean hasUnsafeInlineStyle() {
-        StyleSrcDirective styleSrcDirective = this.getDirectiveByType(StyleSrcDirective.class);
-        if (styleSrcDirective == null) {
-            return this.defaultsHaveUnsafeInline();
+    @Nonnull
+    public <T extends SourceListDirective>  Stream<SourceExpression> getEffectiveSourceExpressions(Class<T> type) {
+        SourceListDirective d = this.getDirectiveByType(type);
+        if (d == null && type != DefaultSrcDirective.class) {
+            d = this.getDirectiveByType(DefaultSrcDirective.class);
         }
-        return styleSrcDirective.values().anyMatch(x -> x == KeywordSource.UnsafeInline);
+        return d != null ? d.values() : Stream.empty();
     }
 
     public boolean allowsUnsafeInlineStyle() {
-        return this.hasUnsafeInlineStyle();
+        return containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline) &&
+                !containsSourceExpression(StyleSrcDirective.class, x -> x instanceof NonceSource) &&
+                !containsSourceExpression(StyleSrcDirective.class, x -> x instanceof HashSource);
     }
 
     public boolean allowsPlugin(@Nonnull MediaType mediaType) {


Test patch:
diff --git a/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java b/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
index 15517f6..dfa2444 100644
--- a/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
+++ b/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
@@ -5,7 +5,11 @@ import com.shapesecurity.salvation.data.GUID;
 import com.shapesecurity.salvation.data.Policy;
 import com.shapesecurity.salvation.data.URI;
 import com.shapesecurity.salvation.directiveValues.HashSource;
+import com.shapesecurity.salvation.directiveValues.HostSource;
+import com.shapesecurity.salvation.directiveValues.KeywordSource;
 import com.shapesecurity.salvation.directiveValues.MediaType;
+import com.shapesecurity.salvation.directiveValues.NonceSource;
+import com.shapesecurity.salvation.directiveValues.SourceExpression;
 import com.shapesecurity.salvation.directives.ChildSrcDirective;
 import com.shapesecurity.salvation.directives.ConnectSrcDirective;
 import com.shapesecurity.salvation.directives.DefaultSrcDirective;
@@ -19,6 +23,8 @@ import com.shapesecurity.salvation.directives.ScriptSrcDirective;
 import com.shapesecurity.salvation.directives.StyleSrcDirective;
 import org.junit.Test;
 
+import java.util.stream.Stream;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -388,12 +394,11 @@ public class PolicyQueryingTest extends CSPTest {
     @Test public void testStrictDynamic() {
         Policy p;
 
-        // NOTE, this behaviour will likely change, but for now presence of hash does not invalidate unsafe-inline
         p = Parser.parse("default-src 'unsafe-inline' 'strict-dynamic'", "http://example.com");
-        assertTrue(p.hasStrictDynamic());
-        assertTrue(p.hasUnsafeInlineScript());
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
         assertFalse(p.allowsUnsafeInlineScript());
-        assertTrue(p.hasUnsafeInlineStyle());
         assertTrue(p.allowsUnsafeInlineStyle());
         assertFalse(p.allowsScriptWithNonce("123"));
         assertTrue(p.allowsStyleWithNonce("123"));
@@ -402,10 +407,9 @@ public class PolicyQueryingTest extends CSPTest {
         assertTrue(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
                 "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
 
-        // NOTE, this behaviour will likely change, but for now presence of hash does not invalidate unsafe-inline
         p = Parser.parse("default-src 'unsafe-inline' 'strict-dynamic' 'nonce-123' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='", "http://example.com");
-        assertTrue(p.hasStrictDynamic());
-        assertTrue(p.hasUnsafeInlineScript());
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
         assertFalse(p.allowsUnsafeInlineScript());
         assertTrue(p.allowsScriptWithNonce("123"));
         assertFalse(p.allowsScriptWithNonce("345"));
@@ -413,20 +417,20 @@ public class PolicyQueryingTest extends CSPTest {
                 "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
         assertFalse(p.allowsScriptWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
 
-        assertTrue(p.allowsUnsafeInlineStyle());
-        assertTrue(p.hasUnsafeInlineStyle());
+        assertFalse(p.allowsUnsafeInlineStyle());
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
         assertTrue(p.allowsStyleWithNonce("123"));
-        assertTrue(p.allowsStyleWithNonce("345"));
+        assertFalse(p.allowsStyleWithNonce("345"));
         assertTrue(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
                 "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
-        assertTrue(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+        assertFalse(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
 
         p = Parser.parse("default-src 'unsafe-inline' 'strict-dynamic' 'nonce-123' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='; script-src;", "http://example.com");
-        assertFalse(p.hasStrictDynamic());
-        assertFalse(p.hasUnsafeInlineScript());
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
         assertFalse(p.allowsUnsafeInlineScript());
-        assertTrue(p.hasUnsafeInlineStyle());
-        assertTrue(p.allowsUnsafeInlineStyle());
+        assertFalse(p.allowsUnsafeInlineStyle());
 
         assertFalse(p.allowsScriptWithNonce("123"));
         assertFalse(p.allowsScriptWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
@@ -435,12 +439,14 @@ public class PolicyQueryingTest extends CSPTest {
         assertTrue(p.allowsStyleWithNonce("123"));
         assertTrue(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
                 "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+        assertFalse(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+                "cGl6ZGE=")));
 
         p = Parser.parse("default-src 'unsafe-inline' 'strict-dynamic' 'nonce-123' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='; style-src;", "http://example.com");
-        assertTrue(p.hasStrictDynamic());
-        assertTrue(p.hasUnsafeInlineScript());
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
         assertFalse(p.allowsUnsafeInlineScript());
-        assertFalse(p.hasUnsafeInlineStyle());
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
         assertFalse(p.allowsUnsafeInlineStyle());
 
         assertTrue(p.allowsScriptWithNonce("123"));
@@ -451,6 +457,64 @@ public class PolicyQueryingTest extends CSPTest {
         assertFalse(p.allowsStyleWithNonce("123"));
         assertFalse(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
                 "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+
+        p = Parser.parse("script-src 'unsafe-inline' 'nonce-forscript' 'strict-dynamic'; style-src 'unsafe-inline' 'nonce-forstyle'", "http://example.com");
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.allowsUnsafeInlineScript());
+        assertFalse(p.allowsUnsafeInlineStyle());
+        assertFalse(p.allowsScriptWithNonce("123"));
+        assertFalse(p.allowsStyleWithNonce("123"));
+        assertFalse(p.allowsScriptWithNonce("1234"));
+        assertFalse(p.allowsStyleWithNonce("1234"));
+        assertFalse(p.allowsScriptWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+                "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+        assertFalse(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+                "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+        assertFalse(p.allowsScriptWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+        assertFalse(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+        assertTrue(p.allowsScriptWithNonce("forscript"));
+        assertFalse(p.allowsStyleWithNonce("forscript"));
+        assertFalse(p.allowsScriptWithNonce("forstyle"));
+        assertTrue(p.allowsStyleWithNonce("forstyle"));
+    }
+
+    @Test public void testHashAndNonceInvalidateUnsafeInline() {
+        Policy p;
+
+        p = Parser.parse("default-src 'unsafe-inline' 'nonce-123' ", "http://example.com");
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x instanceof HashSource));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x instanceof HashSource));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x instanceof NonceSource));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x instanceof NonceSource));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(DefaultSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.allowsUnsafeInlineScript());
+        assertFalse(p.allowsUnsafeInlineStyle());
+        assertTrue(p.allowsScriptWithNonce("123"));
+        assertTrue(p.allowsStyleWithNonce("123"));
+        assertFalse(p.allowsScriptWithNonce("1234"));
+        assertFalse(p.allowsStyleWithNonce("1234"));
+
+        p = Parser.parse("default-src 'unsafe-inline' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==' ", "http://example.com");
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.allowsUnsafeInlineScript());
+        assertFalse(p.allowsUnsafeInlineStyle());
+        assertFalse(p.allowsScriptWithNonce("123"));
+        assertFalse(p.allowsStyleWithNonce("123"));
+        assertFalse(p.allowsScriptWithNonce("1234"));
+        assertFalse(p.allowsStyleWithNonce("1234"));
+        assertTrue(p.allowsScriptWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+                "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+        assertTrue(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+                "vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+        assertFalse(p.allowsScriptWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+        assertFalse(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
     }
 
     @Test public void testWildcards() {
@@ -779,7 +843,153 @@ public class PolicyQueryingTest extends CSPTest {
         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 testContainsSourceExpression() {
+        Policy p;
+
+        p = Parser.parse("", "http://example.com");
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.Self));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x instanceof NonceSource));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.Self));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x instanceof NonceSource));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.Self));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x instanceof NonceSource));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        p = Parser.parse("default-src a 'self' 'unsafe-eval' 'unsafe-redirect' 'nonce-123' 'strict-dynamic' 'unsafe-inline'", "http://example.com");
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.Self));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x instanceof NonceSource));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.Self));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x instanceof NonceSource));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        assertTrue(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.Self));
+        assertTrue(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertTrue(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertTrue(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertTrue(p.containsSourceExpression(ImgSrcDirective.class, x -> x instanceof NonceSource));
+        assertTrue(p.containsSourceExpression(ImgSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+
+        p = Parser.parse("script-src a 'self' 'unsafe-eval' 'nonce-123' 'unsafe-redirect' 'strict-dynamic' 'unsafe-inline'", "http://example.com");
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.Self));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x instanceof NonceSource));
+        assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.Self));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x instanceof NonceSource));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.Self));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x instanceof NonceSource));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        p = Parser.parse("style-src a 'self' 'unsafe-eval' 'unsafe-redirect' 'nonce-123' 'strict-dynamic' 'unsafe-inline'", "http://example.com");
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.Self));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x instanceof NonceSource));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(ScriptSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.Self));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x instanceof NonceSource));
+        assertTrue(p.containsSourceExpression(StyleSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(StyleSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.Self));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x instanceof NonceSource));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+
+        p = Parser.parse("upgrade-insecure-requests", "http://example.com");
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeEval));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeInline));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.UnsafeRedirect));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x == KeywordSource.StrictDynamic));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x instanceof NonceSource));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x.equals(new HostSource(null, "a", Constants.EMPTY_PORT, null))));
+        assertFalse(p.containsSourceExpression(ImgSrcDirective.class, x -> x.equals(new HostSource(null, "b", Constants.EMPTY_PORT, null))));
+    }
+
+    @Test public void testSourceExpressionStream() {
+        Policy p;
+        Stream<SourceExpression> s;
+
+        p = Parser.parse("upgrade-insecure-requests", "http://example.com");
+        s = p.getEffectiveSourceExpressions(DefaultSrcDirective.class);
+        assertEquals(0, s.count());
+
+        p = Parser.parse("script-src a b c", "http://example.com");
+        s = p.getEffectiveSourceExpressions(ScriptSrcDirective.class);
+        assertEquals(3, s.count());
+
+        p = Parser.parse("script-src https: https://a.com http://b.com", "http://example.com");
+        s = p.getEffectiveSourceExpressions(ScriptSrcDirective.class);
+        assertEquals(2, s.filter(x -> x.show().startsWith("https")).count());
+
+        p = Parser.parse("default-src https: https://a.com http://b.com", "http://example.com");
+        s = p.getEffectiveSourceExpressions(ScriptSrcDirective.class);
+        assertEquals(2, s.filter(x -> x.show().startsWith("https")).count());
     }
 
     @Test public void testEmptyPolicy() {