You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2020/08/31 14:45:25 UTC

[cxf] branch 3.2.x-fixes updated (86740dc -> 04c19fe)

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

reta pushed a change to branch 3.2.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git.


    from 86740dc  Update Spring version to 4.3.28.RELEASE
     new 61fba76  Added a test case for @Multipart and form-data
     new 43d30bd  CXF-8331: Reverse proxy url is not reflected when use-x-forwarded-headers is true
     new 24f0af4  CXF-8331: Reverse proxy url is not reflected when use-x-forwarded-headers is true (fixing checkstyle issues)
     new 04c19fe  Recording .gitmergeinfo Changes

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitmergeinfo                                      |   5 +
 rt/transports/http/pom.xml                         |   6 +
 .../cxf/transport/servlet/AbstractHTTPServlet.java |   7 +-
 .../HttpServletRequestXForwardedFilterTest.java    | 208 +++++++++++++++++++++
 .../cxf/systest/jaxrs/JAXRSMultipartTest.java      |  32 ++++
 .../apache/cxf/systest/jaxrs/MultipartStore.java   |  12 ++
 6 files changed, 269 insertions(+), 1 deletion(-)
 create mode 100644 rt/transports/http/src/test/java/org/apache/cxf/transport/servlet/HttpServletRequestXForwardedFilterTest.java


[cxf] 02/04: CXF-8331: Reverse proxy url is not reflected when use-x-forwarded-headers is true

Posted by re...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch 3.2.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 43d30bde8be13ca7c734ac7b6c3ead688ca5eb4e
Author: reta <dr...@gmail.com>
AuthorDate: Sun Aug 30 16:12:43 2020 -0400

    CXF-8331: Reverse proxy url is not reflected when use-x-forwarded-headers is true
    
    (cherry picked from commit 50f6d7fc063b8728cd3903a9a7775c0462860a83)
    (cherry picked from commit a7f082120fa482d7b68a1aee3644412e19d9a35e)
---
 rt/transports/http/pom.xml                         |   6 +
 .../cxf/transport/servlet/AbstractHTTPServlet.java |   7 +-
 .../HttpServletRequestXForwardedFilterTest.java    | 207 +++++++++++++++++++++
 3 files changed, 219 insertions(+), 1 deletion(-)

