FTPClientTransferModeTest.java

/*
 * 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
 *
 *      https://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.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.Authority;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.UserManager;
import org.apache.ftpserver.listener.Listener;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.usermanager.Md5PasswordEncryptor;
import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
import org.apache.ftpserver.usermanager.impl.BaseUser;
import org.apache.ftpserver.usermanager.impl.WritePermission;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

public class FTPClientTransferModeTest {

    private static final class FtpServerAndPort {

        private final int port;
        private final FtpServer ftpServer;

        FtpServerAndPort(final FtpServer ftpServer, final int port) {
            this.port = port;
            this.ftpServer = ftpServer;
        }
    }

    @FunctionalInterface
    interface Runner {
        void run(int port, String user, String password) throws Exception;
    }

    private static final String DEFAULT_HOME = "ftp_root/";

    private static UserManager initUserManager(final String username, final String password) throws FtpException {

        final PropertiesUserManagerFactory propertiesUserManagerFactory = new PropertiesUserManagerFactory();
        // TODO Update to SHA512
        propertiesUserManagerFactory.setPasswordEncryptor(new Md5PasswordEncryptor());
        final UserManager userManager = propertiesUserManagerFactory.createUserManager();
        final BaseUser user = new BaseUser();
        user.setName(username);
        user.setPassword(password);

        final List<Authority> authorities = new ArrayList<>();
        authorities.add(new WritePermission());
        user.setAuthorities(authorities);

        new File(DEFAULT_HOME).mkdirs();
        user.setHomeDirectory(DEFAULT_HOME);
        userManager.save(user);
        return userManager;
    }

    private static void runWithFTPserver(final Runner runner) throws Exception {
        final String userName = "test";
        final String password = "test";
        final FtpServerAndPort ftpServerAndPort = setupPlainFTPserver(userName, password);
        try {
            runner.run(ftpServerAndPort.port, userName, password);
        } finally {
            ftpServerAndPort.ftpServer.stop();
        }
    }

    private static FtpServerAndPort setupPlainFTPserver(final String username, final String password) throws FtpException {
        final FtpServerFactory serverFactory = new FtpServerFactory();

        // Init user
        serverFactory.setUserManager(initUserManager(username, password));

        final ListenerFactory factory = new ListenerFactory();
        // Automatically assign port.
        factory.setPort(0);

        // replace the default listener
        final Listener listener = factory.createListener();
        serverFactory.addListener("default", listener);

        // start the server
        final FtpServer server = serverFactory.createServer();
        server.start();

        return new FtpServerAndPort(server, listener.getPort());
    }

    @BeforeEach
    protected void setUp() throws IOException {
        FileUtils.deleteDirectory(new File(DEFAULT_HOME));
    }

    @AfterEach
    protected void tearDown() throws Exception {
        FileUtils.deleteDirectory(new File(DEFAULT_HOME));
    }

    @ParameterizedTest
    @ValueSource(ints = {FTP.DEFLATE_TRANSFER_MODE})
    public void testRetrievingFiles(final int transferMode) throws Exception {
        new File(DEFAULT_HOME).mkdirs();
        final String filename = "test_download.txt";
        final String fileContent = "Created at " + Instant.now();
        Files.write(Paths.get(DEFAULT_HOME).resolve(filename), fileContent.getBytes(StandardCharsets.UTF_8));

        runWithFTPserver((port, user, password) -> {
            final FTPClient client = new FTPClient();
            try {
                client.connect("localhost", port);
                client.login(user, password);
                assertTrue(client.setFileTransferMode(transferMode));

                final FTPFile[] files = client.listFiles();
                assertEquals(1, files.length);

                final ByteArrayOutputStream bos = new ByteArrayOutputStream();
                assertTrue(client.retrieveFile(files[0].getName(), bos));
                assertEquals(fileContent, new String(bos.toByteArray(), StandardCharsets.UTF_8));
            } finally {
                client.logout();
            }
        });
    }

    @ParameterizedTest
    @ValueSource(ints = {FTP.DEFLATE_TRANSFER_MODE})
    public void testStoringFiles(final int transferMode) throws Exception {
        runWithFTPserver((port, user, password) -> {
            final FTPClient client = new FTPClient();
            try {
                client.connect("localhost", port);
                client.login(user, password);
                assertTrue(client.setFileTransferMode(transferMode));

                final FTPFile[] filesBeforeUpload = client.listFiles();
                assertEquals(0, filesBeforeUpload.length);

                final String fileName = "test_upload.txt";
                final String fileContent = "Created at " + Instant.now();
                assertTrue(client.storeFile(fileName, new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))));

                final FTPFile[] filesAfterUpload = client.listFiles();
                assertEquals(1, filesAfterUpload.length);

                final Path p = Paths.get(DEFAULT_HOME, fileName);
                assertEquals(fileContent, new String(Files.readAllBytes(p), StandardCharsets.UTF_8));
            } finally {
                client.logout();
            }
        });
    }

}