You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ts...@apache.org on 2019/01/17 08:37:15 UTC

[camel] branch master updated: CAMEL-12982: Support RAW{} syntax in URISupport

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

tsato 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 2970e97  CAMEL-12982: Support RAW{} syntax in URISupport
2970e97 is described below

commit 2970e970b66c7af8462c9053032a40e40b420b1f
Author: Tadayoshi Sato <sa...@gmail.com>
AuthorDate: Thu Jan 17 12:34:03 2019 +0900

    CAMEL-12982: Support RAW{} syntax in URISupport
---
 .../org/apache/camel/reifier/ToDynamicReifier.java |  46 +---
 .../org/apache/camel/runtimecatalog/impl/Pair.java |  60 +++++
 .../camel/runtimecatalog/impl/URISupport.java      | 119 +++++++--
 .../impl/UnsafeUriCharactersEncoder.java           |  46 +---
 .../org/apache/camel/support/DefaultComponent.java |   5 +-
 .../issues/EndpointWithRawUriParameterTest.java    |  16 +-
 .../src/main/java/org/apache/camel/util/Pair.java  |  60 +++++
 .../java/org/apache/camel/util/URIScanner.java     | 270 +++++++++++++++++++++
 .../java/org/apache/camel/util/URISupport.java     | 256 +++++++------------
 .../camel/util/UnsafeUriCharactersEncoder.java     |  46 +---
 .../java/org/apache/camel/util/URISupportTest.java | 100 +++++++-
 11 files changed, 701 insertions(+), 323 deletions(-)

diff --git a/camel-core/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java b/camel-core/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
index e9513a1..8ae929b 100644
--- a/camel-core/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
+++ b/camel-core/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
@@ -18,8 +18,6 @@ package org.apache.camel.reifier;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import org.apache.camel.Expression;
 import org.apache.camel.NoSuchLanguageException;
@@ -30,12 +28,12 @@ import org.apache.camel.model.ToDynamicDefinition;
 import org.apache.camel.processor.SendDynamicProcessor;
 import org.apache.camel.spi.Language;
 import org.apache.camel.spi.RouteContext;
