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 df0a84fda895 CAMEL-25295: camel-undertow - match the charset parameter 
of the Content-Type case-insensitively (#27326)
df0a84fda895 is described below

commit df0a84fda895081084132b8dded29b5ea8c91baf
Author: allthingssecurity <[email protected]>
AuthorDate: Sun Oct 4 12:33:01 2026 +0530

    CAMEL-25295: camel-undertow - match the charset parameter of the 
Content-Type case-insensitively (#27326)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../camel/component/undertow/UndertowConsumer.java |  4 +-
 .../camel/component/undertow/UndertowHelper.java   | 29 +++++++++--
 .../undertow/UndertowStringBodyCharsetTest.java    | 59 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  7 +++
 4 files changed, 92 insertions(+), 7 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 7df12a1e2ce2..c42fee7986f6 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
@@ -407,9 +407,7 @@ public class UndertowConsumer extends DefaultConsumer 
implements HttpHandler, Su
         // By not setting the property when charset is absent, Camel's type 
conversion
         // system will use its own default (UTF-8), matching the behavior of 
DefaultHttpBinding.
         String contentType = 
httpExchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE);
-        String charset = contentType != null
-                ? Headers.extractQuotedValueFromHeader(contentType, "charset")
-                : null;
+        String charset = UndertowHelper.getCharsetFromContentType(contentType);
         if (charset != null) {
             exchange.setProperty(ExchangePropertyKey.CHARSET_NAME, charset);
             in.setHeader(UndertowConstants.HTTP_CHARACTER_ENCODING, charset);
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 81035bbe7175..914e3ccd8e74 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
@@ -22,7 +22,6 @@ 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;
@@ -188,16 +187,38 @@ public final class UndertowHelper {
      *                     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)) {
+        String name = getCharsetFromContentType(contentType);
+        if (name == null) {
             return null;
         }
         try {
-            return 
ByteBuffer.wrap(body.getBytes(Charset.forName(IOHelper.normalizeCharset(name))));
+            return ByteBuffer.wrap(body.getBytes(Charset.forName(name)));
         } catch (IllegalArgumentException e) {
             // unknown or unsupported charset in the Content-Type
             return null;
         }
     }
 
+    /**
+     * Gets the charset parameter of a Content-Type. The parameter name is 
case-insensitive (RFC 9110), as in the other
+     * HTTP components. Unlike {@link 
IOHelper#getCharsetNameFromContentType(String)}, there is no default: a
+     * Content-Type without a charset gives <tt>null</tt>.
+     *
+     * @param  contentType the Content-Type, may be <tt>null</tt>
+     * @return             the charset name, or <tt>null</tt> when the 
Content-Type declares no charset
+     */
+    public static String getCharsetFromContentType(String contentType) {
+        if (contentType == null) {
+            return null;
+        }
+        for (String parameter : contentType.split(";")) {
+            parameter = parameter.trim();
+            if (parameter.regionMatches(true, 0, "charset=", 0, 8)) {
+                String name = 
IOHelper.normalizeCharset(parameter.substring(8));
+                return ObjectHelper.isEmpty(name) ? null : name;
+            }
+        }
+        return null;
+    }
+
 }
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
index 0700b891588b..f4470cdacc4e 100644
--- 
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
@@ -22,6 +22,7 @@ import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 
 import org.apache.camel.Exchange;
+import org.apache.camel.ExchangePropertyKey;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.mock.MockEndpoint;
 import org.junit.jupiter.api.Test;
