Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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() {
Expand All @@ -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));
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.