Author: davsclaus
Date: Mon Mar 25 15:52:58 2013
New Revision: 1460733
URL: http://svn.apache.org/r1460733
Log:
CAMEL-6176: Introduce RAW token for configuring uri options to tell Camel to
use the value as is. This allows people to configure passwords more easily.
Also fixed the parseQuery method to split parameters better, as regexp split on
& sign doesnt work if the & is used in values or keys etc.
Added:
camel/trunk/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java
camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FtpProducerRawPasswordTest.java
- copied, changed from r1460549,
camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FtpProducerTempPrefixTest.java
Modified:
camel/trunk/camel-core/src/main/java/org/apache/camel/impl/DefaultComponent.java
camel/trunk/camel-core/src/main/java/org/apache/camel/util/URISupport.java
camel/trunk/camel-core/src/test/java/org/apache/camel/util/URISupportTest.java
camel/trunk/components/camel-ftp/src/test/resources/users.properties
Modified:
camel/trunk/camel-core/src/main/java/org/apache/camel/impl/DefaultComponent.java
URL:
http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/impl/DefaultComponent.java?rev=1460733&r1=1460732&r2=1460733&view=diff
==============================================================================
---
camel/trunk/camel-core/src/main/java/org/apache/camel/impl/DefaultComponent.java
(original)
+++
camel/trunk/camel-core/src/main/java/org/apache/camel/impl/DefaultComponent.java
Mon Mar 25 15:52:58 2013
@@ -81,21 +81,30 @@ public abstract class DefaultComponent e
if (idx > -1) {
path = path.substring(0, idx);
}
+
Map<String, Object> parameters = URISupport.parseParameters(u);
+ // parameters using raw syntax: RAW(value)
+ // should have the token removed, so its only the value we have in
parameters, as we are about to create
+ // an endpoint and want to have the parameter values without the RAW
tokens
+ URISupport.resolveRawParameterValues(parameters);
// use encoded or raw uri?
uri = useRawUri() ? uri : encodedUri;
validateURI(uri, path, parameters);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Creating endpoint uri=[{}], path=[{}],
parameters=[{}]", new Object[]{URISupport.sanitizeUri(uri),
URISupport.sanitizePath(path), parameters});
+ if (LOG.isTraceEnabled()) {
+ // at trace level its okay to have parameters logged, that may
contain passwords
+ LOG.trace("Creating endpoint uri=[{}], path=[{}],
parameters=[{}]", new Object[]{URISupport.sanitizeUri(uri),
URISupport.sanitizePath(path), parameters});
+ } else if (LOG.isDebugEnabled()) {
+ // but at debug level only output sanitized uris
+ LOG.debug("Creating endpoint uri=[{}], path=[{}]", new
Object[]{URISupport.sanitizeUri(uri), URISupport.sanitizePath(path)});
}
Endpoint endpoint = createEndpoint(uri, path, parameters);
if (endpoint == null) {
return null;
}
- if (parameters != null && !parameters.isEmpty()) {
+ if (!parameters.isEmpty()) {
endpoint.configureProperties(parameters);
if (useIntrospectionOnEndpoint()) {
setProperties(endpoint, parameters);
Modified:
camel/trunk/camel-core/src/main/java/org/apache/camel/util/URISupport.java
URL:
http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/util/URISupport.java?rev=1460733&r1=1460732&r2=1460733&view=diff
==============================================================================
--- camel/trunk/camel-core/src/main/java/org/apache/camel/util/URISupport.java
(original)
+++ camel/trunk/camel-core/src/main/java/org/apache/camel/util/URISupport.java
Mon Mar 25 15:52:58 2013
@@ -36,6 +36,9 @@ import java.util.regex.Pattern;
*/
public final class URISupport {
+ public static final String RAW_TOKEN_START = "RAW(";
+ public static final String 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.
@@ -90,16 +93,22 @@ public final class URISupport {
/**
* Parses the query part of the uri (eg the parameters).
+ * <p/>
+ * The URI parameters will by default be URI encoded. However you can
define a parameter
+ * values with the syntax: <tt>key=RAW(value)</tt> which tells Camel to
not encode the value,
+ * and use the value as is (eg key=value) and the value has <b>not</b>
been encoded.
*
* @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_START
+ * @see #RAW_TOKEN_END
*/
public static Map<String, Object> parseQuery(String uri) 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.");
+ + "Check the uri and remove the trailing & marker.");
}
if (ObjectHelper.isEmpty(uri)) {
@@ -107,46 +116,94 @@ public final class URISupport {
return new LinkedHashMap<String, Object>(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<String, Object>();
- if (uri != null) {
- String[] parameters = uri.split("&");
- for (String parameter : parameters) {
- int p = parameter.indexOf("=");
- if (p >= 0) {
- // The replaceAll is an ugly workaround for
CAMEL-4954, awaiting a cleaner fix once CAMEL-4425
- // is fully resolved in all components
- String name = URLDecoder.decode(parameter.substring(0,
p), CHARSET);
- String value = URLDecoder.decode(parameter.substring(p
+ 1).replaceAll("%", "%25"), CHARSET);
-
- // does the key already exist?
- if (rc.containsKey(name)) {
- // yes it does, so make sure we can support
multiple values, but using a list
- // to hold the multiple values
- Object existing = rc.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>();
- String s = existing != null ?
existing.toString() : null;
- if (s != null) {
- list.add(s);
- }
- }
- list.add(value);
- rc.put(name, list);
- } else {
- rc.put(name, value);
- }
- } else {
- rc.put(parameter, null);
+
+ 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,
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, 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, isRaw);
+ }
+
return rc;
+
} catch (UnsupportedEncodingException e) {
URISyntaxException se = new URISyntaxException(e.toString(),
"Invalid encoding");
se.initCause(e);
@@ -154,6 +211,36 @@ public final class URISupport {
}
}
+ 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
+ value = URLDecoder.decode(value.replaceAll("%", "%25"), 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>();
+ 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);
+ }
+ }
+
/**
* Parses the query parameters of the uri (eg the query part).
*
@@ -179,6 +266,28 @@ public final class URISupport {
}
/**
+ * Traverses the given parameters, and resolve any parameter values which
uses the RAW token
+ * syntax: <tt>key=RAW(value)</tt>. This method will then remove the RAW
tokens, and replace
+ * the content of the value, with just the value.
+ *
+ * @param parameters the uri parameters
+ * @see #parseQuery(String)
+ * @see #RAW_TOKEN_START
+ * @see #RAW_TOKEN_END
+ */
+ public static void resolveRawParameterValues(Map<String, Object>
parameters) {
+ for (Map.Entry<String, Object> entry : parameters.entrySet()) {
+ if (entry.getValue() != null) {
+ String value = entry.getValue().toString();
+ if (value.startsWith(RAW_TOKEN_START) &&
value.endsWith(RAW_TOKEN_END)) {
+ value = value.substring(4, value.length() - 1);
+ entry.setValue(value);
+ }
+ }
+ }
+ }
+
+ /**
* Creates a URI with the given query
*
* @param uri the uri
@@ -277,7 +386,12 @@ public final class URISupport {
// only append if value is not null
if (value != null) {
rc.append("=");
- rc.append(URLEncoder.encode(value, CHARSET));
+ if (value.startsWith(RAW_TOKEN_START) &&
value.endsWith(RAW_TOKEN_END)) {
+ // do not encode RAW parameters
+ rc.append(value);
+ } else {
+ rc.append(URLEncoder.encode(value, CHARSET));
+ }
}
}
@@ -297,11 +411,17 @@ public final class URISupport {
/**
* Normalizes the uri by reordering the parameters so they are sorted and
thus
* we can use the uris for endpoint matching.
+ * <p/>
+ * The URI parameters will by default be URI encoded. However you can
define a parameter
+ * values with the syntax: <tt>key=RAW(value)</tt> which tells Camel to
not encode the value,
+ * and use the value as is (eg key=value) and the value has <b>not</b>
been encoded.
*
* @param uri the uri
* @return the normalized uri
* @throws URISyntaxException in thrown if the uri syntax is invalid
* @throws UnsupportedEncodingException is thrown if encoding error
+ * @see #RAW_TOKEN_START
+ * @see #RAW_TOKEN_END
*/
public static String normalizeUri(String uri) throws URISyntaxException,
UnsupportedEncodingException {
Added:
camel/trunk/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java
URL:
http://svn.apache.org/viewvc/camel/trunk/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java?rev=1460733&view=auto
==============================================================================
---
camel/trunk/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java
(added)
+++
camel/trunk/camel-core/src/test/java/org/apache/camel/issues/EndpointWithRawUriParameterTest.java
Mon Mar 25 15:52:58 2013
@@ -0,0 +1,116 @@
+/**
+ * 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.issues;
+
+import java.util.Map;
+
+import org.apache.camel.Component;
+import org.apache.camel.Consumer;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Endpoint;
+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.impl.DefaultComponent;
+import org.apache.camel.impl.DefaultEndpoint;
+import org.apache.camel.impl.DefaultProducer;
+
+public class EndpointWithRawUriParameterTest extends ContextTestSupport {
+
+ public final class MyComponent extends DefaultComponent {
+
+ @Override
+ protected Endpoint createEndpoint(String uri, String remaining,
Map<String, Object> parameters) throws Exception {
+ Endpoint answer = new MyEndpoint(uri, this);
+ setProperties(answer, parameters);
+ return answer;
+ }
+ }
+
+ public final class MyEndpoint extends DefaultEndpoint {
+
+ private String username;
+ private String password;
+
+ public MyEndpoint(String endpointUri, Component component) {
+ super(endpointUri, component);
+ }
+
+ @Override
+ public Producer createProducer() throws Exception {
+ return new DefaultProducer(this) {
+ @Override
+ public void process(Exchange exchange) throws Exception {
+ exchange.getIn().setHeader("username", getUsername());
+ exchange.getIn().setHeader("password", getPassword());
+ }
+ };
+ }
+
+ @Override
+ public Consumer createConsumer(Processor processor) throws Exception {
+ return null;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return true;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+ }
+
+ public void testRawUriParameter() throws Exception {
+ getMockEndpoint("mock:result").expectedMessageCount(1);
+ getMockEndpoint("mock:result").expectedHeaderReceived("username",
"scott");
+ getMockEndpoint("mock:result").expectedHeaderReceived("password",
"++%%w?rd");
+
+ template.sendBody("direct:start", "Hello World");
+
+ assertMockEndpointsSatisfied();
+
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ context.addComponent("mycomponent", new MyComponent());
+
+ from("direct:start")
+
.to("mycomponent:foo?password=RAW(++%%w?rd)&username=scott")
+ .to("mock:result");
+ }
+ };
+ }
+}
Modified:
camel/trunk/camel-core/src/test/java/org/apache/camel/util/URISupportTest.java
URL:
http://svn.apache.org/viewvc/camel/trunk/camel-core/src/test/java/org/apache/camel/util/URISupportTest.java?rev=1460733&r1=1460732&r2=1460733&view=diff
==============================================================================
---
camel/trunk/camel-core/src/test/java/org/apache/camel/util/URISupportTest.java
(original)
+++
camel/trunk/camel-core/src/test/java/org/apache/camel/util/URISupportTest.java
Mon Mar 25 15:52:58 2013
@@ -20,6 +20,8 @@ import java.net.URI;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.apache.camel.ContextTestSupport;
@@ -237,4 +239,49 @@ public class URISupportTest extends Cont
assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=secret&serviceName=someCoolChat",
out1);
}
+ public void testRawParameter() throws Exception {
+ String out =
URISupport.normalizeUri("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(++?w0rd)&serviceName=some
chat");
+
assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(++?w0rd)&serviceName=some+chat",
out);
+
+ String out2 =
URISupport.normalizeUri("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(foo
%% bar)&serviceName=some chat");
+
assertEquals("xmpp://camel-user@localhost:123/test-user@localhost?password=RAW(foo
%% bar)&serviceName=some+chat", out2);
+ }
+
+ public void testParseQuery() throws Exception {
+ Map<String, Object> map =
URISupport.parseQuery("password=secret&serviceName=somechat");
+ assertEquals(2, map.size());
+ assertEquals("secret", map.get("password"));
+ assertEquals("somechat", map.get("serviceName"));
+
+ 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"));
+ }
+
+ public void testResolveRawParameterValues() throws Exception {
+ Map<String, Object> map =
URISupport.parseQuery("password=secret&serviceName=somechat");
+ URISupport.resolveRawParameterValues(map);
+ assertEquals(2, map.size());
+ assertEquals("secret", map.get("password"));
+ assertEquals("somechat", map.get("serviceName"));
+
+ 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"));
+ }
+
}
\ No newline at end of file
Copied:
camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FtpProducerRawPasswordTest.java
(from r1460549,
camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FtpProducerTempPrefixTest.java)
URL:
http://svn.apache.org/viewvc/camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FtpProducerRawPasswordTest.java?p2=camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FtpProducerRawPasswordTest.java&p1=camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FtpProducerTempPrefixTest.java&r1=1460549&r2=1460733&rev=1460733&view=diff
==============================================================================
---
camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FtpProducerTempPrefixTest.java
(original)
+++
camel/trunk/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/FtpProducerRawPasswordTest.java
Mon Mar 25 15:52:58 2013
@@ -22,9 +22,9 @@ import org.apache.camel.converter.IOConv
import org.junit.Test;
/**
- * Unit test to verify that Camel can build remote directory on FTP server if
missing (full or part of).
+ * Unit test for password parameter using RAW value
*/
-public class FtpProducerTempPrefixTest extends FtpServerTestSupport {
+public class FtpProducerRawPasswordTest extends FtpServerTestSupport {
@Override
public boolean isUseRouteBuilder() {
@@ -32,14 +32,18 @@ public class FtpProducerTempPrefixTest e
}
private String getFtpUrl() {
- return "ftp://admin@localhost:" + getPort() +
"/upload/user/claus?binary=false&password=admin&tempPrefix=.uploading";
+ // START SNIPPET: e1
+ // notice how we use RAW(value) to tell Camel that the password field
is a RAW value and should not be
+ // uri encoded. This allows us to use the password 'as is' containing
+ & and other signs
+ return "ftp://joe@localhost:" + getPort() +
"/upload?password=RAW(p+%w0&r)d)&binary=false";
+ // END SNIPPET: e1
}
@Test
- public void testProduceTempPrefixTest() throws Exception {
- sendFile(getFtpUrl(), "Hello World", "claus.txt");
+ public void testRawPassword() throws Exception {
+ sendFile(getFtpUrl(), "Hello World", "camel.txt");
- File file = new File(FTP_ROOT_DIR + "/upload/user/claus/claus.txt");
+ File file = new File(FTP_ROOT_DIR + "/upload/camel.txt");
assertTrue("The uploaded file should exists", file.exists());
assertEquals("Hello World", IOConverter.toString(file, null));
}
Modified: camel/trunk/components/camel-ftp/src/test/resources/users.properties
URL:
http://svn.apache.org/viewvc/camel/trunk/components/camel-ftp/src/test/resources/users.properties?rev=1460733&r1=1460732&r2=1460733&view=diff
==============================================================================
--- camel/trunk/components/camel-ftp/src/test/resources/users.properties
(original)
+++ camel/trunk/components/camel-ftp/src/test/resources/users.properties Mon
Mar 25 15:52:58 2013
@@ -17,3 +17,7 @@ [email protected]=true
ftpserver.user.anonymous
ftpserver.user.anonymous.homedirectory=./target/res/home
ftpserver.user.anonymous.writepermission=false
+ftpserver.user.joe
+ftpserver.user.joe.userpassword=p+%w0&r)d
+ftpserver.user.joe.homedirectory=./target/res/home
+ftpserver.user.joe.writepermission=true