This is an automated email from the ASF dual-hosted git repository.
coheigea 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 f2338600541 CXF-8922: Default form-data parts without Content-Type to
text/plain (#3563)
f2338600541 is described below
commit f23386005410ae1a51962da209633ef517129473
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Mon Oct 12 16:19:10 2026 +0100
CXF-8922: Default form-data parts without Content-Type to text/plain (#3563)
* CXF-8922: Default form-data parts without Content-Type to text/plain
* Addressing review comments
---
.../org/apache/cxf/attachment/AttachmentUtil.java | 34 ++++++++++++---
.../apache/cxf/attachment/AttachmentUtilTest.java | 44 +++++++++++++++++++
.../cxf/jaxrs/provider/MultipartProviderTest.java | 51 ++++++++++++++++++++++
3 files changed, 122 insertions(+), 7 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 0917d1d72a4..3ea312238e1 100644
--- a/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java
+++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java
@@ -412,14 +412,16 @@ public final class AttachmentUtil {
AttachmentImpl att = new AttachmentImpl(id);
+ String cdHeader = getHeader(headers, "Content-Disposition");
+ ContentDisposition cd = StringUtils.isEmpty(cdHeader) ? null : new
ContentDisposition(cdHeader);
+ String fileName = getContentDispositionFileName(cd);
+
String ct = getHeader(headers, "Content-Type");
if (StringUtils.isEmpty(ct)) {
- ct = MessageUtils.getContextualString(message,
ATTACHMENT_CONTENT_TYPE, "application/octet-stream");
+ ct = MessageUtils.getContextualString(message,
ATTACHMENT_CONTENT_TYPE,
+ getDefaultContentType(cd));
}
- String cd = getHeader(headers, "Content-Disposition");
- String fileName = getContentDispositionFileName(cd);
-
String encoding = null;
for (Map.Entry<String, List<String>> e : headers.entrySet()) {
@@ -447,14 +449,32 @@ public final class AttachmentUtil {
return att;
}
+ /**
+ * RFC 7578, section 4.4: a multipart/form-data part without a
Content-Type header
+ * defaults to "text/plain". File parts (those carrying a "filename"
parameter) and
+ * all other attachments keep defaulting to "application/octet-stream".
+ */
+ private static String getDefaultContentType(ContentDisposition cd) {
+ if (cd != null && "form-data".equalsIgnoreCase(cd.getType()) &&
cd.getParameter("filename") == null) {
+ return "text/plain";
+ }
+ return "application/octet-stream";
+ }
+
static String getContentDispositionFileName(String cd) {
if (StringUtils.isEmpty(cd)) {
return null;
}
- ContentDisposition c = new ContentDisposition(cd);
- String s = c.getParameter("filename");
+ return getContentDispositionFileName(new ContentDisposition(cd));
+ }
+
+ private static String getContentDispositionFileName(ContentDisposition cd)
{
+ if (cd == null) {
+ return null;
+ }
+ String s = cd.getParameter("filename");
if (s == null) {
- s = c.getParameter("name");
+ s = cd.getParameter("name");
}
return s;
}
diff --git
a/core/src/test/java/org/apache/cxf/attachment/AttachmentUtilTest.java
b/core/src/test/java/org/apache/cxf/attachment/AttachmentUtilTest.java
index bcab2aabb03..41c76ba274d 100644
--- a/core/src/test/java/org/apache/cxf/attachment/AttachmentUtilTest.java
+++ b/core/src/test/java/org/apache/cxf/attachment/AttachmentUtilTest.java
@@ -18,14 +18,20 @@
*/
package org.apache.cxf.attachment;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.message.Attachment;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;
@@ -364,4 +370,42 @@ public class AttachmentUtilTest {
// Will throw exception
}
}
+
+ @Test
+ public void testCreateAttachmentFormDataFieldDefaultsToTextPlain() throws
IOException {
+ // RFC 7578, section 4.4
+ assertEquals("text/plain",
+ createAttachmentContentType("form-data; name=\"dzchunkindex\"",
null));
+ }
+
+ @Test
+ public void testCreateAttachmentFormDataFileDefaultsToOctetStream() throws
IOException {
+ assertEquals("application/octet-stream",
+ createAttachmentContentType("form-data; name=\"file\";
filename=\"a.bin\"", null));
+ }
+
+ @Test
+ public void
testCreateAttachmentNoContentDispositionDefaultsToOctetStream() throws
IOException {
+ assertEquals("application/octet-stream",
createAttachmentContentType(null, null));
+ }
+
+ @Test
+ public void
testCreateAttachmentFormDataFieldHonoursDefaultContentTypeProperty() throws
IOException {
+ assertEquals("application/json",
+ createAttachmentContentType("form-data; name=\"field\"",
"application/json"));
+ }
+
+ private static String createAttachmentContentType(String cd, String
defaultCt) throws IOException {
+ Map<String, List<String>> headers = new
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ if (cd != null) {
+ headers.put("Content-Disposition", Collections.singletonList(cd));
+ }
+ Message message = new MessageImpl();
+ if (defaultCt != null) {
+ message.put(AttachmentUtil.ATTACHMENT_CONTENT_TYPE, defaultCt);
+ }
+ Attachment att = AttachmentUtil.createAttachment(
+ new ByteArrayInputStream("3".getBytes(StandardCharsets.UTF_8)),
headers, message);
+ return att.getDataHandler().getContentType();
+ }
}
diff --git
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/MultipartProviderTest.java
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/MultipartProviderTest.java
index e3712fb6b31..35499c4ea5a 100644
---
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/MultipartProviderTest.java
+++
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/MultipartProviderTest.java
@@ -28,7 +28,9 @@ import java.util.stream.IntStream;
import jakarta.ws.rs.core.MediaType;
import org.apache.cxf.attachment.AttachmentDeserializer;
+import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxrs.ext.MessageContextImpl;
+import org.apache.cxf.jaxrs.ext.multipart.Multipart;
import org.apache.cxf.jaxrs.impl.MetadataMap;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.ExchangeImpl;
@@ -39,6 +41,8 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class MultipartProviderTest {
@Test
@@ -130,4 +134,51 @@ public class MultipartProviderTest {
new MetadataMap<String, String>(),
msg.getContent(InputStream.class)));
}
+
+ @Test
+ public void testFormDataFieldWithoutContentTypeAsInteger() throws
Exception {
+ assertEquals(Integer.valueOf(3), readFormDataInteger(null));
+ }
+
+ @Test
+ public void testFormDataFieldWithTextPlainContentTypeAsInteger() throws
Exception {
+ assertEquals(Integer.valueOf(3), readFormDataInteger("text/plain"));
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private Object readFormDataInteger(String partContentType) throws
Exception {
+ StringBuilder sb = new StringBuilder()
+ .append("--bound\r\n")
+ .append("Content-Disposition: form-data;
name=\"dzchunkindex\"\r\n");
+ if (partContentType != null) {
+ sb.append("Content-Type: ").append(partContentType).append("\r\n");
+ }
+ sb.append("\r\n")
+ .append("3\r\n")
+ .append("--bound--\r\n");
+
+ final Exchange exchange = new ExchangeImpl();
+ final Endpoint endpoint = mock(Endpoint.class);
+
when(endpoint.get(ServerProviderFactory.class.getName())).thenReturn(ServerProviderFactory.getInstance());
+ exchange.put(Endpoint.class, endpoint);
+ final Message msg = new MessageImpl();
+ msg.setExchange(exchange);
+ exchange.setInMessage(msg);
+ msg.put(Message.CONTENT_TYPE, "multipart/form-data; boundary=bound");
+ InputStream is = new
ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8));
+ msg.setContent(InputStream.class, is);
+
+ final MultipartProvider p = new MultipartProvider();
+ p.setMessageContext(new MessageContextImpl(msg));
+ Annotation[] anns = FormDataResource.class.getMethod("upload",
Integer.class)
+ .getParameterAnnotations()[0];
+ return p.readFrom((Class)Integer.class, Integer.class, anns,
+ MediaType.valueOf("multipart/form-data; boundary=bound"), new
MetadataMap<String, String>(), is);
+ }
+
+ public static class FormDataResource {
+ public void upload(@Multipart(value = "dzchunkindex", required =
false) Integer index) {
+ // complete
+ }
+ }
}