From 27f17218771d998c8398efe4438c332a1efbc292 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 16 Nov 2023 16:41:47 +0000 Subject: [PATCH 01/10] Bump smallrye-jwt version to 4.4.0 (cherry picked from commit 30458904e0ceaa0d6b44e74d6a18aadced2dc250) --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index fef4a34098749..cc7d2e01b9808 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -58,7 +58,7 @@ 2.2.3 3.0.3 6.2.6 - 4.3.0 + 4.4.0 2.1.0 1.0.13 3.0.1 From 62e438a33935ce64681a6a5767f9a597d43c064d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 31 Aug 2023 15:52:35 +0300 Subject: [PATCH 02/10] Properly take Quarkus HTTP body configuration into account for File body (cherry picked from commit e9a0c7c470307e18058529b04aa42c6c2f14e568) --- .../test/MessageBodyReaderTests.java | 8 ++ .../BuiltInReaderOverrideBuildItem.java | 42 ++++++++ .../deployment/ResteasyReactiveProcessor.java | 18 +++- .../test/multipart/AbstractMultipartTest.java | 6 ++ .../multipart/FileInputWithDeleteTest.java | 80 ++++++++++++++++ .../multipart/FileInputWithoutDeleteTest.java | 96 +++++++++++++++++++ .../MultipartInputBodyHandlerTest.java | 5 - .../test/multipart/MultipartInputTest.java | 5 - .../runtime/QuarkusServerFileBodyHandler.java | 95 ++++++++++++++++++ .../serialisers/FileBodyHandler.java | 16 ++-- .../core/ResteasyReactiveRequestContext.java | 6 ++ .../server/spi/ServerRequestContext.java | 3 + 12 files changed, 362 insertions(+), 18 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java index d4e1238aadc60..ab49f42c3381b 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java @@ -3,12 +3,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import jakarta.ws.rs.core.HttpHeaders; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -20,6 +22,7 @@ import org.jboss.resteasy.reactive.common.providers.serialisers.AbstractJsonMessageBodyReader; import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; +import org.jboss.resteasy.reactive.server.jaxrs.HttpHeadersImpl; import org.jboss.resteasy.reactive.server.spi.ContentType; import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; import org.jboss.resteasy.reactive.server.spi.ServerHttpResponse; @@ -266,6 +269,11 @@ public ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo() { return null; } + @Override + public HttpHeaders getRequestHeaders() { + return new HttpHeadersImpl(Collections.emptyList()); + } + @Override public void abortWith(Response response) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java new file mode 100644 index 0000000000000..13b3ce8eb68da --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java @@ -0,0 +1,42 @@ +package io.quarkus.resteasy.reactive.server.deployment; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class BuiltInReaderOverrideBuildItem extends MultiBuildItem { + + private final String readerClassName; + private final String overrideClassName; + + public BuiltInReaderOverrideBuildItem(String readerClassName, String overrideClassName) { + this.readerClassName = readerClassName; + this.overrideClassName = overrideClassName; + } + + public String getReaderClassName() { + return readerClassName; + } + + public String getOverrideClassName() { + return overrideClassName; + } + + public static Map toMap(List items) { + if (items.isEmpty()) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + for (BuiltInReaderOverrideBuildItem item : items) { + String previousOverride = result.put(item.getReaderClassName(), item.getOverrideClassName()); + if (previousOverride != null) { + throw new IllegalStateException( + "Providing multiple BuiltInReaderOverrideBuildItem for the same readerClassName is not supported"); + } + } + return result; + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 63678aaf5dedf..b669e9a965091 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -109,6 +109,7 @@ import org.jboss.resteasy.reactive.server.processor.scanning.ResponseHeaderMethodScanner; import org.jboss.resteasy.reactive.server.processor.scanning.ResponseStatusMethodScanner; import org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerFileBodyHandler; import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; import org.jboss.resteasy.reactive.server.vertx.serializers.ServerMutinyAsyncFileMessageBodyWriter; @@ -165,6 +166,7 @@ import io.quarkus.resteasy.reactive.common.deployment.ServerDefaultProducesHandlerBuildItem; import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveConfig; import io.quarkus.resteasy.reactive.server.EndpointDisabled; +import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerFileBodyHandler; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveInitialiser; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRuntimeRecorder; @@ -998,6 +1000,12 @@ private static String determineHandledGenericTypeOfProviderInterface(Class pr } } + @BuildStep + public void builtInReaderOverrides(BuildProducer producer) { + producer.produce(new BuiltInReaderOverrideBuildItem(ServerFileBodyHandler.class.getName(), + QuarkusServerFileBodyHandler.class.getName())); + } + @BuildStep @Record(value = ExecutionTime.STATIC_INIT, useIdentityComparisonForParameters = false) public void serverSerializers(ResteasyReactiveRecorder recorder, @@ -1007,6 +1015,7 @@ public void serverSerializers(ResteasyReactiveRecorder recorder, List additionalMessageBodyWriters, List messageBodyReaderOverrideBuildItems, List messageBodyWriterOverrideBuildItems, + List builtInReaderOverrideBuildItems, BuildProducer reflectiveClass, BuildProducer serverSerializersProducer) { @@ -1024,11 +1033,16 @@ public void serverSerializers(ResteasyReactiveRecorder recorder, reflectiveClass.produce(ReflectiveClassBuildItem.builder(builtinWriter.writerClass.getName()) .build()); } + Map builtInReaderOverrides = BuiltInReaderOverrideBuildItem.toMap(builtInReaderOverrideBuildItems); for (Serialisers.BuiltinReader builtinReader : ServerSerialisers.BUILTIN_READERS) { - registerReader(recorder, serialisers, builtinReader.entityClass.getName(), builtinReader.readerClass.getName(), + String effectiveReaderClassName = builtinReader.readerClass.getName(); + if (builtInReaderOverrides.containsKey(effectiveReaderClassName)) { + effectiveReaderClassName = builtInReaderOverrides.get(effectiveReaderClassName); + } + registerReader(recorder, serialisers, builtinReader.entityClass.getName(), effectiveReaderClassName, beanContainerBuildItem.getValue(), builtinReader.mediaType, builtinReader.constraint); - reflectiveClass.produce(ReflectiveClassBuildItem.builder(builtinReader.readerClass.getName()) + reflectiveClass.produce(ReflectiveClassBuildItem.builder(effectiveReaderClassName) .build()); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java index 86348aa16e4b8..9bcb1a8dd1d7c 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java @@ -3,6 +3,8 @@ import static org.awaitility.Awaitility.await; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.concurrent.Callable; @@ -40,4 +42,8 @@ public Boolean call() { } }); } + + protected String fileSizeAsStr(File file) throws IOException { + return "" + Files.readAllBytes(file.toPath()).length; + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java new file mode 100644 index 0000000000000..a3d43bad36ab3 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java @@ -0,0 +1,80 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class FileInputWithDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + "quarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + awaitUploadDirectoryToEmpty(uploadDir); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + awaitUploadDirectoryToEmpty(uploadDir); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(File file) throws IOException { + return Files.size(file.toPath()); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java new file mode 100644 index 0000000000000..318b8602e9167 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java @@ -0,0 +1,96 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class FileInputWithoutDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + // keep the files around so we can assert the outcome + "quarkus.http.body.delete-uploaded-files-on-end=false\nquarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @BeforeEach + public void assertEmptyUploads() { + Assertions.assertTrue(isDirectoryEmpty(uploadDir)); + } + + @AfterEach + public void clearDirectory() { + clearDirectory(uploadDir); + } + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(1, uploadDir.toFile().listFiles().length); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(2, uploadDir.toFile().listFiles().length); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(File file) throws IOException { + return Files.size(file.toPath()); + } + } +} 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 f75e0a416573d..9b371cb02996e 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 @@ -5,7 +5,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Consumer; @@ -164,8 +163,4 @@ private String filePath(File file) { return file.toPath().toAbsolutePath().toString(); } - private String fileSizeAsStr(File file) throws IOException { - return "" + Files.readAllBytes(file.toPath()).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 3b3bc76ad6b54..e29c3791fda69 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 @@ -5,7 +5,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Supplier; @@ -245,8 +244,4 @@ private String filePath(File file) { return file.toPath().toAbsolutePath().toString(); } - private String fileSizeAsStr(File file) throws IOException { - return "" + Files.readAllBytes(file.toPath()).length; - } - } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java new file mode 100644 index 0000000000000..01e6b53b33b35 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java @@ -0,0 +1,95 @@ +package io.quarkus.resteasy.reactive.server.runtime; + +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX; +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.CompletionCallback; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler; +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; +import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; +import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; + +public class QuarkusServerFileBodyHandler implements ServerMessageBodyReader { + + private static final Logger log = Logger.getLogger(QuarkusServerFileBodyHandler.class); + + @Override + public boolean isReadable(Class type, Type genericType, ResteasyReactiveResourceInfo lazyMethod, + MediaType mediaType) { + return File.class.equals(type); + } + + @Override + public File readFrom(Class type, Type genericType, MediaType mediaType, ServerRequestContext context) + throws WebApplicationException, IOException { + Path file = createFile(context); + return FileBodyHandler.doRead(context.getRequestHeaders().getRequestHeaders(), context.getInputStream(), file.toFile()); + } + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return File.class.equals(type); + } + + @Override + public File readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + // unfortunately we don't do much here to avoid the file leak + // however this should never be called in a real world scenario + return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()); + } + + private Path createFile(ServerRequestContext context) throws IOException { + RuntimeConfiguration.Body runtimeBodyConfiguration = ResteasyReactiveRecorder.getCurrentDeployment() + .getRuntimeConfiguration().body(); + boolean deleteUploadedFilesOnEnd = runtimeBodyConfiguration.deleteUploadedFilesOnEnd(); + String uploadsDirectoryStr = runtimeBodyConfiguration.uploadsDirectory(); + Path uploadDirectory = Paths.get(uploadsDirectoryStr); + try { + Files.createDirectories(uploadDirectory); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + Path file = Files.createTempFile(uploadDirectory, PREFIX, SUFFIX); + if (deleteUploadedFilesOnEnd) { + context.registerCompletionCallback(new CompletionCallback() { + @Override + public void onComplete(Throwable throwable) { + ResteasyReactiveRecorder.EXECUTOR_SUPPLIER.get().execute(new Runnable() { + @Override + public void run() { + if (Files.exists(file)) { + try { + Files.delete(file); + } catch (NoSuchFileException e) { // ignore + } catch (IOException e) { + log.error("Cannot remove uploaded file " + file, e); + } + } + } + }); + } + }); + } + return file; + } +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java index 82d2045179024..1f502c66555a2 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java @@ -21,8 +21,8 @@ import org.jboss.resteasy.reactive.common.headers.HeaderUtil; public class FileBodyHandler implements MessageBodyReader, MessageBodyWriter { - protected static final String PREFIX = "pfx"; - protected static final String SUFFIX = "sfx"; + public static final String PREFIX = "pfx"; + public static final String SUFFIX = "sfx"; @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { @@ -33,16 +33,20 @@ public boolean isReadable(Class type, Type genericType, Annotation[] annotati public File readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException { - File downloadedFile = Files.createTempFile(PREFIX, SUFFIX).toFile(); + return doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()); + } + + public static File doRead(MultivaluedMap httpHeaders, InputStream entityStream, + File file) throws IOException { if (HeaderUtil.isContentLengthZero(httpHeaders)) { - return downloadedFile; + return file; } - try (OutputStream output = new BufferedOutputStream(new FileOutputStream(downloadedFile))) { + try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) { entityStream.transferTo(output); } - return downloadedFile; + return file; } public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java index 2248a8f7763ac..4fd59f33828db 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java @@ -19,6 +19,7 @@ import jakarta.ws.rs.core.Cookie; import jakarta.ws.rs.core.GenericEntity; +import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.PathSegment; import jakarta.ws.rs.core.Request; @@ -148,6 +149,11 @@ public ResteasyReactiveRequestContext(Deployment deployment, @Override public abstract ServerHttpResponse serverResponse(); + @Override + public HttpHeaders getRequestHeaders() { + return getHttpHeaders(); + } + public Deployment getDeployment() { return deployment; } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java index 50fba8210e0da..4825de1472c11 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java @@ -3,6 +3,7 @@ import java.io.InputStream; import java.io.OutputStream; +import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @@ -22,5 +23,7 @@ public interface ServerRequestContext extends ResteasyReactiveCallbackContext { ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo(); + HttpHeaders getRequestHeaders(); + void abortWith(Response response); } From 08879807f47f59610833d9e8c0a44d3e81093769 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 31 Aug 2023 16:04:51 +0300 Subject: [PATCH 03/10] Add support for Path as a JAX-RS method body type (cherry picked from commit 96135286ba942f45fdf57af3093660720814cea2) --- .../test/MessageBodyReaderTests.java | 2 +- .../deployment/ResteasyReactiveProcessor.java | 10 +- .../multipart/PathInputWithDeleteTest.java | 80 ++++++++++++++++ .../multipart/PathInputWithoutDeleteTest.java | 96 +++++++++++++++++++ .../runtime/QuarkusServerFileBodyHandler.java | 42 +------- .../runtime/QuarkusServerPathBodyHandler.java | 96 +++++++++++++++++++ 6 files changed, 282 insertions(+), 44 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java index ab49f42c3381b..68bd47c204deb 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import jakarta.ws.rs.core.HttpHeaders; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -17,6 +16,7 @@ import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.container.CompletionCallback; import jakarta.ws.rs.container.ConnectionCallback; +import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index b669e9a965091..64fa78c7f4f5e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -12,6 +12,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -167,6 +168,7 @@ import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveConfig; import io.quarkus.resteasy.reactive.server.EndpointDisabled; import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerFileBodyHandler; +import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerPathBodyHandler; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveInitialiser; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRuntimeRecorder; @@ -1001,9 +1003,13 @@ private static String determineHandledGenericTypeOfProviderInterface(Class pr } @BuildStep - public void builtInReaderOverrides(BuildProducer producer) { - producer.produce(new BuiltInReaderOverrideBuildItem(ServerFileBodyHandler.class.getName(), + public void fileHandling(BuildProducer overrideProducer, + BuildProducer readerProducer) { + overrideProducer.produce(new BuiltInReaderOverrideBuildItem(ServerFileBodyHandler.class.getName(), QuarkusServerFileBodyHandler.class.getName())); + readerProducer.produce( + new MessageBodyReaderBuildItem(QuarkusServerPathBodyHandler.class.getName(), Path.class.getName(), List.of( + MediaType.WILDCARD), RuntimeType.SERVER, true, Priorities.USER)); } @BuildStep diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java new file mode 100644 index 0000000000000..b03d853f2fcab --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java @@ -0,0 +1,80 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class PathInputWithDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + "quarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + awaitUploadDirectoryToEmpty(uploadDir); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + awaitUploadDirectoryToEmpty(uploadDir); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(java.nio.file.Path file) throws IOException { + return Files.size(file); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java new file mode 100644 index 0000000000000..08b7f7181da6b --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java @@ -0,0 +1,96 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class PathInputWithoutDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + // keep the files around so we can assert the outcome + "quarkus.http.body.delete-uploaded-files-on-end=false\nquarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @BeforeEach + public void assertEmptyUploads() { + Assertions.assertTrue(isDirectoryEmpty(uploadDir)); + } + + @AfterEach + public void clearDirectory() { + clearDirectory(uploadDir); + } + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(1, uploadDir.toFile().listFiles().length); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(2, uploadDir.toFile().listFiles().length); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(java.nio.file.Path file) throws IOException { + return Files.size(file); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java index 01e6b53b33b35..e8c8effc7300a 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java @@ -1,28 +1,24 @@ package io.quarkus.resteasy.reactive.server.runtime; +import static io.quarkus.resteasy.reactive.server.runtime.QuarkusServerPathBodyHandler.createFile; import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX; import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.nio.file.Paths; import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.container.CompletionCallback; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler; import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; -import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; @@ -56,40 +52,4 @@ public File readFrom(Class type, Type genericType, Annotation[] annotation // however this should never be called in a real world scenario return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()); } - - private Path createFile(ServerRequestContext context) throws IOException { - RuntimeConfiguration.Body runtimeBodyConfiguration = ResteasyReactiveRecorder.getCurrentDeployment() - .getRuntimeConfiguration().body(); - boolean deleteUploadedFilesOnEnd = runtimeBodyConfiguration.deleteUploadedFilesOnEnd(); - String uploadsDirectoryStr = runtimeBodyConfiguration.uploadsDirectory(); - Path uploadDirectory = Paths.get(uploadsDirectoryStr); - try { - Files.createDirectories(uploadDirectory); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - Path file = Files.createTempFile(uploadDirectory, PREFIX, SUFFIX); - if (deleteUploadedFilesOnEnd) { - context.registerCompletionCallback(new CompletionCallback() { - @Override - public void onComplete(Throwable throwable) { - ResteasyReactiveRecorder.EXECUTOR_SUPPLIER.get().execute(new Runnable() { - @Override - public void run() { - if (Files.exists(file)) { - try { - Files.delete(file); - } catch (NoSuchFileException e) { // ignore - } catch (IOException e) { - log.error("Cannot remove uploaded file " + file, e); - } - } - } - }); - } - }); - } - return file; - } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java new file mode 100644 index 0000000000000..e6dcb8ff7dfa9 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java @@ -0,0 +1,96 @@ +package io.quarkus.resteasy.reactive.server.runtime; + +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX; +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.CompletionCallback; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler; +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; +import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; +import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; + +public class QuarkusServerPathBodyHandler implements ServerMessageBodyReader { + + private static final Logger log = Logger.getLogger(QuarkusServerPathBodyHandler.class); + + @Override + public boolean isReadable(Class type, Type genericType, ResteasyReactiveResourceInfo lazyMethod, + MediaType mediaType) { + return Path.class.equals(type); + } + + @Override + public Path readFrom(Class type, Type genericType, MediaType mediaType, ServerRequestContext context) + throws WebApplicationException, IOException { + Path file = createFile(context); + return FileBodyHandler.doRead(context.getRequestHeaders().getRequestHeaders(), context.getInputStream(), file.toFile()) + .toPath(); + } + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return File.class.equals(type); + } + + @Override + public Path readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + // unfortunately we don't do much here to avoid the file leak + // however this should never be called in a real world scenario + return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()).toPath(); + } + + static Path createFile(ServerRequestContext context) throws IOException { + RuntimeConfiguration.Body runtimeBodyConfiguration = ResteasyReactiveRecorder.getCurrentDeployment() + .getRuntimeConfiguration().body(); + boolean deleteUploadedFilesOnEnd = runtimeBodyConfiguration.deleteUploadedFilesOnEnd(); + String uploadsDirectoryStr = runtimeBodyConfiguration.uploadsDirectory(); + Path uploadDirectory = Paths.get(uploadsDirectoryStr); + try { + Files.createDirectories(uploadDirectory); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + Path file = Files.createTempFile(uploadDirectory, PREFIX, SUFFIX); + if (deleteUploadedFilesOnEnd) { + context.registerCompletionCallback(new CompletionCallback() { + @Override + public void onComplete(Throwable throwable) { + ResteasyReactiveRecorder.EXECUTOR_SUPPLIER.get().execute(new Runnable() { + @Override + public void run() { + if (Files.exists(file)) { + try { + Files.delete(file); + } catch (NoSuchFileException e) { // ignore + } catch (IOException e) { + log.error("Cannot remove uploaded file " + file, e); + } + } + } + }); + } + }); + } + return file; + } +} From 42e197177508226412efa60bb5722eb47c1887a2 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 23 Jan 2024 20:36:12 -0300 Subject: [PATCH 04/10] Register JDBC RowSet required bundle (cherry picked from commit 966126615030c26567e31253ce509886fd755004) --- .../java/io/quarkus/agroal/deployment/AgroalProcessor.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index 94932382fa798..fe6f736040b92 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -56,6 +56,7 @@ import io.quarkus.deployment.builditem.RemovedResourceBuildItem; import io.quarkus.deployment.builditem.SslNativeConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.maven.dependency.ArtifactKey; @@ -410,4 +411,9 @@ void adaptOpenTelemetryJdbcInstrumentationForNative(BuildProducer Date: Wed, 24 Jan 2024 10:20:58 -0300 Subject: [PATCH 05/10] Include RowSet properties file in native image This fixes the `Resource javax/sql/rowset/rowset.properties not found` error (cherry picked from commit 131a1223de230f16efb45b714c66e140a146a0da) --- .../io/quarkus/agroal/deployment/AgroalProcessor.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index fe6f736040b92..fd3aa781622b4 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -413,7 +413,14 @@ void adaptOpenTelemetryJdbcInstrumentationForNative(BuildProducer resourceBundleProducer, + BuildProducer nativeResourceProducer, + BuildProducer reflectiveClassProducer) { + resourceBundleProducer.produce(new NativeImageResourceBundleBuildItem("com.sun.rowset.RowSetResourceBundle")); + nativeResourceProducer.produce(new NativeImageResourceBuildItem("javax/sql/rowset/rowset.properties")); + reflectiveClassProducer.produce(ReflectiveClassBuildItem.builder( + "com.sun.rowset.providers.RIOptimisticProvider", + "com.sun.rowset.providers.RIXMLProvider").build()); } } From 028861f6e3fdba9264069e5bd7ae407992bec304 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Mon, 5 Feb 2024 09:46:34 +0100 Subject: [PATCH 06/10] Make the Forwarded Parser syntax parsing case-insensitive As described on https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded, the forwarded header syntax parsing should be case-insensitive. It's not a real spec, but Kong is using `Proto` instead of `proto` and `For` instead of `for`. (cherry picked from commit 17fb8e056fa7366f57301fc4e6ca31e4287e495d) --- .../http/proxy/TrustedForwarderProxyTest.java | 16 ++++++++++++++++ .../vertx/http/runtime/ForwardedParser.java | 6 +++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/proxy/TrustedForwarderProxyTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/proxy/TrustedForwarderProxyTest.java index b7e344d8d73c1..d267b617b99d2 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/proxy/TrustedForwarderProxyTest.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/proxy/TrustedForwarderProxyTest.java @@ -30,4 +30,20 @@ public void testHeadersAreUsed() { .then() .body(Matchers.equalTo("http|somehost2|backend2:5555|/path|http://somehost2/path")); } + + /** + * As described on https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded, + * the syntax should be case-insensitive. + *

+ * Kong, for example, uses `Proto` instead of `proto` and `For` instead of `for`. + */ + @Test + public void testHeadersAreUsedWhenUsingCasedCharacters() { + RestAssured.given() + .header("Forwarded", "Proto=http;For=backend2:5555;Host=somehost2") + .get("/path") + .then() + .body(Matchers.equalTo("http|somehost2|backend2:5555|/path|http://somehost2/path")); + } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java index 1b11431a81c7f..b768c4c6cddfc 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java @@ -42,9 +42,9 @@ class ForwardedParser { private static final AsciiString X_FORWARDED_PORT = AsciiString.cached("X-Forwarded-Port"); private static final AsciiString X_FORWARDED_FOR = AsciiString.cached("X-Forwarded-For"); - private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("host=\"?([^;,\"]+)\"?"); - private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("proto=\"?([^;,\"]+)\"?"); - private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("for=\"?([^;,\"]+)\"?"); + private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("host=\"?([^;,\"]+)\"?", Pattern.CASE_INSENSITIVE); + private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("proto=\"?([^;,\"]+)\"?", Pattern.CASE_INSENSITIVE); + private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("for=\"?([^;,\"]+)\"?", Pattern.CASE_INSENSITIVE); private final static int PORT_MIN_VALID_VALUE = 0; private final static int PORT_MAX_VALID_VALUE = 65535; From e8361c63d1f31b5d398fe00b9fe7284bce694fd8 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 13 Feb 2024 12:19:26 +0000 Subject: [PATCH 07/10] Log when a RestEasy Reactive client close method is called (cherry picked from commit 49c2903b5cca96687d8905fc1b81658de89623ea) --- .../java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java index 476a8f662b887..46345007ce745 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java @@ -217,6 +217,7 @@ public void close() { if (closeVertx) { vertx.close(); } + log.debug("Client is closed"); } void abortIfClosed() { From 01bd62f364a6df69b6a34232bc237d89b6280368 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 13 Feb 2024 10:33:21 +0200 Subject: [PATCH 08/10] Sanitize app.dekorate.io/vcs-url kubernetes annotation Fixes: #38055 (cherry picked from commit 8afe4ff2625aa920fd21c5d136a5fb06b185fde2) --- .../quarkus/kubernetes/deployment/KubernetesCommonHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java index dadabe7723868..73a579d98b457 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java @@ -71,6 +71,7 @@ import io.dekorate.project.Project; import io.dekorate.project.ScmInfo; import io.dekorate.utils.Annotations; +import io.dekorate.utils.Git; import io.dekorate.utils.Labels; import io.dekorate.utils.Strings; import io.fabric8.kubernetes.api.model.ContainerBuilder; @@ -908,7 +909,7 @@ private static List createAnnotationDecorators(Optional Date: Wed, 14 Feb 2024 17:05:53 +0100 Subject: [PATCH 09/10] ArC: fix interception when some methods return void This commit fixes 2 cases when invalid bytecode is generated: - when a `void`-returning method is intercepted and also decorated - when an interceptor declared on a target class returns `void` (cherry picked from commit 13972414d77e4bc8a1930937bc8c637ba9f9e32b) --- .../arc/processor/SubclassGenerator.java | 8 +- .../decorators/VoidMethodDecoratorTest.java | 69 ++++++++++++ ...VoidMethodInterceptorAndDecoratorTest.java | 101 ++++++++++++++++++ 3 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/VoidMethodDecoratorTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/VoidMethodInterceptorAndDecoratorTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java index f665300bc4444..5a86f367a6992 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java @@ -343,7 +343,7 @@ public String apply(List keys) { applicationClassPredicate.test(bean.getBeanClass()), funBytecode.getMethodParam(1), funBytecode.getMethodParam(0)); - funBytecode.returnValue(ret); + funBytecode.returnValue(ret != null ? ret : funBytecode.loadNull()); constructor.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, methodsList, fun.getInstance()); } constructor.writeInstanceField(field.getFieldDescriptor(), constructor.getThis(), methodsList); @@ -464,9 +464,9 @@ public String apply(List keys) { MethodDescriptor virtualMethodDescriptor = MethodDescriptor.ofMethod(declaringClass, originalMethodDescriptor.getName(), decoratorMethodDescriptor.getReturnType(), decoratorMethodDescriptor.getParameterTypes()); - funcBytecode - .returnValue(funcBytecode.invokeVirtualMethod(virtualMethodDescriptor, funDecoratorInstance, - superParamHandles)); + ResultHandle superResult = funcBytecode.invokeVirtualMethod(virtualMethodDescriptor, funDecoratorInstance, + superParamHandles); + funcBytecode.returnValue(superResult != null ? superResult : funcBytecode.loadNull()); } else { ResultHandle superResult = funcBytecode.invokeVirtualMethod(forwardDescriptor, targetHandle, superParamHandles); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/VoidMethodDecoratorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/VoidMethodDecoratorTest.java new file mode 100644 index 0000000000000..f39ed5510e120 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/VoidMethodDecoratorTest.java @@ -0,0 +1,69 @@ +package io.quarkus.arc.test.decorators; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.atomic.AtomicBoolean; + +import jakarta.annotation.Priority; +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class VoidMethodDecoratorTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Performer.class, MainPerformer.class, + PerformerDecorator.class); + + @Test + public void testDecoration() { + MainPerformer performer = Arc.container().instance(MainPerformer.class).get(); + + assertFalse(MainPerformer.DONE.get()); + assertFalse(PerformerDecorator.DONE.get()); + + performer.doSomething(); + + assertTrue(MainPerformer.DONE.get()); + assertTrue(PerformerDecorator.DONE.get()); + } + + interface Performer { + void doSomething(); + } + + @ApplicationScoped + static class MainPerformer implements Performer { + static final AtomicBoolean DONE = new AtomicBoolean(); + + @Override + public void doSomething() { + DONE.set(true); + } + } + + @Dependent + @Priority(1) + @Decorator + static class PerformerDecorator implements Performer { + static final AtomicBoolean DONE = new AtomicBoolean(); + + @Inject + @Delegate + Performer delegate; + + @Override + public void doSomething() { + DONE.set(true); + delegate.doSomething(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/VoidMethodInterceptorAndDecoratorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/VoidMethodInterceptorAndDecoratorTest.java new file mode 100644 index 0000000000000..b17de441bd387 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/VoidMethodInterceptorAndDecoratorTest.java @@ -0,0 +1,101 @@ +package io.quarkus.arc.test.decorators.interceptor; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.atomic.AtomicBoolean; + +import jakarta.annotation.Priority; +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class VoidMethodInterceptorAndDecoratorTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Performer.class, MainPerformer.class, + PerformerDecorator.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void testDecoration() { + MainPerformer performer = Arc.container().instance(MainPerformer.class).get(); + + assertFalse(MainPerformer.DONE.get()); + assertFalse(PerformerDecorator.DONE.get()); + assertFalse(MyInterceptor.INTERCEPTED.get()); + + performer.doSomething(); + + assertTrue(MainPerformer.DONE.get()); + assertTrue(PerformerDecorator.DONE.get()); + assertTrue(MyInterceptor.INTERCEPTED.get()); + } + + interface Performer { + void doSomething(); + } + + @ApplicationScoped + @MyInterceptorBinding + static class MainPerformer implements Performer { + static final AtomicBoolean DONE = new AtomicBoolean(); + + @Override + public void doSomething() { + DONE.set(true); + } + } + + @Dependent + @Priority(1) + @Decorator + static class PerformerDecorator implements Performer { + static final AtomicBoolean DONE = new AtomicBoolean(); + + @Inject + @Delegate + Performer delegate; + + @Override + public void doSomething() { + DONE.set(true); + delegate.doSomething(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Priority(1) + @Interceptor + static class MyInterceptor { + static final AtomicBoolean INTERCEPTED = new AtomicBoolean(); + + @AroundInvoke + Object log(InvocationContext ctx) throws Exception { + INTERCEPTED.set(true); + return ctx.proceed(); + } + } +} From fc714b370bc65ca2b851896ee3950e0429909c8c Mon Sep 17 00:00:00 2001 From: Hendrik Schmitz Date: Mon, 19 Feb 2024 18:54:25 +0100 Subject: [PATCH 10/10] Update commons-compress version to mitigate CVE-2024-25710 (cherry picked from commit 0f6ffef6be9ad81509d921cd49ba3a05e161e7d4) --- bom/application/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index cc7d2e01b9808..27d5b78e771d2 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -199,7 +199,7 @@ 2.1 4.7.4 1.1.0 - 1.25.0 + 1.26.0 1.10.0 2.10.1 1.1.1.Final diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index f762a765eb81c..520d298317d64 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -56,7 +56,7 @@ 2.15.2 4.0.1 5.9.3 - 1.24.0 + 1.26.0 3.5.1.Final 5.3.1 3.2.1