diff --git a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/AbstractCloudFoundryConnectorRelationalServiceTest.java b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/AbstractCloudFoundryConnectorRelationalServiceTest.java index f11cd35..cd10667 100644 --- a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/AbstractCloudFoundryConnectorRelationalServiceTest.java +++ b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/AbstractCloudFoundryConnectorRelationalServiceTest.java @@ -2,6 +2,7 @@ package org.springframework.cloud.cloudfoundry; import org.springframework.cloud.service.ServiceInfo; import org.springframework.cloud.service.common.RelationalServiceInfo; +import org.springframework.cloud.util.UriInfo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertEquals; @@ -19,8 +20,8 @@ public abstract class AbstractCloudFoundryConnectorRelationalServiceTest extends payload = payload.replace("$serviceName", serviceName); payload = payload.replace("$hostname", hostname); payload = payload.replace("$port", Integer.toString(port)); - payload = payload.replace("$user", user); - payload = payload.replace("$password", password); + payload = payload.replace("$user", UriInfo.urlEncode(user)); + payload = payload.replace("$password", UriInfo.urlEncode(password)); payload = payload.replace("$name", name); return payload; diff --git a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorDB2ServiceTest.java b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorDB2ServiceTest.java index 5c39c7c..dcd76f3 100644 --- a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorDB2ServiceTest.java +++ b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorDB2ServiceTest.java @@ -2,11 +2,14 @@ package org.springframework.cloud.cloudfoundry; import org.junit.Test; import org.springframework.cloud.service.ServiceInfo; -import org.springframework.cloud.service.common.MysqlServiceInfo; import org.springframework.cloud.service.common.DB2ServiceInfo; +import org.springframework.cloud.service.common.MysqlServiceInfo; +import org.springframework.cloud.service.common.RelationalServiceInfo; +import org.springframework.cloud.util.UriInfo; import java.util.List; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.when; @@ -31,6 +34,22 @@ public class CloudFoundryConnectorDB2ServiceTest extends AbstractUserProvidedSer assertUriBasedServiceInfoFields(info, DB2_SCHEME, hostname, port, username, password, INSTANCE_NAME); } + @Test + public void db2ServiceCreationWithSpecialChars() { + String userWithSpecialChars = "u%u:u+"; + String passwordWithSpecialChars = "p%p:p+"; + when(mockEnvironment.getEnvValue("VCAP_SERVICES")) + .thenReturn(getServicesPayload( + getUserProvidedServicePayload(SERVICE_NAME, hostname, port, userWithSpecialChars, + passwordWithSpecialChars, INSTANCE_NAME, DB2_SCHEME + ":"))); + List serviceInfos = testCloudConnector.getServiceInfos(); + + ServiceInfo info = getServiceInfo(serviceInfos, SERVICE_NAME); + assertServiceFoundOfType(info, DB2ServiceInfo.class); + assertEquals(getJdbcUrl("db2", hostname, port, INSTANCE_NAME, userWithSpecialChars, passwordWithSpecialChars), ((RelationalServiceInfo) info).getJdbcUrl()); + assertUriBasedServiceInfoFields(info, DB2_SCHEME, hostname, port, userWithSpecialChars, passwordWithSpecialChars, INSTANCE_NAME); + } + @Test public void db2ServiceCreationWithNoUri() { when(mockEnvironment.getEnvValue("VCAP_SERVICES")) @@ -65,6 +84,12 @@ public class CloudFoundryConnectorDB2ServiceTest extends AbstractUserProvidedSer } protected String getJdbcUrl(String scheme, String name) { - return String.format("%s%s://%s:%d/%s:user=%s;password=%s;", JDBC_PREFIX, scheme, hostname, port, name, username, password); + return String.format("%s%s://%s:%d/%s:user=%s;password=%s;", JDBC_PREFIX, scheme, hostname, port, name, + UriInfo.urlEncode(username), UriInfo.urlEncode(password)); + } + + private String getJdbcUrl(String scheme, String hostname, int port, String name, String user, String password) { + return String.format("%s%s://%s:%d/%s:user=%s;password=%s;", JDBC_PREFIX, scheme, hostname, port, name, + UriInfo.urlEncode(user), UriInfo.urlEncode(password)); } } diff --git a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorMysqlServiceTest.java b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorMysqlServiceTest.java index f40b2aa..1bbe624 100644 --- a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorMysqlServiceTest.java +++ b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorMysqlServiceTest.java @@ -1,13 +1,16 @@ package org.springframework.cloud.cloudfoundry; -import static org.mockito.Mockito.when; -import static org.springframework.cloud.service.common.MysqlServiceInfo.MYSQL_SCHEME; - -import java.util.List; - import org.junit.Test; import org.springframework.cloud.service.ServiceInfo; import org.springframework.cloud.service.common.MysqlServiceInfo; +import org.springframework.cloud.service.common.RelationalServiceInfo; +import org.springframework.cloud.util.UriInfo; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; +import static org.springframework.cloud.service.common.MysqlServiceInfo.MYSQL_SCHEME; /** * @@ -81,6 +84,27 @@ public class CloudFoundryConnectorMysqlServiceTest extends AbstractCloudFoundryC assertUriBasedServiceInfoFields(info2, MYSQL_SCHEME, hostname, port, username, password, name2); } + @Test + public void mysqlServiceCreationNoLabelNoTagsWithSpecialChars() { + String name = "database"; + String userWithSpecialChars = "u%u:u+"; + String passwordWithSpecialChars = "p%p:p+"; + + when(mockEnvironment.getEnvValue("VCAP_SERVICES")) + .thenReturn(getServicesPayload( + getMysqlServicePayloadNoLabelNoTags("mysql", hostname, port, userWithSpecialChars, passwordWithSpecialChars, name))); + List serviceInfos = testCloudConnector.getServiceInfos(); + + ServiceInfo info = getServiceInfo(serviceInfos, "mysql"); + + assertServiceFoundOfType(info, MysqlServiceInfo.class); + + assertEquals(getJdbcUrl(hostname, port, name, userWithSpecialChars, passwordWithSpecialChars), + ((RelationalServiceInfo)info).getJdbcUrl()); + + assertUriBasedServiceInfoFields(info, MYSQL_SCHEME, hostname, port, userWithSpecialChars, passwordWithSpecialChars, name); + } + @Test public void mysqlServiceCreationWithLabelNoUri() { String name1 = "database-1"; @@ -127,6 +151,26 @@ public class CloudFoundryConnectorMysqlServiceTest extends AbstractCloudFoundryC assertUriBasedServiceInfoFields(info2, MYSQL_SCHEME, hostname, port, username, password, name2); } + @Test + public void mysqlServiceCreationWithJdbcUrlAndSpecialChars() { + String userWithSpecialChars = "u%u:u+"; + String passwordWithSpecialChars = "p%p:p+"; + String name = "database"; + when(mockEnvironment.getEnvValue("VCAP_SERVICES")) + .thenReturn(getServicesPayload( + getMysqlServicePayloadWithJdbcUrl("mysql", hostname, port, userWithSpecialChars, passwordWithSpecialChars, name))); + List serviceInfos = testCloudConnector.getServiceInfos(); + + ServiceInfo info = getServiceInfo(serviceInfos, "mysql"); + + assertServiceFoundOfType(info, MysqlServiceInfo.class); + + assertEquals(getJdbcUrl(hostname, port, name, userWithSpecialChars, passwordWithSpecialChars), + ((RelationalServiceInfo)info).getJdbcUrl()); + + assertUriBasedServiceInfoFields(info, MYSQL_SCHEME, hostname, port, userWithSpecialChars, passwordWithSpecialChars, name); + } + @Test public void mysqlServiceCreationWithJdbcUrlOnly() { String name1 = "database-1"; @@ -153,6 +197,25 @@ public class CloudFoundryConnectorMysqlServiceTest extends AbstractCloudFoundryC assertJdbcShemeSpecificPartEqual(info2, MYSQL_SCHEME, name2); } + @Test + public void mysqlServiceCreationWithJdbcUrlOnlyWithSpecialChars() { + String name = "database"; + String userWithSpecialChars = "u%u:u+"; + String passwordWithSpecialChars = "p%p:p+"; + when(mockEnvironment.getEnvValue("VCAP_SERVICES")) + .thenReturn(getServicesPayload( + getMysqlServicePayloadWithJdbcUrlOnly("mysql", hostname, port, userWithSpecialChars, passwordWithSpecialChars, name))); + + List serviceInfos = testCloudConnector.getServiceInfos(); + + ServiceInfo info = getServiceInfo(serviceInfos, "mysql"); + + assertServiceFoundOfType(info, MysqlServiceInfo.class); + + assertEquals(getJdbcUrl(hostname, port, name, userWithSpecialChars, passwordWithSpecialChars), + ((RelationalServiceInfo)info).getJdbcUrl()); + } + private String getMysqlServicePayload(String serviceName, String hostname, int port, String user, String password, String name) { @@ -194,4 +257,9 @@ public class CloudFoundryConnectorMysqlServiceTest extends AbstractCloudFoundryC return getRelationalPayload("test-mysql-info-jdbc-url-only.json", serviceName, hostname, port, user, password, name); } + + private String getJdbcUrl(String hostname, int port, String name, String user, String password) { + return "jdbc:mysql://" + hostname + ":" + port + "/" + name + + "?user=" + UriInfo.urlEncode(user) + "&password=" + UriInfo.urlEncode(password); + } } diff --git a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorOracleServiceTest.java b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorOracleServiceTest.java index 1a66360..e96881e 100644 --- a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorOracleServiceTest.java +++ b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorOracleServiceTest.java @@ -2,11 +2,13 @@ package org.springframework.cloud.cloudfoundry; import org.junit.Test; import org.springframework.cloud.service.ServiceInfo; -import org.springframework.cloud.service.common.MysqlServiceInfo; import org.springframework.cloud.service.common.OracleServiceInfo; +import org.springframework.cloud.service.common.RelationalServiceInfo; +import org.springframework.cloud.util.UriInfo; import java.util.List; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.when; @@ -31,6 +33,21 @@ public class CloudFoundryConnectorOracleServiceTest extends AbstractUserProvided assertUriBasedServiceInfoFields(info, ORACLE_SCHEME, hostname, port, username, password, INSTANCE_NAME); } + @Test + public void oracleServiceCreationWithSpecialChars() { + String userWithSpecialChars = "u%u:u+"; + String passwordWithSpecialChars = "p%p:p+"; + when(mockEnvironment.getEnvValue("VCAP_SERVICES")) + .thenReturn(getServicesPayload( + getUserProvidedServicePayload(SERVICE_NAME, hostname, port, userWithSpecialChars, passwordWithSpecialChars, INSTANCE_NAME, ORACLE_SCHEME + ":"))); + List serviceInfos = testCloudConnector.getServiceInfos(); + + ServiceInfo info = getServiceInfo(serviceInfos, SERVICE_NAME); + assertServiceFoundOfType(info, OracleServiceInfo.class); + assertEquals(getJdbcUrl(hostname, port, INSTANCE_NAME, userWithSpecialChars, passwordWithSpecialChars), ((RelationalServiceInfo)info).getJdbcUrl()); + assertUriBasedServiceInfoFields(info, ORACLE_SCHEME, hostname, port, userWithSpecialChars, passwordWithSpecialChars, INSTANCE_NAME); + } + @Test public void oracleServiceCreationWithNoUri() { when(mockEnvironment.getEnvValue("VCAP_SERVICES")) @@ -40,7 +57,7 @@ public class CloudFoundryConnectorOracleServiceTest extends AbstractUserProvided ServiceInfo info = getServiceInfo(serviceInfos, SERVICE_NAME); assertNotNull(info); - assertFalse(MysqlServiceInfo.class.isAssignableFrom(info.getClass())); // service was not detected as MySQL + assertFalse(OracleServiceInfo.class.isAssignableFrom(info.getClass())); // service was not detected as MySQL assertNotNull(info); } @@ -67,4 +84,8 @@ public class CloudFoundryConnectorOracleServiceTest extends AbstractUserProvided protected String getJdbcUrl(String scheme, String name) { return String.format("%s%s:thin:%s/%s@%s:%d/%s", JDBC_PREFIX, scheme, username, password, hostname, port, name); } + + private String getJdbcUrl(String hostname, int port, String name, String user, String password) { + return String.format("%s%s:thin:%s/%s@%s:%d/%s", JDBC_PREFIX, "oracle", UriInfo.urlEncode(user), UriInfo.urlEncode(password), hostname, port, name); + } } diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/DB2ServiceInfo.java b/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/DB2ServiceInfo.java index d91a6eb..d102c66 100644 --- a/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/DB2ServiceInfo.java +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/DB2ServiceInfo.java @@ -1,6 +1,7 @@ package org.springframework.cloud.service.common; import org.springframework.cloud.service.ServiceInfo; +import org.springframework.cloud.util.UriInfo; @ServiceInfo.ServiceLabel("db2") public class DB2ServiceInfo extends RelationalServiceInfo { @@ -18,6 +19,6 @@ public class DB2ServiceInfo extends RelationalServiceInfo { protected String buildJdbcUrl() { return String.format("jdbc:%s://%s:%d/%s:user=%s;password=%s;", jdbcUrlDatabaseType, - getHost(), getPort(), getPath(), getUserName(), getPassword()); + getHost(), getPort(), getPath(), UriInfo.urlEncode(getUserName()), UriInfo.urlEncode(getPassword())); } } diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/OracleServiceInfo.java b/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/OracleServiceInfo.java index 4a8b067..be5782f 100644 --- a/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/OracleServiceInfo.java +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/OracleServiceInfo.java @@ -1,6 +1,7 @@ package org.springframework.cloud.service.common; import org.springframework.cloud.service.ServiceInfo; +import org.springframework.cloud.util.UriInfo; @ServiceInfo.ServiceLabel("oracle") public class OracleServiceInfo extends RelationalServiceInfo { @@ -17,7 +18,7 @@ public class OracleServiceInfo extends RelationalServiceInfo { @Override protected String buildJdbcUrl() { return String.format("jdbc:%s:thin:%s/%s@%s:%d/%s", - jdbcUrlDatabaseType, getUserName(), getPassword(), + jdbcUrlDatabaseType, UriInfo.urlEncode(getUserName()), UriInfo.urlEncode(getPassword()), getHost(), getPort(), getPath()); } } diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/RelationalServiceInfo.java b/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/RelationalServiceInfo.java index 967bfc7..a897c63 100644 --- a/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/RelationalServiceInfo.java +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/service/common/RelationalServiceInfo.java @@ -1,6 +1,7 @@ package org.springframework.cloud.service.common; import org.springframework.cloud.service.UriBasedServiceInfo; +import org.springframework.cloud.util.UriInfo; /** * @author Ramnivas Laddad @@ -42,10 +43,10 @@ public abstract class RelationalServiceInfo extends UriBasedServiceInfo { private String formatUserinfo() { if (getUserName() != null && getPassword() != null) { - return String.format("?user=%s&password=%s", getUserName(), getPassword()); + return String.format("?user=%s&password=%s", UriInfo.urlEncode(getUserName()), UriInfo.urlEncode(getPassword())); } if (getUserName() != null) { - return String.format("?user=%s", getUserName()); + return String.format("?user=%s", UriInfo.urlEncode(getUserName())); } return ""; } diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/util/UriInfo.java b/spring-cloud-core/src/main/java/org/springframework/cloud/util/UriInfo.java index e119b85..671d171 100644 --- a/spring-cloud-core/src/main/java/org/springframework/cloud/util/UriInfo.java +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/util/UriInfo.java @@ -1,8 +1,11 @@ package org.springframework.cloud.util; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; /** * Utility class that allows expressing URIs in alternative forms: individual fields or a URI string @@ -158,6 +161,14 @@ public class UriInfo { throw new RuntimeException(e); } } + + public static String urlEncode(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } @Override public String toString() {