From 0d44152867a5031e116183743d6a908fe47d2307 Mon Sep 17 00:00:00 2001
From: Keith Cirkel <keithamus@users.noreply.github.com>
Date: Wed, 30 Mar 2022 17:53:32 +0100
Subject: [PATCH 1/3] normalize domains with trailing slashes

CSP3 more explicitly calls this out:

> If path A consists of one character that is equal to the U+002F
> SOLIDUS character (/) and path B is empty, return "Matches".

A URL like `example.com/foo` will match a connect-src of `example.com`,
as well as `example.com/`, so having two connect-srcs listed like this
is redundant.
---
 .../headers/content_security_policy.rb             | 14 ++++++++++++++
 .../headers/content_security_policy_spec.rb        | 10 +++++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/lib/secure_headers/headers/content_security_policy.rb b/lib/secure_headers/headers/content_security_policy.rb
index cb75050c..a84ba921 100644
--- a/lib/secure_headers/headers/content_security_policy.rb
+++ b/lib/secure_headers/headers/content_security_policy.rb
@@ -125,6 +125,7 @@ def minify_source_list(directive, source_list)
       else
         source_list = populate_nonces(directive, source_list)
         source_list = reject_all_values_if_none(source_list)
+        source_list = normalize_uri_paths(source_list)
 
         unless directive == REPORT_URI || @preserve_schemes
           source_list = strip_source_schemes(source_list)
@@ -147,6 +148,19 @@ def reject_all_values_if_none(source_list)
       end
     end
 
+    def normalize_uri_paths(source_list)
+      source_list.map do |source|
+        # Normalize domains ending in a single / as without omitting the slash accomplisheg the same.
+        # https://www.w3.org/TR/CSP3/#match-paths § 6.6.2.10 Step 2
+        if source.chomp("/").include?("/")
+          source
+        else
+          source.chomp("/")
+        end
+      end
+    end
+
+
     # Removes duplicates and sources that already match an existing wild card.
     #
     # e.g. *.github.com asdf.github.com becomes *.github.com
diff --git a/spec/lib/secure_headers/headers/content_security_policy_spec.rb b/spec/lib/secure_headers/headers/content_security_policy_spec.rb
index c080283d..17d2c535 100644
--- a/spec/lib/secure_headers/headers/content_security_policy_spec.rb
+++ b/spec/lib/secure_headers/headers/content_security_policy_spec.rb
@@ -48,9 +48,17 @@ module SecureHeaders
         expect(csp.value).to eq("default-src * 'unsafe-inline' 'unsafe-eval' data: blob:")
       end
 
+      it "normalizes source expressions that end with a trailing /" do
+        config = {
+          default_src: %w(a.example.org/ b.example.com/ c.example.net/foo/ b.example.co/bar)
+        }
+        csp = ContentSecurityPolicy.new(config)
+        expect(csp.value).to eq("default-src a.example.org b.example.com c.example.net/foo/ b.example.co/bar")
+      end
+
       it "minifies source expressions based on overlapping wildcards" do
         config = {
-          default_src: %w(a.example.org b.example.org *.example.org https://*.example.org)
+          default_src: %w(a.example.org b.example.org *.example.org https://*.example.org c.example.org/)
         }
         csp = ContentSecurityPolicy.new(config)
         expect(csp.value).to eq("default-src *.example.org")

From 6ffcee7537cd67b317c45001e4a25c538de9cbe3 Mon Sep 17 00:00:00 2001
From: Keith Cirkel <keithamus@users.noreply.github.com>
Date: Mon, 4 Apr 2022 16:29:58 +0100
Subject: [PATCH 2/3] fix: allow URIs with schema to have trailing slashes
 normalised

Co-authored-by: Dusty Greif <dgreif@users.noreply.github.com>
---
 lib/secure_headers/headers/content_security_policy.rb     | 8 ++++++++
 .../headers/content_security_policy_spec.rb               | 4 ++--
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/lib/secure_headers/headers/content_security_policy.rb b/lib/secure_headers/headers/content_security_policy.rb
index a84ba921..ea2473f9 100644
--- a/lib/secure_headers/headers/content_security_policy.rb
+++ b/lib/secure_headers/headers/content_security_policy.rb
@@ -152,6 +152,14 @@ def normalize_uri_paths(source_list)
       source_list.map do |source|
         # Normalize domains ending in a single / as without omitting the slash accomplisheg the same.
         # https://www.w3.org/TR/CSP3/#match-paths § 6.6.2.10 Step 2
+        begin
+          uri = URI(source)
+          if uri.path == "/"
+            next source.chomp("/")
+          end
+        rescue URI::InvalidURIError
+        end
+
         if source.chomp("/").include?("/")
           source
         else
diff --git a/spec/lib/secure_headers/headers/content_security_policy_spec.rb b/spec/lib/secure_headers/headers/content_security_policy_spec.rb
index 17d2c535..182ff43d 100644
--- a/spec/lib/secure_headers/headers/content_security_policy_spec.rb
+++ b/spec/lib/secure_headers/headers/content_security_policy_spec.rb
@@ -50,10 +50,10 @@ module SecureHeaders
 
       it "normalizes source expressions that end with a trailing /" do
         config = {
-          default_src: %w(a.example.org/ b.example.com/ c.example.net/foo/ b.example.co/bar)
+          default_src: %w(a.example.org/ b.example.com/ wss://c.example.com/ c.example.net/foo/ b.example.co/bar wss://b.example.co/)
         }
         csp = ContentSecurityPolicy.new(config)
-        expect(csp.value).to eq("default-src a.example.org b.example.com c.example.net/foo/ b.example.co/bar")
+        expect(csp.value).to eq("default-src a.example.org b.example.com wss://c.example.com c.example.net/foo/ b.example.co/bar wss://b.example.co")
       end
 
       it "minifies source expressions based on overlapping wildcards" do

From 67ca492b445f626fb9ca55c298efa211fdf4538d Mon Sep 17 00:00:00 2001
From: Keith Cirkel <keithamus@users.noreply.github.com>
Date: Mon, 4 Apr 2022 16:31:52 +0100
Subject: [PATCH 3/3] fix typo

Co-authored-by: Dusty Greif <dgreif@users.noreply.github.com>
---
 lib/secure_headers/headers/content_security_policy.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/secure_headers/headers/content_security_policy.rb b/lib/secure_headers/headers/content_security_policy.rb
index ea2473f9..13ad65a7 100644
--- a/lib/secure_headers/headers/content_security_policy.rb
+++ b/lib/secure_headers/headers/content_security_policy.rb
@@ -150,7 +150,7 @@ def reject_all_values_if_none(source_list)
 
     def normalize_uri_paths(source_list)
       source_list.map do |source|
-        # Normalize domains ending in a single / as without omitting the slash accomplisheg the same.
+        # Normalize domains ending in a single / as without omitting the slash accomplishes the same.
         # https://www.w3.org/TR/CSP3/#match-paths § 6.6.2.10 Step 2
         begin
           uri = URI(source)