+import org.apache.camel.util.Pair;
 import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.URISupport;
 
 class ToDynamicReifier<T extends ToDynamicDefinition> extends ProcessorReifier<T> {
 
-    private static final Pattern RAW_PATTERN = Pattern.compile("RAW\\([^\\)]+\\)");
-
     ToDynamicReifier(ProcessorDefinition<?> definition) {
         super((T) definition);
     }
@@ -101,42 +99,6 @@ class ToDynamicReifier<T extends ToDynamicDefinition> extends ProcessorReifier<T
     // Utilities
     // -------------------------------------------------------------------------
 
-    private static class Pair {
-        int left;
-        int right;
-        Pair(int left, int right) {
-            this.left = left;
-            this.right = right;
-        }
-    }
-
-    private static List<Pair> checkRAW(String s) {
-        Matcher matcher = RAW_PATTERN.matcher(s);
-        List<Pair> answer = new ArrayList<>();
-        // Check all occurrences
-        while (matcher.find()) {
-            answer.add(new Pair(matcher.start(), matcher.end() - 1));
-        }
-        return answer;
-    }
-
-    private static boolean isRaw(int index, List<Pair>pairs) {
-        for (Pair pair : pairs) {
-            if (index < pair.left) {
-                return false;
-            } else {
-                if (index >= pair.left) {
-                    if (index <= pair.right) {
-                        return true;
-                    } else {
-                        continue;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
     /**
      * We need to split the string safely for each + sign, but avoid splitting within RAW(...).
      */
@@ -148,12 +110,12 @@ class ToDynamicReifier<T extends ToDynamicDefinition> extends ProcessorReifier<T
             list.add(s);
         } else {
             // there is a plus sign so we need to split in a safe manner
-            List<Pair> rawPairs = checkRAW(s);
+            List<Pair<Integer>> rawPairs = URISupport.scanRaw(s);
             StringBuilder sb = new StringBuilder();
             char chars[] = s.toCharArray();
             for (int i = 0; i < chars.length; i++) {
                 char ch = chars[i];
-                if (ch != '+' || isRaw(i, rawPairs)) {
+                if (ch != '+' || URISupport.isRaw(i, rawPairs)) {
                     sb.append(ch);
                 } else {
                     list.add(sb.toString());
diff --git a/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/Pair.java b/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/Pair.java
new file mode 100644
index 0000000..d2060d7
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/Pair.java
@@ -0,0 +1,60 @@
+/**
+ * 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.runtimecatalog.impl;
+
+import java.util.Objects;
+
+/**
+ * Copied from org.apache.camel.util.Pair
+ */
+public class Pair<T> {
+
+    private T left;
+    private T right;
+
+    public Pair(T left, T right) {
+        this.left = left;
+        this.right = right;
+    }
+
+    public T getLeft() {
+        return left;
+    }
+
+    public T getRight() {
+        return right;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Pair<?> that = (Pair<?>) o;
+        return Objects.equals(left, that.left) &&
+                Objects.equals(right, that.right);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(left, right);
+    }
+
+    @Override
+    public String toString() {
+        return "(" + left + ", " + right + ")";
+    }
+}
diff --git a/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/URISupport.java b/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/URISupport.java
index 73f05a2..12087ee 100644
--- a/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/URISupport.java
+++ b/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/URISupport.java
@@ -26,14 +26,16 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.BiConsumer;
 
 /**
  * Copied from org.apache.camel.util.URISupport
  */
 public final class URISupport {
 
-    public static final String RAW_TOKEN_START = "RAW(";
-    public static final String RAW_TOKEN_END = ")";
+    public static final String RAW_TOKEN_PREFIX = "RAW";
+    public static final char[] RAW_TOKEN_START = { '(', '{' };
+    public static final char[] RAW_TOKEN_END = { ')', '}' };
 
     private static final String CHARSET = "UTF-8";
 
@@ -155,17 +157,17 @@ public final class URISupport {
      * @see #RAW_TOKEN_END
      */
     public static Map<String, Object> parseQuery(String uri, boolean useRaw) throws URISyntaxException {
-        // must check for trailing & as the uri.split("&") will ignore those
-        if (uri != null && uri.endsWith("&")) {
-            throw new URISyntaxException(uri, "Invalid uri syntax: Trailing & marker found. "
-                    + "Check the uri and remove the trailing & marker.");
-        }
-
         if (isEmpty(uri)) {
             // return an empty map
             return new LinkedHashMap<>(0);
         }
 
+        // must check for trailing & as the uri.split("&") will ignore those
+        if (uri.endsWith("&")) {
+            throw new URISyntaxException(uri, "Invalid uri syntax: Trailing & marker found. "
+                    + "Check the uri and remove the trailing & marker.");
+        }
+
         // need to parse the uri query parameters manually as we cannot rely on splitting by &,
         // as & can be used in a parameter value as well.
 
@@ -192,7 +194,15 @@ public final class URISupport {
                 }
 
                 // are we a raw value
-                isRaw = value.toString().startsWith(RAW_TOKEN_START);
+                char rawTokenEnd = 0;
+                for (int j = 0; j < RAW_TOKEN_START.length; j++) {
+                    String rawTokenStart = RAW_TOKEN_PREFIX + RAW_TOKEN_START[j];
+                    isRaw = value.toString().startsWith(rawTokenStart);
+                    if (isRaw) {
+                        rawTokenEnd = RAW_TOKEN_END[j];
+                        break;
+                    }
+                }
 
                 // if we are in raw mode, then we keep adding until we hit the end marker
                 if (isRaw) {
@@ -202,9 +212,9 @@ public final class URISupport {
                         value.append(ch);
                     }
 
-                    // we only end the raw marker if its )& or at the end of the value
+                    // we only end the raw marker if it's ")&", "}&", or at the end of the value
 
-                    boolean end = ch == RAW_TOKEN_END.charAt(0) && (next == '&' || next == '\u0000');
+                    boolean end = ch == rawTokenEnd && (next == '&' || next == '\u0000');
                     if (end) {
                         // raw value end, so add that as a parameter, and reset flags
                         addParameter(key.toString(), value.toString(), rc, useRaw || isRaw);
@@ -302,6 +312,71 @@ public final class URISupport {
         }
     }
 
+    public static List<Pair<Integer>> scanRaw(String str) {
+        List<Pair<Integer>> answer = new ArrayList<>();
+        if (str == null || isEmpty(str)) {
+            return answer;
+        }
+
+        int offset = 0;
+        int start = str.indexOf(RAW_TOKEN_PREFIX);
+        while (start >= 0 && offset < str.length()) {
+            offset = start + RAW_TOKEN_PREFIX.length();
+            for (int i = 0; i < RAW_TOKEN_START.length; i++) {
+                String tokenStart = RAW_TOKEN_PREFIX + RAW_TOKEN_START[i];
+                char tokenEnd = RAW_TOKEN_END[i];
+                if (str.startsWith(tokenStart, start)) {
+                    offset = scanRawToEnd(str, start, tokenStart, tokenEnd, answer);
+                    continue;
+                }
+            }
+            start = str.indexOf(RAW_TOKEN_PREFIX, offset);
+        }
+        return answer;
+    }
+
+    private static int scanRawToEnd(String str, int start, String tokenStart, char tokenEnd,
+                                    List<Pair<Integer>> answer) {
+        // we search the first end bracket to close the RAW token
+        // as opposed to parsing query, this doesn't allow the occurrences of end brackets
+        // inbetween because this may be used on the host/path parts of URI
+        // and thus we cannot rely on '&' for detecting the end of a RAW token
+        int end = str.indexOf(tokenEnd, start + tokenStart.length());
+        if (end < 0) {
+            // still return a pair even if RAW token is not closed
+            answer.add(new Pair<>(start, str.length()));
+            return str.length();
+        }
+        answer.add(new Pair<>(start, end));
+        return end + 1;
+    }
+
+    public static boolean isRaw(int index, List<Pair<Integer>> pairs) {
+        for (Pair<Integer> pair : pairs) {
+            if (index < pair.getLeft()) {
+                return false;
+            }
+            if (index <= pair.getRight()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean resolveRaw(String str, BiConsumer<String, String> consumer) {
+        for (int i = 0; i < RAW_TOKEN_START.length; i++) {
+            String tokenStart = RAW_TOKEN_PREFIX + RAW_TOKEN_START[i];
+            String tokenEnd = String.valueOf(RAW_TOKEN_END[i]);
+            if (str.startsWith(tokenStart) && str.endsWith(tokenEnd)) {
+                String raw = str.substring(tokenStart.length(), str.length() - 1);
+                consumer.accept(str, raw);
+                return true;
+            }
+        }
+        // not RAW value
+        return false;
+    }
+
     /**
      * Assembles a query from the given map.
      *
@@ -346,18 +421,20 @@ public final class URISupport {
         } else {
             rc.append(key);
         }
+        if (value == null) {
+            return;
+        }
         // only append if value is not null
-        if (value != null) {
-            rc.append("=");
-            if (value.startsWith(RAW_TOKEN_START) && value.endsWith(RAW_TOKEN_END)) {
-                // do not encode RAW parameters
-                rc.append(value);
+        rc.append("=");
+        boolean isRaw = resolveRaw(value, (str, raw) -> {
+            // do not encode RAW parameters
+            rc.append(str);
+        });
+        if (!isRaw) {
+            if (encode) {
+                rc.append(URLEncoder.encode(value, CHARSET));
             } else {
-                if (encode) {
-                    rc.append(URLEncoder.encode(value, CHARSET));
-                } else {
-                    rc.append(value);
-                }
+                rc.append(value);
             }
         }
     }
diff --git a/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/UnsafeUriCharactersEncoder.java b/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/UnsafeUriCharactersEncoder.java
index 9dbe30c..5391b44 100644
--- a/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/UnsafeUriCharactersEncoder.java
+++ b/camel-core/src/main/java/org/apache/camel/runtimecatalog/impl/UnsafeUriCharactersEncoder.java
@@ -19,8 +19,6 @@ package org.apache.camel.runtimecatalog.impl;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Encoder for unsafe URI characters.
@@ -32,7 +30,6 @@ public final class UnsafeUriCharactersEncoder {
     private static BitSet unsafeCharactersHttp;
     private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
         'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f'};
-    private static final Pattern RAW_PATTERN = Pattern.compile("RAW\\([^\\)]+\\)");
 
     static {
         unsafeCharactersRfc1738 = new BitSet(256);
@@ -94,48 +91,11 @@ public final class UnsafeUriCharactersEncoder {
         return encode(s, unsafeCharactersHttp, checkRaw);
     }
 
-    private static List<Pair> checkRAW(String s) {
-        Matcher matcher = RAW_PATTERN.matcher(s);
-        List<Pair> answer = new ArrayList<>();
-        // Check all occurrences
-        while (matcher.find()) {
-            answer.add(new Pair(matcher.start(), matcher.end()));
-        }
-        return answer;
-    }
-
-    private static boolean isRaw(int index, List<Pair> pairs) {
-        for (Pair pair : pairs) {
-            if (index < pair.left) {
-                return false;
-            } else {
-                if (index >= pair.left) {
-                    if (index <= pair.right) {
-                        return true;
-                    } else {
-                        continue;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    private static class Pair {
-        int left;
-        int right;
-
-        Pair(int left, int right) {
-            this.left = left;
-            this.right = right;
-        }
-    }
-
     // Just skip the encode for isRAW part
     public static String encode(String s, BitSet unsafeCharacters, boolean checkRaw) {
-        List<Pair> rawPairs;
+        List<Pair<Integer>> rawPairs;
         if (checkRaw) {
-            rawPairs = checkRAW(s);
+            rawPairs = URISupport.scanRaw(s);
         } else {
             rawPairs = new ArrayList<>();
         }
@@ -170,7 +130,7 @@ public final class UnsafeUriCharactersEncoder {
                     char next = i + 1 < chars.length ? chars[i + 1] : ' ';
                     char next2 = i + 2 < chars.length ? chars[i + 2] : ' ';
 
-                    if (isHexDigit(next) && isHexDigit(next2) && !isRaw(i, rawPairs)) {
+                    if (isHexDigit(next) && isHexDigit(next2) && !URISupport.isRaw(i, rawPairs)) {
                         // its already encoded (decimal encoded) so just append as is
                         sb.append(ch);
                     } else {
diff --git a/camel-core/src/main/java/org/apache/camel/support/DefaultComponent.java b/camel-core/src/main/java/org/apache/camel/support/DefaultComponent.java
index 4925f69..c2d6e50 100644
--- a/camel-core/src/main/java/org/apache/camel/support/DefaultComponent.java
+++ b/camel-core/src/main/java/org/apache/camel/support/DefaultComponent.java
@@ -45,7 +45,10 @@ import org.apache.camel.util.function.Suppliers;
  */
 public abstract class DefaultComponent extends ServiceSupport implements Component {
 
-    private static final Pattern RAW_PATTERN = Pattern.compile("RAW(.*&&.*)");
+    /**
+     * Simple RAW() pattern used only for validating URI in this class
+     */
+    private static final Pattern RAW_PATTERN = Pattern.compile("RAW[({].*&&.*[)}]");
 
     private final List<Supplier<ComponentExtension>> extensions = new ArrayList<>();
 
diff --git a/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java b/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java
index 8fa2c4f..d2f9cc7 100644
--- a/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java
+++ b/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java
@@ -27,7 +27,6 @@ import org.apache.camel.Exchange;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
 import org.apache.camel.builder.RouteBuilder;
-import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.support.DefaultComponent;
 import org.apache.camel.support.DefaultEndpoint;
 import org.apache.camel.support.DefaultProducer;
@@ -164,6 +163,17 @@ public class EndpointWithRawUriParameterTest extends ContextTestSupport {
         assertMockEndpointsSatisfied();
     }
 
+    @Test
+    public void testRawUriParameterOkDynamic() throws Exception {
+        getMockEndpoint("mock:result").expectedMessageCount(1);
+        getMockEndpoint("mock:result").expectedHeaderReceived("username", "scott");
+        getMockEndpoint("mock:result").expectedHeaderReceived("password", "foo)+bar");
+
+        template.sendBody("direct:okDynamic", "Hello World");
+
+        assertMockEndpointsSatisfied();
+    }
+
     @Override
     protected RouteBuilder createRouteBuilder() throws Exception {
         return new RouteBuilder() {
@@ -190,6 +200,10 @@ public class EndpointWithRawUriParameterTest extends ContextTestSupport {
                 from("direct:ok")
                     .to("mycomponent:foo?password=RAW(foo)+bar)&username=scott")
                     .to("mock:result");
+
+                from("direct:okDynamic")
+                    .toD("mycomponent:foo?password=RAW{foo)+bar}&username=scott")
+                    .to("mock:result");
             }
         };
     }
diff --git a/camel-util/src/main/java/org/apache/camel/util/Pair.java b/camel-util/src/main/java/org/apache/camel/util/Pair.java
new file mode 100644
index 0000000..8f4e3b4
--- /dev/null
+++ b/camel-util/src/main/java/org/apache/camel/util/Pair.java
@@ -0,0 +1,60 @@
+/**
+ * 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.util;
+
+import java.util.Objects;
+
+/**
+ * Generic holder object for pair values.
+ */
+public class Pair<T> {
+
+    private T left;
+    private T right;
+
+    public Pair(T left, T right) {
+        this.left = left;
+        this.right = right;
+    }
+
+    public T getLeft() {
+        return left;
+    }
+
+    public T getRight() {
+        return right;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Pair<?> that = (Pair<?>) o;
+        return Objects.equals(left, that.left) &&
+                Objects.equals(right, that.right);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(left, right);
+    }
+
+    @Override
+    public String toString() {
+        return "(" + left + ", " + right + ")";
+    }
+}
diff --git a/camel-util/src/main/java/org/apache/camel/util/URIScanner.java b/camel-util/src/main/java/org/apache/camel/util/URIScanner.java
new file mode 100644
index 0000000..7bf813b
--- /dev/null
+++ b/camel-util/src/main/java/org/apache/camel/util/URIScanner.java
@@ -0,0 +1,270 @@
+/**
+ * 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.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+import static org.apache.camel.util.URISupport.RAW_TOKEN_END;
+import static org.apache.camel.util.URISupport.RAW_TOKEN_PREFIX;
+import static org.apache.camel.util.URISupport.RAW_TOKEN_START;
+
+/**
+ * RAW syntax aware URI scanner that provides various URI manipulations.
+ */
+class URIScanner {
+
+    private enum Mode {
+        KEY, VALUE
+    }
+
+    private static final char END = '\u0000';
+
+    private final String charset;
+    private final StringBuilder key;
+    private final StringBuilder value;
+    private Mode mode;
+    private boolean isRaw;
+    private char rawTokenEnd;
+
+    public URIScanner(String charset) {
+        this.charset = charset;
+        key = new StringBuilder();
+        value = new StringBuilder();
+    }
+
+    private void initState() {
+        mode = Mode.KEY;
+        key.setLength(0);
+        value.setLength(0);
+        isRaw = false;
+    }
+
+    private String getDecodedKey() throws UnsupportedEncodingException {
+        return URLDecoder.decode(key.toString(), charset);
+    }
+
+    private String getDecodedValue() throws UnsupportedEncodingException {
+        // need to replace % with %25
+        String s = StringHelper.replaceAll(value.toString(), "%", "%25");
+        String answer = URLDecoder.decode(s, charset);
+        return answer;
+    }
+
+    public Map<String, Object> parseQuery(String uri, boolean useRaw) throws URISyntaxException {
+        // need to parse the uri query parameters manually as we cannot rely on splitting by &,
+        // as & can be used in a parameter value as well.
+
+        try {
+            // use a linked map so the parameters is in the same order
+            Map<String, Object> answer = new LinkedHashMap<>();
+
+            initState();
+
+            // parse the uri parameters char by char
+            for (int i = 0; i < uri.length(); i++) {
+                // current char
+                char ch = uri.charAt(i);
+                // look ahead of the next char
+                char next;
+                if (i <= uri.length() - 2) {
+                    next = uri.charAt(i + 1);
+                } else {
+                    next = END;
+                }
+
+                switch (mode) {
+                case KEY:
+                    // if there is a = sign then the key ends and we are in value mode
+                    if (ch == '=') {
+                        mode = Mode.VALUE;
+                        continue;
+                    }
+
+                    if (ch != '&') {
+                        // regular char so add it to the key
+                        key.append(ch);
+                    }
+                    break;
+                case VALUE:
+                    // are we a raw value
+                    isRaw = checkRaw();
+
+                    // if we are in raw mode, then we keep adding until we hit the end marker
+                    if (isRaw) {
+                        value.append(ch);
+
+                        if (isAtEnd(ch, next)) {
+                            // raw value end, so add that as a parameter, and reset flags
+                            addParameter(answer, useRaw || isRaw);
+                            initState();
+                            // skip to next as we are in raw mode and have already added the value
+                            i++;
+                        }
+                        continue;
+                    }
+
+                    if (ch != '&') {
+                        // regular char so add it to the value
+                        value.append(ch);
+                    }
+                    break;
+                default:
+                    throw new IllegalStateException("Unknown mode: " + mode);
+                }
+
+                // the & denote parameter is ended
+                if (ch == '&') {
+                    // parameter is ended, as we hit & separator
+                    addParameter(answer, useRaw || isRaw);
+                    initState();
+                }
+            }
+
+            // any left over parameters, then add that
+            if (key.length() > 0) {
+                addParameter(answer, useRaw || isRaw);
+            }
+
+            return answer;
+
+        } catch (UnsupportedEncodingException e) {
+            URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding");
+            se.initCause(e);
+            throw se;
+        }
+    }
+
+    private boolean checkRaw() {
+        rawTokenEnd = 0;
+
+        for (int i = 0; i < RAW_TOKEN_START.length; i++) {
+            String rawTokenStart = RAW_TOKEN_PREFIX + RAW_TOKEN_START[i];
+            boolean isRaw = value.toString().startsWith(rawTokenStart);
+            if (isRaw) {
+                rawTokenEnd = RAW_TOKEN_END[i];
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean isAtEnd(char ch, char next) {
+        // we only end the raw marker if it's ")&", "}&", or at the end of the value
+        return ch == rawTokenEnd && (next == '&' || next == END);
+    }
+
+    private void addParameter(Map<String, Object> answer, boolean isRaw) throws UnsupportedEncodingException {
+        String name = getDecodedKey();
+        String value = isRaw ? this.value.toString() : getDecodedValue();
+
+        // does the key already exist?
+        if (answer.containsKey(name)) {
+            // yes it does, so make sure we can support multiple values, but using a list
+            // to hold the multiple values
+            Object existing = answer.get(name);
+            List<String> list;
+            if (existing instanceof List) {
+                list = CastUtils.cast((List<?>) existing);
+            } else {
+                // create a new list to hold the multiple values
+                list = new ArrayList<>();
+                String s = existing != null ? existing.toString() : null;
+                if (s != null) {
+                    list.add(s);
+                }
+            }
+            list.add(value);
+            answer.put(name, list);
+        } else {
+            answer.put(name, value);
+        }
+    }
+
+    public static List<Pair<Integer>> scanRaw(String str) {
+        List<Pair<Integer>> answer = new ArrayList<>();
+        if (str == null || ObjectHelper.isEmpty(str)) {
+            return answer;
+        }
+
+        int offset = 0;
+        int start = str.indexOf(RAW_TOKEN_PREFIX);
+        while (start >= 0 && offset < str.length()) {
+            offset = start + RAW_TOKEN_PREFIX.length();
+            for (int i = 0; i < RAW_TOKEN_START.length; i++) {
+                String tokenStart = RAW_TOKEN_PREFIX + RAW_TOKEN_START[i];
+                char tokenEnd = RAW_TOKEN_END[i];
+                if (str.startsWith(tokenStart, start)) {
+                    offset = scanRawToEnd(str, start, tokenStart, tokenEnd, answer);
+                    continue;
+                }
+            }
+            start = str.indexOf(RAW_TOKEN_PREFIX, offset);
+        }
+        return answer;
+    }
+
+    private static int scanRawToEnd(String str, int start, String tokenStart, char tokenEnd,
+                                    List<Pair<Integer>> answer) {
+        // we search the first end bracket to close the RAW token
+        // as opposed to parsing query, this doesn't allow the occurrences of end brackets
+        // inbetween because this may be used on the host/path parts of URI
+        // and thus we cannot rely on '&' for detecting the end of a RAW token
+        int end = str.indexOf(tokenEnd, start + tokenStart.length());
+        if (end < 0) {
+            // still return a pair even if RAW token is not closed
+            answer.add(new Pair<>(start, str.length()));
+            return str.length();
+        }
+        answer.add(new Pair<>(start, end));
+        return end + 1;
+    }
+
+    public static boolean isRaw(int index, List<Pair<Integer>> pairs) {
+        for (Pair<Integer> pair : pairs) {
+            if (index < pair.getLeft()) {
+                return false;
+            }
+            if (index <= pair.getRight()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean resolveRaw(String str, BiConsumer<String, String> consumer) {
+        for (int i = 0; i < RAW_TOKEN_START.length; i++) {
+            String tokenStart = RAW_TOKEN_PREFIX + RAW_TOKEN_START[i];
+            String tokenEnd = String.valueOf(RAW_TOKEN_END[i]);
+            if (str.startsWith(tokenStart) && str.endsWith(tokenEnd)) {
+                String raw = str.substring(tokenStart.length(), str.length() - 1);
+                consumer.accept(str, raw);
+                return true;
+            }
+        }
+        // not RAW value
+        return false;
+    }
+
+}
diff --git a/camel-util/src/main/java/org/apache/camel/util/URISupport.java b/camel-util/src/main/java/org/apache/camel/util/URISupport.java
index 539e416..7c93201 100644
--- a/camel-util/src/main/java/org/apache/camel/util/URISupport.java
+++ b/camel-util/src/main/java/org/apache/camel/util/URISupport.java
@@ -19,7 +19,6 @@ package org.apache.camel.util;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.URLDecoder;
 import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -33,23 +32,25 @@ import java.util.regex.Pattern;
  */
 public final class URISupport {
 
-    public static final String RAW_TOKEN_START = "RAW(";
-    public static final String RAW_TOKEN_END = ")";
+    public static final String RAW_TOKEN_PREFIX = "RAW";
+    public static final char[] RAW_TOKEN_START = { '(', '{' };
+    public static final char[] RAW_TOKEN_END = { ')', '}' };
 
     // Match any key-value pair in the URI query string whose key contains
     // "passphrase" or "password" or secret key (case-insensitive).
     // First capture group is the key, second is the value.
-    private static final Pattern SECRETS = Pattern.compile("([?&][^=]*(?:passphrase|password|secretKey)[^=]*)=(RAW\\(.*\\)|[^&]*)",
+    private static final Pattern SECRETS = Pattern.compile(
+            "([?&][^=]*(?:passphrase|password|secretKey)[^=]*)=(RAW[({].*[)}]|[^&]*)",
             Pattern.CASE_INSENSITIVE);
-    
+
     // Match the user password in the URI as second capture group
     // (applies to URI with authority component and userinfo token in the form "user:password").
     private static final Pattern USERINFO_PASSWORD = Pattern.compile("(.*://.*:)(.*)(@)");
-    
+
     // Match the user password in the URI path as second capture group
     // (applies to URI path with authority component and userinfo token in the form "user:password").
     private static final Pattern PATH_USERINFO_PASSWORD = Pattern.compile("(.*:)(.*)(@)");
-    
+
     private static final String CHARSET = "UTF-8";
 
     private URISupport() {
@@ -73,12 +74,12 @@ public final class URISupport {
         }
         return sanitized;
     }
-    
+
     /**
      * Removes detected sensitive information (such as passwords) from the
      * <em>path part</em> of an URI (that is, the part without the query
      * parameters or component prefix) and returns the result.
-     * 
+     *
      * @param path the URI path to sanitize
      * @return null if the path is null, otherwise the sanitized path
      */
@@ -122,6 +123,7 @@ public final class URISupport {
      * @param uri the uri
      * @return the parameters, or an empty map if no parameters (eg never null)
      * @throws URISyntaxException is thrown if uri has invalid syntax.
+     * @see #RAW_TOKEN_PREFIX
      * @see #RAW_TOKEN_START
      * @see #RAW_TOKEN_END
      */
@@ -140,6 +142,7 @@ public final class URISupport {
      * @param useRaw whether to force using raw values
      * @return the parameters, or an empty map if no parameters (eg never null)
      * @throws URISyntaxException is thrown if uri has invalid syntax.
+     * @see #RAW_TOKEN_PREFIX
      * @see #RAW_TOKEN_START
      * @see #RAW_TOKEN_END
      */
@@ -159,147 +162,61 @@ public final class URISupport {
      * @param lenient whether to parse lenient and ignore trailing & markers which has no key or value which can happen when using HTTP components
      * @return the parameters, or an empty map if no parameters (eg never null)
      * @throws URISyntaxException is thrown if uri has invalid syntax.
+     * @see #RAW_TOKEN_PREFIX
      * @see #RAW_TOKEN_START
      * @see #RAW_TOKEN_END
      */
     public static Map<String, Object> parseQuery(String uri, boolean useRaw, boolean lenient) throws URISyntaxException {
-        // must check for trailing & as the uri.split("&") will ignore those
-        if (!lenient) {
-            if (uri != null && uri.endsWith("&")) {
-                throw new URISyntaxException(uri, "Invalid uri syntax: Trailing & marker found. "
-                        + "Check the uri and remove the trailing & marker.");
-            }
-        }
-
         if (uri == null || ObjectHelper.isEmpty(uri)) {
             // return an empty map
             return new LinkedHashMap<>(0);
         }
 
-        // need to parse the uri query parameters manually as we cannot rely on splitting by &,
-        // as & can be used in a parameter value as well.
-
-        try {
-            // use a linked map so the parameters is in the same order
-            Map<String, Object> rc = new LinkedHashMap<>();
-
-            boolean isKey = true;
-            boolean isValue = false;
-            boolean isRaw = false;
-            StringBuilder key = new StringBuilder();
-            StringBuilder value = new StringBuilder();
-
-            // parse the uri parameters char by char
-            for (int i = 0; i < uri.length(); i++) {
-                // current char
-                char ch = uri.charAt(i);
-                // look ahead of the next char
-                char next;
-                if (i <= uri.length() - 2) {
-                    next = uri.charAt(i + 1);
-                } else {
-                    next = '\u0000';
-                }
-
-                // are we a raw value
-                isRaw = value.toString().startsWith(RAW_TOKEN_START);
-
-                // if we are in raw mode, then we keep adding until we hit the end marker
-                if (isRaw) {
-                    if (isKey) {
-                        key.append(ch);
-                    } else if (isValue) {
-                        value.append(ch);
-                    }
-
-                    // we only end the raw marker if its )& or at the end of the value
-
-                    boolean end = ch == RAW_TOKEN_END.charAt(0) && (next == '&' || next == '\u0000');
-                    if (end) {
-                        // raw value end, so add that as a parameter, and reset flags
-                        addParameter(key.toString(), value.toString(), rc, useRaw || isRaw);
-                        key.setLength(0);
-                        value.setLength(0);
-                        isKey = true;
-                        isValue = false;
-                        isRaw = false;
-                        // skip to next as we are in raw mode and have already added the value
-                        i++;
-                    }
-                    continue;
-                }
-
-                // if its a key and there is a = sign then the key ends and we are in value mode
-                if (isKey && ch == '=') {
-                    isKey = false;
-                    isValue = true;
-                    isRaw = false;
-                    continue;
-                }
-
-                // the & denote parameter is ended
-                if (ch == '&') {
-                    // parameter is ended, as we hit & separator
-                    addParameter(key.toString(), value.toString(), rc, useRaw || isRaw);
-                    key.setLength(0);
-                    value.setLength(0);
-                    isKey = true;
-                    isValue = false;
-                    isRaw = false;
-                    continue;
-                }
-
-                // regular char so add it to the key or value
-                if (isKey) {
-                    key.append(ch);
-                } else if (isValue) {
-                    value.append(ch);
-                }
-            }
-
-            // any left over parameters, then add that
-            if (key.length() > 0) {
-                addParameter(key.toString(), value.toString(), rc, useRaw || isRaw);
-            }
+        // must check for trailing & as the uri.split("&") will ignore those
+        if (!lenient && uri.endsWith("&")) {
+            throw new URISyntaxException(uri, "Invalid uri syntax: Trailing & marker found. "
+                    + "Check the uri and remove the trailing & marker.");
+        }
 
-            return rc;
+        URIScanner scanner = new URIScanner(CHARSET);
+        return scanner.parseQuery(uri, useRaw);
+    }
 
-        } catch (UnsupportedEncodingException e) {
-            URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding");
-            se.initCause(e);
-            throw se;
-        }
+    /**
+     * Scans RAW tokens in the string and returns the list of pair indexes which tell where
+     * a RAW token starts and ends in the string.
+     * <p/>
+     * This is a companion method with {@link #isRaw(int, List)} and the returned value is
+     * supposed to be used as the parameter of that method.
+     *
+     * @param str the string to scan RAW tokens
+     * @return the list of pair indexes which represent the start and end positions of a RAW token
+     * @see #isRaw(int, List)
+     * @see #RAW_TOKEN_PREFIX
+     * @see #RAW_TOKEN_START
+     * @see #RAW_TOKEN_END
+     */
+    public static List<Pair<Integer>> scanRaw(String str) {
+        return URIScanner.scanRaw(str);
     }
 
-    private static void addParameter(String name, String value, Map<String, Object> map, boolean isRaw) throws UnsupportedEncodingException {
-        name = URLDecoder.decode(name, CHARSET);
-        if (!isRaw) {
-            // need to replace % with %25
-            String s = StringHelper.replaceAll(value, "%", "%25");
-            value = URLDecoder.decode(s, CHARSET);
-        }
-
-        // does the key already exist?
-        if (map.containsKey(name)) {
-            // yes it does, so make sure we can support multiple values, but using a list
-            // to hold the multiple values
-            Object existing = map.get(name);
-            List<String> list;
-            if (existing instanceof List) {
-                list = CastUtils.cast((List<?>) existing);
-            } else {
-                // create a new list to hold the multiple values
-                list = new ArrayList<>();
-                String s = existing != null ? existing.toString() : null;
-                if (s != null) {
-                    list.add(s);
-                }
-            }
-            list.add(value);
-            map.put(name, list);
-        } else {
-            map.put(name, value);
-        }
+    /**
+     * Tests if the index is within any pair of the start and end indexes which represent
+     * the start and end positions of a RAW token.
+     * <p/>
+     * This is a companion method with {@link #scanRaw(String)} and is supposed to consume
+     * the returned value of that method as the second parameter <tt>pairs</tt>.
+     *
+     * @param index the index to be tested
+     * @param pairs the list of pair indexes which represent the start and end positions of a RAW token
+     * @return <tt>true</tt> if the index is within any pair of the indexes, <tt>false</tt> otherwise
+     * @see #scanRaw(String)
+     * @see #RAW_TOKEN_PREFIX
+     * @see #RAW_TOKEN_START
+     * @see #RAW_TOKEN_END
+     */
+    public static boolean isRaw(int index, List<Pair<Integer>> pairs) {
+        return URIScanner.isRaw(index, pairs);
     }
 
     /**
@@ -333,35 +250,35 @@ public final class URISupport {
      *
      * @param parameters the uri parameters
      * @see #parseQuery(String)
+     * @see #RAW_TOKEN_PREFIX
      * @see #RAW_TOKEN_START
      * @see #RAW_TOKEN_END
      */
     @SuppressWarnings("unchecked")
     public static void resolveRawParameterValues(Map<String, Object> parameters) {
         for (Map.Entry<String, Object> entry : parameters.entrySet()) {
-            if (entry.getValue() != null) {
-                // if the value is a list then we need to iterate
-                Object value = entry.getValue();
-                if (value instanceof List) {
-                    List list = (List) value;
-                    for (int i = 0; i < list.size(); i++) {
-                        Object obj = list.get(i);
-                        if (obj != null) {
-                            String str = obj.toString();
-                            if (str.startsWith(RAW_TOKEN_START) && str.endsWith(RAW_TOKEN_END)) {
-                                str = str.substring(4, str.length() - 1);
-                                // update the string in the list
-                                list.set(i, str);
-                            }
-                        }
-                    }
-                } else {
-                    String str = entry.getValue().toString();
-                    if (str.startsWith(RAW_TOKEN_START) && str.endsWith(RAW_TOKEN_END)) {
-                        str = str.substring(4, str.length() - 1);
-                        entry.setValue(str);
+            if (entry.getValue() == null) {
+                continue;
+            }
+            // if the value is a list then we need to iterate
+            Object value = entry.getValue();
+            if (value instanceof List) {
+                List list = (List) value;
+                for (int i = 0; i < list.size(); i++) {
+                    Object obj = list.get(i);
+                    if (obj == null) {
+                        continue;
                     }
+                    String str = obj.toString();
+                    final int index = i;
+                    URIScanner.resolveRaw(str, (s, raw) -> {
+                        // update the string in the list
+                        list.set(index, raw);
+                    });
                 }
+            } else {
+                String str = entry.getValue().toString();
+                URIScanner.resolveRaw(str, (s, raw) -> entry.setValue(raw));
             }
         }
     }
@@ -491,17 +408,19 @@ public final class URISupport {
 
     private static void appendQueryStringParameter(String key, String value, StringBuilder rc) throws UnsupportedEncodingException {
         rc.append(URLEncoder.encode(key, CHARSET));
+        if (value == null) {
+            return;
+        }
         // only append if value is not null
-        if (value != null) {
-            rc.append("=");
-            if (value.startsWith(RAW_TOKEN_START) && value.endsWith(RAW_TOKEN_END)) {
-                // do not encode RAW parameters unless it has %
-                // need to replace % with %25 to avoid losing "%" when decoding
-                String s = StringHelper.replaceAll(value, "%", "%25");
-                rc.append(s);
-            } else {
-                rc.append(URLEncoder.encode(value, CHARSET));
-            }
+        rc.append("=");
+        boolean isRaw = URIScanner.resolveRaw(value, (str, raw) -> {
+            // do not encode RAW parameters unless it has %
+            // need to replace % with %25 to avoid losing "%" when decoding
+            String s = StringHelper.replaceAll(str, "%", "%25");
+            rc.append(s);
+        });
+        if (!isRaw) {
+            rc.append(URLEncoder.encode(value, CHARSET));
         }
     }
 
@@ -549,6 +468,7 @@ public final class URISupport {
      * @return the normalized uri
      * @throws URISyntaxException in thrown if the uri syntax is invalid
      * @throws UnsupportedEncodingException is thrown if encoding error
+     * @see #RAW_TOKEN_PREFIX
      * @see #RAW_TOKEN_START
      * @see #RAW_TOKEN_END
      */
diff --git a/camel-util/src/main/java/org/apache/camel/util/UnsafeUriCharactersEncoder.java b/camel-util/src/main/java/org/apache/camel/util/UnsafeUriCharactersEncoder.java
index 8273c53..d98b88d 100644
--- a/camel-util/src/main/java/org/apache/camel/util/UnsafeUriCharactersEncoder.java
+++ b/camel-util/src/main/java/org/apache/camel/util/UnsafeUriCharactersEncoder.java
@@ -19,8 +19,6 @@ package org.apache.camel.util;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Encoder for unsafe URI characters.
@@ -32,7 +30,6 @@ public final class UnsafeUriCharactersEncoder {
     private static BitSet unsafeCharactersHttp;
     private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
                                               'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f'};
-    private static final Pattern RAW_PATTERN = Pattern.compile("RAW\\([^\\)]+\\)");
 
     static {
         unsafeCharactersRfc1738 = new BitSet(256);
@@ -94,48 +91,11 @@ public final class UnsafeUriCharactersEncoder {
         return encode(s, unsafeCharactersHttp, checkRaw);
     }
 
-    private static List<Pair> checkRAW(String s) {
-        Matcher matcher = RAW_PATTERN.matcher(s);
-        List<Pair> answer = new ArrayList<>();
-        // Check all occurrences
-        while (matcher.find()) {
-            // TODO: should likely be matcher.end() - 1
-            answer.add(new Pair(matcher.start(), matcher.end()));
-        }
-        return answer;
-    }
-    
-    private static boolean isRaw(int index, List<Pair>pairs) {
-        for (Pair pair : pairs) {
-            if (index < pair.left) {
-                return false;
-            } else {
-                if (index >= pair.left) {
-                    if (index <= pair.right) {
-                        return true;
-                    } else {
-                        continue;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-    
-    private static class Pair {
-        int left;
-        int right;
-        Pair(int left, int right) {
-            this.left = left;
-            this.right = right;
-        }
-    }
-    
     // Just skip the encode for isRAW part
     public static String encode(String s, BitSet unsafeCharacters, boolean checkRaw) {
-        List<Pair> rawPairs;
+        List<Pair<Integer>> rawPairs;
         if (checkRaw) {
-            rawPairs = checkRAW(s); 
+            rawPairs = URISupport.scanRaw(s);
         } else {
             rawPairs = new ArrayList<>();
         }
@@ -170,7 +130,7 @@ public final class UnsafeUriCharactersEncoder {
                     char next = i + 1 < chars.length ? chars[i + 1] : ' ';
                     char next2 = i + 2 < chars.length ? chars[i + 2] : ' ';
 
-                    if (isHexDigit(next) && isHexDigit(next2) && !isRaw(i, rawPairs)) {
+                    if (isHexDigit(next) && isHexDigit(next2) && !URISupport.isRaw(i, rawPairs)) {
                         // its already encoded (decimal encoded) so just append as is
                         sb.append(ch);
                     } else {
diff --git a/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java b/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
index 7a79f2e..63e5dc5 100644
--- a/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
+++ b/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
@@ -18,14 +18,17 @@ package org.apache.camel.util;
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.junit.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
@@ -258,16 +261,20 @@ public class URISupportTest {
 
     @Test
     public void testSanitizeUriWithRawPassword() {
-        String uri = "http://foo?username=me&password=RAW(me#@123)&foo=bar";
+        String uri1 = "http://foo?username=me&password=RAW(me#@123)&foo=bar";
+        String uri2 = "http://foo?username=me&password=RAW{me#@123}&foo=bar";
         String expected = "http://foo?username=me&password=xxxxxx&foo=bar";
-        assertEquals(expected, URISupport.sanitizeUri(uri));
+        assertEquals(expected, URISupport.sanitizeUri(uri1));
+        assertEquals(expected, URISupport.sanitizeUri(uri2));
     }
 
     @Test
     public void testSanitizeUriRawUnsafePassword() {
-        String uri = "sftp://localhost/target?password=RAW(beforeAmp&afterAmp)&username=jrandom";
+        String uri1 = "sftp://localhost/target?password=RAW(beforeAmp&afterAmp)&username=jrandom";
+        String uri2 = "sftp://localhost/target?password=RAW{beforeAmp&afterAmp}&username=jrandom";
         String expected = "sftp://localhost/target?password=xxxxxx&username=jrandom";
-        assertEquals(expected, URISupport.sanitizeUri(uri));
+        assertEquals(expected, URISupport.sanitizeUri(uri1));
+        assertEquals(expected, URISupport.sanitizeUri(uri2));
     }
 
     @Test
@@ -302,6 +309,16 @@ public class URISupportTest {
     }
 
     @Test
+    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);
+
+        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", out2);
+    }
+
+    @Test
     public void testParseQuery() throws Exception {
         Map<String, Object> map = URISupport.parseQuery("password=secret&serviceName=somechat");
         assertEquals(2, map.size());
@@ -325,6 +342,24 @@ public class URISupportTest {
     }
 
     @Test
+    public void testParseQueryCurly() throws Exception {
+        Map<String, Object> map = URISupport.parseQuery("password=RAW{++?w0rd}&serviceName=somechat");
+        assertEquals(2, map.size());
+        assertEquals("RAW{++?w0rd}", map.get("password"));
+        assertEquals("somechat", map.get("serviceName"));
+
+        map = URISupport.parseQuery("password=RAW{++?)w&rd}&serviceName=somechat");
+        assertEquals(2, map.size());
+        assertEquals("RAW{++?)w&rd}", map.get("password"));
+        assertEquals("somechat", map.get("serviceName"));
+
+        map = URISupport.parseQuery("password=RAW{%2520w&rd}&serviceName=somechat");
+        assertEquals(2, map.size());
+        assertEquals("RAW{%2520w&rd}", map.get("password"));
+        assertEquals("somechat", map.get("serviceName"));
+    }
+
+    @Test
     public void testParseQueryLenient() throws Exception {
         try {
             URISupport.parseQuery("password=secret&serviceName=somechat&", false, false);
@@ -340,6 +375,48 @@ public class URISupportTest {
     }
 
     @Test
+    public void testScanRaw() {
+        List<Pair<Integer>> pairs1 = URISupport.scanRaw("password=RAW(++?5w0rd)&serviceName=somechat");
+        assertEquals(1, pairs1.size());
+        assertEquals(new Pair(9, 21), pairs1.get(0));
+
+        List<Pair<Integer>> pairs2 = URISupport.scanRaw("password=RAW{++?5w0rd}&serviceName=somechat");
+        assertEquals(1, pairs2.size());
+        assertEquals(new Pair(9, 21), pairs2.get(0));
+
+        List<Pair<Integer>> pairs3 = URISupport.scanRaw("password=RAW{++?)&0rd}&serviceName=somechat");
+        assertEquals(1, pairs3.size());
+        assertEquals(new Pair(9, 21), pairs3.get(0));
+
+        List<Pair<Integer>> pairs4 = URISupport.scanRaw("password1=RAW(++?}&0rd)&password2=RAW{++?)&0rd}&serviceName=somechat");
+        assertEquals(2, pairs4.size());
+        assertEquals(new Pair(10, 22), pairs4.get(0));
+        assertEquals(new Pair(34, 46), pairs4.get(1));
+    }
+
+    @Test
+    public void testIsRaw() {
+        List<Pair<Integer>> pairs = Arrays.asList(
+                new Pair(3, 5),
+                new Pair(8, 10));
+        for (int i = 0; i < 3; i++) {
+            assertFalse(URISupport.isRaw(i, pairs));
+        }
+        for (int i = 3; i < 6; i++) {
+            assertTrue(URISupport.isRaw(i, pairs));
+        }
+        for (int i = 6; i < 8; i++) {
+            assertFalse(URISupport.isRaw(i, pairs));
+        }
+        for (int i = 8; i < 11; i++) {
+            assertTrue(URISupport.isRaw(i, pairs));
+        }
+        for (int i = 11; i < 15; i++) {
+            assertFalse(URISupport.isRaw(i, pairs));
+        }
+    }
+
+    @Test
     public void testResolveRawParameterValues() throws Exception {
         Map<String, Object> map = URISupport.parseQuery("password=secret&serviceName=somechat");
         URISupport.resolveRawParameterValues(map);
@@ -361,6 +438,21 @@ public class URISupportTest {
     }
 
     @Test
+    public void testResolveRawParameterValuesCurly() throws Exception {
+        Map<String, Object> map = URISupport.parseQuery("password=RAW{++?w0rd}&serviceName=somechat");
+        URISupport.resolveRawParameterValues(map);
+        assertEquals(2, map.size());
+        assertEquals("++?w0rd", map.get("password"));
+        assertEquals("somechat", map.get("serviceName"));
+
+        map = URISupport.parseQuery("password=RAW{++?)w&rd}&serviceName=somechat");
+        URISupport.resolveRawParameterValues(map);
+        assertEquals(2, map.size());
+        assertEquals("++?)w&rd", map.get("password"));
+        assertEquals("somechat", map.get("serviceName"));
+    }
+
+    @Test
     public void testAppendParameterToUriAndReplaceExistingOne() throws Exception {
         Map<String, Object> newParameters = new HashMap<>();
         newParameters.put("foo", "456");