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 cd2ec6a CAMEL-12982: Support RAW{} syntax in URISupport (again) (#2716) cd2ec6a is described below commit cd2ec6abdad5c7af1b49dfe98ee902a2e3de87dd Author: Tadayoshi Sato <sato.tadayo...@gmail.com> AuthorDate: Fri Jan 18 23:11:14 2019 +0900 CAMEL-12982: Support RAW{} syntax in URISupport (again) (#2716) Also fixes compile error at camel-catalog --- .../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 +++++++- platforms/camel-catalog/pom.xml | 1 + 12 files changed, 702 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"); diff --git a/platforms/camel-catalog/pom.xml b/platforms/camel-catalog/pom.xml index 5b4068e..ed3bc10 100644 --- a/platforms/camel-catalog/pom.xml +++ b/platforms/camel-catalog/pom.xml @@ -138,6 +138,7 @@ <include>CamelContextJsonSchemaResolver.java</include> <include>CatalogHelper.java</include> <include>JSonSchemaHelper.java</include> + <include>Pair.java</include> <include>SuggestionStrategy.java</include> <include>TimePatternConverter.java</include> <include>UnsafeUriCharactersEncoder.java</include>