This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-net.git
The following commit(s) were added to refs/heads/master by this push:
new 6e387497 Check parser type before instantiating class in
DefaultFTPFileEntryParserFactory.createFileEntryParser() (#403)
6e387497 is described below
commit 6e38749760c0366aac1637c7dc15e4ccf76329c9
Author: Javid Khan <[email protected]>
AuthorDate: Sun Jul 5 21:35:17 2026 +0530
Check parser type before instantiating class in
DefaultFTPFileEntryParserFactory.createFileEntryParser() (#403)
* check parser type before instantiating class in createFileEntryParser
During listing auto-detection the parser key is taken from the server's
SYST reply. A key shaped like a qualified class name was passed to
Class.forName and constructed before the type was checked, so any classpath
class could be instantiated for its side effects. Verify the class implements
FTPFileEntryParser before calling the constructor.
* load parser class without initialising before the type check
Class.forName(key) initialises the class, so an unrelated class named
in the key ran its static initialiser before isAssignableFrom rejected
it. Load with the three-arg forName (initialize=false) so only a real
FTPFileEntryParser is initialised, when it is constructed. Test now
asserts the static initialiser does not run.
Signed-off-by: Javid Khan <[email protected]>
---------
Signed-off-by: Javid Khan <[email protected]>
---
.../parser/DefaultFTPFileEntryParserFactory.java | 11 +++++---
.../DefaultFTPFileEntryParserFactoryTest.java | 29 ++++++++++++++++++++--
2 files changed, 34 insertions(+), 6 deletions(-)
diff --git
a/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java
b/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java
index e3771798..d544a474 100644
---
a/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java
+++
b/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java
@@ -118,12 +118,15 @@ public class DefaultFTPFileEntryParserFactory implements
FTPFileEntryParserFacto
// Is the key a possible class name?
if (JAVA_QUALIFIED_NAME_PATTERN.matcher(key).matches()) {
try {
- final Class<?> parserClass = Class.forName(key);
+ // Load without initialising, so an unrelated class (e.g. one
taken from an untrusted SYST reply)
+ // does not run its static initialiser before the type is
checked.
+ final Class<?> parserClass = Class.forName(key, false,
getClass().getClassLoader());
+ if (!FTPFileEntryParser.class.isAssignableFrom(parserClass)) {
+ throw new ParserInitializationException(
+ parserClass.getName() + " does not implement the
interface " + FTPFileEntryParser.class.getCanonicalName());
+ }
try {
parser = (FTPFileEntryParser)
parserClass.getConstructor().newInstance();
- } catch (final ClassCastException e) {
- throw new ParserInitializationException(
- parserClass.getName() + " does not implement the
interface " + FTPFileEntryParser.class.getCanonicalName(), e);
} catch (final Exception | LinkageError e) {
throw new ParserInitializationException("Error
initializing parser", e);
}
diff --git
a/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java
b/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java
index 4833d67b..1068b87c 100644
---
a/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java
+++
b/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java
@@ -29,6 +29,21 @@ import org.apache.commons.net.ftp.FTPFileEntryParser;
import org.junit.jupiter.api.Test;
class DefaultFTPFileEntryParserFactoryTest {
+
+ /** Set from {@link ConstructorProbe}'s static initialiser; kept here so
reading it does not initialise the probe. */
+ static boolean probeInitialized;
+
+ /** A non-parser class whose static initialiser records that the class was
initialised. */
+ public static final class ConstructorProbe {
+ static {
+ probeInitialized = true;
+ }
+
+ public ConstructorProbe() {
+ // empty
+ }
+ }
+
private void checkParserClass(final FTPFileEntryParserFactory fact, final
String key, final Class<?> expected) {
final FTPClientConfig config = key == null ? new FTPClientConfig() :
new FTPClientConfig(key);
final FTPFileEntryParser parser = fact.createFileEntryParser(config);
@@ -104,7 +119,7 @@ class DefaultFTPFileEntryParserFactoryTest {
factory.createFileEntryParser("org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory");
fail("Exception should have been thrown.
\"DefaultFTPFileEntryParserFactory\" does not implement FTPFileEntryParser");
} catch (final ParserInitializationException pie) {
- assertTrue(pie.getCause() instanceof ClassCastException);
+ assertTrue(pie.getMessage().contains("does not implement the
interface"), pie.getMessage());
}
try {
@@ -112,7 +127,7 @@ class DefaultFTPFileEntryParserFactoryTest {
factory.createFileEntryParser("org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory");
fail("ParserInitializationException should have been thrown.");
} catch (final ParserInitializationException pie) {
- assertTrue(pie.getCause() instanceof ReflectiveOperationException,
pie.getCause().toString());
+ assertTrue(pie.getMessage().contains("does not implement the
interface"), pie.getMessage());
}
try {
// Class exists, but is abstract
@@ -152,4 +167,14 @@ class DefaultFTPFileEntryParserFactoryTest {
// Note: exact matching via config is the only way to generate
NTFTPEntryParser and OS400FTPEntryParser
// using DefaultFTPFileEntryParserFactory
}
+
+ @Test
+ void testNonParserClassIsNotInstantiated() {
+ final DefaultFTPFileEntryParserFactory factory = new
DefaultFTPFileEntryParserFactory();
+ probeInitialized = false;
+ // A qualified class name can arrive from the server's SYST reply
during auto-detection.
+ // Referencing the .class literal does not initialise the probe, so
probeInitialized stays false unless the factory triggers it.
+ assertThrows(ParserInitializationException.class, () ->
factory.createFileEntryParser(ConstructorProbe.class.getName()));
+ assertFalse(probeInitialized, "a class that is not an
FTPFileEntryParser must not be loaded with initialisation");
+ }
}