This is an automated email from the ASF dual-hosted git repository. reta pushed a commit to branch 4.1.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 3309080d23af36d38e1f6ecbcdf1c5d40ff194e1 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) (cherry picked from commit e92e6ebe085113183dacd3286ede8722496f5336) # Conflicts: # rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java --- .../org/apache/cxf/attachment/AttachmentUtil.java | 2 +- .../cxf/jaxrs/provider/EntityPartProvider.java | 7 +- .../cxf/jaxrs/provider/MultipartProvider.java | 27 +++---- .../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, 112 insertions(+), 62 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 b6cfc56591..96fe01855d 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 7618e17d60..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,13 +357,14 @@ public class MultipartProvider extends AbstractConfigurableProvider att.getObject().getClass(), new Annotation[]{}, att.getContentType().toString(), id); MediaType mediaType = httpHeaders.getMediaType(); - Attachment ret = null; - if (MediaType.MULTIPART_FORM_DATA_TYPE.isCompatible(mediaType)) { + 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 { @@ -449,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, @@ -474,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
