From 065c67f567c845e4f2b5f4e36f7a94b53279d23d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 6 Dec 2022 17:37:52 +0200 Subject: [PATCH] Ensure that file is written on disk for multipart when endpoint expects it With this change, if the JAX-RS endpoint expects a multipart request to contain a file, then that file is always written to disk regardless of whether the content-disposition contains the filename attribute of not. Fixes: #20938 (cherry picked from commit ec2a4a465153cb3c90ef08f09c68e1c7ba2d1ab8) --- .../MultipartFormInputDevModeTest.java | 2 +- .../MultipartInputBodyHandlerTest.java | 2 +- .../test/multipart/MultipartInputTest.java | 11 +++-- .../test/multipart/MultipartResource.java | 2 +- .../scanning/ClientEndpointIndexer.java | 9 +++- .../common/processor/EndpointIndexer.java | 11 +++-- .../reactive/common/model/BeanParamInfo.java | 14 ++++++ .../reactive/common/model/InjectableBean.java | 6 +++ .../reactive/common/model/ResourceMethod.java | 12 +++++ .../processor/ServerEndpointIndexer.java | 45 +++++++++++++++++-- .../multipart/FormEncodedDataDefinition.java | 3 +- .../core/multipart/FormParserFactory.java | 8 ++-- .../multipart/MultiPartParserDefinition.java | 13 ++++-- .../core/multipart/MultipartSupport.java | 6 +-- .../startup/RuntimeResourceDeployment.java | 2 +- .../server/handlers/FormBodyHandler.java | 11 +++-- .../MultipartFileContentTypeTest.java | 14 ------ 17 files changed, 124 insertions(+), 47 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartFormInputDevModeTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartFormInputDevModeTest.java index 83a02058088e2..6b33f16667d46 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartFormInputDevModeTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartFormInputDevModeTest.java @@ -74,7 +74,7 @@ private void doTest(String path) { .post("/multipart/" + path + "/2") .then() .statusCode(200) - .body(equalTo("Alice - true - 50 - WORKING - text/html - true - true")); + .body(equalTo("Alice - true - 50 - WORKING - true - true - true")); } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java index a3e7cf8ed1ef4..f75e0a416573d 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java @@ -92,7 +92,7 @@ public void testSimple() { .post("/multipart/simple/2") .then() .statusCode(200) - .body(equalTo("Alice - true - 50 - WORKING - text/html - true - true")); + .body(equalTo("Alice - true - 50 - WORKING - true - true - true")); // ensure that the 3 uploaded files where created on disk Assertions.assertEquals(3, uploadDir.toFile().listFiles().length); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java index 88617b88dbfb2..74789a5170f93 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java @@ -50,6 +50,9 @@ public JavaArchive get() { private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); private final File XML_FILE = new File("./src/test/resources/test.html"); private final File TXT_FILE = new File("./src/test/resources/lorem.txt"); + private final String TXT = "lorem ipsum"; + private final String XML = ""; + private final String HTML = ""; @BeforeEach public void assertEmptyUploads() { @@ -68,15 +71,15 @@ public void testSimple() { .multiPart("active", "true") .multiPart("num", "25") .multiPart("status", "WORKING") - .multiPart("htmlFile", HTML_FILE, "text/html") - .multiPart("xmlFile", XML_FILE, "text/xml") - .multiPart("txtFile", TXT_FILE, "text/plain") + .multiPart("htmlFile", HTML, "text/html") + .multiPart("xmlFile", XML, "text/xml") + .multiPart("txtFile", TXT, "text/plain") .accept("text/plain") .when() .post("/multipart/simple/2") .then() .statusCode(200) - .body(equalTo("Alice - true - 50 - WORKING - text/html - true - true")); + .body(equalTo("Alice - true - 50 - WORKING - true - true - true")); // ensure that the 3 uploaded files where created on disk Assertions.assertEquals(3, uploadDir.toFile().listFiles().length); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartResource.java index 58edf0ceae3a7..5834b5d20f171 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartResource.java @@ -32,7 +32,7 @@ public String simple(@MultipartForm FormData formData, Integer times) { } return formData.getName() + " - " + formData.active + " - " + times * formData.getNum() + " - " + formData.getStatus() + " - " - + formData.getHtmlPart().contentType() + " - " + Files.exists(formData.xmlPart) + " - " + + Files.exists(formData.getHtmlPart().filePath()) + " - " + Files.exists(formData.xmlPart) + " - " + formData.txtFile.exists(); } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java index f3b2e27bfc172..2e2a6d731f9e5 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java @@ -11,6 +11,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.ws.rs.core.MediaType; @@ -115,7 +116,8 @@ protected ResourceMethod createResourceMethod(MethodInfo info, ClassInfo actualE } @Override - protected boolean handleBeanParam(ClassInfo actualEndpointInfo, Type paramType, MethodParameter[] methodParameters, int i) { + protected boolean handleBeanParam(ClassInfo actualEndpointInfo, Type paramType, MethodParameter[] methodParameters, int i, + Set fileFormNames) { ClassInfo beanParamClassInfo = index.getClassByName(paramType.name()); methodParameters[i] = parseClientBeanParam(beanParamClassInfo, index); @@ -127,15 +129,18 @@ private MethodParameter parseClientBeanParam(ClassInfo beanParamClassInfo, Index return new ClientBeanParamInfo(items, beanParamClassInfo.name().toString()); } + @Override protected InjectableBean scanInjectableBean(ClassInfo currentClassInfo, ClassInfo actualEndpointInfo, Map existingConverters, AdditionalReaders additionalReaders, Map injectableBeans, boolean hasRuntimeConverters) { throw new RuntimeException("Injectable beans not supported in client"); } + @Override protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, ClassInfo actualEndpointInfo, boolean encoded, Type paramType, ClientIndexedParam parameterResult, String name, String defaultValue, ParameterType type, - String elementType, boolean single, String signature) { + String elementType, boolean single, String signature, + Set fileFormNames) { DeclaredTypes declaredTypes = getDeclaredTypes(paramType, currentClassInfo, actualEndpointInfo); return new MethodParameter(name, elementType, declaredTypes.getDeclaredType(), declaredTypes.getDeclaredUnresolvedType(), signature, type, diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index 2d31bc302d75a..ce1a8164b9860 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -526,6 +526,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf boolean formParamRequired = false; boolean multipart = false; boolean hasBodyParam = false; + Set fileFormNames = new HashSet<>(); TypeArgMapper typeArgMapper = new TypeArgMapper(currentMethodInfo.declaringClass(), index); for (int i = 0; i < methodParameters.length; ++i) { Map anns = parameterAnnotations[i]; @@ -558,11 +559,11 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf } methodParameters[i] = createMethodParameter(currentClassInfo, actualEndpointInfo, encoded, paramType, parameterResult, name, defaultValue, type, elementType, single, - AsmUtil.getSignature(paramType, typeArgMapper)); + AsmUtil.getSignature(paramType, typeArgMapper), fileFormNames); if (type == ParameterType.BEAN) { // transform the bean param - formParamRequired |= handleBeanParam(actualEndpointInfo, paramType, methodParameters, i); + formParamRequired |= handleBeanParam(actualEndpointInfo, paramType, methodParameters, i, fileFormNames); } else if (type == ParameterType.FORM) { formParamRequired = true; } else if (type == ParameterType.MULTI_PART_FORM) { @@ -686,6 +687,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf .setStreamElementType(streamElementType) .setFormParamRequired(formParamRequired) .setMultipart(multipart) + .setFileFormNames(fileFormNames) .setParameters(methodParameters) .setSimpleReturnType( toClassName(currentMethodInfo.returnType(), currentClassInfo, actualEndpointInfo, index)) @@ -840,7 +842,7 @@ private String determineReturnType(Type returnType, TypeArgMapper typeArgMapper, } protected abstract boolean handleBeanParam(ClassInfo actualEndpointInfo, Type paramType, MethodParameter[] methodParameters, - int i); + int i, Set fileFormNames); protected void handleAdditionalMethodProcessing(METHOD method, ClassInfo currentClassInfo, MethodInfo info, AnnotationStore annotationStore) { @@ -856,7 +858,8 @@ protected abstract InjectableBean scanInjectableBean(ClassInfo currentClassInfo, protected abstract MethodParameter createMethodParameter(ClassInfo currentClassInfo, ClassInfo actualEndpointInfo, boolean encoded, Type paramType, PARAM parameterResult, String name, String defaultValue, - ParameterType type, String elementType, boolean single, String signature); + ParameterType type, String elementType, boolean single, String signature, + Set fileFormNames); private String[] applyDefaultProduces(String[] produces, Type nonAsyncReturnType, DotName httpMethod) { diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/BeanParamInfo.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/BeanParamInfo.java index e9dcd4d9103c0..0040b7f9f304c 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/BeanParamInfo.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/BeanParamInfo.java @@ -1,9 +1,13 @@ package org.jboss.resteasy.reactive.common.model; +import java.util.HashSet; +import java.util.Set; + public class BeanParamInfo implements InjectableBean { private boolean isFormParamRequired; private boolean isInjectionRequired; private int fieldExtractorsCount; + private Set fileFormNames = new HashSet<>(); @Override public boolean isFormParamRequired() { @@ -36,4 +40,14 @@ public int getFieldExtractorsCount() { public void setFieldExtractorsCount(int fieldExtractorsCount) { this.fieldExtractorsCount = fieldExtractorsCount; } + + @Override + public Set getFileFormNames() { + return fileFormNames; + } + + @Override + public void setFileFormNames(Set fileFormNames) { + this.fileFormNames = fileFormNames; + } } diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/InjectableBean.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/InjectableBean.java index aea871820d09c..e659e6bf50608 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/InjectableBean.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/InjectableBean.java @@ -1,5 +1,7 @@ package org.jboss.resteasy.reactive.common.model; +import java.util.Set; + /** * Class that represents information about injectable beans as we scan them, such as * resource endpoint beans, or BeanParam classes. @@ -25,4 +27,8 @@ public interface InjectableBean { int getFieldExtractorsCount(); void setFieldExtractorsCount(int fieldExtractorsCount); + + Set getFileFormNames(); + + void setFileFormNames(Set fileFormNames); } diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceMethod.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceMethod.java index 5c53c010a61b4..391a6f8ea833d 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceMethod.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceMethod.java @@ -69,6 +69,9 @@ public class ResourceMethod { private boolean isFormParamRequired; private boolean isMultipart; + + private Set fileFormNames; + private List subResourceMethods; public ResourceMethod() { @@ -235,6 +238,15 @@ public ResourceMethod setMultipart(boolean isMultipart) { return this; } + public Set getFileFormNames() { + return fileFormNames; + } + + public ResourceMethod setFileFormNames(Set fileFormNames) { + this.fileFormNames = fileFormNames; + return this; + } + public ResourceMethod setStreamElementType(String streamElementType) { this.streamElementType = streamElementType; return this; diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java index b64b2b9ddab57..1e77603d671c0 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java @@ -22,6 +22,8 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.SORTED_SET; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.ZONED_DATE_TIME; +import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -56,6 +58,7 @@ import org.jboss.resteasy.reactive.common.processor.EndpointIndexer; import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore; +import org.jboss.resteasy.reactive.multipart.FileUpload; import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor; import org.jboss.resteasy.reactive.server.core.parameters.converters.ArrayConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.CharParamConverter; @@ -91,6 +94,13 @@ public class ServerEndpointIndexer extends EndpointIndexer { + + private static final DotName FILE_DOT_NAME = DotName.createSimple(File.class.getName()); + private static final DotName PATH_DOT_NAME = DotName.createSimple(Path.class.getName()); + private static final DotName FILEUPLOAD_DOT_NAME = DotName.createSimple(FileUpload.class.getName()); + + private static final Set SUPPORTED_MULTIPART_FILE_TYPES = Set.of(FILE_DOT_NAME, PATH_DOT_NAME, + FILEUPLOAD_DOT_NAME); protected final EndpointInvokerFactory endpointInvokerFactory; protected final List methodScanners; protected final FieldInjectionIndexerExtension fieldInjectionHandler; @@ -172,7 +182,8 @@ protected ServerResourceMethod createResourceMethod(MethodInfo methodInfo, Class } @Override - protected boolean handleBeanParam(ClassInfo actualEndpointInfo, Type paramType, MethodParameter[] methodParameters, int i) { + protected boolean handleBeanParam(ClassInfo actualEndpointInfo, Type paramType, MethodParameter[] methodParameters, int i, + Set fileFormNames) { ClassInfo beanParamClassInfo = index.getClassByName(paramType.name()); InjectableBean injectableBean = scanInjectableBean(beanParamClassInfo, actualEndpointInfo, @@ -182,7 +193,7 @@ protected boolean handleBeanParam(ClassInfo actualEndpointInfo, Type paramType, + "Annotations like `@QueryParam` should be used in fields, not in methods.", beanParamClassInfo.name())); } - + fileFormNames.addAll(injectableBean.getFileFormNames()); return injectableBean.isFormParamRequired(); } @@ -229,6 +240,7 @@ private void validateMethodPath(ServerResourceMethod method, ClassInfo currentCl } } + @Override protected InjectableBean scanInjectableBean(ClassInfo currentClassInfo, ClassInfo actualEndpointInfo, Map existingConverters, AdditionalReaders additionalReaders, Map injectableBeans, boolean hasRuntimeConverters) { @@ -276,6 +288,24 @@ protected InjectableBean scanInjectableBean(ClassInfo currentClassInfo, ClassInf } else if (result.getType() == ParameterType.FORM) { // direct form param requirement currentInjectableBean.setFormParamRequired(true); + + if (SUPPORTED_MULTIPART_FILE_TYPES.contains(field.type().name())) { + String name = field.name(); + AnnotationInstance restForm = field.annotation(ResteasyReactiveDotNames.REST_FORM_PARAM); + AnnotationInstance formParam = field.annotation(ResteasyReactiveDotNames.FORM_PARAM); + if (restForm != null) { + AnnotationValue value = restForm.value(); + if (value != null) { + name = value.asString(); + } + } else if (formParam != null) { + AnnotationValue value = formParam.value(); + if (value != null) { + name = value.asString(); + } + } + currentInjectableBean.getFileFormNames().add(name); + } } } // the TCK expects that fields annotated with @BeanParam are handled last @@ -305,13 +335,20 @@ protected InjectableBean scanInjectableBean(ClassInfo currentClassInfo, ClassInf return currentInjectableBean; } + @Override protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, ClassInfo actualEndpointInfo, boolean encoded, Type paramType, ServerIndexedParameter parameterResult, String name, String defaultValue, ParameterType type, - String elementType, boolean single, String signature) { + String elementType, boolean single, String signature, + Set fileFormNames) { ParameterConverterSupplier converter = parameterResult.getConverter(); DeclaredTypes declaredTypes = getDeclaredTypes(paramType, currentClassInfo, actualEndpointInfo); + String declaredType = declaredTypes.getDeclaredType(); + + if (SUPPORTED_MULTIPART_FILE_TYPES.contains(DotName.createSimple(declaredType))) { + fileFormNames.add(name); + } return new ServerMethodParameter(name, - elementType, declaredTypes.getDeclaredType(), declaredTypes.getDeclaredUnresolvedType(), + elementType, declaredType, declaredTypes.getDeclaredUnresolvedType(), type, single, signature, converter, defaultValue, parameterResult.isObtainedAsCollection(), parameterResult.isOptional(), encoded, parameterResult.getCustomParameterExtractor()); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormEncodedDataDefinition.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormEncodedDataDefinition.java index 4829909c40610..39e499454e13d 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormEncodedDataDefinition.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormEncodedDataDefinition.java @@ -5,6 +5,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Set; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; @@ -37,7 +38,7 @@ public FormEncodedDataDefinition() { } @Override - public FormDataParser create(final ResteasyReactiveRequestContext exchange) { + public FormDataParser create(final ResteasyReactiveRequestContext exchange, Set fileFormNames) { String mimeType = exchange.serverRequest().getRequestHeader(HttpHeaders.CONTENT_TYPE); if (forceCreation || (mimeType != null && mimeType.startsWith(APPLICATION_X_WWW_FORM_URLENCODED))) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormParserFactory.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormParserFactory.java index 7fa20a50e5584..8d382ed75a4b5 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormParserFactory.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/FormParserFactory.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Supplier; @@ -28,11 +29,12 @@ public class FormParserFactory { * Creates a form data parser for this request. * * @param exchange The exchange + * @param fileFormNames * @return A form data parser, or null if there is no parser registered for the request content type */ - public FormDataParser createParser(final ResteasyReactiveRequestContext exchange) { + public FormDataParser createParser(final ResteasyReactiveRequestContext exchange, Set fileFormNames) { for (int i = 0; i < parserDefinitions.length; ++i) { - FormDataParser parser = parserDefinitions[i].create(exchange); + FormDataParser parser = parserDefinitions[i].create(exchange, fileFormNames); if (parser != null) { return parser; } @@ -42,7 +44,7 @@ public FormDataParser createParser(final ResteasyReactiveRequestContext exchange public interface ParserDefinition { - FormDataParser create(final ResteasyReactiveRequestContext exchange); + FormDataParser create(final ResteasyReactiveRequestContext exchange, Set fileFormNames); T setDefaultCharset(String charset); } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java index ef77e6a026c52..9d3465317a90d 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Supplier; @@ -66,7 +67,7 @@ public MultiPartParserDefinition(Supplier executorSupplier, final Path } @Override - public FormDataParser create(final ResteasyReactiveRequestContext exchange) { + public FormDataParser create(final ResteasyReactiveRequestContext exchange, Set fileFormNames) { String mimeType = exchange.serverRequest().getRequestHeader(HttpHeaders.CONTENT_TYPE); if (mimeType != null && mimeType.startsWith(MULTIPART_FORM_DATA)) { String boundary = HeaderUtil.extractQuotedValueFromHeader(mimeType, "boundary"); @@ -77,7 +78,7 @@ public FormDataParser create(final ResteasyReactiveRequestContext exchange) { return null; } final MultiPartUploadHandler parser = new MultiPartUploadHandler(exchange, boundary, maxIndividualFileSize, - fileSizeThreshold, defaultCharset, mimeType, maxAttributeSize, maxEntitySize); + fileSizeThreshold, defaultCharset, mimeType, maxAttributeSize, maxEntitySize, fileFormNames); exchange.registerCompletionCallback(new CompletionCallback() { @Override public void onComplete(Throwable throwable) { @@ -171,6 +172,7 @@ private final class MultiPartUploadHandler implements FormDataParser, MultipartP private final long fileSizeThreshold; private final long maxAttributeSize; private final long maxEntitySize; + private final Set fileFormNames; private String defaultEncoding; private final ByteArrayOutputStream contentBytes = new ByteArrayOutputStream(); @@ -185,13 +187,15 @@ private final class MultiPartUploadHandler implements FormDataParser, MultipartP private MultiPartUploadHandler(final ResteasyReactiveRequestContext exchange, final String boundary, final long maxIndividualFileSize, final long fileSizeThreshold, final String defaultEncoding, - String contentType, long maxAttributeSize, long maxEntitySize) { + String contentType, long maxAttributeSize, long maxEntitySize, + Set fileFormNames) { this.exchange = exchange; this.maxIndividualFileSize = maxIndividualFileSize; this.defaultEncoding = defaultEncoding; this.fileSizeThreshold = fileSizeThreshold; this.maxAttributeSize = maxAttributeSize; this.maxEntitySize = maxEntitySize; + this.fileFormNames = fileFormNames; int maxParameters = 1000; this.data = new FormData(maxParameters); String charset = defaultEncoding; @@ -247,7 +251,8 @@ public void beginPart(final CaseInsensitiveMap headers) { currentName = HeaderUtil.extractQuotedValueFromHeader(disposition, "name"); fileName = HeaderUtil.extractQuotedValueFromHeaderWithEncoding(disposition, "filename"); String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE); - if ((fileName != null || isFileContentType(contentType)) && fileSizeThreshold == 0) { + if (((fileName != null) || isFileContentType(contentType) || fileFormNames.contains(currentName)) + && fileSizeThreshold == 0) { try { if (tempFileLocation != null) { Files.createDirectories(tempFileLocation); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartSupport.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartSupport.java index 8a97d461d9544..d384e11cfaec4 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartSupport.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartSupport.java @@ -167,9 +167,9 @@ public static DefaultFileUpload getFileUpload(String formName, ResteasyReactiveR public static List getFileUploads(String formName, ResteasyReactiveRequestContext context) { List result = new ArrayList<>(); - FormData fileUploads = context.getFormData(); - if (fileUploads != null) { - Collection fileUploadsForName = fileUploads.get(formName); + FormData formData = context.getFormData(); + if (formData != null) { + Collection fileUploadsForName = formData.get(formName); if (fileUploadsForName != null) { for (FormData.FormValue fileUpload : fileUploadsForName) { if (fileUpload.isFileItem()) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index 1ca909d8bd1b9..f6d9902245c99 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -278,7 +278,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, boolean checkReadBodyRequestFilters = false; if (method.isFormParamRequired() || method.isMultipart()) { // read the body as multipart in one go - handlers.add(new FormBodyHandler(bodyParameter != null, executorSupplier)); + handlers.add(new FormBodyHandler(bodyParameter != null, executorSupplier, method.getFileFormNames())); checkReadBodyRequestFilters = true; } else if (bodyParameter != null) { if (!defaultBlocking) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java index 69b578042017c..60806e1b98d88 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java @@ -8,6 +8,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Supplier; @@ -27,11 +28,13 @@ public class FormBodyHandler implements ServerRestHandler, RuntimeConfigurableSe private final boolean alsoSetInputStream; private final Supplier executorSupplier; + private final Set fileFormNames; private volatile FormParserFactory formParserFactory; - public FormBodyHandler(boolean alsoSetInputStream, Supplier executorSupplier) { + public FormBodyHandler(boolean alsoSetInputStream, Supplier executorSupplier, Set fileFormNames) { this.alsoSetInputStream = alsoSetInputStream; this.executorSupplier = executorSupplier; + this.fileFormNames = fileFormNames; } @Override @@ -75,7 +78,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti if (BlockingOperationSupport.isBlockingAllowed()) { //blocking IO approach - FormDataParser factory = formParserFactory.createParser(requestContext); + FormDataParser factory = formParserFactory.createParser(requestContext, fileFormNames); if (factory == null) { return; } @@ -92,7 +95,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti requestContext.setInputStream(new ByteArrayInputStream(cis.baos.toByteArray())); } } else if (alsoSetInputStream) { - FormDataParser factory = formParserFactory.createParser(requestContext); + FormDataParser factory = formParserFactory.createParser(requestContext, fileFormNames); if (factory == null) { return; } @@ -112,7 +115,7 @@ public void run() { } }); } else { - FormDataParser factory = formParserFactory.createParser(requestContext); + FormDataParser factory = formParserFactory.createParser(requestContext, fileFormNames); if (factory == null) { return; } diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/multipart/MultipartFileContentTypeTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/multipart/MultipartFileContentTypeTest.java index 85157244a79b0..93c4771f78959 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/multipart/MultipartFileContentTypeTest.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/multipart/MultipartFileContentTypeTest.java @@ -69,18 +69,4 @@ public void testFilePartWithExpectedContentType() throws IOException { // ensure that the 2 uploaded files where created on disk Assertions.assertEquals(2, uploadDir.toFile().listFiles().length); } - - @Test - public void testFilePartWithUnexpectedContentType() throws IOException { - RestAssured.given() - .multiPart("xmlFile", null, Files.readAllBytes(FILE.toPath()), MediaType.APPLICATION_XML) - .accept("text/plain") - .when() - .post("/multipart/optional") - .then() - .statusCode(200); - - // ensure that no files are created as the content-type is not supported as a file part - Assertions.assertEquals(0, uploadDir.toFile().listFiles().length); - } }