This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new c7a96b7452fa CAMEL-25248: camel-undertow - write a String body in the 
charset that the Content-Type declares (#27245)
c7a96b7452fa is described below

commit c7a96b7452fafdb13395fdedddd0d4e7f666fa7d
Author: allthingssecurity <[email protected]>
AuthorDate: Fri Oct 2 12:55:49 2026 +0530

    CAMEL-25248: camel-undertow - write a String body in the charset that the 
Content-Type declares (#27245)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../camel/component/undertow/UndertowConsumer.java |  12 +-
 .../camel/component/undertow/UndertowHelper.java   |  26 +++++
 .../camel/component/undertow/UndertowProducer.java |  11 +-
 .../undertow/UndertowStringBodyCharsetTest.java    | 123 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |   9 ++
 5 files changed, 177 insertions(+), 4 deletions(-)

diff --git 
a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java
 
b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java
index 0cd70ca89b78..7df12a1e2ce2 100644
--- 
a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java
+++ 
b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java
@@ -288,8 +288,16 @@ public class UndertowConsumer extends DefaultConsumer 
implements HttpHandler, Su
                 IOHelper.copy(input, output, IOHelper.DEFAULT_BUFFER_SIZE, 
true);
             }
         } else {
-            TypeConverter tc = 
getEndpoint().getCamelContext().getTypeConverter();
-            ByteBuffer bodyAsByteBuffer = 
tc.mandatoryConvertTo(ByteBuffer.class, body);
+            ByteBuffer bodyAsByteBuffer = null;
+            if (body instanceof String text) {
+                // write the text in the charset that the response 
Content-Type declares, if any
+                bodyAsByteBuffer = UndertowHelper.toByteBuffer(text,
+                        
httpExchange.getResponseHeaders().getFirst(Headers.CONTENT_TYPE));
+            }
+            if (bodyAsByteBuffer == null) {
+                TypeConverter tc = 
getEndpoint().getCamelContext().getTypeConverter();
+                bodyAsByteBuffer = tc.mandatoryConvertTo(ByteBuffer.class, 
body);
+            }
             httpExchange.getResponseSender().send(bodyAsByteBuffer);
         }
     }
diff --git 
a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowHelper.java
 
b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowHelper.java
index 460d12a30664..81035bbe7175 100644
--- 
a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowHelper.java
+++ 
b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowHelper.java
@@ -18,12 +18,16 @@ package org.apache.camel.component.undertow;
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
 import java.util.Map;
 
+import io.undertow.util.Headers;
 import io.undertow.util.HttpString;
 import io.undertow.util.Methods;
 import org.apache.camel.Exchange;
 import org.apache.camel.util.CollectionHelper;
+import org.apache.camel.util.IOHelper;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.URISupport;
 import org.apache.camel.util.UnsafeUriCharactersEncoder;
@@ -174,4 +178,26 @@ public final class UndertowHelper {
         }
     }
 
+    /**
+     * Encodes a String body to send over HTTP in the charset that the 
Content-Type of the message declares, so that the
+     * bytes match the header.
+     *
+     * @param  body        the String body
+     * @param  contentType the Content-Type of the request or response being 
sent, may be <tt>null</tt>
+     * @return             the encoded body, or <tt>null</tt> when the 
Content-Type declares no charset or one that is
+     *                     not supported, in which case the body is converted 
as before (UTF-8 by default)
+     */
+    public static ByteBuffer toByteBuffer(String body, String contentType) {
+        String name = contentType != null ? 
Headers.extractQuotedValueFromHeader(contentType, "charset") : null;
+        if (ObjectHelper.isEmpty(name)) {
+            return null;
+        }
+        try {
+            return 
ByteBuffer.wrap(body.getBytes(Charset.forName(IOHelper.normalizeCharset(name))));
+        } catch (IllegalArgumentException e) {
+            // unknown or unsupported charset in the Content-Type
+            return null;
+        }
+    }
+
 }
diff --git 
a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowProducer.java
 
