From d020e32a2efdc0b490f24aab6d73b501d45a4969 Mon Sep 17 00:00:00 2001 From: yawkat Date: Mon, 17 Jan 2022 10:43:19 +0100 Subject: [PATCH] Fix binding mixed attributes to body parameters `NettyHttpRequest.buildBody` distinguishes between requests with "received data" and "received content", where the former only applies when `AbstractHttpData` instances are present. `AbstractHttpData` is a base class of both `DiskAttribute` and `MemoryAttribute`, but not of `MixedAttribute`. This makes `buildBody` use the "content" path for mixed requests. This change adds an exception for `MixedAttribute` so that it is also covered by the "data" path. Fixes #6705 --- .../http/server/netty/NettyHttpRequest.java | 12 +++++----- .../micronaut/upload/MixedUploadSpec.groovy | 22 +++++++++++++++++++ .../io/micronaut/upload/UploadController.java | 5 +++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpRequest.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpRequest.java index 3afa5a66264..94f9245b9aa 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpRequest.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpRequest.java @@ -61,6 +61,8 @@ import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.handler.codec.http.cookie.ClientCookieEncoder; import io.netty.handler.codec.http.multipart.AbstractHttpData; +import io.netty.handler.codec.http.multipart.HttpData; +import io.netty.handler.codec.http.multipart.MixedAttribute; import io.netty.handler.codec.http2.Http2ConnectionHandler; import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.handler.ssl.SslHandler; @@ -144,7 +146,7 @@ public class NettyHttpRequest extends AbstractNettyHttpRequest implements private MutableConvertibleValues attributes; private NettyCookies nettyCookies; private List receivedContent = new ArrayList<>(); - private Map receivedData = new LinkedHashMap<>(); + private Map receivedData = new LinkedHashMap<>(); private Supplier> body; private RouteMatch matchedRoute; @@ -297,7 +299,7 @@ protected Object buildBody() { if (!receivedData.isEmpty()) { Map body = new LinkedHashMap(receivedData.size()); - for (AbstractHttpData data: receivedData.values()) { + for (HttpData data: receivedData.values()) { String newValue = getContent(data); //noinspection unchecked body.compute(data.getName(), (key, oldValue) -> { @@ -332,7 +334,7 @@ protected Object buildBody() { } } - private String getContent(AbstractHttpData data) { + private String getContent(HttpData data) { String newValue; try { newValue = data.getString(serverConfiguration.getDefaultCharset()); @@ -412,10 +414,10 @@ public RouteMatch getMatchedRoute() { */ @Internal public void addContent(ByteBufHolder httpContent) { - if (httpContent instanceof AbstractHttpData) { + if (httpContent instanceof AbstractHttpData || httpContent instanceof MixedAttribute) { receivedData.computeIfAbsent(new IdentityWrapper(httpContent), key -> { httpContent.retain(); - return (AbstractHttpData) httpContent; + return (HttpData) httpContent; }); } else { receivedContent.add(httpContent.retain()); diff --git a/test-suite/src/test/groovy/io/micronaut/upload/MixedUploadSpec.groovy b/test-suite/src/test/groovy/io/micronaut/upload/MixedUploadSpec.groovy index e0eea6abc2e..ea87ebd45a6 100644 --- a/test-suite/src/test/groovy/io/micronaut/upload/MixedUploadSpec.groovy +++ b/test-suite/src/test/groovy/io/micronaut/upload/MixedUploadSpec.groovy @@ -430,6 +430,28 @@ class MixedUploadSpec extends AbstractMicronautSpec { result == 'data.json: 16' } + void "test normal form items"() { + given: + MultipartBody requestBody = MultipartBody.builder() + .addPart("title", "foo") + .build() + + + when: + Mono> flowable = Mono.from(client.exchange( + HttpRequest.POST("/upload/receive-multipart", requestBody) + .contentType(MediaType.MULTIPART_FORM_DATA_TYPE) + .accept(MediaType.TEXT_PLAIN_TYPE), + String + )) + HttpResponse response = flowable.block() + def result = response.getBody().get() + + then: + response.code() == HttpStatus.OK.code + result == "Data{title='foo'}" + } + @Override Map getConfiguration() { super.getConfiguration() << ['micronaut.http.client.read-timeout': 300, diff --git a/test-suite/src/test/groovy/io/micronaut/upload/UploadController.java b/test-suite/src/test/groovy/io/micronaut/upload/UploadController.java index 9adbc91eef1..c9226a848e8 100644 --- a/test-suite/src/test/groovy/io/micronaut/upload/UploadController.java +++ b/test-suite/src/test/groovy/io/micronaut/upload/UploadController.java @@ -75,6 +75,11 @@ public String receiveBytes(byte[] data, String title) { return title + ": " + data.length; } + @Post(value = "/receive-multipart", consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.TEXT_PLAIN) + public String receiveMultipart(@Body Data data) { + return data.toString(); + } + @Post(value = "/receive-file-upload", consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.TEXT_PLAIN) public Publisher> receiveFileUpload(StreamingFileUpload data, String title) { long size = data.getDefinedSize();