This is an automated email from the ASF dual-hosted git repository. joerghoh pushed a commit to branch SLING-13351 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-engine.git
commit 50d2118b8de151960bd44ec1ace0aee32ff7b17d Author: Joerg Hoh <[email protected]> AuthorDate: Wed Sep 16 10:37:02 2026 +0200 SLING-13351 make content-type handling consistent the content-type can be set also via setHeader/addHeader, so in these cases the logic of setContenType should be applied. --- .../impl/SlingJakartaHttpServletResponseImpl.java | 14 ++++++ .../impl/SlingHttpServletResponseImplTest.java | 57 ++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/main/java/org/apache/sling/engine/impl/SlingJakartaHttpServletResponseImpl.java b/src/main/java/org/apache/sling/engine/impl/SlingJakartaHttpServletResponseImpl.java index 5ed609b..2a4a19d 100644 --- a/src/main/java/org/apache/sling/engine/impl/SlingJakartaHttpServletResponseImpl.java +++ b/src/main/java/org/apache/sling/engine/impl/SlingJakartaHttpServletResponseImpl.java @@ -59,6 +59,8 @@ public class SlingJakartaHttpServletResponseImpl extends HttpServletResponseWrap private static final String CALL_STACK_MESSAGE = "Call stack causing the content type override violation: "; + private static final String HEADER_CONTENT_TYPE = "Content-Type"; + private static final Logger LOG = LoggerFactory.getLogger(SlingJakartaHttpServletResponseImpl.class); // this regex matches TIMER_START{ followed by any characters except }, and then @@ -274,6 +276,12 @@ public class SlingJakartaHttpServletResponseImpl extends HttpServletResponseWrap @Override public void addHeader(final String name, final String value) { + if (this.isInclude() && HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) { + // changing the Content-Type header during an include must be subject + // to the same enforcement as setContentType (see setHeader) + this.setContentType(value); + return; + } if (!this.isProtectHeadersOnInclude()) { logHeaderModificationCallOnIncludeForMethod("addHeader()"); super.addHeader(name, value); @@ -307,6 +315,12 @@ public class SlingJakartaHttpServletResponseImpl extends HttpServletResponseWrap @Override public void setHeader(final String name, final String value) { + if (this.isInclude() && HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) { + // changing the Content-Type header during an include must be subject + // to the same enforcement as setContentType (see setHeader) + this.setContentType(value); + return; + } if (!this.isProtectHeadersOnInclude()) { logHeaderModificationCallOnIncludeForMethod("setHeader()"); super.setHeader(name, value); diff --git a/src/test/java/org/apache/sling/engine/impl/SlingHttpServletResponseImplTest.java b/src/test/java/org/apache/sling/engine/impl/SlingHttpServletResponseImplTest.java index fd658f5..b2e84b3 100644 --- a/src/test/java/org/apache/sling/engine/impl/SlingHttpServletResponseImplTest.java +++ b/src/test/java/org/apache/sling/engine/impl/SlingHttpServletResponseImplTest.java @@ -374,6 +374,63 @@ public class SlingHttpServletResponseImplTest { .collect(Collectors.joining(System.lineSeparator()))))); } + @Test + public void testContentTypeOverrideEnforcedForSetHeader() { + final SlingJakartaHttpServletResponse orig = Mockito.mock(SlingJakartaHttpServletResponse.class); + final RequestData requestData = mock(RequestData.class); + final DispatchingInfo info = new DispatchingInfo(DispatcherType.INCLUDE); + final RequestProgressTracker requestProgressTracker = mock(RequestProgressTracker.class); + when(requestData.getDispatchingInfo()).thenReturn(info); + when(orig.getContentType()).thenReturn("text/plain"); + when(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + ArrayList<String> logMessagesList = new ArrayList<>(Arrays.asList(logMessages)); + when(requestProgressTracker.getMessages()).thenAnswer(invocation -> logMessagesList.iterator()); + info.setCheckContentTypeOnInclude(true); + + final SlingRequestProcessorImpl requestProcessor = mock(SlingRequestProcessorImpl.class); + when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor); + when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + Throwable setHeaderThrowable = null; + try { + include.setHeader("Content-Type", "text/html"); + } catch (RuntimeException e) { + setHeaderThrowable = e; + } + assertNotNull("Expected setHeader(\"Content-Type\", ...) to be blocked.", setHeaderThrowable); + + Throwable addHeaderThrowable = null; + try { + include.addHeader("content-type", "text/html"); + } catch (RuntimeException e) { + addHeaderThrowable = e; + } + assertNotNull("Expected addHeader(\"content-type\", ...) to be blocked.", addHeaderThrowable); + + Mockito.verify(orig, never()).setHeader(Mockito.anyString(), Mockito.anyString()); + Mockito.verify(orig, never()).addHeader(Mockito.anyString(), Mockito.anyString()); + Mockito.verify(orig, never()).setContentType(Mockito.anyString()); + } + + @Test + public void testUnrelatedHeadersNotRoutedThroughContentTypeCheck() { + final SlingJakartaHttpServletResponse orig = Mockito.mock(SlingJakartaHttpServletResponse.class); + final RequestData requestData = mock(RequestData.class); + final DispatchingInfo info = new DispatchingInfo(DispatcherType.INCLUDE); + when(requestData.getDispatchingInfo()).thenReturn(info); + info.setCheckContentTypeOnInclude(true); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + include.setHeader("X-Custom", "value"); + include.addHeader("X-Custom", "another"); + + Mockito.verify(orig, times(1)).setHeader("X-Custom", "value"); + Mockito.verify(orig, times(1)).addHeader("X-Custom", "another"); + } + @Test public void testContentTypeOverrideDisabled() { final SlingJakartaHttpServletResponse orig = Mockito.mock(SlingJakartaHttpServletResponse.class);
