From 6ecf70805f0532f223db1b1f69a282455b7e76a7 Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 13 Oct 2022 13:27:03 +0200 Subject: [PATCH] Validate media types for sub resources in Resteasy Reactive At the moment, there are two places where resteasy reactive is verifying the media types: (1) at the handler `ClassRoutingHandler` only for the main resources (it's not invoked for sub-resources; and (2) at the handler `RequestDeserializeHandler` for the rest. The issue is that the verification at `RequestDeserializeHandler` takes the one from the user which might not be compatible with the one set by the user using the annotation `@Consumes`. Note that I tried to make this handler to be invoked also for sub resources but it caused more troubles and it would not fix the root cause of the issue that is the logic in the handler `RequestDeserializeHandler` is wrong. Fix https://github.com/quarkusio/quarkus/issues/28460 (cherry picked from commit bfbfea6c47e3580095978a410e8b165328c76659) --- .../simple/SimpleQuarkusRestTestCase.java | 8 ++++++ .../server/test/simple/SubResource.java | 10 +++++++ .../startup/RuntimeResourceDeployment.java | 4 +-- .../handlers/RequestDeserializeHandler.java | 28 +++++++++++++------ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java index 51ef18e647cc1..9dea4e08961c7 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java @@ -8,6 +8,7 @@ import java.util.function.Supplier; +import org.apache.http.HttpStatus; import org.hamcrest.Matchers; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; @@ -19,6 +20,7 @@ import io.quarkus.test.QuarkusUnitTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; import io.restassured.http.Headers; public class SimpleQuarkusRestTestCase { @@ -107,6 +109,12 @@ public void testSubResource() { .then().body(Matchers.equalTo("otherSub")); RestAssured.get("/simple/sub") .then().body(Matchers.equalTo("sub")); + + RestAssured.with() + .contentType(ContentType.JSON) + .body("{\"test\": true}") + .patch("/simple/sub/patch/text") + .then().statusCode(HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE); } @Test diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SubResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SubResource.java index 00b37fdf42c6c..e1546a77d6fc8 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SubResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SubResource.java @@ -1,7 +1,10 @@ package io.quarkus.resteasy.reactive.server.test.simple; +import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.PATCH; import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; public class SubResource { @@ -15,4 +18,11 @@ public String sub() { public String otherPath() { return "otherSub"; } + + @Path("patch/text") + @PATCH + @Consumes(MediaType.TEXT_PLAIN) + public String patchWithTextPlain(String patch) { + return "test-value: " + patch; + } } 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 67118258789de..1ca909d8bd1b9 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 @@ -312,8 +312,8 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, // we only need to parse the signature and create generic type when the declared type differs from the type genericType = TypeSignatureParser.parse(bodyParameter.signature); } - handlers.add(new RequestDeserializeHandler(typeClass, genericType, - consumesMediaTypes.isEmpty() ? null : consumesMediaTypes.get(0), serialisers, bodyParameterIndex)); + handlers.add(new RequestDeserializeHandler(typeClass, genericType, consumesMediaTypes, serialisers, + bodyParameterIndex)); } // given that we may inject form params in the endpoint we need to make sure we read the body before diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/RequestDeserializeHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/RequestDeserializeHandler.java index 0203a0285b238..701946a95ca5c 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/RequestDeserializeHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/RequestDeserializeHandler.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.util.Collections; import java.util.List; import javax.ws.rs.BadRequestException; @@ -17,6 +18,7 @@ import javax.ws.rs.ext.ReaderInterceptor; import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.common.util.MediaTypeHelper; import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; import org.jboss.resteasy.reactive.server.core.ServerSerialisers; import org.jboss.resteasy.reactive.server.jaxrs.ReaderInterceptorContextImpl; @@ -29,30 +31,40 @@ public class RequestDeserializeHandler implements ServerRestHandler { private final Class type; private final Type genericType; - private final MediaType mediaType; + private final List acceptableMediaTypes; private final ServerSerialisers serialisers; private final int parameterIndex; - public RequestDeserializeHandler(Class type, Type genericType, MediaType mediaType, ServerSerialisers serialisers, + public RequestDeserializeHandler(Class type, Type genericType, List acceptableMediaTypes, + ServerSerialisers serialisers, int parameterIndex) { this.type = type; this.genericType = genericType; - this.mediaType = mediaType; + this.acceptableMediaTypes = acceptableMediaTypes; this.serialisers = serialisers; this.parameterIndex = parameterIndex; } @Override public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { - MediaType effectiveRequestType = mediaType; - String requestTypeString = requestContext.serverRequest().getRequestHeader(HttpHeaders.CONTENT_TYPE); - if (requestTypeString != null) { + MediaType effectiveRequestType = null; + Object requestType = requestContext.getHeader(HttpHeaders.CONTENT_TYPE, true); + if (requestType != null) { try { - effectiveRequestType = MediaType.valueOf(requestTypeString); + effectiveRequestType = MediaType.valueOf((String) requestType); } catch (Exception e) { throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).build()); } - } else if (effectiveRequestType == null) { + + // We need to verify media type for sub-resources, this mimics what is done in {@code ClassRoutingHandler} + if (MediaTypeHelper.getFirstMatch( + acceptableMediaTypes, + Collections.singletonList(effectiveRequestType)) == null) { + throw new NotSupportedException("The content-type header value did not match the value in @Consumes"); + } + } else if (!acceptableMediaTypes.isEmpty()) { + effectiveRequestType = acceptableMediaTypes.get(0); + } else { effectiveRequestType = MediaType.APPLICATION_OCTET_STREAM_TYPE; } List> readers = serialisers.findReaders(null, type, effectiveRequestType, RuntimeType.SERVER);