@@ -30,6 +31,7 @@ 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;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 /**
  * A String body is written in the charset that the Content-Type declares, 
otherwise as UTF-8.
@@ -38,6 +40,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";
+    // parameter names are case-insensitive (RFC 9110)
+    private static final String LATIN1_UPPER_CASE = "text/plain; 
Charset=ISO-8859-1";
 
     @Test
     public void testResponseUsesCharsetOfContentType() throws Exception {
@@ -85,6 +89,52 @@ public class UndertowStringBodyCharsetTest extends 
BaseUndertowTest {
         assertArrayEquals(TEXT.getBytes(ISO_8859_1), 
mock.getExchanges().get(0).getIn().getBody(byte[].class));
     }
 
+    @Test
+    public void testResponseUsesCharsetParameterInAnyCase() throws Exception {
+        HttpResponse<byte[]> response = 
send(HttpRequest.newBuilder(uri("latin1UpperCase")).GET().build());
+
+        assertEquals(LATIN1_UPPER_CASE, 
response.headers().firstValue("Content-Type").orElse(null));
+        assertArrayEquals(TEXT.getBytes(ISO_8859_1), response.body());
+    }
+
+    @Test
+    public void testRequestUsesCharsetParameterInAnyCase() throws Exception {
+        HttpResponse<byte[]> response = 
send(HttpRequest.newBuilder(uri("decode"))
+                .header("Content-Type", LATIN1_UPPER_CASE)
+                
.POST(HttpRequest.BodyPublishers.ofByteArray(TEXT.getBytes(ISO_8859_1))).build());
+
+        assertArrayEquals(TEXT.getBytes(UTF_8), response.body());
+    }
+
+    @Test
+    public void testProducerUsesCharsetParameterInAnyCase() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:received");
+        mock.expectedMessageCount(1);
+
+        
template.sendBodyAndHeader("undertow:http://localhost:{{port}}/received";, TEXT, 
Exchange.CONTENT_TYPE,
+                LATIN1_UPPER_CASE);
+
+        MockEndpoint.assertIsSatisfied(context);
+        assertArrayEquals(TEXT.getBytes(ISO_8859_1), 
mock.getExchanges().get(0).getIn().getBody(byte[].class));
+    }
+
+    @Test
+    public void testRequestWithoutCharsetDoesNotSetCharset() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:received");
+        mock.expectedMessageCount(1);
+
+        HttpResponse<byte[]> response = 
send(HttpRequest.newBuilder(uri("received"))
+                .header("Content-Type", "text/plain")
+                
.POST(HttpRequest.BodyPublishers.ofByteArray(TEXT.getBytes(UTF_8))).build());
+
+        // only a charset that the request declares is set, otherwise Camel 
uses its default
+        assertEquals(200, response.statusCode());
+        MockEndpoint.assertIsSatisfied(context);
+        Exchange received = mock.getExchanges().get(0);
+        assertNull(received.getProperty(ExchangePropertyKey.CHARSET_NAME));
+        
assertNull(received.getIn().getHeader(UndertowConstants.HTTP_CHARACTER_ENCODING));
+    }
+
     private URI uri(String path) {
         return URI.create("http://localhost:"; + getPort() + "/" + path);
     }
@@ -102,6 +152,15 @@ public class UndertowStringBodyCharsetTest extends 
BaseUndertowTest {
                         .setHeader(Exchange.CONTENT_TYPE, constant(LATIN1))
                         .setBody(constant(TEXT));
 
+                from("undertow:http://localhost:{{port}}/latin1UpperCase";)
+                        .setHeader(Exchange.CONTENT_TYPE, 
constant(LATIN1_UPPER_CASE))
+                        .setBody(constant(TEXT));
+
+                // decodes the request in its charset and answers in UTF-8
+                from("undertow:http://localhost:{{port}}/decode";)
+                        .convertBodyTo(String.class)
+                        .setHeader(Exchange.CONTENT_TYPE, 
constant("text/plain; charset=UTF-8"));
+
                 from("undertow:http://localhost:{{port}}/echo";)
                         .convertBodyTo(String.class);
 
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 b0d4eeb66d52..4f6537cc7a39 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
@@ -764,6 +764,13 @@ written in the charset that the `Content-Type` declares. 
Bodies that are not a `
 read such a message as UTF-8 must now use the declared charset, and characters 
that the declared charset cannot
 represent are written as `?`.
 
+==== The charset parameter of the Content-Type in any case
+
+The `charset` parameter of a `Content-Type` was only recognized in lower case. 
A request with
+`Content-Type: text/plain; Charset=ISO-8859-1` did not set `CamelCharsetName` 
(so its body was read as UTF-8), and a
+`String` body with such a `Content-Type` was written as UTF-8. The parameter 
name is now matched case-insensitively
+(RFC 9110), as in the other HTTP components, on the consumer (request and 
response) and on the producer.
+
 === camel-snmp - GET_NEXT (walk)
 
 A `GET_NEXT` walk now ends at the end of the agent's MIB view (`endOfMibView`, 
or `noSuchName` for SNMPv1) and at

Reply via email to