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 a0d69d00d2c64..0316e038c7800 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); @@ -110,15 +113,15 @@ public void testSimpleParam() { .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/param/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 5548a439f98fa..a3958b01ae37a 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 @@ -37,7 +37,7 @@ public String simple(@BeanParam 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(); } @@ -74,7 +74,7 @@ public String simple( } return name + " - " + active + " - " + times * num + " - " + status + " - " - + htmlPart.contentType() + " - " + Files.exists(xmlPart) + " - " + + Files.exists(htmlPart.filePath()) + " - " + Files.exists(xmlPart) + " - " + 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 7fb5137508642..6d7951ed1f80d 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); String mimePart = getPartMime(parameterResult.getAnns()); String partFileName = getPartFileName(parameterResult.getAnns()); 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 f0c129876ac7e..8669075c42caa 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 @@ -571,6 +571,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf boolean suspended = false; boolean sse = false; boolean formParamRequired = false; + Set fileFormNames = new HashSet<>(); Type bodyParamType = null; TypeArgMapper typeArgMapper = new TypeArgMapper(currentMethodInfo.declaringClass(), index); for (int i = 0; i < methodParameters.length; ++i) { @@ -604,12 +605,12 @@ 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 || type == ParameterType.MULTI_PART_FORM) { // transform the bean param - formParamRequired |= handleBeanParam(actualEndpointInfo, paramType, methodParameters, i); + formParamRequired |= handleBeanParam(actualEndpointInfo, paramType, methodParameters, i, fileFormNames); } else if (type == ParameterType.FORM) { formParamRequired = true; } @@ -731,6 +732,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf .setSse(sse) .setStreamElementType(streamElementType) .setFormParamRequired(formParamRequired) + .setFileFormNames(fileFormNames) .setParameters(methodParameters) .setSimpleReturnType( toClassName(currentMethodInfo.returnType(), currentClassInfo, actualEndpointInfo, index)) @@ -885,7 +887,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) { @@ -901,7 +903,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 7725306fefc1b..54463c2a191fa 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 @@ -68,6 +68,8 @@ public class ResourceMethod { private boolean isFormParamRequired; + private Set fileFormNames; + private List subResourceMethods; public ResourceMethod() { @@ -224,6 +226,15 @@ public ResourceMethod setFormParamRequired(boolean isFormParamRequired) { 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 caf772ebb5e66..cae5bc7227def 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 @@ -95,6 +95,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; @@ -176,7 +183,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, @@ -186,7 +194,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(); } @@ -233,6 +241,7 @@ private void validateMethodPath(ServerResourceMethod method, ClassInfo currentCl } } + @Override protected InjectableBean scanInjectableBean(ClassInfo currentClassInfo, ClassInfo actualEndpointInfo, Map existingConverters, AdditionalReaders additionalReaders, Map injectableBeans, boolean hasRuntimeConverters) { @@ -280,6 +289,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 @@ -309,15 +336,22 @@ 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 mimeType = getPartMime(parameterResult.getAnns()); String separator = getSeparator(parameterResult.getAnns()); + 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(), mimeType, separator); 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 5506f976f1d92..0c44ebb2b0941 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 @@ -283,9 +283,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 1b93682704e40..ecd7adcbc900a 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 @@ -283,7 +283,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, boolean checkReadBodyRequestFilters = false; if (method.isFormParamRequired()) { // 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 196d76632a83a..579eae04a83a6 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; @@ -26,11 +27,13 @@ public class FormBodyHandler implements GenericRuntimeConfigurableServerRestHand 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 @@ -79,7 +82,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; } @@ -96,7 +99,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; } @@ -116,7 +119,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); - } }