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 00:10:41 UTC

[cxf] branch 3.3.x-fixes updated (a67732d -> 8b0149a)

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

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


    from a67732d  [CXF-8329] avoid NPE with empty query parameters
     new 0bc26e1  Added a test case for @Multipart and form-data
     new a7f0821  CXF-8331: Reverse proxy url is not reflected when use-x-forwarded-headers is true
     new d96c877  CXF-8331: Reverse proxy url is not reflected when use-x-forwarded-headers is true (fixing checkstyle issues)
     new 8b0149a  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      |  33 ++++
 .../apache/cxf/systest/jaxrs/MultipartStore.java   |  12 ++
 6 files changed, 270 insertions(+), 1 deletion(-)
 create mode 100644 rt/transports/http/src/test/java/org/apache/cxf/transport/servlet/HttpServletRequestXForwardedFilterTest.java


[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.3.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 8b0149ac566159c30ea361c41529f3939b31cb37
Author: reta <dr...@gmail.com>
AuthorDate: Sun Aug 30 17:01:19 2020 -0400

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

diff --git a/.gitmergeinfo b/.gitmergeinfo
index a58ef73..e1eda15 100644
--- a/.gitmergeinfo
+++ b/.gitmergeinfo
@@ -361,6 +361,8 @@ B ff7b028eeea39fedeb06207753fe6dcb9b0d8cca
 M 00c77f3e98985d339b4bb3488a7ea8799eefcdec
 M 02de4c435f4de72fc93291bebd953dc11d53c02d
 M 09ddfdeaef6f32537dba23fa6d7ef36992b3217b
+M 10251d33f0f379a5bebc640466b639382cbe42bb
+M 113e426f6b717925736433fe04a1b72f801dd574
 M 12d2f091afde535edab04e56c9df026cc9aad743
 M 160f50ed9bd9391b61417716b3a67653c9e81a98
 M 169be66c11851d723e4a8d93f752e0b3dff1fa60
@@ -368,6 +370,7 @@ M 16d285838dc2209ca84af21723478f199403c2b7
 M 177afff3595c2a248e55457809118e842e1593f4
 M 180578d01f361d77ce7f42dd760fa8d5f245ce7e
 M 1e3817e3f7d51d093bff81d2520fb7cef82f0df8
+M 209407e09d8b1466d1c524184690915c1aa1a3ea
 M 2244024ba6d438f44adffd56b1dc8e02cb45cf71
 M 224fa741d6753053c6f47d361f8cc7e93fd34ed9
 M 2275d49772676ac72bc14ebbc3173719cd72e635
@@ -412,6 +415,7 @@ M 7971b506283502c26aafb97c0f6e50633f126cdd
 M 7a655b49fd6d432127a55b38f15c1212f7064196
 M 7aa6e0a402b8ce7dfff8a931a507b884d7152cea
 M 7b50181ebc445bb84d3463b284643432399bc0f8
+M 7c6bf1a843f2d4860d1bd7541139911ad47c7964
 M 7e1bc67acf02fe6049a4bfb7e13fe6933e618ae8
 M 7ef814556d727c147d6f625cbb1170edfd24a752
 M 7f733555945f1faed66b0b6163d24889d4cf60fc
@@ -462,6 +466,7 @@ M e1b81269089d6e7cb53e949a9fde629d37bce421
 M e38fc4aa14d7741369de5d027b30e52def3a0549
 M e72ad770c9a8f4439b93e2c19e61c43562ac5877
 M edd100917a2f73e43d6e9e34efdc09a854f5c942
+M ee2cfb039edbe35a6404350de877361ce62c821d
 M f22022b1f094efdcc81b63d9f3f10b85894fcdb8
 M f3e2a52faec1210b1a45eaa36e3812ca03a4969d
 M f5899862b61bc39a7e2bb7945071ebfa3e6a8153


[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.3.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit d96c877ad205d1e445e2366c77f43011d8951892
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)
---
 .../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] 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.3.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit a7f082120fa482d7b68a1aee3644412e19d9a35e
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)
---
 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 e84f672..778c21a 100644
--- a/rt/transports/http/pom.xml
+++ b/rt/transports/http/pom.xml
@@ -69,6 +69,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 b53a1ab..a6b5cbb 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.Enumeration;
 import java.util.HashMap;
 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;
@@ -303,7 +305,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] 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.3.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 0bc26e1124a52d3a31bfc8d5ef786cb75bf6867e
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)
---
 .../cxf/systest/jaxrs/JAXRSMultipartTest.java      | 33 ++++++++++++++++++++++
 .../apache/cxf/systest/jaxrs/MultipartStore.java   | 12 ++++++++
 2 files changed, 45 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 ce485b4..ba9292e 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
@@ -27,6 +27,7 @@ import java.io.InputStream;
 import java.io.PushbackInputStream;
 import java.lang.annotation.Annotation;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -37,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;
@@ -44,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;
@@ -52,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;
@@ -72,10 +79,12 @@ import org.apache.http.util.EntityUtils;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 public class JAXRSMultipartTest extends AbstractBusClientServerTestBase {
@@ -1009,6 +1018,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 b109af9..a1a7c60 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();