b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowProducer.java
index 0d053cffb241..151e564ad26e 100644
--- 
a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowProducer.java
+++ 
b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowProducer.java
@@ -152,8 +152,15 @@ public class UndertowProducer extends DefaultAsyncProducer 
{
                     camelExchange, callback, getEndpoint(),
                     request, (InputStream) body);
         } else {
-            final TypeConverter tc = 
endpoint.getCamelContext().getTypeConverter();
-            final ByteBuffer bodyAsByte = tc.tryConvertTo(ByteBuffer.class, 
body);
+            ByteBuffer bodyAsByte = null;
+            if (body instanceof String text) {
+                // write the text in the charset that the request Content-Type 
declares, if any
+                bodyAsByte = UndertowHelper.toByteBuffer(text, 
requestHeaders.getFirst(Headers.CONTENT_TYPE));
+            }
+            if (bodyAsByte == null) {
+                final TypeConverter tc = 
endpoint.getCamelContext().getTypeConverter();
+                bodyAsByte = tc.tryConvertTo(ByteBuffer.class, body);
+            }
 
             // As tryConvertTo is used to convert the body, we should do null 
check
             // or the call bodyAsByte.remaining() may throw an NPE
diff --git 
a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowStringBodyCharsetTest.java
 
b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowStringBodyCharsetTest.java
new file mode 100644
index 000000000000..0700b891588b
--- /dev/null
+++ 
b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowStringBodyCharsetTest.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.undertow;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A String body is written in the charset that the Content-Type declares, 
otherwise as UTF-8.
+ */
+public class UndertowStringBodyCharsetTest extends BaseUndertowTest {
+
+    private static final String TEXT = "Grüße aus Köln";
+    private static final String LATIN1 = "text/plain; charset=ISO-8859-1";
+
+    @Test
+    public void testResponseUsesCharsetOfContentType() throws Exception {
+        HttpResponse<byte[]> response = 
send(HttpRequest.newBuilder(uri("latin1")).GET().build());
+
+        assertEquals(LATIN1, 
response.headers().firstValue("Content-Type").orElse(null));
+        assertArrayEquals(TEXT.getBytes(ISO_8859_1), response.body());
+    }
+
+    @Test
+    public void testEchoUsesCharsetOfRequest() throws Exception {
+        HttpResponse<byte[]> response = 
send(HttpRequest.newBuilder(uri("echo"))
+                .header("Content-Type", LATIN1)
+                
.POST(HttpRequest.BodyPublishers.ofByteArray(TEXT.getBytes(ISO_8859_1))).build());
+
+        assertArrayEquals(TEXT.getBytes(ISO_8859_1), response.body());
+    }
+
+    @Test
+    public void testResponseWithoutCharsetIsUtf8() throws Exception {
+        HttpResponse<byte[]> response = 
send(HttpRequest.newBuilder(uri("utf8")).GET().build());
+
+        assertArrayEquals(TEXT.getBytes(UTF_8), response.body());
+    }
+
+    @Test
+    public void testResponseWithoutCharsetToLatin1RequestIsUtf8() throws 
Exception {
+        // the request charset (CamelCharsetName) must not be used for a 
response that does not declare it
+        HttpResponse<byte[]> response = 
send(HttpRequest.newBuilder(uri("noCharset"))
+                .header("Content-Type", LATIN1)
+                
.POST(HttpRequest.BodyPublishers.ofByteArray(TEXT.getBytes(ISO_8859_1))).build());
+
+        assertEquals("text/plain", 
response.headers().firstValue("Content-Type").orElse(null));
+        assertArrayEquals(TEXT.getBytes(UTF_8), response.body());
+    }
+
+    @Test
+    public void testProducerUsesCharsetOfContentType() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:received");
+        mock.expectedMessageCount(1);
+
+        
template.sendBodyAndHeader("undertow:http://localhost:{{port}}/received";, TEXT, 
Exchange.CONTENT_TYPE, LATIN1);
+
+        MockEndpoint.assertIsSatisfied(context);
+        assertArrayEquals(TEXT.getBytes(ISO_8859_1), 
mock.getExchanges().get(0).getIn().getBody(byte[].class));
+    }
+
+    private URI uri(String path) {
+        return URI.create("http://localhost:"; + getPort() + "/" + path);
+    }
+
+    private static HttpResponse<byte[]> send(HttpRequest request) throws 
Exception {
+        return HttpClient.newHttpClient().send(request, 
HttpResponse.BodyHandlers.ofByteArray());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("undertow:http://localhost:{{port}}/latin1";)
+                        .setHeader(Exchange.CONTENT_TYPE, constant(LATIN1))
+                        .setBody(constant(TEXT));
+
+                from("undertow:http://localhost:{{port}}/echo";)
+                        .convertBodyTo(String.class);
+
+                from("undertow:http://localhost:{{port}}/noCharset";)
+                        .convertBodyTo(String.class)
+                        .setHeader(Exchange.CONTENT_TYPE, 
constant("text/plain"));
+
+                from("undertow:http://localhost:{{port}}/utf8";)
+                        .setHeader(Exchange.CONTENT_TYPE, 
constant("text/plain"))
+                        .setBody(constant(TEXT));
+
+                from("undertow:http://localhost:{{port}}/received";)
+                        .convertBodyTo(byte[].class)
+                        .to("mock:received")
+                        .setBody(constant("OK"));
+            }
+        };
+    }
+}
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 2f36e4a00e02..1800adfbabfd 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
@@ -737,6 +737,15 @@ in the charset that the `Content-Type` declares, so the 
bytes match the header.
 the declared charset and read such a response as UTF-8 must now use the 
declared charset, and characters that the
 declared charset cannot represent are written as `?`.
 
+=== camel-undertow - String bodies in the charset of the Content-Type
+
+The Undertow consumer (response) and producer (request) always wrote a 
`String` body as UTF-8, even when the
+`Content-Type` declared another charset (for example `text/plain; 
charset=ISO-8859-1`). A `String` body is now
+written in the charset that the `Content-Type` declares. Bodies that are not a 
`String`, and messages whose
+`Content-Type` declares no charset, are sent as before (UTF-8 by default). A 
peer that ignored the declared charset and
+read such a message as UTF-8 must now use the declared charset, and characters 
that the declared charset cannot
+represent are written as `?`.
+
 === Components and Language removal
 
 ==== camel-csimple, camel-csimple-joor and csimple-maven-plugin

Reply via email to