This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.18.x by this push:
new 6a647ce0be0c [backport camel-4.18.x] CAMEL-24973: camel-util - Align
URISupport.sanitizeUri with the endpoint URI parser (#26962)
6a647ce0be0c is described below
commit 6a647ce0be0cc9825aeb4b064df35d048261cb81
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Sep 28 18:37:05 2026 +0200
[backport camel-4.18.x] CAMEL-24973: camel-util - Align
URISupport.sanitizeUri with the endpoint URI parser (#26962)
- End RAW(...) and RAW{...} values the same way URIScanner does: at the
closing bracket followed by & or the end
- Use one userinfo masking rule for URISupport.sanitizeUri and
URISupport.sanitizePath, applied to every uri in the text
- Match sensitive keys ignoring dashes, as options can be configured in
dash case
- Apply the keywords from addSanitizeKeywords to every parameter and keep
the ones added earlier
- Mask sensitive values in the "Unknown parameters" error messages
Adapted to 4.18.x: SensitiveUtils.maskUserInfoCredentials (CAMEL-24256) is
not on this branch, so only the
package-private userinfo helpers that URISupport uses are added (and the
javadoc points at them), and the
SensitiveUtilsTest changes are left out. The upgrade guide notes live on
main.
Backport of #26803 (cherry picked from commit
84f265e8d1b3548fd03f0fd0474cf6c410e5e9e6).
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../component/jetty12/JettyHttpComponent12.java | 4 +-
.../impl/engine/DefaultCamelContextExtension.java | 2 +-
.../apache/camel/impl/DefaultComponentTest.java | 13 ++
.../org/apache/camel/support/DefaultComponent.java | 2 +-
.../camel/support/ScheduledPollConsumer.java | 3 +-
.../java/org/apache/camel/util/SensitiveUtils.java | 115 ++++++++++++++++
.../java/org/apache/camel/util/URISupport.java | 152 ++++++++++++++++-----
.../java/org/apache/camel/util/URISupportTest.java | 113 +++++++++++++++
8 files changed, 367 insertions(+), 37 deletions(-)
diff --git
a/components/camel-jetty/src/main/java/org/apache/camel/component/jetty12/JettyHttpComponent12.java
b/components/camel-jetty/src/main/java/org/apache/camel/component/jetty12/JettyHttpComponent12.java
index 223644a03925..11ee5dc65a60 100644
---
a/components/camel-jetty/src/main/java/org/apache/camel/component/jetty12/JettyHttpComponent12.java
+++
b/components/camel-jetty/src/main/java/org/apache/camel/component/jetty12/JettyHttpComponent12.java
@@ -29,6 +29,7 @@ import org.apache.camel.component.jetty.JettyHttpEndpoint;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.PropertyBindingSupport;
+import org.apache.camel.util.URISupport;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
@@ -118,7 +119,8 @@ public class JettyHttpComponent12 extends
JettyHttpComponent {
throw new IllegalArgumentException(
"There are " + properties.size() + "
parameters that couldn't be set on the SocketConnector."
+ " Check the uri
if the parameters are spelt correctly and that they are properties of the
SelectChannelConnector."
- + " Unknown
parameters=[" + properties + "]");
+ + " Unknown
parameters=["
+ +
URISupport.sanitizeParameters(properties) + "]");
}
}
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
index 944770e21380..351af81b5ffb 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
@@ -591,7 +591,7 @@ class DefaultCamelContextExtension implements
ExtendedCamelContext {
@Override
public void setAdditionalSensitiveKeywords(String
additionalSensitiveKeywords) {
this.additionalSensitiveKeywords = additionalSensitiveKeywords;
- // re-configure sensitive keywords asap so they take effect immediately
+ // add the keywords to the ones used when sanitizing uris, they apply
to the whole JVM and cannot be removed
URISupport.addSanitizeKeywords(additionalSensitiveKeywords);
}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultComponentTest.java
b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultComponentTest.java
index 54ace772ad9d..4e054ebfe9f5 100644
---
a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultComponentTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultComponentTest.java
@@ -26,15 +26,18 @@ import org.apache.camel.CamelContext;
import org.apache.camel.ContextTestSupport;
import org.apache.camel.Endpoint;
import org.apache.camel.NoSuchBeanException;
+import org.apache.camel.ResolveEndpointFailedException;
import org.apache.camel.TypeConversionException;
import org.apache.camel.spi.Registry;
import org.apache.camel.support.DefaultComponent;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Unit test for helper methods on the DefaultComponent.
@@ -270,6 +273,16 @@ public class DefaultComponentTest extends
ContextTestSupport {
assertEquals("camelContext must be specified", e.getMessage());
}
+ @Test
+ public void testUnknownParametersAreSanitized() {
+ ResolveEndpointFailedException e =
assertThrows(ResolveEndpointFailedException.class,
+ () ->
context.getEndpoint("timer:foo?privateKeyPassphrase=secret&foo=bar"),
+ "Should have thrown a ResolveEndpointFailedException");
+
+ assertTrue(e.getMessage().endsWith("Unknown parameters=[{foo=bar,
privateKeyPassphrase=xxxxxx}]"), e.getMessage());
+ assertFalse(e.getMessage().contains("secret"), e.getMessage());
+ }
+
@Override
protected Registry createCamelRegistry() throws Exception {
Date bean1 = new Date(10);
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/DefaultComponent.java
b/core/camel-support/src/main/java/org/apache/camel/support/DefaultComponent.java
index 5e70d60749e5..3ee462f4f6ab 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/DefaultComponent.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/DefaultComponent.java
@@ -302,7 +302,7 @@ public abstract class DefaultComponent extends
ServiceSupport implements Compone
uri, "There are " + param.size()
+ " parameters that couldn't be set on the endpoint."
+ " Check the uri if the parameters are spelt
correctly and that they are properties of the endpoint."
- + " Unknown parameters=[" + param + "]");
+ + " Unknown parameters=[" +
URISupport.sanitizeParameters(param) + "]");
}
}
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java
b/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java
index c9cfdcb1258a..d1cc3a3764f6 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/ScheduledPollConsumer.java
@@ -39,6 +39,7 @@ import org.apache.camel.spi.ScheduledPollConsumerScheduler;
import org.apache.camel.support.service.ServiceHelper;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.PropertiesHelper;
+import org.apache.camel.util.URISupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -668,7 +669,7 @@ public abstract class ScheduledPollConsumer extends
DefaultConsumer
getEndpoint(), "There are " + copy.size()
+ " scheduler parameters that couldn't
be set on the endpoint."
+ " Check the uri if the parameters are
spelt correctly and that they are properties of the endpoint."
- + " Unknown parameters=[" + copy + "]");
+ + " Unknown parameters=[" +
URISupport.sanitizeParameters(copy) + "]");
}
}
afterConfigureScheduler(scheduler, newScheduler);
diff --git
a/core/camel-util/src/main/java/org/apache/camel/util/SensitiveUtils.java
b/core/camel-util/src/main/java/org/apache/camel/util/SensitiveUtils.java
index ca9cb41fb7b7..2f06aa898cac 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/SensitiveUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/SensitiveUtils.java
@@ -240,4 +240,119 @@ public final class SensitiveUtils {
return SENSITIVE_KEYS.contains(text);
}
+ /**
+ * Masks the userinfo password of every {@code scheme://user:password@} in
the source.
+ *
+ * @param source the source
+ * @param mask the replacement string for the password
+ * @param text whether the source is free text, where a whitespace or a
quote ends a URI
+ */
+ static String maskUserInfo(String source, String mask, boolean text) {
+ int idx = source.indexOf("://");
+ if (idx == -1) {
+ return source;
+ }
+ StringBuilder sb = null;
+ int copied = 0;
+ while (idx != -1) {
+ int next = idx + 3;
+ // there must be a scheme before ://
+ if (idx > 0 && isSchemeChar(source.charAt(idx - 1))) {
+ int[] password = passwordRange(source, idx + 3, text);
+ if (password != null) {
+ if (sb == null) {
+ sb = new StringBuilder(source.length());
+ }
+ sb.append(source, copied, password[0]).append(mask);
+ copied = password[1];
+ next = password[1];
+ }
+ }
+ idx = source.indexOf("://", next);
+ }
+ if (sb == null) {
+ return source;
+ }
+ return sb.append(source, copied, source.length()).toString();
+ }
+
+ /**
+ * Masks the password of a URI path (without the scheme) that starts with
{@code user:password@}.
+ *
+ * @param path the path
+ * @param mask the replacement string for the password
+ */
+ static String maskPathUserInfo(String path, String mask) {
+ int[] password = passwordRange(path, 0, false);
+ if (password == null) {
+ return path;
+ }
+ return path.substring(0, password[0]) + mask +
path.substring(password[1]);
+ }
+
+ // the start and end of the password in the userinfo of the authority that
begins at start, or null if there is none
+ private static int[] passwordRange(String source, int start, boolean text)
{
+ int len = source.length();
+ // the user ends at the first colon
+ int colon = -1;
+ for (int i = start; i < len && colon == -1; i++) {
+ char ch = source.charAt(i);
+ if (ch == ':') {
+ colon = i;
+ } else if (ch == '/' || ch == '?' || text && isTextDelimiter(ch)) {
+ return null;
+ }
+ }
+ if (colon == -1) {
+ return null;
+ }
+ // the userinfo ends at the last @ before the path or query
+ int at = -1;
+ for (int i = colon + 1; i < len; i++) {
+ char ch = source.charAt(i);
+ if (ch == '@') {
+ at = i;
+ } else if (ch == '/' || ch == '?' || text && isTextDelimiter(ch)) {
+ break;
+ }
+ }
+ if (at == -1) {
+ // a password with an unencoded / or ? ends at the last @ before a
query parameter, the next uri,
+ // or (in free text) a whitespace or a quote
+ for (int i = colon + 1; i < len; i++) {
+ char ch = source.charAt(i);
+ if (ch == '@') {
+ at = i;
+ } else if ((ch == '?' || ch == '&') &&
isQueryParameter(source, i + 1)
+ || ch == ':' && source.startsWith("//", i + 1)
+ || text && isTextDelimiter(ch)) {
+ break;
+ }
+ }
+ }
+ return at == -1 ? null : new int[] { colon + 1, at };
+ }
+
+ // whether the text at the given index is a query parameter key followed
by =
+ private static boolean isQueryParameter(String source, int from) {
+ for (int i = from; i < source.length(); i++) {
+ char ch = source.charAt(i);
+ if (ch == '=') {
+ return i > from;
+ }
+ if (ch == '&' || ch == '?' || ch == '@' || ch == '/' || ch == ':'
|| Character.isWhitespace(ch)) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isSchemeChar(char ch) {
+ return Character.isLetterOrDigit(ch) || ch == '+' || ch == '.' || ch
== '-';
+ }
+
+ private static boolean isTextDelimiter(char ch) {
+ return Character.isWhitespace(ch) || ch == '"' || ch == '\'';
+ }
+
}
diff --git
a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
index 05610ddb1520..358bb33af274 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
@@ -27,6 +27,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -34,6 +35,7 @@ import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.apache.camel.util.CamelURIParser.URI_ALREADY_NORMALIZED;
@@ -51,31 +53,22 @@ public final class URISupport {
public static final char[] RAW_TOKEN_START = { '(', '{' };
public static final char[] RAW_TOKEN_END = { ')', '}' };
- @SuppressWarnings("RegExpUnnecessaryNonCapturingGroup")
- private static final String PRE_SECRETS_FORMAT =
"([?&][^=]*(?:%s)[^=]*)=(RAW(([{][^}]*[}])|([(][^)]*[)]))|[^&]*)";
+ // Match the key of a query parameter as first capture group (the value
starts after the = sign)
+ private static final Pattern QUERY_PARAMETER_KEY =
Pattern.compile("[?&]([^?&=]*)=");
- // 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 ALL_SECRETS
- =
Pattern.compile(PRE_SECRETS_FORMAT.formatted(SensitiveUtils.getSensitivePattern()),
- Pattern.CASE_INSENSITIVE);
+ // Match any of the sensitive keywords (such as passphrase, password or
secret key) in a query parameter key
+ private static final Pattern SENSITIVE_KEYWORDS
+ = Pattern.compile(SensitiveUtils.getSensitivePattern(),
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("(.*?:)(.*)(@)");
+ // use xxxxxx as replacement as that works well with JMX also
+ private static final String MASK = "xxxxxx";
private static final Charset CHARSET = StandardCharsets.UTF_8;
private static final String EMPTY_QUERY_STRING = "";
- private static Pattern EXTRA_SECRETS;
+ // custom keywords (lower case and without dashes) added by
addSanitizeKeywords
+ private static volatile Set<String> extraKeywords = Set.of();
private URISupport() {
// Helper class
@@ -88,16 +81,26 @@ public final class URISupport {
* @param keywords keywords separated by comma
*/
public static synchronized void addSanitizeKeywords(String keywords) {
- StringJoiner pattern = new StringJoiner("|");
+ if (keywords == null) {
+ return;
+ }
+ Set<String> answer = new LinkedHashSet<>(extraKeywords);
for (String key : keywords.split(",")) {
- // skip existing keys
- key = key.toLowerCase(Locale.ROOT).trim();
- if (!SensitiveUtils.containsSensitive(key)) {
- pattern.add("\\Q" + key.toLowerCase(Locale.ROOT) + "\\E");
+ // keys are matched without dashes, the same way as the parameter
names
+ key = key.toLowerCase(Locale.ROOT).trim().replace("-", "");
+ // skip empty and existing keys
+ if (!key.isEmpty() && !SensitiveUtils.containsSensitive(key)) {
+ answer.add(key);
}
}
- EXTRA_SECRETS = Pattern.compile(PRE_SECRETS_FORMAT.formatted(pattern),
- Pattern.CASE_INSENSITIVE);
+ extraKeywords = Set.copyOf(answer);
+ }
+
+ /**
+ * Removes the keywords added by {@link #addSanitizeKeywords(String)}.
Only for testing.
+ */
+ static synchronized void resetSanitizeKeywords() {
+ extraKeywords = Set.of();
}
/**
@@ -106,21 +109,104 @@ public final class URISupport {
* @param uri The uri to sanitize.
* @return Returns null if the uri is null, otherwise the URI with the
passphrase, password or secretKey
* sanitized.
- * @see #ALL_SECRETS and #USERINFO_PASSWORD for the matched pattern
+ * @see SensitiveUtils#maskUserInfo(String, String, boolean) for
how the userinfo password is found
*/
public static String sanitizeUri(String uri) {
- // use xxxxx as replacement as that works well with JMX also
String sanitized = uri;
if (uri != null) {
- sanitized = ALL_SECRETS.matcher(sanitized).replaceAll("$1=xxxxxx");
- if (EXTRA_SECRETS != null) {
- sanitized =
EXTRA_SECRETS.matcher(sanitized).replaceFirst("$1=xxxxxx");
- }
- sanitized =
USERINFO_PASSWORD.matcher(sanitized).replaceFirst("$1xxxxxx$3");
+ sanitized = sanitizeQueryParameters(sanitized);
+ sanitized = SensitiveUtils.maskUserInfo(sanitized, MASK, false);
}
return sanitized;
}
+ /**
+ * Returns a copy of the parameters where the values of sensitive
parameters (such as passwords) are masked, using
+ * the same rules as {@link #sanitizeUri(String)}.
+ *
+ * @param parameters the parameters
+ * @return null if the parameters are null, otherwise a copy of
the parameters with the sensitive values
+ * masked
+ */
+ public static Map<String, Object> sanitizeParameters(Map<String, Object>
parameters) {
+ if (parameters == null) {
+ return null;
+ }
+ Map<String, Object> answer = new LinkedHashMap<>(parameters.size());
+ for (Map.Entry<String, Object> entry : parameters.entrySet()) {
+ Object value = entry.getValue();
+ answer.put(entry.getKey(), value != null &&
isSensitiveKey(entry.getKey()) ? MASK : value);
+ }
+ return answer;
+ }
+
+ private static String sanitizeQueryParameters(String uri) {
+ StringBuilder sb = null;
+ int copied = 0;
+ int pos = 0;
+ Matcher matcher = QUERY_PARAMETER_KEY.matcher(uri);
+ while (matcher.find(pos)) {
+ // the value starts after the = sign
+ pos = matcher.end();
+ if (isSensitiveKey(matcher.group(1))) {
+ int end = valueEnd(uri, pos);
+ if (sb == null) {
+ sb = new StringBuilder(uri.length());
+ }
+ sb.append(uri, copied, pos).append(MASK);
+ copied = end;
+ pos = end;
+ }
+ // otherwise continue searching from the start of the value, as it
can contain another uri
+ }
+ if (sb == null) {
+ return uri;
+ }
+ return sb.append(uri, copied, uri.length()).toString();
+ }
+
+ private static boolean isSensitiveKey(String key) {
+ // ignore dashes the same way as SensitiveUtils.containsSensitive, as
options can be configured in dash case
+ String text = key.toLowerCase(Locale.ROOT).replace("-", "");
+ if (SENSITIVE_KEYWORDS.matcher(text).find()) {
+ return true;
+ }
+ for (String keyword : extraKeywords) {
+ if (text.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static int valueEnd(String uri, int start) {
+ int open = start + RAW_TOKEN_PREFIX.length();
+ if (uri.startsWith(RAW_TOKEN_PREFIX, start) && open < uri.length()) {
+ for (int i = 0; i < RAW_TOKEN_START.length; i++) {
+ if (uri.charAt(open) == RAW_TOKEN_START[i]) {
+ return rawValueEnd(uri, open + 1, RAW_TOKEN_END[i]);
+ }
+ }
+ }
+ int end = uri.indexOf('&', start);
+ return end != -1 ? end : uri.length();
+ }
+
+ private static int rawValueEnd(String uri, int from, char tokenEnd) {
+ // a RAW value ends at the closing bracket that is followed by & or
the end, the same way as URIScanner parses it
+ int last = -1;
+ for (int i = from; i < uri.length(); i++) {
+ if (uri.charAt(i) == tokenEnd) {
+ if (i + 1 == uri.length() || uri.charAt(i + 1) == '&') {
+ return i + 1;
+ }
+ last = i;
+ }
+ }
+ // no such bracket, which happens when the uri is part of a longer
text, so mask up to the last closing bracket
+ return last != -1 ? last + 1 : uri.length();
+ }
+
public static String textBlockToSingleLine(String uri) {
// Java 17 text blocks have new lines with optional white space
if (uri != null) {
@@ -159,7 +245,7 @@ public final class URISupport {
public static String sanitizePath(String path) {
String sanitized = path;
if (path != null) {
- sanitized =
PATH_USERINFO_PASSWORD.matcher(sanitized).replaceFirst("$1xxxxxx$3");
+ sanitized = SensitiveUtils.maskPathUserInfo(sanitized, MASK);
}
return sanitized;
}
diff --git
a/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
b/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
index 48f908ba55d8..5f2b0717434f 100644
--- a/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
+++ b/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
@@ -25,9 +25,11 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -39,6 +41,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class URISupportTest {
+ @AfterEach
+ public void resetSanitizeKeywords() {
+ // the keywords added by some tests are global
+ URISupport.resetSanitizeKeywords();
+ }
+
@Test
public void testNormalizeEndpointUri() throws Exception {
String out1 =
URISupport.normalizeUri("smtp://localhost?username=davsclaus&password=secret");
@@ -659,4 +667,109 @@ public class URISupportTest {
assertEquals(expected, URISupport.sanitizeUri(uri1));
}
+ @Test
+ public void testSanitizeCustomKeysAppliedToAllParameters() {
+ URISupport.addSanitizeKeywords("pincode");
+ assertThat(URISupport.sanitizeUri("my:host?pincode=1111&pincode=2222"))
+ .isEqualTo("my:host?pincode=xxxxxx&pincode=xxxxxx");
+
+ // keywords added by an earlier call are kept
+ URISupport.addSanitizeKeywords("otp-code");
+
assertThat(URISupport.sanitizeUri("my:host?otpCode=123456&pincode=1111&otp-code=654321"))
+
.isEqualTo("my:host?otpCode=xxxxxx&pincode=xxxxxx&otp-code=xxxxxx");
+
+ // only built-in or empty keywords, which must not mask any other
parameter
+ URISupport.addSanitizeKeywords("password, ");
+ URISupport.addSanitizeKeywords(null);
+
assertThat(URISupport.sanitizeUri("my:host?foo=bar&timeout=5000")).isEqualTo("my:host?foo=bar&timeout=5000");
+ }
+
+ @Test
+ public void testSanitizeUriRawValueWithClosingBracket() {
+ // a RAW value ends at the closing bracket followed by & or the end,
the same way as when parsing the query
+
assertThat(URISupport.sanitizeUri("ftp://host/dir?password=RAW(se)cret)&binary=true"))
+ .isEqualTo("ftp://host/dir?password=xxxxxx&binary=true");
+
assertThat(URISupport.sanitizeUri("ftp://host/dir?password=RAW{se}cret}&binary=true"))
+ .isEqualTo("ftp://host/dir?password=xxxxxx&binary=true");
+
assertThat(URISupport.sanitizeUri("ftp://host/dir?binary=true&password=RAW()s3cr3t)"))
+ .isEqualTo("ftp://host/dir?binary=true&password=xxxxxx");
+
assertThat(URISupport.sanitizeUri("foo:bar?password=RAW(++?)w&rd)&serviceName=somechat"))
+ .isEqualTo("foo:bar?password=xxxxxx&serviceName=somechat");
+ }
+
+ @Test
+ public void testSanitizeUriRawValueInText() {
+ // the uri is part of a longer text, such as a route label, so the RAW
value is masked up to the last bracket
+
assertThat(URISupport.sanitizeUri("from[ftp://host/dir?password=RAW(se)cret)]"))
+ .isEqualTo("from[ftp://host/dir?password=xxxxxx]");
+
assertThat(URISupport.sanitizeUri("from[ftp://host/dir?password=RAW(secr...]"))
+ .isEqualTo("from[ftp://host/dir?password=xxxxxx");
+ }
+
+ @Test
+ public void testSanitizeUriWithUserInfoAndOtherUri() {
+ assertThat(URISupport.sanitizeUri(
+
"ftp://joe:[email protected]/in?callbackUrl=http://cb.example.com:8080/x&[email protected]"))
+ .isEqualTo(
+
"ftp://joe:[email protected]/in?callbackUrl=http://cb.example.com:8080/x&[email protected]");
+ // the userinfo ends at the last @ before the path
+
assertThat(URISupport.sanitizeUri("ftp://joe:p@[email protected]/in"))
+ .isEqualTo("ftp://joe:[email protected]/in");
+ // every uri in the text is sanitized
+ assertThat(URISupport.sanitizeUri("From[ftp://joe:secret1@host1/in] ->
To[sftp://bob:secret2@host2/out]"))
+ .isEqualTo("From[ftp://joe:xxxxxx@host1/in] ->
To[sftp://bob:xxxxxx@host2/out]");
+ // no userinfo
+
assertThat(URISupport.sanitizeUri("smtp://host:[email protected]")).isEqualTo("smtp://host:[email protected]");
+
assertThat(URISupport.sanitizeUri("ftp://joe@host:21/[email protected]"))
+ .isEqualTo("ftp://joe@host:21/[email protected]");
+ }
+
+ @Test
+ public void testSanitizeUriDashCaseKeys() {
+ assertThat(URISupport.sanitizeUri(
+
"my:host?access-key=A1&private-key=P2&connection-string=C3&sasl-jaas-config=J4&pass-phrase=P5&foo-bar=keep"))
+ .isEqualTo(
+
"my:host?access-key=xxxxxx&private-key=xxxxxx&connection-string=xxxxxx&sasl-jaas-config=xxxxxx&pass-phrase=xxxxxx&foo-bar=keep");
+ }
+
+ @Test
+ public void testSanitizeUriNestedUri() {
+
assertThat(URISupport.sanitizeUri("http://host/cb?url=http://other/path?token=abc&foo=bar"))
+
.isEqualTo("http://host/cb?url=http://other/path?token=xxxxxx&foo=bar");
+ }
+
+ @Test
+ public void testSanitizeParameters() {
+ Map<String, Object> parameters = new LinkedHashMap<>();
+ parameters.put("password", "secret");
+ parameters.put("private-key", "pk");
+ parameters.put("token", null);
+ parameters.put("foo", "bar");
+
+ assertThat(URISupport.sanitizeParameters(parameters))
+ .containsExactly(entry("password", "xxxxxx"),
entry("private-key", "xxxxxx"), entry("token", null),
+ entry("foo", "bar"));
+ // the given parameters are not changed
+ assertThat(parameters).containsEntry("password", "secret");
+ assertThat(URISupport.sanitizeParameters(null)).isNull();
+ }
+
+ @Test
+ public void testSanitizeUriWithUserInfoPasswordWithSlashOrQuestionMark() {
+
assertThat(URISupport.sanitizeUri("ftp://joe:pa/ss@host/dir")).isEqualTo("ftp://joe:xxxxxx@host/dir");
+
assertThat(URISupport.sanitizeUri("ftp://joe:pa?ss@host/dir?binary=true"))
+ .isEqualTo("ftp://joe:xxxxxx@host/dir?binary=true");
+
assertThat(URISupport.sanitizeUri("ftp://joe:p/a@b@host/dir")).isEqualTo("ftp://joe:xxxxxx@host/dir");
+ // a password that contains a query parameter (?key=value) cannot be
told apart from host:port?key=value@...
+ // (see smtp://host:[email protected]), so it is not masked
+
assertThat(URISupport.sanitizeUri("ftp://joe:p?a=b@host/dir")).isEqualTo("ftp://joe:p?a=b@host/dir");
+ }
+
+ @Test
+ public void testSanitizePathWithUserInfoAndOtherAt() {
+
assertThat(URISupport.sanitizePath("joe:secret@host/[email protected]"))
+ .isEqualTo("joe:xxxxxx@host/[email protected]");
+
assertThat(URISupport.sanitizePath("joe:pa/ss@host/dir")).isEqualTo("joe:xxxxxx@host/dir");
+ }
+
}