This is an automated email from the ASF dual-hosted git repository. joerghoh pushed a commit to branch SLING-13357 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-engine.git
commit 5dc158de299d95bc9eff4acd967aa4cd307aa269 Author: Joerg Hoh <[email protected]> AuthorDate: Mon Sep 21 18:45:51 2026 +0200 SLING-13357 add header-modification validation for SetCharacterEncoding --- .../impl/SlingJakartaHttpServletResponseImpl.java | 132 +++++++- .../impl/SlingHttpServletResponseImplTest.java | 332 +++++++++++++++++++++ 2 files changed, 454 insertions(+), 10 deletions(-) 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 be9d38e..7c66780 100644 --- a/src/main/java/org/apache/sling/engine/impl/SlingJakartaHttpServletResponseImpl.java +++ b/src/main/java/org/apache/sling/engine/impl/SlingJakartaHttpServletResponseImpl.java @@ -20,9 +20,9 @@ package org.apache.sling.engine.impl; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.Charset; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Arrays; import java.util.Deque; import java.util.Iterator; import java.util.LinkedList; @@ -375,9 +375,81 @@ public class SlingJakartaHttpServletResponseImpl extends HttpServletResponseWrap } } + @Override + public void setCharacterEncoding(final String charset) { + boolean isCommitedDueToSendErrorOrRedirect = this.isCommitted() + && (CommitReason.SEND_ERROR == this.committedReason + || CommitReason.SEND_REDIRECT == this.committedReason); + if (isCommitedDueToSendErrorOrRedirect || !isInclude()) { + super.setCharacterEncoding(charset); + return; + } + final Optional<String> message = checkCharacterEncodingOverride(charset); + if (message.isPresent()) { + if (isCheckContentTypeOnInclude()) { + requestData.getRequestProgressTracker().log("ERROR: " + message.get()); + LOG.error(CALL_STACK_MESSAGE + getCurrentStackTrace()); + throw new ContentTypeChangeException(message.get()); + } + if (isProtectHeadersOnInclude()) { + LOG.error(message.get()); + LOG.error(CALL_STACK_MESSAGE + getCurrentStackTrace()); + requestData.getRequestProgressTracker().log("ERROR: " + message.get()); + return; + } + LOG.warn(message.get()); + LOG.warn(CALL_STACK_MESSAGE + getCurrentStackTrace()); + requestData.getRequestProgressTracker().log("WARN: " + message.get()); + super.setCharacterEncoding(charset); + } else { + super.setCharacterEncoding(charset); + } + } + + @Override + public void setCharacterEncoding(final Charset charset) { + // funnel the Charset variant through the checked String variant so + // that the include protections cannot be bypassed via this method + this.setCharacterEncoding(charset == null ? null : charset.name()); + } + + /** + * Checks if the response character encoding is being changed by an include + * and provides a message to log if it is. Changing the character encoding + * changes the charset parameter of the 'Content-Type' header and is + * therefore subject to the same include protections as + * {@link #setContentType(String)}. + * + * @param charset the character encoding that is being set + * @return an optional message to log + */ + protected Optional<String> checkCharacterEncodingOverride(@Nullable String charset) { + // A previously detected violation must not disable the check itself - + // otherwise the second and any later override attempt within the same + // request would pass unchecked even though the first one was blocked. + final boolean isFirstViolation = + requestData.getSlingRequestProcessor().getContentTypeHeaderState() != ContentTypeHeaderState.VIOLATED; + final String currentCharset = getCharacterEncoding(); + if (charset != null && charset.equalsIgnoreCase(currentCharset)) { + // not an effective change + return Optional.empty(); + } + requestData.getSlingRequestProcessor().setContentTypeHeaderState(ContentTypeHeaderState.VIOLATED); + final String currentContentType = getContentType(); + final String base = currentContentType == null ? "" : getMimeTypePart(currentContentType); + final String newContentType = (charset == null) ? base : base + ";charset=" + charset; + return Optional.of( + isFirstViolation + ? getMessage(currentContentType, newContentType) + : getShortMessage(currentContentType, newContentType)); + } + /** * Checks if the 'Content-Type' header is being overridden and provides a - * message to log if it is. + * message to log if it is. Both the media type and the charset parameter + * are compared: changing only the charset (e.g. from + * 'text/html;charset=UTF-8' to 'text/html;charset=UTF-7') changes the + * effective response header just as much as changing the media type. * * @param contentType the 'Content-Type' value that is being set * @return an optional message to log @@ -397,14 +469,23 @@ public class SlingJakartaHttpServletResponseImpl extends HttpServletResponseWrap isFirstViolation ? getMessage(currentContentType, null) : getShortMessage(currentContentType, null)); - } else { - Optional<String> currentMime = currentContentType == null - ? Optional.of("null") - : Arrays.stream(currentContentType.split(";")).findFirst(); - Optional<String> setMime = Arrays.stream(contentType.split(";")).findFirst(); - if (currentMime.isPresent() - && setMime.isPresent() - && !currentMime.get().equals(setMime.get())) { + } + final String currentMime = currentContentType == null ? "null" : getMimeTypePart(currentContentType); + final String setMime = getMimeTypePart(contentType); + if (!currentMime.equalsIgnoreCase(setMime)) { + requestData.getSlingRequestProcessor().setContentTypeHeaderState(ContentTypeHeaderState.VIOLATED); + return Optional.of( + isFirstViolation + ? getMessage(currentContentType, contentType) + : getShortMessage(currentContentType, contentType)); + } + final String setCharset = getCharsetPart(contentType); + if (setCharset != null) { + String currentCharset = currentContentType == null ? null : getCharsetPart(currentContentType); + if (currentCharset == null) { + currentCharset = getCharacterEncoding(); + } + if (!setCharset.equalsIgnoreCase(currentCharset)) { requestData.getSlingRequestProcessor().setContentTypeHeaderState(ContentTypeHeaderState.VIOLATED); return Optional.of( isFirstViolation @@ -415,6 +496,37 @@ public class SlingJakartaHttpServletResponseImpl extends HttpServletResponseWrap return Optional.empty(); } + /** + * Returns the media type of a 'Content-Type' header value, i.e. the part + * before the first parameter separator, trimmed. + */ + private static String getMimeTypePart(final String contentType) { + final int semi = contentType.indexOf(';'); + final String mime = semi >= 0 ? contentType.substring(0, semi) : contentType; + return mime.trim(); + } + + /** + * Returns the value of the charset parameter of a 'Content-Type' header + * value, or {@code null} if no charset parameter is present. + */ + @Nullable + private static String getCharsetPart(final String contentType) { + final String[] parts = contentType.split(";"); + for (int i = 1; i < parts.length; i++) { + final String param = parts[i].trim(); + final int eq = param.indexOf('='); + if (eq > 0 && "charset".equalsIgnoreCase(param.substring(0, eq).trim())) { + String value = param.substring(eq + 1).trim(); + if (value.length() > 1 && value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"') { + value = value.substring(1, value.length() - 1).trim(); + } + return value.isEmpty() ? null : value; + } + } + return null; + } + /** * Short variant of {@link #getMessage(String, String)} used for repeated * violations within the same request: it omits the include stack and the 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 58ac359..9584364 100644 --- a/src/test/java/org/apache/sling/engine/impl/SlingHttpServletResponseImplTest.java +++ b/src/test/java/org/apache/sling/engine/impl/SlingHttpServletResponseImplTest.java @@ -19,6 +19,8 @@ package org.apache.sling.engine.impl; import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.stream.Collectors; @@ -542,6 +544,336 @@ public class SlingHttpServletResponseImplTest { Mockito.verifyNoInteractions(requestProgressTracker); } + @Test + public void testCharacterEncodingProtectedOnInclude() { + 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(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME); + when(orig.getContentType()).thenReturn("text/html;charset=UTF-8"); + when(orig.getCharacterEncoding()).thenReturn("UTF-8"); + ArrayList<String> logMessagesList = new ArrayList<>(Arrays.asList(logMessages)); + when(requestProgressTracker.getMessages()).thenAnswer(invocation -> logMessagesList.iterator()); + info.setProtectHeadersOnInclude(true); + + final SlingRequestProcessorImpl requestProcessor = mock(SlingRequestProcessorImpl.class); + when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + include.setCharacterEncoding("ISO-2022-JP"); + + Mockito.verify(orig, never()).setCharacterEncoding(Mockito.anyString()); + ArgumentCaptor<String> logCaptor = ArgumentCaptor.forClass(String.class); + verify(requestProgressTracker, times(1)).log(logCaptor.capture()); + assertTrue(logCaptor.getValue().startsWith("ERROR: ")); + } + + @Test + public void testCharacterEncodingCharsetVariantProtectedOnInclude() { + 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(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME); + when(orig.getCharacterEncoding()).thenReturn("UTF-8"); + ArrayList<String> logMessagesList = new ArrayList<>(Arrays.asList(logMessages)); + when(requestProgressTracker.getMessages()).thenAnswer(invocation -> logMessagesList.iterator()); + info.setProtectHeadersOnInclude(true); + + final SlingRequestProcessorImpl requestProcessor = mock(SlingRequestProcessorImpl.class); + when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + include.setCharacterEncoding(StandardCharsets.UTF_16); + + Mockito.verify(orig, never()).setCharacterEncoding(Mockito.anyString()); + Mockito.verify(orig, never()).setCharacterEncoding(Mockito.any(Charset.class)); + } + + @Test + public void testCharacterEncodingUnchangedNotFlaggedOnInclude() { + 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(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + when(orig.getCharacterEncoding()).thenReturn("UTF-8"); + info.setProtectHeadersOnInclude(true); + + final SlingRequestProcessorImpl requestProcessor = mock(SlingRequestProcessorImpl.class); + when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + include.setCharacterEncoding("utf-8"); + + Mockito.verify(orig, times(1)).setCharacterEncoding("utf-8"); + Mockito.verifyNoInteractions(requestProgressTracker); + } + + @Test + public void testCharacterEncodingDelegatedOutsideInclude() { + final SlingJakartaHttpServletResponse orig = Mockito.mock(SlingJakartaHttpServletResponse.class); + final RequestData requestData = mock(RequestData.class); + final DispatchingInfo info = new DispatchingInfo(DispatcherType.FORWARD); + when(requestData.getDispatchingInfo()).thenReturn(info); + + final HttpServletResponse response = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + response.setCharacterEncoding("ISO-2022-JP"); + + Mockito.verify(orig, times(1)).setCharacterEncoding("ISO-2022-JP"); + } + + @Test + public void testContentTypeCharsetChangeDetectedOnInclude() { + 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/html;charset=UTF-8"); + when(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME); + 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); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + Throwable throwable = null; + try { + include.setContentType("text/html;charset=UTF-7"); + } catch (RuntimeException e) { + throwable = e; + } + assertNotNull("Expected a RuntimeException for the charset change.", throwable); + Mockito.verify(orig, never()).setContentType(Mockito.anyString()); + } + + @Test + public void testContentTypeSameMimeAndCharsetCaseInsensitiveNotFlagged() { + 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/html; charset=UTF-8"); + when(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + info.setProtectHeadersOnInclude(true); + info.setCheckContentTypeOnInclude(true); + + final SlingRequestProcessorImpl requestProcessor = mock(SlingRequestProcessorImpl.class); + when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + include.setContentType("TEXT/HTML;charset=utf-8"); + + Mockito.verify(orig, times(1)).setContentType("TEXT/HTML;charset=utf-8"); + Mockito.verifyNoInteractions(requestProgressTracker); + } + + @Test + public void testCharacterEncodingCheckContentTypeOnIncludeThrows() { + 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(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME); + when(orig.getContentType()).thenReturn("text/html;charset=UTF-8"); + when(orig.getCharacterEncoding()).thenReturn("UTF-8"); + 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); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + Throwable throwable = null; + try { + include.setCharacterEncoding("ISO-2022-JP"); + } catch (RuntimeException e) { + throwable = e; + } + assertNotNull("Expected a RuntimeException for the character encoding change.", throwable); + Mockito.verify(orig, never()).setCharacterEncoding(Mockito.anyString()); + Mockito.verify(requestProgressTracker, times(1)).log(Mockito.startsWith("ERROR: ")); + } + + @Test + public void testCharacterEncodingStillEnforcedAfterPreviousViolation() { + 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/html;charset=UTF-8"); + when(orig.getCharacterEncoding()).thenReturn("UTF-8"); + when(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME); + info.setCheckContentTypeOnInclude(true); + + final SlingRequestProcessorImpl requestProcessor = mock(SlingRequestProcessorImpl.class); + // a violation has already been detected earlier within this request + when(requestProcessor.getContentTypeHeaderState()).thenReturn(ContentTypeHeaderState.VIOLATED); + when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + Throwable throwable = null; + try { + include.setCharacterEncoding("ISO-2022-JP"); + } catch (RuntimeException e) { + throwable = e; + } + assertNotNull("Expected the repeated override attempt to still be blocked.", throwable); + Mockito.verify(orig, never()).setCharacterEncoding(Mockito.anyString()); + // the short message must not require the RequestProgressTracker messages again + Mockito.verify(requestProgressTracker, never()).getMessages(); + } + + @Test + public void testCharacterEncodingNoViolationChecksOnCommittedResponseWhenSendRedirect() throws IOException { + final SlingJakartaHttpServletResponse orig = Mockito.mock(SlingJakartaHttpServletResponse.class); + Mockito.when(orig.isCommitted()).thenReturn(true); + + final RequestData requestData = mock(RequestData.class); + final DispatchingInfo info = new DispatchingInfo(DispatcherType.INCLUDE); + when(requestData.getDispatchingInfo()).thenReturn(info); + when(requestData.getRequestProgressTracker()).thenReturn(mock(RequestProgressTracker.class)); + + final SlingJakartaHttpServletResponseImpl include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + SlingJakartaHttpServletResponseImpl spyInclude = Mockito.spy(include); + + spyInclude.sendRedirect("somewhere"); + + spyInclude.setCharacterEncoding("ISO-2022-JP"); + Mockito.verify(orig, times(1)).setCharacterEncoding("ISO-2022-JP"); + Mockito.verify(spyInclude, never()).checkCharacterEncodingOverride(Mockito.any()); + } + + @Test + public void testCharacterEncodingNoViolationChecksOnCommittedResponseWhenSendError() throws IOException { + 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); + when(requestData.getSlingRequestProcessor()).thenReturn(mock(SlingRequestProcessorImpl.class)); + when(requestData.getRequestProgressTracker()).thenReturn(mock(RequestProgressTracker.class)); + + final SlingJakartaHttpServletResponseImpl include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + SlingJakartaHttpServletResponseImpl spyInclude = Mockito.spy(include); + + spyInclude.sendError(501); + // send error will eventually commit the response, let's mock this + Mockito.when(orig.isCommitted()).thenReturn(true); + + spyInclude.setCharacterEncoding("ISO-2022-JP"); + Mockito.verify(orig, times(1)).setCharacterEncoding("ISO-2022-JP"); + Mockito.verify(spyInclude, never()).checkCharacterEncodingOverride(Mockito.any()); + } + + @Test + public void testCharacterEncodingNullCharsetFallsBackToDelegate() { + 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(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME); + when(orig.getContentType()).thenReturn("text/html;charset=UTF-8"); + when(orig.getCharacterEncoding()).thenReturn("UTF-8"); + ArrayList<String> logMessagesList = new ArrayList<>(Arrays.asList(logMessages)); + when(requestProgressTracker.getMessages()).thenAnswer(invocation -> logMessagesList.iterator()); + info.setProtectHeadersOnInclude(true); + + final SlingRequestProcessorImpl requestProcessor = mock(SlingRequestProcessorImpl.class); + when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + include.setCharacterEncoding((Charset) null); + + Mockito.verify(orig, never()).setCharacterEncoding(Mockito.anyString()); + Mockito.verify(requestProgressTracker, times(1)).log(Mockito.startsWith("ERROR: ")); + } + + @Test + public void testContentTypeCharsetFallsBackToCurrentCharacterEncodingWhenNotInContentType() { + 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); + // current 'Content-Type' has no charset parameter, but the response + // already has a character encoding assigned + when(orig.getContentType()).thenReturn("text/html"); + when(orig.getCharacterEncoding()).thenReturn("UTF-8"); + when(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME); + 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); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + Throwable throwable = null; + try { + include.setContentType("text/html;charset=UTF-7"); + } catch (RuntimeException e) { + throwable = e; + } + assertNotNull( + "Expected a RuntimeException since the charset differs from the current character encoding.", + throwable); + Mockito.verify(orig, never()).setContentType(Mockito.anyString()); + } + + @Test + public void testContentTypeQuotedCharsetParsedCorrectly() { + 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/html; charset=\"UTF-8\""); + when(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker); + info.setProtectHeadersOnInclude(true); + info.setCheckContentTypeOnInclude(true); + + final SlingRequestProcessorImpl requestProcessor = mock(SlingRequestProcessorImpl.class); + when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor); + + final HttpServletResponse include = new SlingJakartaHttpServletResponseImpl(requestData, orig); + + // same mime type and same (quoted vs. unquoted) charset must not be flagged + include.setContentType("text/html; charset=UTF-8"); + + Mockito.verify(orig, times(1)).setContentType("text/html; charset=UTF-8"); + Mockito.verifyNoInteractions(requestProgressTracker); + } + @Test public void testCookies() { final SlingJakartaHttpServletResponse orig = Mockito.mock(SlingJakartaHttpServletResponse.class);
