This is an automated email from the ASF dual-hosted git repository.

pedrosans pushed a commit to branch WICKET-7107-backport
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit d970362e436fc5aecb51f7c1b9be654282b001d0
Author: Pedro Santos <[email protected]>
AuthorDate: Wed May 6 20:52:23 2026 -0300

    WICKET-7107 move CPS headers writing to WebPage
---
 .../org/apache/wicket/csp/CSPHeaderWriterTest.java | 175 +++++++++++++++++++++
 .../org/apache/wicket/csp/CSPHeaderWriter.java     |  53 +++++++
 .../wicket/csp/ContentSecurityPolicySettings.java  |  29 ++--
 .../org/apache/wicket/markup/html/WebPage.java     |   6 +
 4 files changed, 253 insertions(+), 10 deletions(-)

diff --git 
a/wicket-core-tests/src/test/java/org/apache/wicket/csp/CSPHeaderWriterTest.java
 
b/wicket-core-tests/src/test/java/org/apache/wicket/csp/CSPHeaderWriterTest.java
new file mode 100644
index 0000000000..e491144992
--- /dev/null
+++ 
b/wicket-core-tests/src/test/java/org/apache/wicket/csp/CSPHeaderWriterTest.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.csp;
+
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.RestartResponseException;
+import org.apache.wicket.core.request.handler.PageProvider;
+import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
+import org.apache.wicket.markup.IMarkupResourceStreamProvider;
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.link.StatelessLink;
+import org.apache.wicket.protocol.http.BufferedWebResponse;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.protocol.http.mock.MockHttpServletResponse;
+import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
+import org.apache.wicket.request.Response;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.WebResponse;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.CssResourceReference;
+import org.apache.wicket.util.resource.IResourceStream;
+import org.apache.wicket.util.resource.StringResourceStream;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.apache.wicket.util.tester.WicketTester;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.wicket.csp.CSPDirective.STYLE_SRC;
+import static org.apache.wicket.csp.CSPDirectiveSrcValue.SELF;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+class CSPHeaderWriterTest extends WicketTestCase
+{
+
+       @BeforeEach
+       void setup()
+       {
+               
tester.getApplication().getCspSettings().blocking().strict().add(STYLE_SRC, 
SELF);
+       }
+
+       @Test
+       void addCspDirectiveToBufferedPage()
+       {
+               tester.startPage(Page.class);
+               tester.clickLink("link_to_page_instance");
+
+               
assertThat(tester.getLastResponse().getHeader("Content-Security-Policy")).contains(
+                       STYLE_SRC.getValue());
+       }
+
+       @Test
+       void dontAddCSPHeaderToRedirectResponses()
+       {
+               tester.setFollowRedirects(false);
+               
tester.getApplication().getCspSettings().blocking().add(STYLE_SRC, SELF);
+               tester.startPage(Page.class);
+
+               var requestCycle = tester.getRequestCycle();
+
+               tester.clickLink("link_to_page_instance");
+
+               var response = 
((MockHttpServletResponse)requestCycle.getResponse().getContainerResponse());
+               assertEquals(302, response.getStatus());
+               
assertFalse(response.containsHeader(CSPHeaderMode.BLOCKING.getHeader()));
+       }
+
+       @Test
+       void addCspDirectiveToBufferedPageAfterRedirect()
+       {
+               tester.startPage(AutoRedirectPage.class);
+
+               
assertThat(tester.getLastRenderedPage()).isInstanceOf(Page.class);
+               
assertThat(tester.getLastResponse().getHeader("Content-Security-Policy")).contains(
+                       STYLE_SRC.getValue());
+       }
+
+       @Test
+       void addCspDirectiveToStatelessPageAfterRedirect()
+       {
+               tester.startPage(AlwaysRedirectPage.class);
+
+               
assertThat(tester.getLastRenderedPage()).isInstanceOf(Page.class);
+               
assertThat(tester.getLastResponse().getHeader("Content-Security-Policy")).contains(
+                       STYLE_SRC.getValue());
+       }
+
+       @Test
+       void addCspDirectiveToStatelessPageAfterNoRedirect()
+       {
+               tester.startPage(NeverRedirectPage.class);
+
+               
assertThat(tester.getLastRenderedPage()).isInstanceOf(Page.class);
+               
assertThat(tester.getLastResponse().getHeader("Content-Security-Policy")).contains(
+                       STYLE_SRC.getValue());
+       }
+
+       public static class Page extends WebPage implements 
IMarkupResourceStreamProvider
+       {
+               @Override
+               protected void onInitialize()
+               {
+                       super.onInitialize();
+                       add(new StatelessLink<Void>("link_to_page_instance")
+                       {
+                               @Override
+                               public void onClick()
+                               {
+                                       setResponsePage(new Page());
+                               }
+                       });
+               }
+
+               @Override
+               public void renderHead(IHeaderResponse response)
+               {
+                       response.render(CssHeaderItem.forReference(
+                               new 
CssResourceReference(CSPHeaderWriterTest.class, "style.css"), "screen"));
+               }
+
+               @Override
+               public IResourceStream getMarkupResourceStream(MarkupContainer 
container,
+                       Class<?> containerClass)
+               {
+                       return new StringResourceStream(
+                               "<html><head></head><body><a 
wicket:id=\"link_to_page_instance\">link</a></body></html>");
+               }
+       }
+
+       public static class AutoRedirectPage extends Page
+       {
+               public AutoRedirectPage()
+               {
+                       throw new RestartResponseException(new Page());
+               }
+       }
+
+       public static class AlwaysRedirectPage extends Page
+       {
+               public AlwaysRedirectPage()
+               {
+                       throw new RestartResponseException(Page.class, new 
PageParameters());
+               }
+       }
+
+       public static class NeverRedirectPage extends Page
+       {
+               public NeverRedirectPage()
+               {
+                       throw new RestartResponseException(new 
PageProvider(Page.class),
+                               
RenderPageRequestHandler.RedirectPolicy.NEVER_REDIRECT);
+               }
+       }
+
+}
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderWriter.java 
b/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderWriter.java
new file mode 100644
index 0000000000..f40afe50f5
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderWriter.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.csp;
+
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.WebResponse;
+
+/**
+ * Adds {@code Content-Security-Policy} and/or {@code 
Content-Security-Policy-Report-Only} headers
+ * based on the supplied configuration.
+ *
+ * @author Sven Haster
+ * @author Emond Papegaaij
+ */
+public class CSPHeaderWriter
+{
+       private final ContentSecurityPolicySettings settings;
+
+       public CSPHeaderWriter(ContentSecurityPolicySettings settings)
+       {
+               this.settings = settings;
+       }
+
+       public void write(WebResponse webResponse, RequestCycle cycle)
+       {
+               settings.getConfiguration().entrySet().stream().filter(entry -> 
entry.getValue().isSet())
+                       .forEach(entry -> {
+                               CSPHeaderMode mode = entry.getKey();
+                               CSPHeaderConfiguration config = 
entry.getValue();
+                               String headerValue = 
config.renderHeaderValue(settings, cycle);
+                               webResponse.setHeader(mode.getHeader(), 
headerValue);
+                               if (config.isAddLegacyHeaders())
+                               {
+                                       
webResponse.setHeader(mode.getLegacyHeader(), headerValue);
+                               }
+                       });
+       }
+
+}
\ No newline at end of file
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicySettings.java
 
