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

reta pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/main by this push:
     new e92e6ebe08 [CXF-9207] Add space after semicolon in multipart 
Content-Disposition headers (#3018)
e92e6ebe08 is described below

commit e92e6ebe085113183dacd3286ede8722496f5336
Author: sstremler <[email protected]>
AuthorDate: Fri Apr 10 02:23:34 2026 +0200

    [CXF-9207] Add space after semicolon in multipart Content-Disposition 
headers (#3018)
---
 .../org/apache/cxf/attachment/AttachmentUtil.java  |   2 +-
 .../cxf/jaxrs/provider/EntityPartProvider.java     |   7 +-
 .../cxf/jaxrs/provider/MultipartProvider.java      |  24 +++----
 .../cxf/systest/jaxrs/JAXRSEntityPartTest.java     |  80 +++++++++++++++------
 .../cxf/systest/jaxrs/JAXRSMultipartTest.java      |  58 ++++++++-------
 .../cxf/systest/jaxrs/resources/java;test.jpg      | Bin 0 -> 1262 bytes
 6 files changed, 110 insertions(+), 61 deletions(-)

diff --git a/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java 
b/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java
index 039189385c..42cffc8c02 100644
--- a/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java
+++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java
@@ -556,7 +556,7 @@ public final class AttachmentUtil {
             if (f.exists() && f.isFile()) {
                 file = f.getName();
             }
-            att.setHeader("Content-Disposition", "attachment;name=\"" + file + 
"\"");
+            att.setHeader("Content-Disposition", "attachment; name=\"" + file 
+ "\"");
         }
         att.setXOP(isXop);
         return att;
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/EntityPartProvider.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/EntityPartProvider.java
index d02fc8de8c..5fcb68da9a 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/EntityPartProvider.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/EntityPartProvider.java
@@ -183,7 +183,7 @@ public class EntityPartProvider extends 
AbstractConfigurableProvider
     }
 
     private static <T> List<Attachment> convertToDataHandlers(final 
List<EntityPart> parts, final  Class<T> type,
-            final Type genericType, final Annotation[] anns)  throws 
IOException {
+            final Type genericType, final Annotation[] anns) {
         final List<Attachment> attachments = new ArrayList<>(parts.size());
         for (EntityPart part: parts) {
             attachments.add(createDataHandler(part, type, genericType, anns));
@@ -192,7 +192,7 @@ public class EntityPartProvider extends 
AbstractConfigurableProvider
     }
 
     private static <T> Attachment createDataHandler(final EntityPart part, 
final Class<T> type,
-            final Type genericType, final Annotation[] anns) throws 
IOException {
+            final Type genericType, final Annotation[] anns) {
 
         final String mt = Objects
             .requireNonNullElse(part.getMediaType(), 
MediaType.APPLICATION_OCTET_STREAM_TYPE)
@@ -205,7 +205,8 @@ public class EntityPartProvider extends 
AbstractConfigurableProvider
 
         return part.getFileName()
             .map(fileName -> {
-                final ContentDisposition cd = new 
ContentDisposition("form-data;name=file;filename=" + fileName);
+                final ContentDisposition cd = new ContentDisposition(
+                        "form-data; name=\"file\"; filename=\"" + fileName + 
"\"");
                 return new Attachment(part.getName(), part.getContent(), cd);
             })
             .orElseGet(() -> {
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java
index 319bacf54f..9322a2cb67 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java
@@ -346,7 +346,7 @@ public class MultipartProvider extends 
AbstractConfigurableProvider
         } else if (File.class.isAssignableFrom(obj.getClass())) {
             File f = (File)obj;
             ContentDisposition cd = 
mainMediaType.startsWith(MediaType.MULTIPART_FORM_DATA)
-                ? new ContentDisposition("form-data;name=file;filename=" + 
f.getName()) :  null;
+                ? new ContentDisposition("form-data; name=\"file\"; 
filename=\"" + f.getName() + "\"") :  null;
             return new Attachment(AttachmentUtil.BODY_ATTACHMENT_ID, 
Files.newInputStream(f.toPath()), cd);
         } else if (Attachment.class.isAssignableFrom(obj.getClass())) {
             Attachment att = (Attachment)obj;
@@ -357,14 +357,14 @@ public class MultipartProvider extends 
AbstractConfigurableProvider
                                      att.getObject().getClass(), new 
Annotation[]{},
                                      att.getContentType().toString(), id);
             MediaType mediaType = httpHeaders.getMediaType();
-            Attachment ret = null;
+            Attachment ret;
             if (MediaType.MULTIPART_FORM_DATA_TYPE.isCompatible(mediaType)
                 && att.getHeader("Content-Disposition") == null) {
                 ContentDisposition cd = new 
-                    ContentDisposition("form-data;name=\"" 
+                    ContentDisposition("form-data; name=\""
                         + att.getContentId() + "\"");
-                MultivaluedMap<String, String> newHeaders = 
-                    new MetadataMap<String, String>(att.getHeaders(), false, 
true);
+                MultivaluedMap<String, String> newHeaders =
+                        new MetadataMap<>(att.getHeaders(), false, true);
                 newHeaders.putSingle("Content-Disposition", cd.toString());
                 ret = new Attachment(att.getContentId(), dh, newHeaders);
             } else {
@@ -450,12 +450,12 @@ public class MultipartProvider extends 
AbstractConfigurableProvider
     }
 
     private static class MessageBodyWriterDataHandler<T> extends DataHandler {
-        private MessageBodyWriter<T> writer;
-        private T obj;
-        private Class<T> cls;
-        private Type genericType;
-        private Annotation[] anns;
-        private MediaType contentType;
+        private final MessageBodyWriter<T> writer;
+        private final T obj;
+        private final Class<T> cls;
+        private final Type genericType;
+        private final Annotation[] anns;
+        private final MediaType contentType;
         MessageBodyWriterDataHandler(MessageBodyWriter<T> writer,
                                      T obj,
                                      Class<T> cls,
@@ -475,7 +475,7 @@ public class MultipartProvider extends 
AbstractConfigurableProvider
         public void writeTo(OutputStream os) {
             try {
                 writer.writeTo(obj, cls, genericType, anns, contentType,
-                               new MetadataMap<String, Object>(), os);
+                        new MetadataMap<>(), os);
             } catch (IOException ex) {
                 throw ExceptionUtils.toInternalServerErrorException(ex, null);
             }
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
index 6506372b72..c7ecd4b1a0 100644
--- 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
+++ 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
@@ -19,9 +19,13 @@
 
 package org.apache.cxf.systest.jaxrs;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.io.SequenceInputStream;
+import java.net.URISyntaxException;
 import java.nio.file.Files;
 import java.util.List;
 
@@ -29,9 +33,9 @@ import jakarta.ws.rs.core.EntityPart;
 import jakarta.ws.rs.core.GenericType;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
+import org.apache.cxf.ext.logging.LoggingOutInterceptor;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.cxf.jaxrs.client.WebClient;
-import org.apache.cxf.jaxrs.utils.ParameterizedListType;
 import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
 
 import org.junit.BeforeClass;
@@ -49,8 +53,7 @@ import static org.junit.Assert.assertTrue;
 
 public class JAXRSEntityPartTest extends AbstractBusClientServerTestBase {
     public static final String PORT = EntityPartServer.PORT;
-    public static final String PORTINV = 
allocatePort(JAXRSEntityPartTest.class, 1);
-    
+
     @BeforeClass
     public static void startServers() throws Exception {
         assertTrue("server did not launch correctly", 
launchServer(EntityPartServer.class, true));
@@ -59,33 +62,37 @@ public class JAXRSEntityPartTest extends 
AbstractBusClientServerTestBase {
 
     @Test
     public void testUploadImage() throws Exception {
+        this.uploadImage("/org/apache/cxf/systest/jaxrs/resources/java.jpg");
+    }
+
+    @Test
+    public void testUploadImageWithSemicolon() throws Exception {
+        
this.uploadImage("/org/apache/cxf/systest/jaxrs/resources/java;test.jpg");
+    }
+
+    @Test
+    public void testContentDispositionInRequestIsWhitespaceSeparated() throws 
URISyntaxException, IOException {
         final File file = new 
File(getClass().getResource("/org/apache/cxf/systest/jaxrs/resources/java.jpg")
                 .toURI().getPath());
         final String address = "http://localhost:"; + PORT + 
"/bookstore/books/images";
+
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        PrintWriter writer = new PrintWriter(output, true);
+        LoggingOutInterceptor out = new LoggingOutInterceptor(writer);
+
         final WebClient client = WebClient.create(address);
+        client.getConfiguration().getOutInterceptors().add(out);
         
client.type(MediaType.MULTIPART_FORM_DATA).accept(MediaType.MULTIPART_FORM_DATA);
 
         try (InputStream is = Files.newInputStream(file.toPath())) {
             final EntityPart part = EntityPart
-                .withFileName(file.getName())
-                .content(is)
-                .build();
-
-            try (Response response = client.postCollection(List.of(part), 
EntityPart.class)) {
-                assertThat(response.getStatus(), equalTo(200));
-
-                @SuppressWarnings("unchecked")
-                final List<EntityPart> parts = (List<EntityPart>) response
-                    .readEntity(new GenericType<>(new 
ParameterizedListType(EntityPart.class)));
-
-                assertThat(parts, hasSize(1));
-                assertThat(parts.get(0), is(not(nullValue())));
+                    .withFileName(file.getName())
+                    .content(is)
+                    .build();
 
-                assertThat(parts.get(0).getFileName().isPresent(), is(true));
-                assertThat(parts.get(0).getFileName().get(), 
equalTo(part.getFileName().get()));
-
-                
assertArrayEquals(IOUtils.readBytesFromStream(parts.get(0).getContent()), 
-                    
IOUtils.readBytesFromStream(Files.newInputStream(file.toPath())));
+            try (Response ignored = client.postCollection(List.of(part), 
EntityPart.class)) {
+                String expected = "Content-Disposition: form-data; 
name=\"file\"; filename=\"java.jpg\"";
+                assertTrue(output.toString().contains(expected));
             }
         }
     }
@@ -152,4 +159,35 @@ public class JAXRSEntityPartTest extends 
AbstractBusClientServerTestBase {
         }
         return str;
     }
+
+    private void uploadImage(String path) throws URISyntaxException, 
IOException {
+        final File file = new File(getClass().getResource(path)
+                .toURI().getPath());
+        final String address = "http://localhost:"; + PORT + 
"/bookstore/books/images";
+
+        final WebClient client = WebClient.create(address);
+        
client.type(MediaType.MULTIPART_FORM_DATA).accept(MediaType.MULTIPART_FORM_DATA);
+
+        try (InputStream is = Files.newInputStream(file.toPath())) {
+            final EntityPart part = EntityPart
+                    .withFileName(file.getName())
+                    .content(is)
+                    .build();
+
+            try (Response response = client.postCollection(List.of(part), 
EntityPart.class)) {
+                assertThat(response.getStatus(), equalTo(200));
+
+                final List<EntityPart> parts = response.readEntity(new 
GenericType<>() { });
+
+                assertThat(parts, hasSize(1));
+                assertThat(parts.get(0), is(not(nullValue())));
+
+                assertThat(parts.get(0).getFileName().isPresent(), is(true));
+                assertThat(parts.get(0).getFileName().get(), 
equalTo(part.getFileName().get()));
+
+                
assertArrayEquals(IOUtils.readBytesFromStream(parts.get(0).getContent()),
+                        
IOUtils.readBytesFromStream(Files.newInputStream(file.toPath())));
+            }
+        }
+    }
 }
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 706ebdf970..030923dfc4 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
@@ -28,6 +28,7 @@ import java.io.InputStream;
 import java.io.PrintWriter;
 import java.io.PushbackInputStream;
 import java.lang.annotation.Annotation;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -94,7 +95,7 @@ import static org.junit.Assert.assertTrue;
 public class JAXRSMultipartTest extends AbstractBusClientServerTestBase {
     public static final String PORT = MultipartServer.PORT;
     public static final String PORTINV = 
allocatePort(JAXRSMultipartTest.class, 1);
-    
+
     @BeforeClass
     public static void startServers() throws Exception {
         assertTrue("server did not launch correctly",
@@ -104,8 +105,8 @@ public class JAXRSMultipartTest extends 
AbstractBusClientServerTestBase {
         
bus.setProperty(ProviderFactory.SKIP_JAKARTA_JSON_PROVIDERS_REGISTRATION, true);
     }
 
-    
-    
+
+
     @Test
     public void testBookAsRootAttachmentStreamSource() throws Exception {
         String address = "http://localhost:"; + PORT + 
"/bookstore/books/stream";
@@ -529,7 +530,7 @@ public class JAXRSMultipartTest extends 
AbstractBusClientServerTestBase {
     }
 
     @Test
-    public void testNullPartProxy() throws Exception {
+    public void testNullPartProxy() {
         ByteArrayOutputStream output = new ByteArrayOutputStream();
         PrintWriter writer = new PrintWriter(output, true);
 
@@ -539,8 +540,8 @@ public class JAXRSMultipartTest extends 
AbstractBusClientServerTestBase {
             JAXRSClientFactory.create("http://localhost:"; + PORT, 
MultipartStore.class);
         WebClient.getConfig(store).getOutInterceptors().add(out);
         assertEquals("nobody home2", store.testNullParts("value1", null));
-        String expected = "Content-Disposition: form-data;name=\"someid\"";
-        assertTrue(output.toString().indexOf(expected) != -1);
+        String expected = "Content-Disposition: form-data; name=\"someid\"";
+        assertTrue(output.toString().contains(expected));
     }
 
     @Test
@@ -1032,23 +1033,12 @@ public class JAXRSMultipartTest extends 
AbstractBusClientServerTestBase {
 
     @Test
     public void testUploadImageFromForm2() throws Exception {
-        File file =
-            new 
File(getClass().getResource("/org/apache/cxf/systest/jaxrs/resources/java.jpg")
-                               .toURI().getPath());
-        String address = "http://localhost:"; + PORT + 
"/bookstore/books/formimage2";
-        WebClient client = WebClient.create(address);
-        client.type("multipart/form-data").accept("multipart/form-data");
-        
WebClient.getConfig(client).getRequestContext().put("support.type.as.multipart",
-                                                            "true");
-        MultipartBody body2 = client.post(file, MultipartBody.class);
-        InputStream is2 = 
body2.getRootAttachment().getDataHandler().getInputStream();
-        byte[] image1 = IOUtils.readBytesFromStream(
-            
getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg"));
-        byte[] image2 = IOUtils.readBytesFromStream(is2);
-        assertArrayEquals(image1, image2);
-        ContentDisposition cd2 = 
body2.getRootAttachment().getContentDisposition();
-        assertEquals("form-data;name=file;filename=java.jpg", cd2.toString());
-        assertEquals("java.jpg", cd2.getParameter("filename"));
+        this.uploadImageFromForm2("java.jpg");
+    }
+
+    @Test
+    public void testUploadImageFromForm2WithSemicolon() throws Exception {
+        this.uploadImageFromForm2("java;test.jpg");
     }
 
     @Test
@@ -1168,7 +1158,7 @@ public class JAXRSMultipartTest extends 
AbstractBusClientServerTestBase {
 
         Attachment att = new Attachment(headers, handler, null);
         try (Response response = client.post(att)) {
-            assertEquals(response.getStatus(), 200);
+            assertEquals(200, response.getStatus());
         }
 
         client.close();
@@ -1281,6 +1271,26 @@ public class JAXRSMultipartTest extends 
AbstractBusClientServerTestBase {
         return str;
     }
 
+    private void uploadImageFromForm2(String filename) throws 
URISyntaxException, IOException {
+        File file =
+                new 
File(getClass().getResource("/org/apache/cxf/systest/jaxrs/resources/" + 
filename)
+                        .toURI().getPath());
+        String address = "http://localhost:"; + PORT + 
"/bookstore/books/formimage2";
+        WebClient client = WebClient.create(address);
+        client.type("multipart/form-data").accept("multipart/form-data");
+        
WebClient.getConfig(client).getRequestContext().put("support.type.as.multipart",
+                "true");
+        MultipartBody body2 = client.post(file, MultipartBody.class);
+        InputStream is2 = 
body2.getRootAttachment().getDataHandler().getInputStream();
+        byte[] image1 = IOUtils.readBytesFromStream(
+                
getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/" + 
filename));
+        byte[] image2 = IOUtils.readBytesFromStream(is2);
+        assertArrayEquals(image1, image2);
+        ContentDisposition cd2 = 
body2.getRootAttachment().getContentDisposition();
+        assertEquals("form-data; name=\"file\"; filename=\"" + filename + 
"\"", cd2.toString());
+        assertEquals(filename, cd2.getParameter("filename"));
+    }
+
     /**
      * Windows attachment handling
      */
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/resources/java;test.jpg
 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/resources/java;test.jpg
new file mode 100644
index 0000000000..dc59bee5e2
Binary files /dev/null and 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/resources/java;test.jpg
 differ

Reply via email to