FELIX-6553 : RequestDispatcher include is not ignoring header modifications
diff --git a/http/base-4.x/src/main/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapper.java b/http/base-4.x/src/main/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapper.java
new file mode 100644
index 0000000..61937b7
--- /dev/null
+++ b/http/base-4.x/src/main/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapper.java
@@ -0,0 +1,129 @@
+/*

+ * 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.felix.http.base.internal.dispatch;

+

+import java.io.IOException;

+import java.util.Locale;

+

+import javax.servlet.http.Cookie;

+import javax.servlet.http.HttpServletResponse;

+import javax.servlet.http.HttpServletResponseWrapper;

+

+/**

+ * Wrapper to handle dispatching of includes where all changes to headers

+ * are simply silently ignored (see section 9.3 of the servlet specification)

+ */

+public class IncludeResponseWrapper extends HttpServletResponseWrapper {

+

+    public IncludeResponseWrapper(final HttpServletResponse response) {

+        super(response);

+    }

+

+    @Override

+    public void reset() {

+        // ignore if not committed

+        if (getResponse().isCommitted()) {

+            getResponse().reset();

+        }

+    }

+

+    @Override

+    public void setContentLength(final int len) {

+        // ignore

+    }

+

+    @Override

+    public void setContentLengthLong(final long len) {

+        // ignore

+    }

+

+    @Override

+    public void setContentType(final String type) {

+        // ignore

+    }

+

+    @Override

+    public void setLocale(final Locale loc) {

+        // ignore

+    }

+

+    @Override

+    public void setBufferSize(final int size) {

+        // ignore

+    }

+

+    @Override

+    public void addCookie(final Cookie cookie) {

+        // ignore

+    }

+

+

+    @Override

+    public void addDateHeader(final String name, final long value) {

+        // ignore

+    }

+

+    @Override

+    public void addHeader(final String name, final String value) {

+        // ignore

+    }

+

+    @Override

+    public void addIntHeader(final String name, final int value) {

+        // ignore

+    }

+

+    @Override

+    public void sendError(final int sc) throws IOException {

+        // ignore

+    }

+

+    @Override

+    public void sendError(final int sc, final String msg) throws IOException {

+        // ignore

+    }

+

+    @Override

+    public void sendRedirect(final String location) throws IOException {

+        // ignore

+    }

+

+    @Override

+    public void setDateHeader(final String name, final long value) {

+        // ignore

+    }

+

+    @Override

+    public void setHeader(final String name, final String value) {

+        // ignore

+    }

+

+    @Override

+    public void setIntHeader(final String name, final int value) {

+        // ignore

+    }

+

+    @Override

+    public void setStatus(final int sc) {

+        // ignore

+    }

+

+    @Override

+    public void setStatus(final int sc, final String msg) {

+        // ignore

+    }

+}

diff --git a/http/base-4.x/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java b/http/base-4.x/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java
index ea9813c..9663fcc 100644
--- a/http/base-4.x/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java
+++ b/http/base-4.x/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java
@@ -25,6 +25,7 @@
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.http.base.internal.handler.FilterHandler;
 import org.apache.felix.http.base.internal.registry.ServletResolution;
@@ -114,6 +115,6 @@
         final FilterHandler[] filterHandlers = this.resolution.handlerRegistry.getFilterHandlers(this.resolution.handler, DispatcherType.INCLUDE, requestURI);
 
         final FilterChain filterChain = new InvocationChain(resolution.handler, filterHandlers);
-        filterChain.doFilter( req, response);
+        filterChain.doFilter( req, new IncludeResponseWrapper((HttpServletResponse)response));
     }
 }
