Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ protected Object[][] getContents() {
{"R_pvkParseError", "Could not read Private Key from PVK, check the password provided."},
{"R_pvkHeaderError", "Cannot parse the PVK, PVK file does not contain the correct header."},
{"R_readCertError", "Error reading certificate, please verify the location of the certificate."},
{"R_invalidClassNameForProperty", "The value specified by the {0} property is not a valid Java class name: {1}."},
{"R_unassignableError", "The class specified by the {0} property must be assignable to {1}."},
{"R_InvalidCSVQuotes", "Failed to parse the CSV file, verify that the fields are correctly enclosed in double quotes."},
{"R_TokenRequireUrl", "Token credentials require a URL using the HTTPS protocol scheme."},
Expand Down
26 changes: 21 additions & 5 deletions src/main/java/com/microsoft/sqlserver/jdbc/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.regex.Pattern;


/**
Expand All @@ -46,6 +47,8 @@ private Util() {
// any vendor or version specific decisions
static final String SYSTEM_JRE = System.getProperty("java.vendor") + " " + System.getProperty("java.version");
private static final Lock LOCK = new ReentrantLock();
private static final Pattern JAVA_BINARY_CLASS_NAME = Pattern.compile(
"\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*");

private static Boolean isIBM = null;

Expand Down Expand Up @@ -1022,11 +1025,9 @@ static boolean checkIfNeedNewAccessToken(SQLServerConnection connection, Date ac
@SuppressWarnings("unchecked")
static <T> T newInstance(Class<?> returnType, String className, String constructorArg,
Object[] msgArgs) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = Util.class.getClassLoader();
}
Class<?> clazz = Class.forName(className, false, classLoader);
validateClassName(className, msgArgs);

Class<?> clazz = Class.forName(className, false, Util.class.getClassLoader());
if (!returnType.isAssignableFrom(clazz)) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unassignableError"));
throw new IllegalArgumentException(form.format(msgArgs));
Expand All @@ -1038,6 +1039,21 @@ static <T> T newInstance(Class<?> returnType, String className, String construct
}
}

private static void validateClassName(String className, Object[] msgArgs) {
if (isValidJavaBinaryClassName(className)) {
return;
}

String propertyName = (null != msgArgs && msgArgs.length > 0 && null != msgArgs[0]) ? msgArgs[0].toString()
: "unknown";
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidClassNameForProperty"));
throw new IllegalArgumentException(form.format(new Object[] { propertyName, className }));
}

private static boolean isValidJavaBinaryClassName(String className) {
Comment thread
divang marked this conversation as resolved.
return null != className && JAVA_BINARY_CLASS_NAME.matcher(className).matches();
}

/**
* Escapes single quotes (') in object name to convert and pass it as String safely.
*
Expand Down
89 changes: 89 additions & 0 deletions src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.math.BigDecimal;
import java.math.BigInteger;
Expand All @@ -14,8 +17,14 @@
import java.util.UUID;
import java.util.logging.Logger;

import javax.net.SocketFactory;
import javax.net.ssl.TrustManager;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

Expand All @@ -29,6 +38,86 @@
@RunWith(JUnitPlatform.class)
public class UtilTest {

public static final class TestRunnable implements Runnable {
@Override
public void run() {
}
}

@Test
public void testNewInstanceUsesUtilClassLoader() throws Exception {
Thread currentThread = Thread.currentThread();
ClassLoader originalClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(new ClassLoader(null) {
});
try {
Runnable instance = Util.newInstance(Runnable.class, TestRunnable.class.getName(), null,
new Object[] { "testClass", Runnable.class.getName() });

assertNotNull(instance);
} finally {
currentThread.setContextClassLoader(originalClassLoader);
}
}

@ParameterizedTest
@NullSource
@ValueSource(strings = { "", " ", "com..example.Foo", ".com.example.Foo", "com.example.Foo.",
"1com.example.Foo", "com.example.Foo Bar", "com/example/Foo", "jar:file:Foo", "http://example/Foo" })
public void testNewInstanceRejectsInvalidTrustManagerClassNames(String className) {
Object[] msgArgs = { "trustManagerClass", TrustManager.class.getName() };

IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> Util.newInstance(TrustManager.class, className, null, msgArgs));

assertTrue(exception.getMessage().contains("trustManagerClass"));
}

@Test
public void testNewInstanceRejectsValidClassNameWithInvalidType() {
Object[] msgArgs = { "socketFactoryClass", SocketFactory.class.getName() };

IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> Util.newInstance(SocketFactory.class, String.class.getName(), null, msgArgs));

assertTrue(exception.getMessage().contains("socketFactoryClass"));
assertTrue(exception.getMessage().contains(SocketFactory.class.getName()));
}

@Test
public void testNewInstanceRejectsInvalidSocketFactoryClassName() {
String className = "jar:file:.proc.self.fd.!.fd_SqlServerSocketFactorykanqvbkjhp";
Object[] msgArgs = { "socketFactoryClass", SocketFactory.class.getName() };

IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> Util.newInstance(SocketFactory.class, className, null, msgArgs));

assertTrue(exception.getMessage().contains("socketFactoryClass"));
assertTrue(exception.getMessage().contains(className));
}

@Test
public void testNewInstanceRejectsInvalidAccessTokenCallbackClassName() {
String className = "jar:file:.proc.self.fd.!.fd_SQLServerAccessTokenCallback";
Object[] msgArgs = { "accessTokenCallbackClass", SQLServerAccessTokenCallback.class.getName() };

IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> Util.newInstance(SQLServerAccessTokenCallback.class, className, null, msgArgs));

assertTrue(exception.getMessage().contains("accessTokenCallbackClass"));
assertTrue(exception.getMessage().contains(className));
}

@ParameterizedTest
@ValueSource(strings = { "java.lang.Object", "java.lang.String", "java.lang.Thread",
"java.util.ArrayList" })
public void testNewInstanceAcceptsValidClassNames(String className) throws Exception {
Object instance = Util.newInstance(Object.class, className, null,
new Object[] { "testClass", Object.class.getName() });

assertNotNull(instance);
}

@Test
public void readGUIDtoUUID() throws SQLException {
UUID expected = UUID.fromString("6F9619FF-8B86-D011-B42D-00C04FC964FF");
Expand Down
Loading