b/wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicySettings.java
index 65b510b7f4..1e96d52b1f 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicySettings.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicySettings.java
@@ -16,22 +16,21 @@
  */
 package org.apache.wicket.csp;
 
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.Map;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-
 import org.apache.wicket.Application;
 import org.apache.wicket.MetaDataKey;
 import org.apache.wicket.Page;
 import org.apache.wicket.core.request.handler.IPageRequestHandler;
-import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
 import org.apache.wicket.protocol.http.WebApplication;
 import org.apache.wicket.request.IRequestHandler;
 import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.util.lang.Args;
 
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
 /**
  * Build the CSP configuration like this:
  * 
@@ -69,18 +68,28 @@ public class ContentSecurityPolicySettings
        private final Map<CSPHeaderMode, CSPHeaderConfiguration> configs = new 
EnumMap<>(
                CSPHeaderMode.class);
 
-       private Predicate<IRequestHandler> protectedFilter = 
RenderPageRequestHandler.class::isInstance;
+       private Predicate<IRequestHandler> protectedFilter = (handler) -> false;
+
+       private final CSPHeaderWriter cspHeaderWriter;
+
 
        private Supplier<String> nonceCreator;
-       
+
        public ContentSecurityPolicySettings(Application application)
        {
                Args.notNull(application, "application");
-               
+
+               cspHeaderWriter = new CSPHeaderWriter(this);
+
                nonceCreator = () ->
                                
application.getSecuritySettings().getRandomSupplier().getRandomBase64(NONCE_LENGTH);
        }
 
+       public CSPHeaderWriter getHeaderWriter()
+       {
+               return cspHeaderWriter;
+       }
+
        public CSPHeaderConfiguration blocking()
        {
                return configs.computeIfAbsent(CSPHeaderMode.BLOCKING, x -> new 
CSPHeaderConfiguration());
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/html/WebPage.java 
b/wicket-core/src/main/java/org/apache/wicket/markup/html/WebPage.java
index c1e8e584f8..a43ed80ef3 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/html/WebPage.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/WebPage.java
@@ -147,6 +147,12 @@ public class WebPage extends Page
         */
        protected void configureResponse(final WebResponse response)
        {
+               var cspSettings = WebApplication.get().getCspSettings();
+               if (cspSettings.isEnabled() && response.isHeaderSupported())
+               {
+                       cspSettings.getHeaderWriter().write(response, 
getRequestCycle());
+               }
+
                // Users may subclass setHeader() to set there own headers
                setHeaders(response);
 

Reply via email to