diff --git a/http/base-4.x/src/test/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapperTest.java b/http/base-4.x/src/test/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapperTest.java
new file mode 100644
index 0000000..41b3d70
--- /dev/null
+++ b/http/base-4.x/src/test/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapperTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.felix.http.base.internal.dispatch;
+
+import static org.mockito.Mockito.times;
+
+import java.io.IOException;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class IncludeResponseWrapperTest {
+
+    @Test public void testReset() {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+        
+        Mockito.when(orig.isCommitted()).thenReturn(false);
+        include.reset();
+        Mockito.verify(orig, times(1)).isCommitted();
+        Mockito.verifyNoMoreInteractions(orig);
+
+        Mockito.when(orig.isCommitted()).thenReturn(true);
+        include.reset();
+        Mockito.verify(orig, times(2)).isCommitted();
+        Mockito.verify(orig, times(1)).reset();
+        Mockito.verifyNoMoreInteractions(orig);
+    }
+
+    @Test public void testContentMethods() {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+
+        include.setContentLength(54);
+        include.setContentLengthLong(33L);
+        include.setContentType("text/plain");
+        include.setLocale(null);
+        include.setBufferSize(4500);
+
+        Mockito.verifyNoInteractions(orig);
+    }
+
+    @Test public void testCookies() {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+
+        include.addCookie(new Cookie("foo", "bar"));
+
+        Mockito.verifyNoInteractions(orig);
+    }
+
+    @Test public void testSendError() throws IOException {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+
+        include.sendError(500);
+        include.sendError(500, "Error");
+
+        Mockito.verifyNoInteractions(orig);
+    }
+
+    @Deprecated
+    @Test public void testSetStatus() {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+
+        include.setStatus(500);
+        include.setStatus(500, "Error");
+
+        Mockito.verifyNoInteractions(orig);
+    }
+
+    @Test public void testHeaders() {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+
+        include.setDateHeader("foo-d", 2000L);
+        include.addDateHeader("bar-d", 3000L);
+        include.setIntHeader("foo-i", 1);
+        include.addIntHeader("bar-i", 2);
+        include.setHeader("foo", "value");
+        include.addHeader("bar", "another");
+
+        Mockito.verifyNoInteractions(orig);
+    }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapper.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapper.java
new file mode 100644
index 0000000..7264db4
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapper.java
@@ -0,0 +1,129 @@
+/*

+ * 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.felix.http.base.internal.dispatch;

+

+import java.io.IOException;

+import java.util.Locale;

+

+import jakarta.servlet.http.Cookie;

+import jakarta.servlet.http.HttpServletResponse;

+import jakarta.servlet.http.HttpServletResponseWrapper;

+

+/**

+ * Wrapper to handle dispatching of includes where all changes to headers

+ * are simply silently ignored (see section 9.3 of the servlet specification)

+ */

+public class IncludeResponseWrapper extends HttpServletResponseWrapper {

+

+    public IncludeResponseWrapper(final HttpServletResponse response) {

+        super(response);

+    }

+

+    @Override

+    public void reset() {

+        // ignore if not committed

+        if (getResponse().isCommitted()) {

+            getResponse().reset();

+        }

+    }

+

+    @Override

+    public void setContentLength(final int len) {

+        // ignore

+    }

+

+    @Override

+    public void setContentLengthLong(final long len) {

+        // ignore

+    }

+

+    @Override

+    public void setContentType(final String type) {

+        // ignore

+    }

+

+    @Override

+    public void setLocale(final Locale loc) {

+        // ignore

+    }

+

+    @Override

+    public void setBufferSize(final int size) {

+        // ignore

+    }

+

+    @Override

+    public void addCookie(final Cookie cookie) {

+        // ignore

+    }

+

+

+    @Override

+    public void addDateHeader(final String name, final long value) {

+        // ignore

+    }

+

+    @Override

+    public void addHeader(final String name, final String value) {

+        // ignore

+    }

+

+    @Override

+    public void addIntHeader(final String name, final int value) {

+        // ignore

+    }

+

+    @Override

+    public void sendError(final int sc) throws IOException {

+        // ignore

+    }

+

+    @Override

+    public void sendError(final int sc, final String msg) throws IOException {

+        // ignore

+    }

+

+    @Override

+    public void sendRedirect(final String location) throws IOException {

+        // ignore

+    }

+

+    @Override

+    public void setDateHeader(final String name, final long value) {

+        // ignore

+    }

+

+    @Override

+    public void setHeader(final String name, final String value) {

+        // ignore

+    }

+

+    @Override

+    public void setIntHeader(final String name, final int value) {

+        // ignore

+    }

+

+    @Override

+    public void setStatus(final int sc) {

+        // ignore

+    }

+

+    @Override

+    public void setStatus(final int sc, final String msg) {

+        // ignore

+    }

+}

diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java
index fe70c4f..f95fef9 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java
@@ -25,6 +25,7 @@
 import jakarta.servlet.ServletRequest;
 import jakarta.servlet.ServletResponse;
 import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
 
 import org.apache.felix.http.base.internal.handler.FilterHandler;
 import org.apache.felix.http.base.internal.registry.ServletResolution;
@@ -114,6 +115,6 @@
         final FilterHandler[] filterHandlers = this.resolution.handlerRegistry.getFilterHandlers(this.resolution.handler, DispatcherType.INCLUDE, requestURI);
 
         final FilterChain filterChain = new InvocationChain(resolution.handler, filterHandlers);
-        filterChain.doFilter( req, response);
+        filterChain.doFilter( req, new IncludeResponseWrapper((HttpServletResponse)response));
     }
 }
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapperTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapperTest.java
new file mode 100644
index 0000000..3c841ba
--- /dev/null
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/dispatch/IncludeResponseWrapperTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.felix.http.base.internal.dispatch;
+
+import static org.mockito.Mockito.times;
+
+import java.io.IOException;
+
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class IncludeResponseWrapperTest {
+
+    @Test public void testReset() {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+        
+        Mockito.when(orig.isCommitted()).thenReturn(false);
+        include.reset();
+        Mockito.verify(orig, times(1)).isCommitted();
+        Mockito.verifyNoMoreInteractions(orig);
+
+        Mockito.when(orig.isCommitted()).thenReturn(true);
+        include.reset();
+        Mockito.verify(orig, times(2)).isCommitted();
+        Mockito.verify(orig, times(1)).reset();
+        Mockito.verifyNoMoreInteractions(orig);
+    }
+
+    @Test public void testContentMethods() {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+
+        include.setContentLength(54);
+        include.setContentLengthLong(33L);
+        include.setContentType("text/plain");
+        include.setLocale(null);
+        include.setBufferSize(4500);
+
+        Mockito.verifyNoInteractions(orig);
+    }
+
+    @Test public void testCookies() {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+
+        include.addCookie(new Cookie("foo", "bar"));
+
+        Mockito.verifyNoInteractions(orig);
+    }
+
+    @Test public void testSendError() throws IOException {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+
+        include.sendError(500);
+        include.sendError(500, "Error");
+
+        Mockito.verifyNoInteractions(orig);
+    }
+
+    @Deprecated
+    @Test public void testSetStatus() {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+
+        include.setStatus(500);
+        include.setStatus(500, "Error");
+
+        Mockito.verifyNoInteractions(orig);
+    }
+
+    @Test public void testHeaders() {
+        final HttpServletResponse orig = Mockito.mock(HttpServletResponse.class);
+        final HttpServletResponse include = new IncludeResponseWrapper(orig);
+
+        include.setDateHeader("foo-d", 2000L);
+        include.addDateHeader("bar-d", 3000L);
+        include.setIntHeader("foo-i", 1);
+        include.addIntHeader("bar-i", 2);
+        include.setHeader("foo", "value");
+        include.addHeader("bar", "another");
+
+        Mockito.verifyNoInteractions(orig);
+    }
+}
diff --git a/http/jetty-4.x/pom.xml b/http/jetty-4.x/pom.xml
index a138b62..f6c99d7 100644
--- a/http/jetty-4.x/pom.xml
+++ b/http/jetty-4.x/pom.xml
@@ -400,7 +400,7 @@
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.http.base</artifactId>
-            <version>4.2.0</version>
+            <version>4.2.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>commons-fileupload</groupId>