Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@
/**
* @deprecated use {@code createAttachment(InputStream stream, Map<String, List<String>> headers, Message message)}
*/
public static Attachment createAttachment(InputStream stream, Map<String, List<String>> headers)

Check warning on line 403 in core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java

View workflow job for this annotation

GitHub Actions / Analyze

deprecated item is not annotated with @deprecated

Check warning on line 403 in core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 17)

deprecated item is not annotated with @deprecated
throws IOException {
return createAttachment(stream, headers, null /* no Message */);
}
Expand All @@ -412,14 +412,16 @@

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));
Comment thread
coheigea marked this conversation as resolved.
}

String cd = getHeader(headers, "Content-Disposition");
String fileName = getContentDispositionFileName(cd);

String encoding = null;

for (Map.Entry<String, List<String>> e : headers.entrySet()) {
Expand Down Expand Up @@ -447,14 +449,32 @@
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -364,4 +370,42 @@ public void objectAsAttachmentDirectory() throws IOException {
// 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@

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;
Expand All @@ -39,6 +41,8 @@

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
Expand Down Expand Up @@ -130,4 +134,51 @@ public void testManyAttachmentHeaders() throws Exception {
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
}
}
}
Loading