Repository: shapesecurity/salvation (java)

Issue:
warn on duplicate declarations
`script-src a b; script-src b c` should produce a duplicate declaration warning, even though we have given it intersectional merge semantics (matching common implementation behaviour).

From [the spec](https://www.w3.org/TR/CSP2/#policy-parsing):

> If the set of directives already contains a directive whose name is a case insensitive match for directive name, ignore this instance of the directive and continue to the next token.

Pull request:
fixes #112: warn on duplicate directives
Apparently, we don't merge duplicate directives and we follow the spec. I am sure you would like to report it from different place but this approach is simple and works for me, so giving it a try.

Patch:
diff --git a/src/main/java/com/shapesecurity/salvation/Parser.java b/src/main/java/com/shapesecurity/salvation/Parser.java
index 76c56f0..529c73d 100644
--- a/src/main/java/com/shapesecurity/salvation/Parser.java
+++ b/src/main/java/com/shapesecurity/salvation/Parser.java
@@ -145,7 +145,13 @@ public class Parser {
             if (this.eat(DirectiveSeparatorToken.class))
                 continue;
             try {
-                policy.addDirective(this.parseDirective());
+                Directive<? extends DirectiveValue> directive = this.parseDirective();
+                // only add a directive if it doesn't exist; used for handling duplicate directives in CSP headers
+                if (policy.getDirectiveByType(directive.getClass()) == null) {
+                    policy.addDirective(directive);
+                } else {
+                    this.warn(this.tokens[this.index - 2], "Policy contains more than one " + directive.name + " directive. All but the first instance will be ignored.");
+                }
             } catch (DirectiveParseException ignored) {
             }
         }
diff --git a/src/main/java/com/shapesecurity/salvation/directives/Directive.java b/src/main/java/com/shapesecurity/salvation/directives/Directive.java
index 9cdfdb5..09d2225 100644
--- a/src/main/java/com/shapesecurity/salvation/directives/Directive.java
+++ b/src/main/java/com/shapesecurity/salvation/directives/Directive.java
@@ -15,7 +15,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 public abstract class Directive<Value extends DirectiveValue> implements Show {
-    @Nonnull private final String name;
+    @Nonnull public final String name;
 
     @Nonnull private Set<Value> values;
 


Test patch:
diff --git a/src/test/java/com/shapesecurity/salvation/ParserTest.java b/src/test/java/com/shapesecurity/salvation/ParserTest.java
index 11b05c0..b0f9c09 100644
--- a/src/test/java/com/shapesecurity/salvation/ParserTest.java
+++ b/src/test/java/com/shapesecurity/salvation/ParserTest.java
@@ -38,11 +38,20 @@ public class ParserTest extends CSPTest {
         ImgSrcDirective imgSrcDirective = p.getDirectiveByType(ImgSrcDirective.class);
         assertNotNull(imgSrcDirective);
         assertTrue(firstDirective instanceof ImgSrcDirective);
-        assertEquals("", imgSrcDirective, (ImgSrcDirective) firstDirective);
+        assertEquals("", imgSrcDirective, firstDirective);
         assertEquals("", "img-src", ImgSrcDirective.name);
         assertEquals("", "img-src a", imgSrcDirective.show());
     }
 
+    @Test public void testDuplicatesWithLocation() {
+        Policy p;
+        ArrayList<Notice> notices = new ArrayList<>();
+        p = ParserWithLocation.parse("img-src a ;;; img-src b", "https://example.com", notices);
+        assertEquals(1, notices.size());
+        assertEquals("1:15: Policy contains more than one img-src directive. All but the first instance will be ignored.", notices.get(0).show());
+
+    }
+
     @Test public void testDirectiveNameParsing() {
         ArrayList<Notice> notices = new ArrayList<>();
         Policy p;