You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/08/22 05:15:51 UTC

[camel] branch main updated: CAMEL-19768: Ensure content is encoded with expected charset (#11160)

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 dc1ca4e5580 CAMEL-19768: Ensure content is encoded with expected charset (#11160)
dc1ca4e5580 is described below

commit dc1ca4e55806faa5b17c91f4dd33d47c2b8ee007
Author: Michael Pätzold <pa...@gmail.com>
AuthorDate: Tue Aug 22 07:15:44 2023 +0200

    CAMEL-19768: Ensure content is encoded with expected charset (#11160)
    
    * CAMEL-19768: Ensure content type contains charset information to encode message body accordingly
    
    * CAMEL-19768: Replace special chars with their escaped unicode representation
    
    ---------
    
    Co-authored-by: Michael Pätzold <mi...@aoe.com>
---
 .../apache/camel/component/http/HttpProducer.java  |  14 ++-
 .../HttpProducerContentTypeWithCharsetTest.java    |  98 +++++++++++++++++++
 .../http/HttpProducerWithSpecialCharsBodyTest.java | 104 +++++++++++++++++++++
 3 files changed, 208 insertions(+), 8 deletions(-)

diff --git a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
index ba92e6e8c63..da9c7e0d034 100644
--- a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
+++ b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
@@ -772,15 +772,13 @@ public class HttpProducer extends DefaultProducer {
                                 charset = cs.name();
                             }
                         }
-                        final StringEntity entity;
-                        if (contentType != null) {
-                            entity = new StringEntity(content, contentType);
-                        } else if (charset != null) {
-                            entity = new StringEntity(content, null, charset, false);
-                        } else {
-                            entity = new StringEntity(content, null, null, false);
+
+                        // sync contentType.charset and charset
+                        if (contentType != null && contentType.getCharset() == null && charset != null) {
+                            contentType = ContentType.parse(contentType + ";charset=" + charset);
                         }
-                        answer = entity;
+
+                        answer = new StringEntity(content, contentType, charset, false);
                     }
 
                     // fallback as input stream
diff --git a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerContentTypeWithCharsetTest.java b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerContentTypeWithCharsetTest.java
new file mode 100644
index 00000000000..eef598225ef
--- /dev/null
+++ b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerContentTypeWithCharsetTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.http;
+
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.Exchange;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
+import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class HttpProducerContentTypeWithCharsetTest extends BaseHttpTest {
+
+    private static final String CONTENT_TYPE_WITHOUT_CHARSET
+            = "multipart/form-data;boundary=---------------------------j2radvtrk";
+    private static final String CONTENT_TYPE_WITH_CHARSET = CONTENT_TYPE_WITHOUT_CHARSET + ";charset=utf-8";
+
+    private HttpServer localServer;
+
+    private String endpointUrl;
+
+    @BeforeEach
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        localServer = ServerBootstrap.bootstrap().setHttpProcessor(getBasicHttpProcessor())
+                .setConnectionReuseStrategy(getConnectionReuseStrategy()).setResponseFactory(getHttpResponseFactory())
+                .setSslContext(getSSLContext())
+                .register("/content", (request, response, context) -> {
+                    String contentType = request.getFirstHeader(Exchange.CONTENT_TYPE).getValue();
+
+                    assertEquals(CONTENT_TYPE_WITH_CHARSET.replace(";", "; "), contentType);
+
+                    response.setEntity(new StringEntity(contentType, StandardCharsets.US_ASCII));
+                    response.setCode(HttpStatus.SC_OK);
+                }).create();
+        localServer.start();
+
+        endpointUrl = "http://localhost:" + localServer.getLocalPort();
+    }
+
+    @AfterEach
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        if (localServer != null) {
+            localServer.stop();
+        }
+    }
+
+    @Test
+    void testContentTypeWithCharsetInCharsetHeader() throws Exception {
+        Exchange out = template.request(endpointUrl + "/content", exchange -> {
+            exchange.getIn().setHeader(Exchange.CONTENT_TYPE, CONTENT_TYPE_WITHOUT_CHARSET);
+            exchange.getIn().setHeader(Exchange.CHARSET_NAME, "utf-8");
+            exchange.getIn().setBody("This is content");
+        });
+
+        assertNotNull(out);
+        assertFalse(out.isFailed(), "Should not fail");
+        assertEquals(CONTENT_TYPE_WITH_CHARSET.replace(";", "; "), out.getMessage().getBody(String.class));
+    }
+
+    @Test
+    void testContentTypeWithCharsetInContentTypeHeader() throws Exception {
+        Exchange out = template.request(endpointUrl + "/content", exchange -> {
+            exchange.getIn().setHeader(Exchange.CONTENT_TYPE, CONTENT_TYPE_WITH_CHARSET);
+            exchange.getIn().setBody("This is content");
+        });
+
+        assertNotNull(out);
+        assertFalse(out.isFailed(), "Should not fail");
+        assertEquals(CONTENT_TYPE_WITH_CHARSET.replace(";", "; "), out.getMessage().getBody(String.class));
+    }
+
+}
diff --git a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerWithSpecialCharsBodyTest.java b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerWithSpecialCharsBodyTest.java
new file mode 100644
index 00000000000..b651f7ff1a0
--- /dev/null
+++ b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerWithSpecialCharsBodyTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.http;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.CamelExchangeException;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.camel.Exchange.CHARSET_NAME;
+import static org.apache.hc.core5.http.ContentType.APPLICATION_JSON;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class HttpProducerWithSpecialCharsBodyTest {
+
+    private static final String TEST_MESSAGE_WITH_SPECIAL_CHARACTERS
+            = """
+                    {
+                        "description": "Example with special characters",
+                        "BasicLatin": "\u0021 \u0023 \u0024 \u0025 \u0026 \u0027 \u0028 \u0029 \u002A \u002B \u002C \u002D \u002E \u002F \u003A \u003B \u003C \u003D \u003E \u003F",
+                        "Latin1": "\u00A1 \u00BF \u00A9 \u00AE \u00F1 \u00F6 \u00FC",
+                        "Greek": "\u0391 \u03B2 \u03B3 \u0394 \u03A9",
+                        "Cyrillic": "\u0410 \u0431 \u0432 \u0413 \u0434",
+                        "Hebrew": "\u05D0 \u05D1 \u05D2 \u05D3 \u05D4",
+                        "Arabic": "\u0627 \u0628 \u062A \u062B \u062C",
+                        "Devanagari": "\u0905 \u0906 \u0907 \u0908 \u0909",
+                        "CJK Unified Ideographs" : "\u4E00 \u4E8C \u4E09 \u56DB \u4E94",
+                        "Emoticons": "\uD83D\uDE00 \uD83D\uDE0E \uD83D\uDE0A \uD83C\uDF0D"
+                    }
+                    """;
+
+    private static final String APPLICATION_JSON_UTF8
+            = APPLICATION_JSON.getMimeType() + "; charset=" + StandardCharsets.UTF_8.name();
+
+    @Test
+    void createRequestEntityJsonUtf8ThroughContentType() throws CamelExchangeException, IOException {
+        HttpEndpoint httpEndpoint = mock(HttpEndpoint.class);
+        HttpProducer httpProducer = new HttpProducer(httpEndpoint);
+
+        Message message = mock(Message.class);
+        when(message.getBody()).thenReturn(TEST_MESSAGE_WITH_SPECIAL_CHARACTERS);
+        when(message.getHeader(Exchange.CONTENT_TYPE, String.class)).thenReturn(APPLICATION_JSON_UTF8);
+
+        Exchange exchange = mock(Exchange.class);
+        when(exchange.getIn()).thenReturn(message);
+
+        HttpEntity requestEntity = httpProducer.createRequestEntity(exchange);
+
+        assertTrue(requestEntity instanceof StringEntity);
+        StringEntity entity = (StringEntity) requestEntity;
+        assertEquals(APPLICATION_JSON_UTF8, entity.getContentType(), "Content type should be given content type and charset");
+        assertEquals(StandardCharsets.UTF_8.name(), entity.getContentEncoding(), "Content encoding should be given charset");
+        assertEquals(TEST_MESSAGE_WITH_SPECIAL_CHARACTERS,
+                new String(entity.getContent().readAllBytes(), StandardCharsets.UTF_8),
+                "Reading entity content with intended charset should result in the original (readable) message");
+    }
+
+    @Test
+    void createRequestEntityJsonUtf8ThroughCharset() throws CamelExchangeException, IOException {
+        HttpEndpoint httpEndpoint = mock(HttpEndpoint.class);
+        HttpProducer httpProducer = new HttpProducer(httpEndpoint);
+
+        Message message = mock(Message.class);
+        when(message.getBody()).thenReturn(TEST_MESSAGE_WITH_SPECIAL_CHARACTERS);
+        when(message.getHeader(Exchange.CONTENT_TYPE, String.class)).thenReturn(APPLICATION_JSON.getMimeType());
+        when(message.getHeader(CHARSET_NAME, String.class)).thenReturn(StandardCharsets.UTF_8.name());
+
+        Exchange exchange = mock(Exchange.class);
+        when(exchange.getIn()).thenReturn(message);
+
+        HttpEntity requestEntity = httpProducer.createRequestEntity(exchange);
+
+        assertTrue(requestEntity instanceof StringEntity);
+        StringEntity entity = (StringEntity) requestEntity;
+        assertEquals(APPLICATION_JSON_UTF8, entity.getContentType(), "Content type should be given content type and charset");
+        assertEquals(StandardCharsets.UTF_8.name(), entity.getContentEncoding(), "Content encoding should be given charset");
+        assertEquals(TEST_MESSAGE_WITH_SPECIAL_CHARACTERS,
+                new String(entity.getContent().readAllBytes(), StandardCharsets.UTF_8),
+                "Reading entity content with intended charset should result in the original (readable) message");
+    }
+
+}