Repository: shapesecurity/salvation
Pull request #177 (2017-03-20 06:37:32)

GH-147: Support worker-src and frame-src
`worker-src` defaults to `script-src`, `frame-src` defaults to deprecated `child-src`.
fixes GH-147. 




Patch:
diff --git a/src/main/java/com/shapesecurity/salvation/Parser.java b/src/main/java/com/shapesecurity/salvation/Parser.java
index 16a18c8..43307ce 100644
--- a/src/main/java/com/shapesecurity/salvation/Parser.java
+++ b/src/main/java/com/shapesecurity/salvation/Parser.java
@@ -214,6 +214,8 @@ public class Parser {
                     result = new BlockAllMixedContentDirective();
                     break;
                 case ChildSrc:
+                    this.warn(token,
+                            "The child-src directive is deprecated as of CSP level 3. Authors who wish to regulate nested browsing contexts and workers SHOULD use the frame-src and worker-src directives, respectively.");
                     result = new ChildSrcDirective(this.parseSourceList());
                     break;
                 case ConnectSrc:
@@ -288,14 +290,15 @@ public class Parser {
                     this.enforceMissingDirectiveValue(token);
                     result = new UpgradeInsecureRequestsDirective();
                     break;
+                case WorkerSrc:
+                    result = new WorkerSrcDirective(this.parseSourceList());
+                    break;
                 case Allow:
                     this.error(token,
                         "The allow directive has been replaced with default-src and is not in the CSP specification.");
                     this.eat(DirectiveValueToken.class);
                     throw INVALID_DIRECTIVE_NAME;
                 case FrameSrc:
-                    this.warn(token,
-                        "The frame-src directive is deprecated as of CSP version 1.1. Authors who wish to govern nested browsing contexts SHOULD use the child-src directive instead.");
                     result = new FrameSrcDirective(this.parseSourceList());
                     break;
                 case Options:
diff --git a/src/main/java/com/shapesecurity/salvation/Tokeniser.java b/src/main/java/com/shapesecurity/salvation/Tokeniser.java
index 1de9a3d..8990a21 100644
--- a/src/main/java/com/shapesecurity/salvation/Tokeniser.java
+++ b/src/main/java/com/shapesecurity/salvation/Tokeniser.java
@@ -132,6 +132,7 @@ public class Tokeniser {
                     case ObjectSrc:
                     case ScriptSrc:
                     case StyleSrc:
+                    case WorkerSrc:
                 // media-type-list
                     case PluginTypes:
                 // ancestor-source-list
diff --git a/src/main/java/com/shapesecurity/salvation/data/Policy.java b/src/main/java/com/shapesecurity/salvation/data/Policy.java
index 6450c98..ad39601 100644
--- a/src/main/java/com/shapesecurity/salvation/data/Policy.java
+++ b/src/main/java/com/shapesecurity/salvation/data/Policy.java
@@ -29,6 +29,7 @@ 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.directives.WorkerSrcDirective;
 import com.shapesecurity.salvation.interfaces.Show;
 
 import javax.annotation.Nonnull;
@@ -116,38 +117,51 @@ public class Policy implements Show {
     private void expandDefaultSrc() {
         DefaultSrcDirective defaultSrcDirective = this.getDirectiveByType(DefaultSrcDirective.class);
         Set<SourceExpression> defaultSources;
-        if (defaultSrcDirective == null) {
-            return;
-        } else {
+        if (defaultSrcDirective != null) {
+
             defaultSources = defaultSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
-        }
 
-        if (!this.directives.containsKey(ScriptSrcDirective.class)) {
-            this.unionDirectivePrivate(new ScriptSrcDirective(defaultSources));
-        }
-        if (!this.directives.containsKey(StyleSrcDirective.class)) {
-            this.unionDirectivePrivate(new StyleSrcDirective(defaultSources));
-        }
-        if (!this.directives.containsKey(ImgSrcDirective.class)) {
-            this.unionDirectivePrivate(new ImgSrcDirective(defaultSources));
-        }
-        if (!this.directives.containsKey(ChildSrcDirective.class)) {
-            this.unionDirectivePrivate(new ChildSrcDirective(defaultSources));
-        }
-        if (!this.directives.containsKey(ConnectSrcDirective.class)) {
-            this.unionDirectivePrivate(new ConnectSrcDirective(defaultSources));
-        }
-        if (!this.directives.containsKey(FontSrcDirective.class)) {
-            this.unionDirectivePrivate(new FontSrcDirective(defaultSources));
-        }
-        if (!this.directives.containsKey(MediaSrcDirective.class)) {
-            this.unionDirectivePrivate(new MediaSrcDirective(defaultSources));
+            if (!this.directives.containsKey(ScriptSrcDirective.class)) {
+                this.unionDirectivePrivate(new ScriptSrcDirective(defaultSources));
+            }
+            if (!this.directives.containsKey(StyleSrcDirective.class)) {
+                this.unionDirectivePrivate(new StyleSrcDirective(defaultSources));
+            }
+            if (!this.directives.containsKey(ImgSrcDirective.class)) {
+                this.unionDirectivePrivate(new ImgSrcDirective(defaultSources));
+            }
+            if (!this.directives.containsKey(ChildSrcDirective.class)) {
+                this.unionDirectivePrivate(new ChildSrcDirective(defaultSources));
+            }
+            if (!this.directives.containsKey(ConnectSrcDirective.class)) {
+                this.unionDirectivePrivate(new ConnectSrcDirective(defaultSources));
+            }
+            if (!this.directives.containsKey(FontSrcDirective.class)) {
+                this.unionDirectivePrivate(new FontSrcDirective(defaultSources));
+            }
+            if (!this.directives.containsKey(MediaSrcDirective.class)) {
+                this.unionDirectivePrivate(new MediaSrcDirective(defaultSources));
+            }
+            if (!this.directives.containsKey(ObjectSrcDirective.class)) {
+                this.unionDirectivePrivate(new ObjectSrcDirective(defaultSources));
+            }
+            if (!this.directives.containsKey(ManifestSrcDirective.class)) {
+                this.unionDirectivePrivate(new ManifestSrcDirective(defaultSources));
+            }
         }
-        if (!this.directives.containsKey(ObjectSrcDirective.class)) {
-            this.unionDirectivePrivate(new ObjectSrcDirective(defaultSources));
+
+        // expand child-src
+        if (this.directives.containsKey(ChildSrcDirective.class) && !this.directives.containsKey(FrameSrcDirective.class)) {
+            ChildSrcDirective childSrcDirective = this.getDirectiveByType(ChildSrcDirective.class);
+            Set<SourceExpression> childSources = childSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
+            this.unionDirectivePrivate(new FrameSrcDirective(childSources));
         }
-        if (!this.directives.containsKey(ManifestSrcDirective.class)) {
-            this.unionDirectivePrivate(new ManifestSrcDirective(defaultSources));
+
+        // expand script-src
+        if (this.directives.containsKey(ScriptSrcDirective.class) && !this.directives.containsKey(WorkerSrcDirective.class)) {
+            ScriptSrcDirective scriptSrcDirective = this.getDirectiveByType(ScriptSrcDirective.class);
+            Set<SourceExpression> scriptSources = scriptSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
+            this.unionDirectivePrivate(new WorkerSrcDirective(scriptSources));
         }
     }
 
@@ -203,6 +217,20 @@ public class Policy implements Show {
             }
         }
 
+        // * remove frame source directive if equivalent to child-src
+        ChildSrcDirective childSrcDirective = this.getDirectiveByType(ChildSrcDirective.class);
+        if (childSrcDirective != null) {
+            Set<SourceExpression> childSources = childSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
+            this.eliminateRedundantSourceExpression(childSources, FrameSrcDirective.class);
+        }
+
+        // * remove worker source directive if equivalent to script-src
+        ScriptSrcDirective scriptSrcDirective = this.getDirectiveByType(ScriptSrcDirective.class);
+        if (scriptSrcDirective != null) {
+            Set<SourceExpression> scriptSources = scriptSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
+            this.eliminateRedundantSourceExpression(scriptSources, WorkerSrcDirective.class);
+        }
+
         DefaultSrcDirective defaultSrcDirective = this.getDirectiveByType(DefaultSrcDirective.class);
 
         Set<SourceExpression> defaultSources;
@@ -614,6 +642,30 @@ public class Policy implements Show {
         return childSrcDirective.matchesSource(this.origin, source);
     }
 
+    public boolean allowsChildFromSource(@Nonnull GUID source) {
+        ChildSrcDirective childSrcDirective = this.getDirectiveByType(ChildSrcDirective.class);
+        if (childSrcDirective == null) {
+            return this.defaultsAllowSource(source);
+        }
+        return childSrcDirective.matchesSource(this.origin, source);
+    }
+
+    public boolean allowsWorkerFromSource(@Nonnull URI source) {
+        WorkerSrcDirective workerSrcDirective = this.getDirectiveByType(WorkerSrcDirective.class);
+        if (workerSrcDirective == null) {
+            return this.allowsScriptFromSource(source);
+        }
+        return workerSrcDirective.matchesSource(this.origin, source);
+    }
+
+    public boolean allowsWorkerFromSource(@Nonnull GUID source) {
+        WorkerSrcDirective workerSrcDirective = this.getDirectiveByType(WorkerSrcDirective.class);
+        if (workerSrcDirective == null) {
+            return this.allowsScriptFromSource(source);
+        }
+        return workerSrcDirective.matchesSource(this.origin, source);
+    }
+
     public boolean allowsFrameFromSource(@Nonnull URI source) {
         FrameSrcDirective frameSrcDirective = this.getDirectiveByType(FrameSrcDirective.class);
         if (frameSrcDirective == null) {
@@ -622,6 +674,14 @@ public class Policy implements Show {
         return frameSrcDirective.matchesSource(this.origin, source);
     }
 
+    public boolean allowsFrameFromSource(@Nonnull GUID source) {
+        FrameSrcDirective frameSrcDirective = this.getDirectiveByType(FrameSrcDirective.class);
+        if (frameSrcDirective == null) {
+            return this.allowsChildFromSource(source);
+        }
+        return frameSrcDirective.matchesSource(this.origin, source);
+    }
+
     public boolean allowsFrameAncestor(@Nonnull URI source) {
         FrameAncestorsDirective frameAncestorsDirective = this.getDirectiveByType(FrameAncestorsDirective.class);
         if (frameAncestorsDirective == null) {
diff --git a/src/main/java/com/shapesecurity/salvation/directives/Directive.java b/src/main/java/com/shapesecurity/salvation/directives/Directive.java
index 65ca733..6ff1af5 100644
--- a/src/main/java/com/shapesecurity/salvation/directives/Directive.java
+++ b/src/main/java/com/shapesecurity/salvation/directives/Directive.java
@@ -20,11 +20,20 @@ public abstract class Directive<Value extends DirectiveValue> implements Show {
         return fetchDirectives;
     }
 
+    public static List<Class<? extends Directive>> getNestedContextDirectives() {
+        return nestedContextDirectives;
+    }
+
     static List<Class<? extends Directive>> fetchDirectives = new ArrayList<>();
+    static List<Class<? extends Directive>> nestedContextDirectives = new ArrayList<>();
+
     static public final int FETCH_DIRECIVE_COUNT;
+    static public final int NESTED_CONTEXT_DIRECTIVE_COUNT;
 
     static void register(Class<? extends Directive> directiveClass) {
-        if (FetchDirective.class.isAssignableFrom(directiveClass) && directiveClass != DefaultSrcDirective.class) {
+        if (NestedContextDirective.class.isAssignableFrom(directiveClass)) {
+            nestedContextDirectives.add(directiveClass);
+        } else if (FetchDirective.class.isAssignableFrom(directiveClass) && directiveClass != DefaultSrcDirective.class && directiveClass != FrameSrcDirective.class && directiveClass != WorkerSrcDirective.class) {
             fetchDirectives.add(directiveClass);
         }
     }
@@ -51,10 +60,11 @@ public abstract class Directive<Value extends DirectiveValue> implements Show {
         register(PluginTypesDirective.class);
         register(FormActionDirective.class);
         register(UpgradeInsecureRequestsDirective.class);
+        register(WorkerSrcDirective.class);
         register(BlockAllMixedContentDirective.class);
         register(BaseUriDirective.class);
         FETCH_DIRECIVE_COUNT = fetchDirectives.size();
-
+        NESTED_CONTEXT_DIRECTIVE_COUNT = nestedContextDirectives.size();
     }
 
     Directive(@Nonnull String name, @Nonnull Set<Value> values) {
diff --git a/src/main/java/com/shapesecurity/salvation/directives/FrameSrcDirective.java b/src/main/java/com/shapesecurity/salvation/directives/FrameSrcDirective.java
index 645026f..5df3185 100644
--- a/src/main/java/com/shapesecurity/salvation/directives/FrameSrcDirective.java
+++ b/src/main/java/com/shapesecurity/salvation/directives/FrameSrcDirective.java
@@ -5,7 +5,7 @@ import com.shapesecurity.salvation.directiveValues.SourceExpression;
 import javax.annotation.Nonnull;
 import java.util.Set;
 
-public class FrameSrcDirective extends SourceListDirective {
+public class FrameSrcDirective extends NestedContextDirective {
     @Nonnull private static final String name = "frame-src";
 
     public FrameSrcDirective(@Nonnull Set<SourceExpression> sourceExpressions) {
diff --git a/src/main/java/com/shapesecurity/salvation/directives/NestedContextDirective.java b/src/main/java/com/shapesecurity/salvation/directives/NestedContextDirective.java
new file mode 100644
index 0000000..19b1f61
--- /dev/null
+++ b/src/main/java/com/shapesecurity/salvation/directives/NestedContextDirective.java
@@ -0,0 +1,13 @@
+package com.shapesecurity.salvation.directives;
+
+import com.shapesecurity.salvation.directiveValues.SourceExpression;
+
+import javax.annotation.Nonnull;
+import java.util.Set;
+
+public abstract class NestedContextDirective extends FetchDirective {
+
+    public NestedContextDirective(@Nonnull String name, @Nonnull Set<SourceExpression> sourceExpressions) {
+        super(name, sourceExpressions);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/shapesecurity/salvation/directives/WorkerSrcDirective.java b/src/main/java/com/shapesecurity/salvation/directives/WorkerSrcDirective.java
new file mode 100644
index 0000000..f5b795b
--- /dev/null
+++ b/src/main/java/com/shapesecurity/salvation/directives/WorkerSrcDirective.java
@@ -0,0 +1,18 @@
+package com.shapesecurity.salvation.directives;
+
+import com.shapesecurity.salvation.directiveValues.SourceExpression;
+
+import javax.annotation.Nonnull;
+import java.util.Set;
+
+public class WorkerSrcDirective extends NestedContextDirective {
+    @Nonnull private static final String NAME = "worker-src";
+
+    public WorkerSrcDirective(@Nonnull Set<SourceExpression> sourceExpressions) {
+        super(WorkerSrcDirective.NAME, sourceExpressions);
+    }
+
+    @Nonnull @Override public Directive<SourceExpression> construct(Set<SourceExpression> newValues) {
+        return new WorkerSrcDirective(newValues);
+    }
+}
diff --git a/src/main/java/com/shapesecurity/salvation/tokens/DirectiveNameToken.java b/src/main/java/com/shapesecurity/salvation/tokens/DirectiveNameToken.java
index 7e0d28c..b97252d 100644
--- a/src/main/java/com/shapesecurity/salvation/tokens/DirectiveNameToken.java
+++ b/src/main/java/com/shapesecurity/salvation/tokens/DirectiveNameToken.java
@@ -38,6 +38,7 @@ public class DirectiveNameToken extends Token {
         Sandbox,
         ScriptSrc,
         StyleSrc,
+        WorkerSrc,
 
         UpgradeInsecureRequests, // W3C Candidate Recommendation at https://www.w3.org/TR/upgrade-insecure-requests/#delivery as of 2015-10-08
 
@@ -89,6 +90,8 @@ public class DirectiveNameToken extends Token {
                     return StyleSrc;
                 case "upgrade-insecure-requests":
                     return UpgradeInsecureRequests;
+                case "worker-src":
+                    return WorkerSrc;
 
                 // deprecated directives
                 case "allow":


Test patch:
diff --git a/src/test/java/com/shapesecurity/salvation/LocationTest.java b/src/test/java/com/shapesecurity/salvation/LocationTest.java
index cfa76f1..6e1847e 100644
--- a/src/test/java/com/shapesecurity/salvation/LocationTest.java
+++ b/src/test/java/com/shapesecurity/salvation/LocationTest.java
@@ -147,9 +147,9 @@ public class LocationTest extends CSPTest {
         assertEquals(18, tokens[2].endLocation.offset);
     }
 
-    @Test public void testWarningLocationFrameSrc() {
+    @Test public void testWarningLocationChildSrc() {
         ArrayList<Notice> notices = new ArrayList<>();
-        ParserWithLocation.parse("frame-src aaa", "https://origin", notices);
+        ParserWithLocation.parse("child-src aaa", "https://origin", notices);
         assertEquals(1, notices.size());
         Notice notice = notices.get(0);
         assertNotNull(notice);
@@ -484,12 +484,12 @@ public class LocationTest extends CSPTest {
         ParserWithLocation.parse(
             "script-src 'unsafe-redirect' aaa; manifest-src; script-src д; стайл-соурс 22; frame-src 'none'; style-src 'nonce-123'",
             URI.parse("https://origin"), notices);
-        assertEquals(6, notices.size());
+        assertEquals(5, notices.size());
         ArrayList<Notice> errors = Notice.getAllErrors(notices);
         ArrayList<Notice> warnings = Notice.getAllWarnings(notices);
         ArrayList<Notice> infos = Notice.getAllInfos(notices);
         assertEquals(2, errors.size());
-        assertEquals(4, warnings.size());
+        assertEquals(3, warnings.size());
         assertEquals(0, infos.size());
 
         notices.clear();
diff --git a/src/test/java/com/shapesecurity/salvation/ParserTest.java b/src/test/java/com/shapesecurity/salvation/ParserTest.java
index 817545c..a829634 100644
--- a/src/test/java/com/shapesecurity/salvation/ParserTest.java
+++ b/src/test/java/com/shapesecurity/salvation/ParserTest.java
@@ -66,6 +66,10 @@ public class ParserTest extends CSPTest {
         assertNotNull("policy should not be null", p);
         assertEquals("directive count", 1, p.getDirectives().size());
 
+        p = parse("worker-src a");
+        assertNotNull("policy should not be null", p);
+        assertEquals("directive count", 1, p.getDirectives().size());
+
         p = parse("img-src a");
         assertNotNull("policy should not be null", p);
         assertEquals("directive count", 1, p.getDirectives().size());
@@ -677,12 +681,12 @@ public class ParserTest extends CSPTest {
 
     @Test public void testWarnings() {
         ArrayList<Notice> notices = new ArrayList<>();
-        Policy p1 = Parser.parse("frame-src aaa", "https://origin", notices);
+        Policy p1 = Parser.parse("child-src aaa", "https://origin", notices);
 
-        assertEquals("frame-src aaa", p1.show());
+        assertEquals("child-src aaa", p1.show());
         assertEquals(1, notices.size());
         assertEquals(
-            "The frame-src directive is deprecated as of CSP version 1.1. Authors who wish to govern nested browsing contexts SHOULD use the child-src directive instead.",
+            "The child-src directive is deprecated as of CSP level 3. Authors who wish to regulate nested browsing contexts and workers SHOULD use the frame-src and worker-src directives, respectively.",
             notices.iterator().next().message);
     }
 
@@ -912,13 +916,13 @@ public class ParserTest extends CSPTest {
         assertEquals("1:36: 'unsafe-redirect' has been removed from CSP as of version 2.0.", notices.get(0).show());
 
         notices = new ArrayList<>();
-        pl = ParserWithLocation.parseMulti("script-src a, frame-src b", URI.parse("https://origin.com"), notices);
+        pl = ParserWithLocation.parseMulti("script-src a, child-src b", URI.parse("https://origin.com"), notices);
         assertEquals(2, pl.size());
         assertEquals("script-src a", pl.get(0).show());
-        assertEquals("frame-src b", pl.get(1).show());
+        assertEquals("child-src b", pl.get(1).show());
         assertEquals(1, notices.size());
         assertEquals(
-            "1:15: The frame-src directive is deprecated as of CSP version 1.1. Authors who wish to govern nested browsing contexts SHOULD use the child-src directive instead.",
+            "1:15: The child-src directive is deprecated as of CSP level 3. Authors who wish to regulate nested browsing contexts and workers SHOULD use the frame-src and worker-src directives, respectively.",
             notices.get(0).show());
 
         pl.clear();
diff --git a/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java b/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java
index 47bcce6..45c1657 100644
--- a/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java
+++ b/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java
@@ -450,4 +450,74 @@ public class PolicyMergeTest extends CSPTest {
         assertEquals("default-src 'self' http://abc/a", p.show());
     }
 
+    @Test public void testUnionChildSrc(){
+        Policy p = parse("child-src 'self' example.org; ");
+        Policy q = parse("child-src 'self' EXAMPLE.ORG; ");
+        p.union(q);
+        assertEquals("child-src 'self' example.org", p.show());
+
+        p = parse("child-src 'self' EXAMPLE.ORG; ");
+        q = parse("child-src 'self' example.org; ");
+        p.union(q);
+        assertEquals("child-src 'self' EXAMPLE.ORG", p.show());
+
+        p = parse("child-src a; ");
+        q = parse("worker-src a");
+        p.union(q);
+        p.postProcessOptimisation();
+        assertEquals("child-src a; worker-src a", p.show());
+
+        p = parse("child-src a; ");
+        q = parse("worker-src a ");
+        p.union(q);
+        p.postProcessOptimisation();
+        assertEquals("child-src a; worker-src a", p.show());
+
+        p = parse("child-src a b");
+        q = parse("child-src; worker-src x; frame-src y");
+        p.union(q);
+        p.postProcessOptimisation();
+        assertEquals("child-src a b; frame-src a b y; worker-src x", p.show());
+
+        p = parse("child-src *; worker-src");
+        q = parse("child-src; worker-src b");
+        p.union(q);
+        assertEquals("child-src *; worker-src b", p.show());
+
+        p = parse("child-src *; frame-src");
+        q = parse("child-src; frame-src b");
+        p.union(q);
+        assertEquals("child-src *; frame-src b", p.show());
+
+        p = parse("child-src a");
+        q = parse("child-src; worker-src b");
+        p.union(q);
+        assertEquals("child-src a; worker-src b", p.show());
+
+        p = parse("child-src a; worker-src b");
+        q = parse("child-src; worker-src c");
+        p.union(q);
+        assertEquals("child-src a; worker-src b c", p.show());
+
+        p = parse("child-src; worker-src a; frame-src b");
+        q = parse("child-src c");
+        p.union(q);
+        assertEquals("child-src c; worker-src a; frame-src b c", p.show());
+
+        p = parse("child-src a; worker-src b");
+        q = parse("child-src c; frame-src d");
+        p.union(q);
+        assertEquals("child-src a c; worker-src b; frame-src a d", p.show());
+
+        p = parse("child-src b; worker-src a");
+        q = parse("child-src a");
+        p.union(q);
+        assertEquals("child-src b a; worker-src a", p.show());
+
+        p = parse("default-src a");
+        q = parse("worker-src b; frame-src b;");
+        p.union(q);
+        assertEquals("default-src a; frame-src a b; worker-src a b", p.show());
+    }
+
 }
diff --git a/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java b/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
index f3fc103..f1bdfb6 100644
--- a/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
+++ b/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
@@ -10,17 +10,7 @@ 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;
-import com.shapesecurity.salvation.directives.DirectiveValue;
-import com.shapesecurity.salvation.directives.FontSrcDirective;
-import com.shapesecurity.salvation.directives.ImgSrcDirective;
-import com.shapesecurity.salvation.directives.MediaSrcDirective;
-import com.shapesecurity.salvation.directives.ObjectSrcDirective;
-import com.shapesecurity.salvation.directives.ReportUriDirective;
-import com.shapesecurity.salvation.directives.ScriptSrcDirective;
-import com.shapesecurity.salvation.directives.StyleSrcDirective;
+import com.shapesecurity.salvation.directives.*;
 import org.junit.Test;
 
 import java.util.Collections;
@@ -43,6 +33,8 @@ public class PolicyQueryingTest extends CSPTest {
         assertEquals("object-src", parse("object-src").getDirectiveByType(ObjectSrcDirective.class).show());
         assertEquals("script-src", parse("script-src").getDirectiveByType(ScriptSrcDirective.class).show());
         assertEquals("style-src", parse("style-src").getDirectiveByType(StyleSrcDirective.class).show());
+        assertEquals("frame-src", parse("frame-src").getDirectiveByType(FrameSrcDirective.class).show());
+        assertEquals("worker-src", parse("worker-src").getDirectiveByType(WorkerSrcDirective.class).show());
     }
 
     @Test public void testDirectiveContains() {
@@ -105,6 +97,7 @@ public class PolicyQueryingTest extends CSPTest {
         assertFalse("resource is not allowed", p.allowsStyleFromSource(URI.parse("ftp://www.abc.am:555")));
         assertFalse("resource is not allowed", p.allowsScriptFromSource(URI.parse("https://www.def.am:555")));
         assertFalse("resource is not allowed", p.allowsFrameFromSource(URI.parse("https://www.def.am:555")));
+        assertFalse("resource is not allowed", p.allowsWorkerFromSource(URI.parse("https://www.def.am:555")));
         assertFalse("resource is not allowed", p.allowsChildFromSource(URI.parse("https://www.def.am:555")));
 
 
@@ -118,6 +111,11 @@ public class PolicyQueryingTest extends CSPTest {
         assertTrue("resource is allowed", p.allowsFrameFromSource(URI.parse("http://www.def.am:555")));
         assertFalse("resource is not allowed", p.allowsChildFromSource(URI.parse("http://www.def.am:555")));
 
+        p = Parser.parse("default-src 'none'; worker-src http:;", URI.parse("https://abc.com"));
+        assertTrue("resource is allowed", p.allowsWorkerFromSource(URI.parse("https://www.def.am:555")));
+        assertTrue("resource is allowed", p.allowsWorkerFromSource(URI.parse("http://www.def.am:555")));
+        assertFalse("resource is not allowed", p.allowsChildFromSource(URI.parse("http://www.def.am:555")));
+
         p = Parser.parse("child-src http:;", URI.parse("https://abc.com"));
         assertTrue("resource is allowed", p.allowsFrameFromSource(URI.parse("https://www.def.am:555")));
         assertTrue("resource is allowed", p.allowsFrameFromSource(URI.parse("http://www.def.am:555")));
@@ -1161,13 +1159,13 @@ public class PolicyQueryingTest extends CSPTest {
         h = new HostSource(null, "example.com", Constants.EMPTY_PORT, "/a;jsessionid=123");
         d = new ScriptSrcDirective(Collections.singleton(h));
         p.unionDirective(d);
-        assertEquals("script-src 'self' example.com/a%3Bjsessionid=123", p.show());
+        assertEquals("script-src 'self' example.com/a%3Bjsessionid=123; worker-src 'self'", p.show());
 
         p = Parser.parse("script-src 'self'", "http://example.com");
         h = new HostSource(null, "example.com", Constants.EMPTY_PORT, "/a,b");
         d = new ScriptSrcDirective(Collections.singleton(h));
         p.unionDirective(d);
-        assertEquals("script-src 'self' example.com/a%2Cb", p.show());
+        assertEquals("script-src 'self' example.com/a%2Cb; worker-src 'self'", p.show());
 
         p = Parser.parse("script-src example.com/a%3Bjsessionid=123", "http://example.com");
         assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x.equals(new HostSource(null, "example.com", Constants.EMPTY_PORT, "/a%3Bjsessionid=123"))));
@@ -1222,4 +1220,56 @@ public class PolicyQueryingTest extends CSPTest {
         p = Parser.parse("report-to a", "http://example.com");
         assertFalse(p.hasSomeEffect());
     }
+
+    @Test public void testAllowsChild() {
+        Policy p = Parser.parse("default-src 'none'; child-src 'self'", "http://example.com");
+        assertTrue(p.allowsChildFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsFrameFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsWorkerFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsScriptFromSource(URI.parse("http://example.com")));
+
+        p = Parser.parse("child-src 'none'; default-src 'self'", "http://example.com");
+        assertFalse(p.allowsChildFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsFrameFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsWorkerFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsScriptFromSource(URI.parse("http://example.com")));
+
+        p = Parser.parse(" default-src 'self'", "http://example.com");
+        assertTrue(p.allowsChildFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsFrameFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsWorkerFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsScriptFromSource(URI.parse("http://example.com")));
+
+        p = Parser.parse(" child-src 'self'", "http://example.com");
+        assertTrue(p.allowsChildFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsFrameFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsWorkerFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsScriptFromSource(URI.parse("http://example.com")));
+    }
+
+    @Test public void testAllowsWorker() {
+        Policy p = Parser.parse("default-src 'none'; script-src 'self'", "http://example.com");
+        assertFalse(p.allowsChildFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsFrameFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsWorkerFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsScriptFromSource(URI.parse("http://example.com")));
+
+        p = Parser.parse("script-src 'none'; worker-src 'self'", "http://example.com");
+        assertFalse(p.allowsChildFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsFrameFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsWorkerFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsScriptFromSource(URI.parse("http://example.com")));
+
+        p = Parser.parse(" default-src 'self'", "http://example.com");
+        assertTrue(p.allowsChildFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsFrameFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsWorkerFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsScriptFromSource(URI.parse("http://example.com")));
+
+        p = Parser.parse(" script-src 'self'", "http://example.com");
+        assertFalse(p.allowsChildFromSource(URI.parse("http://example.com")));
+        assertFalse(p.allowsFrameFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsWorkerFromSource(URI.parse("http://example.com")));
+        assertTrue(p.allowsScriptFromSource(URI.parse("http://example.com")));
+    }
 }