Skip to content

Commit 2e09104

Browse files
committed
Don't assume that multipart part without filename is text
Fixes: quarkusio#38703
1 parent a618ff7 commit 2e09104

File tree

4 files changed

+93
-10
lines changed

4 files changed

+93
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package io.quarkus.resteasy.reactive.server.test.multipart;
2+
3+
import static io.restassured.RestAssured.given;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
6+
import java.io.File;
7+
import java.io.IOException;
8+
import java.nio.file.Files;
9+
import java.util.function.Supplier;
10+
11+
import jakarta.ws.rs.POST;
12+
import jakarta.ws.rs.Path;
13+
import jakarta.ws.rs.core.MediaType;
14+
15+
import org.jboss.resteasy.reactive.PartType;
16+
import org.jboss.resteasy.reactive.RestForm;
17+
import org.jboss.shrinkwrap.api.ShrinkWrap;
18+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.RegisterExtension;
21+
22+
import io.quarkus.test.QuarkusUnitTest;
23+
24+
public class MultipartBinaryWithoutFilenameTest {
25+
26+
@RegisterExtension
27+
static QuarkusUnitTest test = new QuarkusUnitTest()
28+
.setArchiveProducer(new Supplier<>() {
29+
@Override
30+
public JavaArchive get() {
31+
return ShrinkWrap.create(JavaArchive.class)
32+
.addClasses(MultipartDataInputTest.Resource.class, MultipartDataInputTest.Item.class,
33+
MultipartDataInputTest.Result.class);
34+
}
35+
});
36+
private final File IMAGE_FILE = new File("./src/test/resources/image.png");
37+
38+
@Test
39+
public void test() throws IOException {
40+
byte[] bytes = given()
41+
.contentType("multipart/form-data")
42+
.multiPart("bytes", IMAGE_FILE, "application/png")
43+
.when()
44+
.post("/test")
45+
.then()
46+
.statusCode(200)
47+
.extract().body().asByteArray();
48+
49+
assertThat(bytes).isEqualTo(Files.readAllBytes(IMAGE_FILE.toPath()));
50+
}
51+
52+
@Path("/test")
53+
public static class Resource {
54+
55+
@POST
56+
public byte[] testMultipart(Input input) {
57+
return input.bytes;
58+
}
59+
}
60+
61+
public static class Input {
62+
@RestForm("bytes")
63+
@PartType(MediaType.APPLICATION_OCTET_STREAM)
64+
public byte[] bytes;
65+
66+
}
67+
}

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java

+19-8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import jakarta.ws.rs.WebApplicationException;
2424
import jakarta.ws.rs.container.CompletionCallback;
2525
import jakarta.ws.rs.core.HttpHeaders;
26+
import jakarta.ws.rs.core.MediaType;
2627
import jakarta.ws.rs.core.Response;
2728

2829
import org.jboss.logging.Logger;
@@ -339,24 +340,34 @@ public void endPart() {
339340
contentBytes.reset();
340341
} else {
341342

342-
try {
343-
String charset = defaultEncoding;
344-
String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
345-
if (contentType != null) {
343+
String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
344+
if (isText(contentType)) {
345+
try {
346+
String charset = defaultEncoding;
346347
String cs = HeaderUtil.extractQuotedValueFromHeader(contentType, "charset");
347348
if (cs != null) {
348349
charset = cs;
349350
}
350-
}
351351

352-
data.add(currentName, contentBytes.toString(charset), charset, headers);
353-
} catch (UnsupportedEncodingException e) {
354-
throw new RuntimeException(e);
352+
data.add(currentName, contentBytes.toString(charset), charset, headers);
353+
} catch (UnsupportedEncodingException e) {
354+
throw new RuntimeException(e);
355+
}
356+
} else {
357+
data.add(currentName, Arrays.copyOf(contentBytes.toByteArray(), contentBytes.size()), null, headers);
355358
}
359+
356360
contentBytes.reset();
357361
}
358362
}
359363

364+
private boolean isText(String contentType) {
365+
if (contentType == null || contentType.isEmpty()) { // https://www.rfc-editor.org/rfc/rfc7578.html#section-4.4 says the default content-type if missing is text/plain
366+
return true;
367+
}
368+
return MediaType.TEXT_PLAIN_TYPE.isCompatible(MediaType.valueOf(contentType));
369+
}
370+
360371
public List<Path> getCreatedFiles() {
361372
return createdFiles;
362373
}

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartSupport.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
3131
import org.jboss.resteasy.reactive.server.core.ServerSerialisers;
3232
import org.jboss.resteasy.reactive.server.handlers.RequestDeserializeHandler;
33+
import org.jboss.resteasy.reactive.server.multipart.FileItem;
3334
import org.jboss.resteasy.reactive.server.multipart.FormValue;
3435
import org.jboss.resteasy.reactive.server.multipart.MultipartFormDataInput;
3536
import org.jboss.resteasy.reactive.server.multipart.MultipartPartReadingException;
@@ -90,7 +91,7 @@ private static Object read(MessageBodyReader<?> reader, String attributeName, Fo
9091
@Override
9192
public InputStream get() {
9293
try {
93-
return Files.newInputStream(value.getFileItem().getFile());
94+
return value.getFileItem().getInputStream();
9495
} catch (IOException e) {
9596
throw new UncheckedIOException(e);
9697
}
@@ -248,7 +249,11 @@ public static byte[] getByteArray(String formName, ResteasyReactiveRequestContex
248249
}
249250
if (value.isFileItem()) {
250251
try {
251-
return Files.readAllBytes(value.getFileItem().getFile());
252+
FileItem fileItem = value.getFileItem();
253+
if (fileItem.isInMemory()) {
254+
return fileItem.getInputStream().readAllBytes();
255+
}
256+
return Files.readAllBytes(fileItem.getFile());
252257
} catch (IOException e) {
253258
throw new MultipartPartReadingException(e);
254259
}

0 commit comments

Comments
 (0)