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

joerghoh pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-engine.git


The following commit(s) were added to refs/heads/master by this push:
     new 3cf85e6  SLING-12661 stick with the first violation for recursive 
calls (#56)
3cf85e6 is described below

commit 3cf85e6f0a00d4a90520e414dad3f078bffcf7b6
Author: Remo Liechti <[email protected]>
AuthorDate: Thu Mar 13 15:15:54 2025 +0100

    SLING-12661 stick with the first violation for recursive calls (#56)
    
    * SLING-12661 use last 500 messages only for potential origins matching
    * Apply suggestions from code review
    * SLING-12661 rework duplicated test code
    * SLING-12661 move test method to previous location
    * SLING-12661 rename test method
    * SLING-12661 stop analyzing for succeeding violations once the first 
violation is found, especially useful for recursive calls
    * SLING-12661 add stacktrace whenviolation is detected
    * SLING-12661 remove xss from naming
    * SLING-12661 review comments
    * SLING-12661 remove duplicated infromation string
    
    ---------
    
    Co-authored-by: Jörg Hoh <[email protected]>
---
 .../sling/engine/impl/ContentTypeHeaderState.java  | 29 +++++++++++++++++
 .../engine/impl/SlingHttpServletResponseImpl.java  | 25 ++++++++++++++-
 .../engine/impl/SlingRequestProcessorImpl.java     | 36 ++++++++++++++++++----
 .../impl/SlingHttpServletResponseImplTest.java     | 15 +++++++++
 4 files changed, 98 insertions(+), 7 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/engine/impl/ContentTypeHeaderState.java 
b/src/main/java/org/apache/sling/engine/impl/ContentTypeHeaderState.java
new file mode 100644
index 0000000..b7c305f
--- /dev/null
+++ b/src/main/java/org/apache/sling/engine/impl/ContentTypeHeaderState.java
@@ -0,0 +1,29 @@
+/*
+ * 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.sling.engine.impl;
+
+/**
+ * This enumeration defines states hold in a ThreadLocal to indicate a
+ * previously detected violation to change the content type header.
+ */
+public enum ContentTypeHeaderState {
+    UNSET,
+    VIOLATED,
+    NOT_VIOLATED
+}
diff --git 
a/src/main/java/org/apache/sling/engine/impl/SlingHttpServletResponseImpl.java 
b/src/main/java/org/apache/sling/engine/impl/SlingHttpServletResponseImpl.java
index ad57faf..8fb5b9b 100644
--- 
a/src/main/java/org/apache/sling/engine/impl/SlingHttpServletResponseImpl.java
+++ 
b/src/main/java/org/apache/sling/engine/impl/SlingHttpServletResponseImpl.java
@@ -48,6 +48,8 @@ import org.slf4j.LoggerFactory;
 
 public class SlingHttpServletResponseImpl extends HttpServletResponseWrapper 
implements SlingHttpServletResponse {
 
+    private static final String CALL_STACK_MESSAGE = "Call stack causing the 
content type override violation: ";
+
     private static final Logger LOG = 
LoggerFactory.getLogger(SlingHttpServletResponseImpl.class);
 
     // this regex matches TIMER_START{ followed by any characters except }, 
and then
@@ -312,6 +314,15 @@ public class SlingHttpServletResponseImpl extends 
HttpServletResponseWrapper imp
         }
     }
 
+    private String getCurrentStackTrace() {
+        StackTraceElement[] stackTraceElements = 
Thread.currentThread().getStackTrace();
+        StringBuilder stackTraceBuilder = new StringBuilder();
+        for (StackTraceElement element : stackTraceElements) {
+            
stackTraceBuilder.append(element.toString()).append(System.lineSeparator());
+        }
+        return stackTraceBuilder.toString();
+    }
+
     @Override
     public void setContentType(final String type) {
         if (!isInclude()) {
@@ -321,14 +332,17 @@ public class SlingHttpServletResponseImpl extends 
HttpServletResponseWrapper imp
             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.setContentType(type);
             } else {
@@ -345,8 +359,15 @@ public class SlingHttpServletResponseImpl extends 
HttpServletResponseWrapper imp
      * @return an optional message to log
      */
     private Optional<String> checkContentTypeOverride(@Nullable String 
contentType) {
+        if (requestData.getSlingRequestProcessor().getContentTypeHeaderState() 
== ContentTypeHeaderState.VIOLATED) {
+            // return immediatly as the content type header has already been 
violated
+            // prevoiously, no more checks needed
+            return Optional.empty();
+        }
+
         String currentContentType = getContentType();
         if (contentType == null) {
+            
requestData.getSlingRequestProcessor().setContentTypeHeaderState(ContentTypeHeaderState.VIOLATED);
             return Optional.of(getMessage(currentContentType, null));
         } else {
             Optional<String> currentMime = currentContentType == null
@@ -356,6 +377,7 @@ public class SlingHttpServletResponseImpl extends 
HttpServletResponseWrapper imp
             if (currentMime.isPresent()
                     && setMime.isPresent()
                     && !currentMime.get().equals(setMime.get())) {
+                
requestData.getSlingRequestProcessor().setContentTypeHeaderState(ContentTypeHeaderState.VIOLATED);
                 return Optional.of(getMessage(currentContentType, 
contentType));
             }
         }
@@ -363,7 +385,8 @@ public class SlingHttpServletResponseImpl extends 
HttpServletResponseWrapper imp
     }
 
     private List<String> getLastMessagesOfProgressTracker() {
-        // Collect the last MAX_NR_OF_MESSAGES messages from the 
RequestProgressTracker to prevent excessive memory
+        // Collect the last MAX_NR_OF_MESSAGES messages from the 
RequestProgressTracker
+        // to prevent excessive memory
         // consumption errors when close to infinite recursive calls are made
         int nrOfOriginalMessages = 0;
         boolean gotCut = false;
diff --git 
a/src/main/java/org/apache/sling/engine/impl/SlingRequestProcessorImpl.java 
b/src/main/java/org/apache/sling/engine/impl/SlingRequestProcessorImpl.java
index 144c529..82f2734 100644
--- a/src/main/java/org/apache/sling/engine/impl/SlingRequestProcessorImpl.java
+++ b/src/main/java/org/apache/sling/engine/impl/SlingRequestProcessorImpl.java
@@ -117,6 +117,9 @@ public class SlingRequestProcessorImpl implements 
SlingRequestProcessor {
     private volatile boolean checkContentTypeOnInclude;
     private volatile boolean disableCheckCompliantGetUserPrincipal;
 
+    private static final ThreadLocal<ContentTypeHeaderState> 
contentTypeHeaderState =
+            ThreadLocal.withInitial(() -> ContentTypeHeaderState.UNSET);
+
     @Activate
     public void activate(final Config config) {
         this.modified(config);
@@ -206,7 +209,8 @@ public class SlingRequestProcessorImpl implements 
SlingRequestProcessor {
         if (resourceResolver == null || sr == null) {
             // Dependencies are missing
             // In this case we must not use the Sling error handling 
infrastructure but
-            // just return a 503 status response handled by the servlet 
container environment
+            // just return a 503 status response handled by the servlet 
container
+            // environment
 
             final int status = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
             String errorMessage = "Required service missing (";
@@ -236,6 +240,13 @@ public class SlingRequestProcessorImpl implements 
SlingRequestProcessor {
         final SlingHttpServletResponse response = 
requestData.getSlingResponse();
 
         try {
+            if (getContentTypeHeaderState() != ContentTypeHeaderState.UNSET) {
+                log.error(
+                        "Content Type Header state has not been cleared 
properly, is set to {}",
+                        getContentTypeHeaderState());
+            }
+            setContentTypeHeaderState(ContentTypeHeaderState.NOT_VIOLATED);
+
             // initialize the request data - resolve resource and servlet
             final Resource resource = 
requestData.initResource(resourceResolver);
             requestData.initServlet(resource, sr);
@@ -307,6 +318,8 @@ public class SlingRequestProcessorImpl implements 
SlingRequestProcessor {
             if (localBean != null) {
                 localBean.addRequestData(requestData);
             }
+
+            setContentTypeHeaderState(ContentTypeHeaderState.UNSET);
         }
     }
 
@@ -328,7 +341,9 @@ public class SlingRequestProcessorImpl implements 
SlingRequestProcessor {
     // ---------- SlingRequestProcessor interface
 
     /**
-     * @see 
org.apache.sling.engine.SlingRequestProcessor#processRequest(javax.servlet.http.HttpServletRequest,
 javax.servlet.http.HttpServletResponse, 
org.apache.sling.api.resource.ResourceResolver)
+     * @see 
org.apache.sling.engine.SlingRequestProcessor#processRequest(javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse,
+     *      org.apache.sling.api.resource.ResourceResolver)
      */
     @Override
     public void processRequest(
@@ -379,10 +394,11 @@ public class SlingRequestProcessorImpl implements 
SlingRequestProcessor {
     /**
      * Dispatches the request on behalf of the
      * {@link org.apache.sling.engine.impl.request.SlingRequestDispatcher}.
-     * @param request The request
-     * @param response The response
-     * @param resource The resource
-     * @param resolvedURL Request path info
+     *
+     * @param request         The request
+     * @param response        The response
+     * @param resource        The resource
+     * @param resolvedURL     Request path info
      * @param DispatchingInfo dispatching info
      */
     public void dispatchRequest(
@@ -523,4 +539,12 @@ public class SlingRequestProcessorImpl implements 
SlingRequestProcessor {
         log.debug("getMimeType: MimeTypeService not available, cannot resolve 
mime type for {}", name);
         return null;
     }
+
+    public ContentTypeHeaderState getContentTypeHeaderState() {
+        return contentTypeHeaderState.get();
+    }
+
+    public void setContentTypeHeaderState(ContentTypeHeaderState newState) {
+        contentTypeHeaderState.set(newState);
+    }
 }
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 98eb7b8..4bf21ff 100644
--- 
a/src/test/java/org/apache/sling/engine/impl/SlingHttpServletResponseImplTest.java
+++ 
b/src/test/java/org/apache/sling/engine/impl/SlingHttpServletResponseImplTest.java
@@ -39,6 +39,7 @@ import org.mockito.Mockito;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.atMostOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -117,6 +118,9 @@ public class SlingHttpServletResponseImplTest {
         
when(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker);
         
when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME);
 
+        final SlingRequestProcessorImpl requestProcessor = 
mock(SlingRequestProcessorImpl.class);
+        
when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor);
+
         ArrayList<String> logMessagesList = new 
ArrayList<>(Arrays.asList(logMessages));
         when(requestProgressTracker.getMessages()).thenAnswer(invocation -> 
logMessagesList.iterator());
         info.setProtectHeadersOnInclude(true);
@@ -135,6 +139,8 @@ public class SlingHttpServletResponseImplTest {
         Mockito.verify(orig, never()).setLocale(null);
         Mockito.verify(orig, never()).setBufferSize(4500);
 
+        Mockito.verify(requestProcessor, 
atMostOnce()).setContentTypeHeaderState(Mockito.any());
+
         ArgumentCaptor<String> logCaptor = 
ArgumentCaptor.forClass(String.class);
         verify(requestProgressTracker, times(1)).log(logCaptor.capture());
         return logCaptor.getValue();
@@ -214,6 +220,9 @@ public class SlingHttpServletResponseImplTest {
         final HttpServletResponse include = new 
SlingHttpServletResponseImpl(requestData, orig);
         
when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME);
 
+        final SlingRequestProcessorImpl requestProcessor = 
mock(SlingRequestProcessorImpl.class);
+        
when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor);
+
         Throwable throwable = null;
         try {
             include.setContentType("application/json");
@@ -257,6 +266,9 @@ public class SlingHttpServletResponseImplTest {
         ArrayList<String> logMessagesList = new 
ArrayList<>(Arrays.asList(logMessages));
         when(requestProgressTracker.getMessages()).thenAnswer(invocation -> 
logMessagesList.iterator());
 
+        final SlingRequestProcessorImpl requestProcessor = 
mock(SlingRequestProcessorImpl.class);
+        
when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor);
+
         final HttpServletResponse include = new 
SlingHttpServletResponseImpl(requestData, orig);
         
when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME);
         include.setContentType("application/json");
@@ -288,6 +300,9 @@ public class SlingHttpServletResponseImplTest {
         when(orig.getContentType()).thenReturn("application/json");
         
when(requestData.getRequestProgressTracker()).thenReturn(requestProgressTracker);
 
+        final SlingRequestProcessorImpl requestProcessor = 
mock(SlingRequestProcessorImpl.class);
+        
when(requestData.getSlingRequestProcessor()).thenReturn(requestProcessor);
+
         final HttpServletResponse include = new 
SlingHttpServletResponseImpl(requestData, orig);
         
when(requestData.getActiveServletName()).thenReturn(ACTIVE_SERVLET_NAME);
         include.setContentType("application/json");

Reply via email to