diff --git a/rt/transports/http/pom.xml b/rt/transports/http/pom.xml
index eea21b6..a644246 100644
--- a/rt/transports/http/pom.xml
+++ b/rt/transports/http/pom.xml
@@ -63,6 +63,12 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>${cxf.mockito.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
             <scope>provided</scope>
diff --git a/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractHTTPServlet.java b/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractHTTPServlet.java
index 0363876..28ad966 100644
--- a/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractHTTPServlet.java
+++ b/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractHTTPServlet.java
@@ -28,10 +28,12 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Properties;
 import java.util.ResourceBundle;
 import java.util.logging.Logger;
 import java.util.regex.Pattern;
+import java.util.stream.Stream;
 
 import javax.servlet.Filter;
 import javax.servlet.FilterConfig;
@@ -308,7 +310,10 @@ public abstract class AbstractHTTPServlet extends HttpServlet implements Filter
             String originalPrefix = request.getHeader(X_FORWARDED_PREFIX_HEADER);
             String originalHost = request.getHeader(X_FORWARDED_HOST_HEADER);
             String originalPort = request.getHeader(X_FORWARDED_PORT_HEADER);
-            if (originalProtocol != null || originalRemoteAddr != null) {
+            
+            // If at least one of the X-Forwarded-Xxx headers is set, try to use them
+            if (Stream.of(originalProtocol, originalRemoteAddr, originalPrefix, 
+                    originalHost, originalPort).anyMatch(Objects::nonNull)) {
                 return new HttpServletRequestXForwardedFilter(request, 
                                                               originalProtocol, 
                                                               originalRemoteAddr,
diff --git a/rt/transports/http/src/test/java/org/apache/cxf/transport/servlet/HttpServletRequestXForwardedFilterTest.java b/rt/transports/http/src/test/java/org/apache/cxf/transport/servlet/HttpServletRequestXForwardedFilterTest.java
new file mode 100644
index 0000000..4561ac1
--- /dev/null
+++ b/rt/transports/http/src/test/java/org/apache/cxf/transport/servlet/HttpServletRequestXForwardedFilterTest.java
@@ -0,0 +1,207 @@
+/**
+ * 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.cxf.transport.servlet;
+
+import java.io.IOException;
+import java.util.function.BiConsumer;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletConfig;
+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.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class HttpServletRequestXForwardedFilterTest {
+    private AbstractHTTPServlet servlet;
+    private HttpServletRequest request;
+    private HttpServletResponse response;
+    private ServletConfig config;
+    
+    @Before
+    public void setUp() throws ServletException {
+        request = mock(HttpServletRequest.class);
+        response = mock(HttpServletResponse.class);
+        
+        when(request.getRequestURI()).thenReturn("/test");
+        when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost/api/test"));
+        when(request.getContextPath()).thenReturn("/api");
+        when(request.getServletPath()).thenReturn("");
+        
+        config = mock(ServletConfig.class);
+        when(config.getInitParameter(eq("use-x-forwarded-headers"))).thenReturn("true");
+    }
+
+    @Test
+    public void testNoXForwardedHeadersSpecified() throws Exception {
+        servlet = servlet((req, resp) -> {
+            assertThat(req.getRequestURI(), equalTo("/test"));
+            assertThat(req.getContextPath(), equalTo("/api"));
+            assertThat(req.getRequestURL().toString(), equalTo("http://localhost/api/test"));
+            assertThat(req.getRemoteAddr(), nullValue());
+            assertThat(req.isSecure(), equalTo(false));
+        });
+        
+        servlet.init(config);
+        servlet.service(request, response);
+    }
+
+    @Test
+    public void testAllXForwardedHeadersSpecified() throws Exception {
+        servlet = servlet((req, resp) -> {
+            assertThat(req.getRequestURI(), equalTo("/forwarded/test"));
+            assertThat(req.getContextPath(), equalTo("/forwarded"));
+            assertThat(req.getServletPath(), equalTo("/api"));
+            assertThat(req.getRequestURL().toString(), equalTo("https://abc:50000/forwarded/api/test"));
+            assertThat(req.getRemoteAddr(), equalTo("203.0.113.195"));
+            assertThat(req.isSecure(), equalTo(true));
+        });
+        
+        when(request.getHeader(eq("X-Forwarded-Proto"))).thenReturn("https");
+        when(request.getHeader(eq("X-Forwarded-For"))).thenReturn("203.0.113.195, 70.41.3.18, 150.172.238.178");
+        when(request.getHeader(eq("X-Forwarded-Prefix"))).thenReturn("/forwarded");
+        when(request.getHeader(eq("X-Forwarded-Host"))).thenReturn("abc");
+        when(request.getHeader(eq("X-Forwarded-Port"))).thenReturn("50000");
+
+        servlet.init(config);
+        servlet.service(request, response);
+    }
+
+    @Test
+    public void testXForwardedProtoHeaderSpecified() throws Exception {
+        servlet = servlet((req, resp) -> {
+            assertThat(req.getRequestURI(), equalTo("/test"));
+            assertThat(req.getContextPath(), equalTo("/api"));
+            assertThat(req.getServletPath(), equalTo(""));
+            assertThat(req.getRequestURL().toString(), equalTo("https://localhost/api/test"));
+            assertThat(req.getRemoteAddr(), nullValue());
+            assertThat(req.isSecure(), equalTo(true));
+        });
+        
+        when(request.getHeader(eq("X-Forwarded-Proto"))).thenReturn("https");
+        
+        servlet.init(config);
+        servlet.service(request, response);
+    }
+    
+    @Test
+    public void testXForwardedForHeaderSpecified() throws Exception {
+        servlet = servlet((req, resp) -> {
+            assertThat(req.getRequestURI(), equalTo("/test"));
+            assertThat(req.getContextPath(), equalTo("/api"));
+            assertThat(req.getServletPath(), equalTo(""));
+            assertThat(req.getRequestURL().toString(), equalTo("http://localhost/api/test"));
+            assertThat(req.getRemoteAddr(), equalTo("203.0.113.195"));
+            assertThat(req.isSecure(), equalTo(false));
+        });
+        
+        when(request.getHeader(eq("X-Forwarded-For"))).thenReturn("203.0.113.195, 70.41.3.18, 150.172.238.178");
+        
+        servlet.init(config);
+        servlet.service(request, response);
+    }
+
+    @Test
+    public void testXForwardedPrefixHeaderSpecified() throws Exception {
+        servlet = servlet((req, resp) -> {
+            assertThat(req.getRequestURI(), equalTo("/forwarded/test"));
+            assertThat(req.getContextPath(), equalTo("/forwarded"));
+            assertThat(req.getServletPath(), equalTo("/api"));
+            assertThat(req.getRequestURL().toString(), equalTo("http://localhost/forwarded/api/test"));
+            assertThat(req.getRemoteAddr(), nullValue());
+            assertThat(req.isSecure(), equalTo(false));
+        });
+        
+        when(request.getHeader(eq("X-Forwarded-Prefix"))).thenReturn("/forwarded");
+
+        servlet.init(config);
+        servlet.service(request, response);
+    }
+    
+    @Test
+    public void testXForwardedHostHeaderSpecified() throws Exception {
+        servlet = servlet((req, resp) -> {
+            assertThat(req.getRequestURI(), equalTo("/test"));
+            assertThat(req.getContextPath(), equalTo("/api"));
+            assertThat(req.getServletPath(), equalTo(""));
+            assertThat(req.getRequestURL().toString(), equalTo("http://abc/api/test"));
+            assertThat(req.getRemoteAddr(), nullValue());
+            assertThat(req.isSecure(), equalTo(false));
+        });
+        
+        when(request.getHeader(eq("X-Forwarded-Host"))).thenReturn("abc");
+
+        servlet.init(config);
+        servlet.service(request, response);
+    }
+    
+    @Test
+    public void testXForwardedPortHeaderSpecified() throws Exception {
+        servlet = servlet((req, resp) -> {
+            assertThat(req.getRequestURI(), equalTo("/test"));
+            assertThat(req.getContextPath(), equalTo("/api"));
+            assertThat(req.getServletPath(), equalTo(""));
+            assertThat(req.getRequestURL().toString(), equalTo("http://localhost:50000/api/test"));
+            assertThat(req.getRemoteAddr(), nullValue());
+            assertThat(req.isSecure(), equalTo(false));
+        });
+        
+        when(request.getHeader(eq("X-Forwarded-Port"))).thenReturn("50000");
+
+        servlet.init(config);
+        servlet.service(request, response);
+    }
+    
+    private AbstractHTTPServlet servlet(BiConsumer<HttpServletRequest, HttpServletResponse> assertions) {
+        return new AbstractHTTPServlet() {
+            private static final long serialVersionUID = -3870709934037062681L;
+
+            @Override
+            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
+                    throws IOException, ServletException {
+            }
+            
+            @Override
+            protected void invoke(HttpServletRequest request, HttpServletResponse response) 
+                    throws ServletException {
+                assertions.accept(request, response);
+            }
+            
+            @Override
+            protected Bus getBus() {
+                return BusFactory.getDefaultBus();
+            }
+        };
+    }
+    
+}


[cxf] 04/04: Recording .gitmergeinfo Changes

Posted by re...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch 3.2.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 04c19fee0d7fe759188882c58e9fa83975e18474
Author: reta <dr...@gmail.com>
AuthorDate: Sun Aug 30 23:51:10 2020 -0400

    Recording .gitmergeinfo Changes
---
 .gitmergeinfo | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/.gitmergeinfo b/.gitmergeinfo
index 636dfb2..39fd13d 100644
--- a/.gitmergeinfo
+++ b/.gitmergeinfo
@@ -541,6 +541,7 @@ B 8a4c09ff9cb1c8114ea338943932716d56b9c66b
 B 8a5537b399780761ffdcd04a072b38d37a41de85
 B 8ad0d9bea287db8c3c0acd737a1d577351c79d24
 B 8ad341b9700632ba4a949762b37ec0cea604bfbf
+B 8b0149ac566159c30ea361c41529f3939b31cb37
 B 8c5d3db8ec16b35e43b578c405218678d2e2f562
 B 8c740b8f03fc72cd6c8a273a06f677313795f3e1
 B 8c9895c7b214616221aef78c84512b6e2662b58b
@@ -980,6 +981,7 @@ M 08a548eed354207c7307a6d91ea92f6cc54f0b5a
 M 08c1b24ee15812c5f8d5a9ac9780a4289f07daf0
 M 0b41ab1647b06aedbb3db9812cdca6b2102b640c
 M 0b920d7275800e651b2c48c982b1977249516ddf
+M 0bc26e1124a52d3a31bfc8d5ef786cb75bf6867e
 M 0dc54f4549a68a573e38e0d01396efbb46e87f41
 M 0ef506e13f227ce325e09ae5042e003df32c1e0f
 M 0f6490cb4e63118dcf4deedd0345ed4b6a35b669
@@ -1077,6 +1079,7 @@ M 869eeb6a5be5f71d1a9497cbdd01d93e585be7c1
 M 86d7c657499b73bdbbd6ba9f1579e76ca0715357
 M 886a055d49d844e445721e4752c7a6360cbe8b9a
 M 890a0186ff92bcf08a82ecbf4b48c4a37997462c
+M 8911a2394b2f9ef58d9132a22927666bd77adf0d
 M 8bb64836e76077572349a19829adcbbe9c58689d
 M 8df4b33f4dad2934b05cb1c0e706276f76bc7044
 M 8e78fae173033a3118e0eb278816fc3088aecbea
@@ -1096,7 +1099,9 @@ M 9c02f616f6aec1cf43f1d6bac810d606dab98a72
 M 9e1788a8e08264a2735ba5373a96f09215d60769
 M 9fb285e9895c4754578d61934fe20994778c0e17
 M a3ef872d21cc20059622fab6fec4f6b9d1ac6ceb
+M a5a629e3bad6bbdd69c0dd368d5c019bf969d248
 M a7638c1562631b9c66c0de8a35b0cb47f2622d8a
+M a7f082120fa482d7b68a1aee3644412e19d9a35e
 M a8132de2a0204a1247f3e63e9140aa7a34bdf628
 M aacfb57762fd92c835fc543a6d30570f0e2edd95
 M ac1be213e67767f01af5b7077d882ee6f52e5895


[cxf] 03/04: CXF-8331: Reverse proxy url is not reflected when use-x-forwarded-headers is true (fixing checkstyle issues)

Posted by re...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch 3.2.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 24f0af473058a28df9c2a45a29e2913a803667a2
Author: reta <dr...@gmail.com>
AuthorDate: Sun Aug 30 16:31:26 2020 -0400

    CXF-8331: Reverse proxy url is not reflected when use-x-forwarded-headers is true (fixing checkstyle issues)
    
    (cherry picked from commit 1296a77af86fd531e0eb5eb0365dce49a9dcdfd6)
    (cherry picked from commit d96c877ad205d1e445e2366c77f43011d8951892)
---
 .../cxf/transport/servlet/HttpServletRequestXForwardedFilterTest.java  | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/rt/transports/http/src/test/java/org/apache/cxf/transport/servlet/HttpServletRequestXForwardedFilterTest.java b/rt/transports/http/src/test/java/org/apache/cxf/transport/servlet/HttpServletRequestXForwardedFilterTest.java
index 4561ac1..229d44f 100644
--- a/rt/transports/http/src/test/java/org/apache/cxf/transport/servlet/HttpServletRequestXForwardedFilterTest.java
+++ b/rt/transports/http/src/test/java/org/apache/cxf/transport/servlet/HttpServletRequestXForwardedFilterTest.java
@@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.cxf.Bus;
 import org.apache.cxf.BusFactory;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -182,7 +183,7 @@ public class HttpServletRequestXForwardedFilterTest {
         servlet.service(request, response);
     }
     
-    private AbstractHTTPServlet servlet(BiConsumer<HttpServletRequest, HttpServletResponse> assertions) {
+    private static AbstractHTTPServlet servlet(BiConsumer<HttpServletRequest, HttpServletResponse> assertions) {
         return new AbstractHTTPServlet() {
             private static final long serialVersionUID = -3870709934037062681L;
 


[cxf] 01/04: Added a test case for @Multipart and form-data

Posted by re...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch 3.2.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 61fba762467e32fde35f48ec47eca07db4203e9a
Author: reta <dr...@gmail.com>
AuthorDate: Wed Aug 12 20:46:15 2020 -0400

    Added a test case for @Multipart and form-data
    
    (cherry picked from commit f0541e8cda2a323377b0decf0fd53a6a2cc3e766)
    (cherry picked from commit 0bc26e1124a52d3a31bfc8d5ef786cb75bf6867e)
    
    # Conflicts:
    #	systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java
---
 .../cxf/systest/jaxrs/JAXRSMultipartTest.java      | 32 ++++++++++++++++++++++
 .../apache/cxf/systest/jaxrs/MultipartStore.java   | 12 ++++++++
 2 files changed, 44 insertions(+)

diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java
index b2e5c6d..6741b89 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java
@@ -38,6 +38,9 @@ import java.util.Map;
 import javax.activation.DataHandler;
 import javax.imageio.ImageIO;
 import javax.mail.util.ByteArrayDataSource;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
@@ -45,6 +48,8 @@ import javax.ws.rs.core.Response;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.Unmarshaller;
 
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+
 import org.apache.cxf.ext.logging.LoggingInInterceptor;
 import org.apache.cxf.ext.logging.LoggingOutInterceptor;
 import org.apache.cxf.helpers.FileUtils;
@@ -53,6 +58,7 @@ import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
 import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.AttachmentBuilder;
 import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
 import org.apache.cxf.jaxrs.ext.multipart.InputStreamDataSource;
 import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
@@ -73,6 +79,8 @@ import org.apache.http.util.EntityUtils;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import static org.hamcrest.CoreMatchers.equalTo;
+
 public class JAXRSMultipartTest extends AbstractBusClientServerTestBase {
     public static final String PORT = MultipartServer.PORT;
 
@@ -1004,6 +1012,30 @@ public class JAXRSMultipartTest extends AbstractBusClientServerTestBase {
         client.close();
     }
 
+    @Test
+    public void testUpdateBookMultipart() {
+        final WebTarget target = ClientBuilder
+            .newClient()
+            .register(JacksonJsonProvider.class)
+            .target("http://localhost:" + PORT + "/bookstore");
+
+        final MultipartBody builder = new MultipartBody(Arrays.asList(
+                new AttachmentBuilder()
+                    .id("name")
+                    .contentDisposition(new ContentDisposition("form-data; name=\"name\""))
+                    .object("The Book")
+                    .build()
+            ));
+        
+        try (Response response = target
+                .path("1")
+                .request()
+                .put(Entity.entity(builder, MediaType.MULTIPART_FORM_DATA))) {
+            assertThat(response.getStatus(), equalTo(200));
+            assertThat(response.readEntity(Book.class).getName(), equalTo("The Book"));
+        }
+    }
+
     private void doAddBook(String address, String resourceName, int status) throws Exception {
         doAddBook("multipart/related", address, resourceName, status);
     }
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java
index c843f7b..08f51fa 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java
@@ -36,7 +36,9 @@ import javax.ws.rs.Consumes;
 import javax.ws.rs.FormParam;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
@@ -763,6 +765,16 @@ public class MultipartStore {
         b.setName("CXF in Action - 2");
         return Response.ok(b).build();
     }
+    
+    @PUT
+    @Path("{id}")
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Consumes({ MediaType.MULTIPART_FORM_DATA })
+    public Response updateBook(@PathParam("id") long id, @Multipart("name") String name) {
+        Book book = new Book(name, id);
+        return Response.ok().entity(book).build();
+    }
+
     private Response readBookFromInputStream(InputStream is) throws Exception {
         JAXBContext c = JAXBContext.newInstance(new Class[]{Book.class});
         Unmarshaller u = c.createUnmarshaller();