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 2021/03/26 10:55:23 UTC

[camel] branch master updated: CAMEL-15410: camel-core - URI encoding for query parameters should use %20 for space instead of plus sign.

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

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


The following commit(s) were added to refs/heads/master by this push:
     new f2fc19f  CAMEL-15410: camel-core - URI encoding for query parameters should use %20 for space instead of plus sign.
f2fc19f is described below

commit f2fc19f1c8fc249ed2757e187deb9fe10c2ad8b9
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Fri Mar 26 11:30:01 2021 +0100

    CAMEL-15410: camel-core - URI encoding for query parameters should use %20 for space instead of plus sign.
---
 .../apache/camel/component/cron/CronComponent.java | 12 ++++++--
 .../camel/component/exec/ExecEndpointTest.java     |  6 ++--
 .../apache/camel/component/http/HttpQueryTest.java |  2 +-
 .../http/HttpSendDynamicAwareRawTest.java          |  2 +-
 .../camel/component/irc/IrcConfigurationTest.java  |  4 +--
 .../jetty/JettyHttpBridgeEncodedPathTest.java      |  5 ++--
 .../JettyHttpGetWithParamAsExchangeHeaderTest.java |  8 +++---
 .../component/kamelet/KameletPropertiesTest.java   |  5 +++-
 .../component/master/EndpointUriEncodingTest.java  |  2 +-
 .../netty/http/NettyHttpBridgeEncodedPathTest.java |  3 +-
 .../netty/http/NettyHttpCompressTest.java          |  4 +--
 .../NettyHttpGetWithParamAsExchangeHeaderTest.java | 12 ++++----
 .../QuartzScheduledPollConsumerScheduler.java      |  2 ++
 .../SpringScheduledPollConsumerScheduler.java      |  2 ++
 .../producer/RestSwaggerGetUriParamTest.java       |  2 +-
 .../org/apache/camel/catalog/impl/URISupport.java  | 14 +++++++--
 .../catalog/CustomEndpointUriFactoryTest.java      | 25 ++++++++++++++--
 .../camel/component/file/FileURLDecodingTest.java  |  4 +--
 .../issues/EndpointWithRawUriParameterTest.java    |  9 +++---
 .../camel/util/UnsafeCharactersEncoderTest.java    | 11 ++++++++
 .../java/org/apache/camel/util/URIScanner.java     |  3 ++
 .../java/org/apache/camel/util/URISupport.java     | 12 ++++++--
 .../java/org/apache/camel/util/URISupportTest.java | 33 +++++++++++++++-------
 .../ROOT/pages/camel-3x-upgrade-guide-3_10.adoc    | 19 +++++++++++++
 24 files changed, 152 insertions(+), 49 deletions(-)

diff --git a/components/camel-cron/src/main/java/org/apache/camel/component/cron/CronComponent.java b/components/camel-cron/src/main/java/org/apache/camel/component/cron/CronComponent.java
index ef0160f..8d66b97 100644
--- a/components/camel-cron/src/main/java/org/apache/camel/component/cron/CronComponent.java
+++ b/components/camel-cron/src/main/java/org/apache/camel/component/cron/CronComponent.java
@@ -43,12 +43,20 @@ public class CronComponent extends DefaultComponent {
     }
 
     @Override
