You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cz...@apache.org on 2022/08/05 09:16:32 UTC

[felix-dev] branch master updated: FELIX-6553 : RequestDispatcher include is not ignoring header modifications

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

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new c11f99e04a FELIX-6553 : RequestDispatcher include is not ignoring header modifications
c11f99e04a is described below

commit c11f99e04ae01f64be1bf3182424bfac7f5e1d54
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Fri Aug 5 11:16:17 2022 +0200

    FELIX-6553 : RequestDispatcher include is not ignoring header modifications
---
 .../internal/dispatch/IncludeResponseWrapper.java  | 129 +++++++++++++++++++++
 .../internal/dispatch/RequestDispatcherImpl.java   |   3 +-
 .../dispatch/IncludeResponseWrapperTest.java       | 103 ++++++++++++++++
 .../internal/dispatch/IncludeResponseWrapper.java  | 129 +++++++++++++++++++++
 .../internal/dispatch/RequestDispatcherImpl.java   |   3 +-
 .../dispatch/IncludeResponseWrapperTest.java       | 103 ++++++++++++++++
 http/jetty-4.x/pom.xml                             |   2 +-
 7 files changed, 469 insertions(+), 3 deletions(-)

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 0000000000..61937b7212
--- /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 ea9813cae7..9663fcc820 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.ServletException;
 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 @@ public final class RequestDispatcherImpl implements RequestDispatcher
         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 0000000000..41b3d70ecb
--- /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 0000000000..7264db4c13
--- /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 fe70c4fd4e..f95fef94ac 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.ServletException;
 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 @@ public final class RequestDispatcherImpl implements RequestDispatcher
         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 0000000000..3c841ba4b0
--- /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 a138b627bc..f6c99d70a7 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>