blob: cdd90e5f1e6d2ed83cc5012f10308f5c35934f45 [file] [log] [blame]
/*
* 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.commons.net.ftp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.net.SocketException;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.util.Calendar;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.net.PrintCommandListener;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.UserManager;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.ssl.SslConfigurationFactory;
import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
import org.apache.ftpserver.usermanager.impl.BaseUser;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
/**
* Tests {@link FTPSClient}.
* <p>
* To get our test cert to work on Java 11, this test must be run with:
* </p>
*
* <pre>
* -Djdk.tls.client.protocols="TLSv1.1"
* </pre>
* <p>
* This test does the above programmatically.
* </p>
*/
@RunWith(Parameterized.class)
public class FTPSClientTest {
private static int SocketPort;
private static FtpServer EmbeddedFtpServer;
private static final String USER_PROPS_RES = "org/apache/commons/net/ftpsserver/users.properties";
// TEMPORARY hack to see if old JKS works
private static final String SERVER_JKS_RES_JRE_8 = "org/apache/commons/net/ftpsserver/ftpserver-old.jks";
private static final String SERVER_JKS_RES_JRE_16 = "org/apache/commons/net/ftpsserver/ftpserver-old.jks";
private static final boolean IMPLICIT = false;
/**
* Returns the test directory as a String.
*
* @return the test directory as a String
*/
private static String getTestHomeDirectory() {
return System.getProperty("test.basedir", "target/test-classes/org/apache/commons/net/test-data");
}
private static final long TEST_TIMEOUT = 10000; // individual test timeout
private static final long startTime = System.nanoTime();
private static void trace(String msg) {
// System.err.println(msg + " " + (System.nanoTime() - startTime));
}
@BeforeClass
public static void setUpClass() throws Exception {
trace(">>setUpClass");
setUpClass(IMPLICIT);
trace("<<setUpClass");
}
/**
* Creates and starts an embedded Apache MINA FTP Server.
*
* @param IMPLICIT FTPS connection mode
* @throws FtpException
*/
private synchronized static void setUpClass(final boolean implicit) throws FtpException {
if (EmbeddedFtpServer != null) {
trace("Server already active");
return;
}
trace("Server startup");
// Use an ephemeral port.
SocketPort = 0;
final FtpServerFactory serverFactory = new FtpServerFactory();
final PropertiesUserManagerFactory propertiesUserManagerFactory = new PropertiesUserManagerFactory();
final URL userPropsResource = ClassLoader.getSystemClassLoader().getResource(USER_PROPS_RES);
Assert.assertNotNull(USER_PROPS_RES, userPropsResource);
propertiesUserManagerFactory.setUrl(userPropsResource);
final UserManager userManager = propertiesUserManagerFactory.createUserManager();
final BaseUser user = (BaseUser) userManager.getUserByName("test");
// Pickup the home dir value at runtime even though we have it set in the user
// prop file
// The user prop file requires the "homedirectory" to be set
user.setHomeDirectory(getTestHomeDirectory());
serverFactory.setUserManager(userManager);
final ListenerFactory factory = new ListenerFactory();
// set the port of the listener
factory.setPort(SocketPort);
// define SSL configuration
final URL serverJksResource = SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_16)
? ClassLoader.getSystemClassLoader().getResource(SERVER_JKS_RES_JRE_16)
: ClassLoader.getSystemClassLoader().getResource(SERVER_JKS_RES_JRE_8);
System.out.println("Loading " + serverJksResource);
Assert.assertNotNull(SERVER_JKS_RES_JRE_8, serverJksResource);
final SslConfigurationFactory sllConfigFactory = new SslConfigurationFactory();
final File keyStoreFile = FileUtils.toFile(serverJksResource);
Assert.assertTrue(keyStoreFile.toString(), keyStoreFile.exists());
sllConfigFactory.setKeystoreFile(keyStoreFile);
sllConfigFactory.setKeystorePassword("password");
// set the SSL configuration for the listener
factory.setSslConfiguration(sllConfigFactory.createSslConfiguration());
factory.setImplicitSsl(implicit);
// replace the default listener
serverFactory.addListener("default", factory.createListener());
// start the server
trace("Server starting");
EmbeddedFtpServer = serverFactory.createServer();
EmbeddedFtpServer.start();
SocketPort = ((org.apache.ftpserver.impl.DefaultFtpServer) EmbeddedFtpServer).getListener("default").getPort();
// System.out.printf("jdk.tls.disabledAlgorithms = %s%n", System.getProperty("jdk.tls.disabledAlgorithms"));
trace("Server started");
}
@Parameters(name = "endpointCheckingEnabled={0}")
public static Boolean[] testConstructurData() {
return new Boolean[] {Boolean.FALSE, Boolean.TRUE};
}
private final boolean endpointCheckingEnabled;
public FTPSClientTest(final boolean endpointCheckingEnabled) {
this.endpointCheckingEnabled = endpointCheckingEnabled;
}
private void assertClientCode(final FTPSClient client) {
final int replyCode = client.getReplyCode();
assertTrue(FTPReply.isPositiveCompletion(replyCode));
}
private FTPSClient loginClient() throws SocketException, IOException {
trace(">>loginClient");
final FTPSClient client = new FTPSClient(IMPLICIT);
client.addProtocolCommandListener(new PrintCommandListener(System.err));
//
client.setControlKeepAliveReplyTimeout(null);
assertEquals(0, client.getControlKeepAliveReplyTimeoutDuration().getSeconds());
client.setControlKeepAliveReplyTimeout(Duration.ofSeconds(60));
assertEquals(60, client.getControlKeepAliveReplyTimeoutDuration().getSeconds());
//
client.setControlKeepAliveTimeout(null);
assertEquals(0, client.getControlKeepAliveTimeoutDuration().getSeconds());
client.setControlKeepAliveTimeout(Duration.ofSeconds(61));
assertEquals(61, client.getControlKeepAliveTimeoutDuration().getSeconds());
//
client.setDataTimeout(null);
assertEquals(0, client.getDataTimeout().getSeconds());
client.setDataTimeout(Duration.ofSeconds(62));
assertEquals(62, client.getDataTimeout().getSeconds());
//
client.setEndpointCheckingEnabled(endpointCheckingEnabled);
trace(">>loginClient-connect");
client.connect("localhost", SocketPort);
//
assertClientCode(client);
assertEquals(SocketPort, client.getRemotePort());
//
trace(">>loginClient-login");
try {
Thread.sleep(100); // See if a short sleep before USER command helps
} catch (InterruptedException e) {};
assertTrue(client.login("test", "test"));
assertClientCode(client);
//
trace(">>loginClient-binary");
client.setFileType(FTP.BINARY_FILE_TYPE);
assertClientCode(client);
//
trace(">>loginClientPBSZ");
client.execPBSZ(0);
assertClientCode(client);
//
trace(">>loginClientPROT");
client.execPROT("P");
assertClientCode(client);
trace("<<loginClient");
return client;
}
private void retrieveFile(final String pathname) throws SocketException, IOException {
final FTPSClient client = loginClient();
try {
// Do it twice.
// Just testing that we are not getting an SSL error (the file MUST be present).
assertTrue(pathname, client.retrieveFile(pathname, NullOutputStream.NULL_OUTPUT_STREAM));
assertTrue(pathname, client.retrieveFile(pathname, NullOutputStream.NULL_OUTPUT_STREAM));
} finally {
trace(">>disconnect");
client.disconnect();
trace("<<disconnect");
}
}
@Test(timeout = TEST_TIMEOUT)
public void testHasFeature() throws SocketException, IOException {
trace(">>testHasFeature");
loginClient().disconnect();
trace("<<testHasFeature");
}
private void testListFiles(final String pathname) throws SocketException, IOException {
final FTPSClient client = loginClient();
try {
// do it twice
assertNotNull(client.listFiles(pathname));
assertNotNull(client.listFiles(pathname));
} finally {
trace(">>disconnect");
client.disconnect();
trace("<<disconnect");
}
}
@Test(timeout = TEST_TIMEOUT)
public void testListFilesPathNameEmpty() throws SocketException, IOException {
trace(">>testListFilesPathNameEmpty");
testListFiles("");
trace("<<testListFilesPathNameEmpty");
}
@Test(timeout = TEST_TIMEOUT)
public void testListFilesPathNameJunk() throws SocketException, IOException {
trace(">>testListFilesPathNameJunk");
testListFiles(" Junk ");
trace("<<testListFilesPathNameJunk");
}
@Test(timeout = TEST_TIMEOUT)
public void testListFilesPathNameNull() throws SocketException, IOException {
trace(">>testListFilesPathNameNull");
testListFiles(null);
trace("<<testListFilesPathNameNull");
}
@Test(timeout = TEST_TIMEOUT)
public void testListFilesPathNameRoot() throws SocketException, IOException {
trace(">>testListFilesPathNameRoot");
testListFiles("/");
trace("<<testListFilesPathNameRoot");
}
@Test(timeout = TEST_TIMEOUT)
public void testMdtmCalendar() throws SocketException, IOException {
trace(">>testMdtmCalendar");
testMdtmCalendar("/file.txt");
trace("<<testMdtmCalendar");
}
private void testMdtmCalendar(final String pathname) throws SocketException, IOException {
final FTPSClient client = loginClient();
try {
// do it twice
final Calendar mdtmCalendar1 = client.mdtmCalendar(pathname);
final Calendar mdtmCalendar2 = client.mdtmCalendar(pathname);
assertNotNull(mdtmCalendar1);
assertNotNull(mdtmCalendar2);
assertEquals(mdtmCalendar1, mdtmCalendar2);
} finally {
trace(">>disconnect");
client.disconnect();
trace("<<disconnect");
}
}
public void testMdtmFile() throws SocketException, IOException {
testMdtmFile("/file.txt");
}
private void testMdtmFile(final String pathname) throws SocketException, IOException {
final FTPSClient client = loginClient();
try {
// do it twice
final FTPFile mdtmFile1 = client.mdtmFile(pathname);
final FTPFile mdtmFile2 = client.mdtmFile(pathname);
assertNotNull(mdtmFile1);
assertNotNull(mdtmFile2);
assertEquals(mdtmFile1, mdtmFile2);
} finally {
trace(">>disconnect");
client.disconnect();
trace("<<disconnect");
}
}
@Test(timeout = TEST_TIMEOUT)
public void testMdtmInstant() throws SocketException, IOException {
trace(">>testMdtmInstant");
testMdtmInstant("/file.txt");
trace("<<testMdtmInstant");
}
private void testMdtmInstant(final String pathname) throws SocketException, IOException {
final FTPSClient client = loginClient();
try {
// do it twice
final Instant mdtmInstant1 = client.mdtmInstant(pathname);
final Instant mdtmInstant2 = client.mdtmInstant(pathname);
assertNotNull(mdtmInstant1);
assertNotNull(mdtmInstant2);
assertEquals(mdtmInstant1, mdtmInstant2);
} finally {
trace(">>disconnect");
client.disconnect();
trace("<<disconnect");
}
}
@Test(timeout = TEST_TIMEOUT)
public void testOpenClose() throws SocketException, IOException {
trace(">>testOpenClose");
final FTPSClient ftpsClient = loginClient();
try {
assertTrue(ftpsClient.hasFeature("MODE"));
assertTrue(ftpsClient.hasFeature(FTPCmd.MODE));
} finally {
trace(">>disconnect");
ftpsClient.disconnect();
trace("<<disconnect");
}
trace("<<testOpenClose");
}
@Test(timeout = TEST_TIMEOUT)
public void testRetrieveFilePathNameRoot() throws SocketException, IOException {
trace(">>testRetrieveFilePathNameRoot");
retrieveFile("/file.txt");
trace("<<testRetrieveFilePathNameRoot");
}
}