diff --git a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/rest/RestJettyRequiredBodyTest.java b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/rest/RestJettyRequiredBodyTest.java index 093bbb58f1f78..2616e574a7e93 100644 --- a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/rest/RestJettyRequiredBodyTest.java +++ b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/rest/RestJettyRequiredBodyTest.java @@ -26,11 +26,15 @@ import org.junit.jupiter.api.Test; import static org.apache.camel.test.junit6.TestSupport.assertIsInstanceOf; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public class RestJettyRequiredBodyTest extends BaseJettyTest { + // bytes that are not valid UTF-8, so they would be replaced if the body was turned into a String + private static final byte[] BINARY_BODY = { 0x00, 0x01, (byte) 0xFF, (byte) 0xFE, (byte) 0x80, 0x7F, (byte) 0xC3, 0x28 }; + @Test public void testJettyValid() { String out = fluentTemplate.withHeader(Exchange.CONTENT_TYPE, "application/json") @@ -70,6 +74,18 @@ public void testJettyInvalidEmptyBody() { assertEquals("The request body is missing.", cause.getResponseBody()); } + @Test + public void testJettyBinaryBodyNotCorrupted() { + byte[] out = fluentTemplate.withHeader(Exchange.CONTENT_TYPE, "application/octet-stream") + .withHeader("Accept", "application/octet-stream") + .withHeader(Exchange.HTTP_METHOD, "post") + .withBody(BINARY_BODY) + .to("http://localhost:" + getPort() + "/users/123/upload") + .request(byte[].class); + + assertArrayEquals(BINARY_BODY, out); + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @@ -85,6 +101,13 @@ public void configure() { .name("body").required(true).type(RestParamType.body) .endParam().to("direct:update"); from("direct:update").setBody(constant("{ \"status\": \"ok\" }")); + + // a binary service that echoes back what it received + rest("/users/").post("{id}/upload").consumes("application/octet-stream") + .produces("application/octet-stream").param() + .name("body").required(true).type(RestParamType.body) + .endParam().to("direct:upload"); + from("direct:upload").setBody(bodyAs(byte[].class)); } }; } diff --git a/core/camel-support/src/main/java/org/apache/camel/support/processor/DefaultRestClientRequestValidator.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/DefaultRestClientRequestValidator.java index a9add96dfeaa5..af9ca20a755ef 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/processor/DefaultRestClientRequestValidator.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/DefaultRestClientRequestValidator.java @@ -74,11 +74,11 @@ public ValidationError validate(Exchange exchange, ValidationContext validationC Object body = exchange.getMessage().getBody(); if (validationContext.requiredBody()) { // the body is required, so we need to know if we have a body or not - // so force reading the body as a String which we can work with + // so force reading the body as a String which we can work with. + // extractBodyAsString uses stream caching so the message body stays re-readable, + // and the body is deliberately not replaced with the String as that would + // corrupt binary payloads such as application/octet-stream body = MessageHelper.extractBodyAsString(exchange.getIn()); - if (ObjectHelper.isNotEmpty(body)) { - exchange.getIn().setBody(body); - } if (ObjectHelper.isEmpty(body)) { // this is a bad request, the client did not include a message body return new ValidationError(400, "The request body is missing."); diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc index 69a1910ad9844..39ac96390335e 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc @@ -1252,3 +1252,10 @@ Local downloads configured with a plain `downloadFileName` directory now resolve path segments before checking that the destination remains inside that directory. Downloads through a symbolic link that resolves outside the configured directory are rejected. Valid object names using `/` as a pseudo-directory separator continue to work. + +=== camel-core - REST client request validation and binary bodies + +When `clientRequestValidation` is enabled and the REST service declares a required body, the incoming +message body is no longer replaced with a `String` version of itself. That conversion corrupted binary +payloads such as `application/octet-stream`. The body is still read to check that it is present, using +stream caching so it stays re-readable, but it now keeps its original type.