-    public Endpoint createEndpoint(String uri, String remaining, Map<String, Object> properties) throws Exception {
+    public Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
         CamelCronConfiguration configuration = new CamelCronConfiguration();
         configuration.setName(remaining);
 
+        // special for schedule where we replace + as space
+        String schedule = getAndRemoveParameter(parameters, "schedule", String.class);
+        if (schedule != null) {
+            // replace + as space
+            schedule = schedule.replace('+', ' ');
+        }
+        configuration.setSchedule(schedule);
+
         CronEndpoint answer = new CronEndpoint(uri, this, configuration);
-        setProperties(answer, properties);
+        setProperties(answer, parameters);
 
         // validate configuration
         validate(configuration);
diff --git a/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecEndpointTest.java b/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecEndpointTest.java
index f08bd1d..a34e801 100644
--- a/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecEndpointTest.java
+++ b/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecEndpointTest.java
@@ -108,9 +108,11 @@ public class ExecEndpointTest {
     @DirtiesContext
     public void testCreateEndpointWithArgs() throws Exception {
         String args = "arg1 arg2 arg3";
-        // Need to properly encode the URI
-        ExecEndpoint e = createExecEndpoint("exec:test?args=" + args.replaceAll(" ", "+"));
+        // can use space or %20
+        ExecEndpoint e = createExecEndpoint("exec:test?args=" + args.replaceAll(" ", "%20"));
         assertEquals(args, e.getArgs());
+        ExecEndpoint e2 = createExecEndpoint("exec:test?args=" + args);
+        assertEquals(args, e2.getArgs());
     }
 
     @Test
diff --git a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpQueryTest.java b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpQueryTest.java
index f466870..4bad857 100644
--- a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpQueryTest.java
+++ b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpQueryTest.java
@@ -41,7 +41,7 @@ public class HttpQueryTest extends BaseHttpTest {
                 .setConnectionReuseStrategy(getConnectionReuseStrategy()).setResponseFactory(getHttpResponseFactory())
                 .setExpectationVerifier(getHttpExpectationVerifier()).setSslContext(getSSLContext())
                 .registerHandler("/", new BasicValidationHandler(GET.name(), "hl=en&q=camel", null, getExpectedContent()))
-                .registerHandler("/test/", new BasicValidationHandler(GET.name(), "my=@+camel", null, getExpectedContent()))
+                .registerHandler("/test/", new BasicValidationHandler(GET.name(), "my=@ camel", null, getExpectedContent()))
                 .registerHandler("/user/pass",
                         new BasicValidationHandler(GET.name(), "password=baa&username=foo", null, getExpectedContent()))
                 .create();
diff --git a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpSendDynamicAwareRawTest.java b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpSendDynamicAwareRawTest.java
index 34a544e..c4ed69f 100644
--- a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpSendDynamicAwareRawTest.java
+++ b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpSendDynamicAwareRawTest.java
@@ -69,7 +69,7 @@ public class HttpSendDynamicAwareRawTest extends BaseHttpTest {
             public void configure() throws Exception {
                 from("direct:moes")
                         .toD("http://localhost:" + localServer.getLocalPort()
-                             + "/moes?throwExceptionOnFailure=false&drink=${header.drink}&password=se+%ret");
+                             + "/moes?throwExceptionOnFailure=false&drink=${header.drink}&password=RAW(se+%ret)");
 
                 from("direct:joes")
                         .toD("http://localhost:" + localServer.getLocalPort()
diff --git a/components/camel-irc/src/test/java/org/apache/camel/component/irc/IrcConfigurationTest.java b/components/camel-irc/src/test/java/org/apache/camel/component/irc/IrcConfigurationTest.java
index 168f5ca..d8955b2 100644
--- a/components/camel-irc/src/test/java/org/apache/camel/component/irc/IrcConfigurationTest.java
+++ b/components/camel-irc/src/test/java/org/apache/camel/component/irc/IrcConfigurationTest.java
@@ -81,7 +81,7 @@ public class IrcConfigurationTest extends CamelTestSupport {
 
         // irc:nick@host[:port]/#room[?options]
         IrcEndpoint endpoint = (IrcEndpoint) component.createEndpoint(
-                "irc://badnick@irc.freenode.net?keys=foo,&channels=#camel,#smx&realname=Camel+Bot&nickname=camelbot");
+                "irc://badnick@irc.freenode.net?keys=foo,&channels=#camel,#smx&realname=Camel Bot&nickname=camelbot");
 
         IrcConfiguration conf = endpoint.getConfiguration();
         assertEquals("camelbot", conf.getNickname());
@@ -99,7 +99,7 @@ public class IrcConfigurationTest extends CamelTestSupport {
 
         // irc:nick@host[:port]/#room[?options]
         IrcEndpoint endpoint = (IrcEndpoint) component.createEndpoint(
-                "irc://badnick@irc.freenode.net?keys=foo,bar&channels=#camel,#smx&realname=Camel+Bot&nickname=camelbot");
+                "irc://badnick@irc.freenode.net?keys=foo,bar&channels=#camel,#smx&realname=Camel%20Bot&nickname=camelbot");
 
         IrcConfiguration conf = endpoint.getConfiguration();
         assertEquals("camelbot", conf.getNickname());
diff --git a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/JettyHttpBridgeEncodedPathTest.java b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/JettyHttpBridgeEncodedPathTest.java
index 4ef4cba..2c638ed 100644
--- a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/JettyHttpBridgeEncodedPathTest.java
+++ b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/JettyHttpBridgeEncodedPathTest.java
@@ -30,7 +30,7 @@ public class JettyHttpBridgeEncodedPathTest extends BaseJettyTest {
     public void testJettyHttpClient() throws Exception {
         String response = template.requestBody("http://localhost:" + port2 + "/jettyTestRouteA?param1=%2B447777111222", null,
                 String.class);
-        assertEquals("param1=+447777111222", response, "Get a wrong response");
+        assertEquals("param1=%2B447777111222", response, "Get a wrong response");
     }
 
     @Override
@@ -44,7 +44,8 @@ public class JettyHttpBridgeEncodedPathTest extends BaseJettyTest {
                         // %2B becomes decoded to a space
                         Object s = exchange.getIn().getHeader("param1");
                         // can be either + or %2B
-                        assertTrue(s.equals(" 447777111222") || s.equals("+447777111222") || s.equals("%2B447777111222"));
+                        assertTrue(s.equals(" 447777111222") || s.equals("%20447777111222") || s.equals("+447777111222")
+                                || s.equals("%2B447777111222"));
 
                         // send back the query
                         exchange.getMessage().setBody(exchange.getIn().getHeader(Exchange.HTTP_QUERY));
diff --git a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/JettyHttpGetWithParamAsExchangeHeaderTest.java b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/JettyHttpGetWithParamAsExchangeHeaderTest.java
index 5a4f8c7..98e2ca3 100644
--- a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/JettyHttpGetWithParamAsExchangeHeaderTest.java
+++ b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/JettyHttpGetWithParamAsExchangeHeaderTest.java
@@ -68,27 +68,27 @@ public class JettyHttpGetWithParamAsExchangeHeaderTest extends BaseJettyTest {
     }
 
     @Test
-    public void testHttpGetWithSpaceInParams() throws Exception {
+    public void testHttpGetWithSpaceEncodedInParams() throws Exception {
         MockEndpoint mock = getMockEndpoint("mock:result");
         mock.expectedMessageCount(1);
         mock.expectedHeaderReceived("message", " World");
         mock.expectedHeaderReceived(Exchange.HTTP_METHOD, "GET");
 
         // parameter starts with a space using %2B as decimal encoded
-        template.requestBody(serverUri + "?message=%2BWorld", null, Object.class);
+        template.requestBody(serverUri + "?message=%20World", null, Object.class);
 
         assertMockEndpointsSatisfied();
     }
 
     @Test
-    public void testHttpGetWithSpaceAsPlusInParams() throws Exception {
+    public void testHttpGetWithSpaceInParams() throws Exception {
         MockEndpoint mock = getMockEndpoint("mock:result");
         mock.expectedMessageCount(1);
         mock.expectedHeaderReceived("message", " World");
         mock.expectedHeaderReceived(Exchange.HTTP_METHOD, "GET");
 
         // parameter starts with a space using + decoded
-        template.requestBody(serverUri + "?message=+World", null, Object.class);
+        template.requestBody(serverUri + "?message= World", null, Object.class);
 
         assertMockEndpointsSatisfied();
     }
diff --git a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletPropertiesTest.java b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletPropertiesTest.java
index 94e5f09..3e1ec6b 100644
--- a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletPropertiesTest.java
+++ b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletPropertiesTest.java
@@ -111,9 +111,12 @@ public class KameletPropertiesTest extends CamelTestSupport {
 
     @Test
     public void urlEncodingIsRespected() {
-        assertThat(context.getEndpoint("kamelet:timer-source?message=Hello+Kamelets&period=1000", KameletEndpoint.class)
+        assertThat(context.getEndpoint("kamelet:timer-source?message=Hello Kamelets&period=1000", KameletEndpoint.class)
                 .getKameletProperties())
                         .containsEntry("message", "Hello Kamelets");
+        assertThat(context.getEndpoint("kamelet:timer-source?message=Hi%20Kamelets&period=1000", KameletEndpoint.class)
+                .getKameletProperties())
+                        .containsEntry("message", "Hi Kamelets");
         assertThat(context
                 .getEndpoint("kamelet:timer-source?message=messaging.knative.dev%2Fv1beta1&period=1000", KameletEndpoint.class)
                 .getKameletProperties())
diff --git a/components/camel-master/src/test/java/org/apache/camel/component/master/EndpointUriEncodingTest.java b/components/camel-master/src/test/java/org/apache/camel/component/master/EndpointUriEncodingTest.java
index 9ec78a6..66b5372 100644
--- a/components/camel-master/src/test/java/org/apache/camel/component/master/EndpointUriEncodingTest.java
+++ b/components/camel-master/src/test/java/org/apache/camel/component/master/EndpointUriEncodingTest.java
@@ -49,7 +49,7 @@ public class EndpointUriEncodingTest extends CamelTestSupport {
         return new RouteBuilder() {
             public void configure() {
                 context.addComponent("dummy", new DummyComponent());
-                from("master:test:dummy://path?foo=hello}+world&bar=RAW(hello}+world)")
+                from("master:test:dummy://path?foo=hello} world&bar=RAW(hello}+world)")
                         .to("mock:result");
             }
         };
diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBridgeEncodedPathTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBridgeEncodedPathTest.java
index e26bb7f..7f825b3 100644
--- a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBridgeEncodedPathTest.java
+++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBridgeEncodedPathTest.java
@@ -80,7 +80,8 @@ public class NettyHttpBridgeEncodedPathTest extends BaseNettyTest {
                     // %2B becomes decoded to a space
                     Object s = exchange.getIn().getHeader("param1");
                     // can be either + or %2B
-                    assertTrue(s.equals(" 447777111222") || s.equals("+447777111222") || s.equals("%2B447777111222"));
+                    assertTrue(s.equals(" 447777111222") || s.equals("%20447777111222") || s.equals("+447777111222")
+                            || s.equals("%2B447777111222"));
 
                     // send back the query
                     exchange.getMessage().setBody(exchange.getIn().getHeader(Exchange.HTTP_QUERY));
diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpCompressTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpCompressTest.java
index fa70fa5..3f64007 100644
--- a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpCompressTest.java
+++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpCompressTest.java
@@ -27,9 +27,11 @@ import io.netty.handler.codec.http.HttpContentDecompressor;
 import org.apache.camel.BindToRegistry;
 import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Isolated;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+@Isolated
 public class NettyHttpCompressTest extends BaseNettyTest {
 
     // setup the decompress decoder here
@@ -42,7 +44,6 @@ public class NettyHttpCompressTest extends BaseNettyTest {
 
     @Test
     public void testContentType() throws Exception {
-
         byte[] data = "Hello World".getBytes(Charset.forName("UTF-8"));
         Map<String, Object> headers = new HashMap<>();
         headers.put("content-type", "text/plain; charset=\"UTF-8\"");
@@ -51,7 +52,6 @@ public class NettyHttpCompressTest extends BaseNettyTest {
                 headers, String.class);
         // The decoded out has some space to clean up.
         assertEquals("Bye World", out.trim());
-
     }
 
     @Override
diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpGetWithParamAsExchangeHeaderTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpGetWithParamAsExchangeHeaderTest.java
index 16b4ac9..ce1b063 100644
--- a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpGetWithParamAsExchangeHeaderTest.java
+++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpGetWithParamAsExchangeHeaderTest.java
@@ -65,27 +65,27 @@ public class NettyHttpGetWithParamAsExchangeHeaderTest extends BaseNettyTest {
     }
 
     @Test
-    public void testHttpGetWithSpaceInParams() throws Exception {
+    public void testHttpGetWithSpaceEncodedInParams() throws Exception {
         MockEndpoint mock = getMockEndpoint("mock:result");
         mock.expectedMessageCount(1);
         mock.expectedHeaderReceived("message", " World");
         mock.expectedHeaderReceived(Exchange.HTTP_METHOD, "GET");
 
-        // parameter starts with a space using %2B as decimal encoded
-        template.requestBody(serverUri + "&message=%2BWorld", null, Object.class);
+        // parameter starts with a space using %20 as decimal encoded
+        template.requestBody(serverUri + "&message=%20World", null, Object.class);
 
         assertMockEndpointsSatisfied();
     }
 
     @Test
-    public void testHttpGetWithSpaceAsPlusInParams() throws Exception {
+    public void testHttpGetWithSpaceInParams() throws Exception {
         MockEndpoint mock = getMockEndpoint("mock:result");
         mock.expectedMessageCount(1);
         mock.expectedHeaderReceived("message", " World");
         mock.expectedHeaderReceived(Exchange.HTTP_METHOD, "GET");
 
-        // parameter starts with a space using + decoded
-        template.requestBody(serverUri + "&message=+World", null, Object.class);
+        // parameter starts with a space
+        template.requestBody(serverUri + "&message= World", null, Object.class);
 
         assertMockEndpointsSatisfied();
     }
diff --git a/components/camel-quartz/src/main/java/org/apache/camel/pollconsumer/quartz/QuartzScheduledPollConsumerScheduler.java b/components/camel-quartz/src/main/java/org/apache/camel/pollconsumer/quartz/QuartzScheduledPollConsumerScheduler.java
index 5ad07bc..7276a93 100644
--- a/components/camel-quartz/src/main/java/org/apache/camel/pollconsumer/quartz/QuartzScheduledPollConsumerScheduler.java
+++ b/components/camel-quartz/src/main/java/org/apache/camel/pollconsumer/quartz/QuartzScheduledPollConsumerScheduler.java
@@ -168,6 +168,8 @@ public class QuartzScheduledPollConsumerScheduler extends ServiceSupport
     @Override
     protected void doStart() throws Exception {
         StringHelper.notEmpty(cron, "cron", this);
+        // special for cron where we replace + as space
+        cron = cron.replace('+', ' ');
 
         if (quartzScheduler == null) {
             // get the scheduler form the quartz component
diff --git a/components/camel-spring/src/main/java/org/apache/camel/spring/pollingconsumer/SpringScheduledPollConsumerScheduler.java b/components/camel-spring/src/main/java/org/apache/camel/spring/pollingconsumer/SpringScheduledPollConsumerScheduler.java
index 454273b..a20239f 100644
--- a/components/camel-spring/src/main/java/org/apache/camel/spring/pollingconsumer/SpringScheduledPollConsumerScheduler.java
+++ b/components/camel-spring/src/main/java/org/apache/camel/spring/pollingconsumer/SpringScheduledPollConsumerScheduler.java
@@ -114,6 +114,8 @@ public class SpringScheduledPollConsumerScheduler extends ServiceSupport
     @Override
     protected void doStart() throws Exception {
         StringHelper.notEmpty(cron, "cron", this);
+        // special for cron where we replace + as space
+        cron = cron.replace('+', ' ');
 
         trigger = new CronTrigger(getCron(), getTimeZone());
 
diff --git a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/producer/RestSwaggerGetUriParamTest.java b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/producer/RestSwaggerGetUriParamTest.java
index beed34c..1855e47 100644
--- a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/producer/RestSwaggerGetUriParamTest.java
+++ b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/producer/RestSwaggerGetUriParamTest.java
@@ -29,7 +29,7 @@ public class RestSwaggerGetUriParamTest extends CamelTestSupport {
 
     @Test
     public void testSwaggerGet() throws Exception {
-        getMockEndpoint("mock:result").expectedBodiesReceived("Bye Donald+Duck");
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye Donald%20Duck");
 
         template.sendBodyAndHeader("direct:start", null, "name", "Donald Duck");
 
diff --git a/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/URISupport.java b/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/URISupport.java
index 83599a1..dbc22ba 100644
--- a/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/URISupport.java
+++ b/core/camel-core-catalog/src/main/java/org/apache/camel/catalog/impl/URISupport.java
@@ -28,6 +28,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.function.BiConsumer;
 
+import org.apache.camel.util.StringHelper;
+
 /**
  * Copied from org.apache.camel.util.URISupport
  */
@@ -425,7 +427,11 @@ public final class URISupport {
     private static void appendQueryStringParameter(String key, String value, StringBuilder rc, boolean encode)
             throws UnsupportedEncodingException {
         if (encode) {
-            rc.append(URLEncoder.encode(key, CHARSET));
+            // the URLEncoder is for HTML forms, so it encodes spaces as + sign
+            // but we want this to be decimal encoded for URI query parameter
+            String encoded = URLEncoder.encode(key, CHARSET);
+            encoded = StringHelper.replaceAll(encoded, "+", "%20");
+            rc.append(encoded);
         } else {
             rc.append(key);
         }
@@ -440,7 +446,11 @@ public final class URISupport {
         });
         if (!isRaw) {
             if (encode) {
-                rc.append(URLEncoder.encode(value, CHARSET));
+                // the URLEncoder is for HTML forms, so it encodes spaces as + sign
+                // but we want this to be decimal encoded for URI query parameter
+                String encoded = URLEncoder.encode(value, CHARSET);
+                encoded = StringHelper.replaceAll(encoded, "+", "%20");
+                rc.append(encoded);
             } else {
                 rc.append(value);
             }
diff --git a/core/camel-core/src/test/java/org/apache/camel/catalog/CustomEndpointUriFactoryTest.java b/core/camel-core/src/test/java/org/apache/camel/catalog/CustomEndpointUriFactoryTest.java
index 97bb0c7..2d634ae 100644
--- a/core/camel-core/src/test/java/org/apache/camel/catalog/CustomEndpointUriFactoryTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/catalog/CustomEndpointUriFactoryTest.java
@@ -225,10 +225,10 @@ public class CustomEndpointUriFactoryTest extends ContextTestSupport {
         params.put("cql", "insert into test_data(id, text) values (now(), ?)");
 
         Assertions.assertEquals(
-                "cql:localhost/test?cql=insert+into+test_data%28id%2C+text%29+values+%28now%28%29%2C+%3F%29",
+                "cql:localhost/test?cql=insert%20into%20test_data%28id%2C%20text%29%20values%20%28now%28%29%2C%20%3F%29",
                 assembler.buildUri("cql", new LinkedHashMap<>(params)));
         Assertions.assertEquals(
-                "cql:localhost/test?cql=insert+into+test_data%28id%2C+text%29+values+%28now%28%29%2C+%3F%29",
+                "cql:localhost/test?cql=insert%20into%20test_data%28id%2C%20text%29%20values%20%28now%28%29%2C%20%3F%29",
                 assembler.buildUri("cql", new LinkedHashMap<>(params), true));
         Assertions.assertEquals(
                 "cql:localhost/test?cql=insert into test_data(id, text) values (now(), ?)",
@@ -236,6 +236,27 @@ public class CustomEndpointUriFactoryTest extends ContextTestSupport {
     }
 
     @Test
+    public void testCQLWithPlus() throws Exception {
+        EndpointUriFactory assembler = new MyCQLAssembler();
+        assembler.setCamelContext(context);
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("host", "localhost");
+        params.put("keyspace", "test");
+        params.put("cql", "add(4 + 5)");
+
+        Assertions.assertEquals(
+                "cql:localhost/test?cql=add%284%20%2B%205%29",
+                assembler.buildUri("cql", new LinkedHashMap<>(params)));
+        Assertions.assertEquals(
+                "cql:localhost/test?cql=add%284%20%2B%205%29",
+                assembler.buildUri("cql", new LinkedHashMap<>(params), true));
+        Assertions.assertEquals(
+                "cql:localhost/test?cql=add(4 + 5)",
+                assembler.buildUri("cql", new LinkedHashMap<>(params), false));
+    }
+
+    @Test
     public void testJmsSecrets() throws Exception {
         EndpointUriFactory assembler = new MyJmsxAssembler();
         assembler.setCamelContext(context);
diff --git a/core/camel-core/src/test/java/org/apache/camel/component/file/FileURLDecodingTest.java b/core/camel-core/src/test/java/org/apache/camel/component/file/FileURLDecodingTest.java
index 3fe23c8..906bd3e 100644
--- a/core/camel-core/src/test/java/org/apache/camel/component/file/FileURLDecodingTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/component/file/FileURLDecodingTest.java
@@ -44,7 +44,7 @@ public class FileURLDecodingTest extends ContextTestSupport {
 
     @Test
     public void testFilePlus() throws Exception {
-        assertTargetFile("data+.txt", "data .txt");
+        assertTargetFile("data .txt", "data .txt");
     }
 
     @Test
@@ -54,7 +54,7 @@ public class FileURLDecodingTest extends ContextTestSupport {
 
     @Test
     public void testFile2B() throws Exception {
-        assertTargetFile("data%2B.txt", "data .txt");
+        assertTargetFile("data .txt", "data .txt");
     }
 
     @Test
diff --git a/core/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java b/core/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java
index ac13fb5..e047546 100644
--- a/core/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java
@@ -145,15 +145,14 @@ public class EndpointWithRawUriParameterTest extends ContextTestSupport {
     }
 
     @Test
-    public void testRawUriParameterFail() throws Exception {
+    public void testRawUriParameterPlusSign() throws Exception {
         getMockEndpoint("mock:result").expectedMessageCount(1);
         getMockEndpoint("mock:result").expectedHeaderReceived("username", "scott");
         getMockEndpoint("mock:result").expectedHeaderReceived("password", "foo)+bar");
 
-        template.sendBody("direct:fail", "Hello World");
+        template.sendBody("direct:plus", "Hello World");
 
-        // should fail as the password has + sign which gets escaped
-        getMockEndpoint("mock:result").assertIsNotSatisfied();
+        getMockEndpoint("mock:result").assertIsSatisfied();
     }
 
     @Test
@@ -191,7 +190,7 @@ public class EndpointWithRawUriParameterTest extends ContextTestSupport {
 
                 from("direct:rawlines").to("mycomponent:foo?lines=RAW(++abc++)&lines=RAW(++def++)").to("mock:result");
 
-                from("direct:fail").to("mycomponent:foo?password=foo)+bar&username=scott").to("mock:result");
+                from("direct:plus").to("mycomponent:foo?password=foo)+bar&username=scott").to("mock:result");
 
                 from("direct:ok").to("mycomponent:foo?password=RAW(foo)+bar)&username=scott").to("mock:result");
 
diff --git a/core/camel-core/src/test/java/org/apache/camel/util/UnsafeCharactersEncoderTest.java b/core/camel-core/src/test/java/org/apache/camel/util/UnsafeCharactersEncoderTest.java
index d229a7f..cc87e6f 100644
--- a/core/camel-core/src/test/java/org/apache/camel/util/UnsafeCharactersEncoderTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/util/UnsafeCharactersEncoderTest.java
@@ -89,6 +89,17 @@ public class UnsafeCharactersEncoderTest {
     }
 
     @Test
+    public void testPlusInQuery() {
+        String beforeEncoding = "http://www.example.com?param1=%2B447777111222";
+        String afterEncoding = "http://www.example.com?param1=%2B447777111222";
+        testEncoding(beforeEncoding, afterEncoding);
+
+        beforeEncoding = "http://www.example.com?param1=+447777111222";
+        afterEncoding = "http://www.example.com?param1=+447777111222";
+        testEncoding(beforeEncoding, afterEncoding);
+    }
+
+    @Test
     public void testPasswordEncodingInRawMode() {
         String password = "RAW(%j#7%c6i)";
         String result = UnsafeUriCharactersEncoder.encode(password, true);
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/URIScanner.java b/core/camel-util/src/main/java/org/apache/camel/util/URIScanner.java
index e2ca9df..50508b3 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/URIScanner.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/URIScanner.java
@@ -171,7 +171,10 @@ class URIScanner {
         if (isRaw) {
             text = value.toString();
         } else {
+            // need to replace % with %25 to avoid losing "%" when decoding
             String s = StringHelper.replaceAll(value.toString(), "%", "%25");
+            // the URLDecoder is for HTML forms, so it decodes + as spaces
+            s = StringHelper.replaceAll(s, "+", "%2B");
             text = URLDecoder.decode(s, CHARSET);
         }
 
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
index 3839a86..335fedc 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
@@ -491,7 +491,11 @@ public final class URISupport {
     private static void appendQueryStringParameter(String key, String value, StringBuilder rc, boolean encode)
             throws UnsupportedEncodingException {
         if (encode) {
-            rc.append(URLEncoder.encode(key, CHARSET));
+            // the URLEncoder is for HTML forms, so it encodes spaces as + sign
+            // but we want this to be decimal encoded for URI query parameter
+            String encoded = URLEncoder.encode(key, CHARSET);
+            encoded = StringHelper.replaceAll(encoded, "+", "%20");
+            rc.append(encoded);
         } else {
             rc.append(key);
         }
@@ -508,7 +512,11 @@ public final class URISupport {
             rc.append(s);
         } else {
             if (encode) {
-                rc.append(URLEncoder.encode(value, CHARSET));
+                // the URLEncoder is for HTML forms, so it encodes spaces as + sign
+                // but we want this to be decimal encoded for URI query parameter
+                String encoded = URLEncoder.encode(value, CHARSET);
+                encoded = StringHelper.replaceAll(encoded, "+", "%20");
+                rc.append(encoded);
             } else {
                 rc.append(value);
             }
diff --git a/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java b/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
index 2fd7e46..cb7bbc2 100644
--- a/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
+++ b/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
@@ -134,12 +134,12 @@ public class URISupportTest {
     @Test
     public void testNormalizeHttpEndpointURLEncodedParameter() throws Exception {
         String out = URISupport.normalizeUri("http://www.google.com?q=S%C3%B8ren%20Hansen");
-        assertEquals("http://www.google.com?q=S%C3%B8ren+Hansen", out);
+        assertEquals("http://www.google.com?q=S%C3%B8ren%20Hansen", out);
     }
 
     @Test
     public void testParseParametersURLEncodedValue() throws Exception {
-        String out = URISupport.normalizeUri("http://www.google.com?q=S%C3%B8ren+Hansen");
+        String out = URISupport.normalizeUri("http://www.google.com?q=S%C3%B8ren%20Hansen");
         URI uri = new URI(out);
 
         Map<String, Object> parameters = URISupport.parseParameters(uri);
@@ -200,12 +200,12 @@ public class URISupportTest {
 
     @Test
     public void testParseParameters() throws Exception {
-        URI u = new URI("quartz:myGroup/myTimerName?cron=0+0+*+*+*+?");
+        URI u = new URI("quartz:myGroup/myTimerName?cron=0%200%20*%20*%20*%20?");
         Map<String, Object> params = URISupport.parseParameters(u);
         assertEquals(1, params.size());
         assertEquals("0 0 * * * ?", params.get("cron"));
 
-        u = new URI("quartz:myGroup/myTimerName?cron=0+0+*+*+*+?&bar=123");
+        u = new URI("quartz:myGroup/myTimerName?cron=0%200%20*%20*%20*%20?&bar=123");
         params = URISupport.parseParameters(u);
         assertEquals(2, params.size());
         assertEquals("0 0 * * * ?", params.get("cron"));
@@ -226,8 +226,8 @@ public class URISupportTest {
         // create new uri with the parameters
         URI out = URISupport.createRemainingURI(new URI(uri), map);
         assertNotNull(out);
-        assertEquals("http://localhost:23271/myapp/mytest?foo=abc+def&bar=123%2C456&name=S%C3%B8ren", out.toString());
-        assertEquals("http://localhost:23271/myapp/mytest?foo=abc+def&bar=123%2C456&name=S%C3%B8ren", out.toASCIIString());
+        assertEquals("http://localhost:23271/myapp/mytest?foo=abc%20def&bar=123%2C456&name=S%C3%B8ren", out.toString());
+        assertEquals("http://localhost:23271/myapp/mytest?foo=abc%20def&bar=123%2C456&name=S%C3%B8ren", out.toASCIIString());
     }
 
     @Test
@@ -348,12 +348,12 @@ public class URISupportTest {
     public void testRawParameter() throws Exception {
         String out = URISupport.normalizeUri(
                 "xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(++?w0rd)&serviceName=some chat");
-        assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(++?w0rd)&serviceName=some+chat", out);
+        assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(++?w0rd)&serviceName=some%20chat", out);
 
         String out2 = URISupport.normalizeUri(
                 "xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(foo %% bar)&serviceName=some chat");
         // Just make sure the RAW parameter can be resolved rightly, we need to replace the % into %25
-        assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(foo %25%25 bar)&serviceName=some+chat",
+        assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(foo %25%25 bar)&serviceName=some%20chat",
                 out2);
     }
 
@@ -361,12 +361,12 @@ public class URISupportTest {
     public void testRawParameterCurly() throws Exception {
         String out = URISupport.normalizeUri(
                 "xmpp://camel-user@localhost:123/test-user@localhost?password=RAW{++?w0rd}&serviceName=some chat");
-        assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW{++?w0rd}&serviceName=some+chat", out);
+        assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW{++?w0rd}&serviceName=some%20chat", out);
 
         String out2 = URISupport.normalizeUri(
                 "xmpp://camel-user@localhost:123/test-user@localhost?password=RAW{foo %% bar}&serviceName=some chat");
         // Just make sure the RAW parameter can be resolved rightly, we need to replace the % into %25
-        assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW{foo %25%25 bar}&serviceName=some+chat",
+        assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW{foo %25%25 bar}&serviceName=some%20chat",
                 out2);
     }
 
@@ -577,4 +577,17 @@ public class URISupportTest {
         assertEquals("recursive=true&delete=true", URISupport.extractQuery("file:foo?recursive=true&delete=true"));
     }
 
+    @Test
+    public void testPlusInQuery() throws Exception {
+        Map<String, Object> map = new HashMap<>();
+        map.put("param1", "+447777111222");
+        String q = URISupport.createQueryString(map);
+        assertEquals("param1=%2B447777111222", q);
+
+        // will be double encoded however
+        map.put("param1", "%2B447777111222");
+        q = URISupport.createQueryString(map);
+        assertEquals("param1=%252B447777111222", q);
+    }
+
 }
diff --git a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_10.adoc b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_10.adoc
index cd88179..7ac110c 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_10.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_10.adoc
@@ -10,6 +10,25 @@ from both 3.0 to 3.1 and 3.1 to 3.2.
 
 The method `concurrentTasks` on `org.apache.camel.support.DefaultScheduledPollConsumerScheduler ` has been renamed to `concurrentConsumers`.
 
+=== Endpoint URIs with spaces
+
+The URI encoder/decode in camel-core has been improved to avoid in some use-cases to use `+` (plus sign)
+as a space in query parameters. Now spaces are encoded to decimal `%20`.
+
+The decoder will now also decode a `+` (plus sign) as plus.
+
+For example before you may call a REST endpoint with a query parameter with the value `Hello World`
+
+    to("rest:get:foo/?msg=Hello+World")
+
+Which should now be either
+
+    to("rest:get:foo/?msg=Hello World")
+
+or use decimal encoded space
+
+    to("rest:get:foo/?msg=Hello%20World")
+
 === camel-scheduler
 
 The option `concurrentTasks` has been renamed to `poolSize` to better reflect its purpose.