com.apporiented.hermesftp.client.FtpTestClient.java Source code

Java tutorial

Introduction

Here is the source code for com.apporiented.hermesftp.client.FtpTestClient.java

Source

/*
 * ------------------------------------------------------------------------------
 * Hermes FTP Server
 * Copyright (c) 2005-2014 Lars Behnke
 * ------------------------------------------------------------------------------
 * 
 * This file is part of Hermes FTP Server.
 * 
 * Hermes FTP Server is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * Hermes FTP Server is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Hermes FTP Server; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * ------------------------------------------------------------------------------
 */

package com.apporiented.hermesftp.client;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ServerSocketFactory;

import com.apporiented.hermesftp.common.FtpConstants;
import com.apporiented.hermesftp.utils.NetUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

// CHECKSTYLE:OFF
/**
 * FTP test client for test purposes.
 * 
 * @author Lars Behnke
 */
public class FtpTestClient {

    /**
     * On Linux ports below 1024 can only be bound by root.
     */
    private static final int TEST_FTP_PORT = 2121;

    private static final int LOG_LINE_LENGTH = 80;

    private static Log log = LogFactory.getLog(FtpTestClient.class);

    private PrintWriter out;

    private BufferedReader in;

    private String server;

    private InputStream transIs;

    private OutputStream transOut;

    private Socket passiveModeSocket;

    private ServerSocket activeModeServerSocket;

    private Socket serverSocket;

    private StringBuffer textBuffer;

    private byte[] rawBuffer;

    private final Object lock = new Object();

    /**
     * Returns the text data.
     * 
     * @return The text data.
     */
    public String getTextData() {
        return textBuffer.toString();
    }

    /**
     * Returns the raw data.
     * 
     * @return The text data.
     */
    public byte[] getRawData() {
        return rawBuffer;
    }

    /**
     * Opens a anonymous FTP connection.
     * 
     * @throws IOException Error on connection.
     */
    public void openConnection() throws IOException {
        openConnection("anonymous", "my@mail");
    }

    /**
     * Opens a FTP connection.
     * 
     * @param user The user name.
     * @param pass The user password.
     * @throws IOException Error on connection.
     */
    public void openConnection(String user, String pass) throws IOException {
        openConnection(null, user, pass, TEST_FTP_PORT);

    }

    /**
     * Closes the FTP connection.
     */
    public void closeConnection() {
        try {
            if (serverSocket != null) {
                serverSocket.close();
                serverSocket = null;
            }

        } catch (IOException e) {
            log.debug(e.toString());
        }
        try {
            if (passiveModeSocket != null) {
                passiveModeSocket.close();
                passiveModeSocket = null;
            }

        } catch (IOException e) {
            log.debug(e.toString());
        }
    }

    /**
     * Opening a connection to the FTP server.
     * 
     * @param svr The server name. If null is passed, the local machine is used.
     * @param user The user name.
     * @param pass The user password.
     * @throws IOException Error on connection.
     */
    public void openConnection(String svr, String user, String pass) throws IOException {
        openConnection(svr, user, pass, TEST_FTP_PORT);
    }

    /**
     * Opening a connection to the FTP server.
     * 
     * @param svr The server name. If null is passed, the local machine is used.
     * @param user The user name.
     * @param pass The user password.
     * @param port FTP port.
     * @throws IOException Error on connection.
     */
    public void openConnection(String svr, String user, String pass, int port) throws IOException {
        this.server = svr;

        if (server == null || server.startsWith("127.0.0.")) {
            this.server = NetUtils.getMachineAddress(true).getHostAddress();
        }
        serverSocket = new Socket(server, port);
        in = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()));
        out = new PrintWriter(serverSocket.getOutputStream(), true);
        getResponse();
        sendAndReceive("USER " + user);
        sendAndReceive("PASS " + pass);
    }

    public String openPassiveMode() throws IOException {
        sendCommand("PASV");
        String response = getResponse();
        int parentStart = response.lastIndexOf('(');
        int parentEnd = response.lastIndexOf(')');

        String pasv = response.substring(parentStart + 1, parentEnd);
        StringTokenizer st = new StringTokenizer(pasv, ",");
        int[] iPs = new int[8];
        for (int i = 0; st.hasMoreTokens(); i++) {
            iPs[i] = Integer.valueOf(st.nextToken());
        }
        int port = (iPs[4] << FtpConstants.BYTE_LENGTH) + iPs[5];

        resetDataSockets();
        passiveModeSocket = new Socket(server, port);
        return response;
    }

    public String openActiveMode() throws IOException {
        InetAddress addr = NetUtils.getMachineAddress(true);
        String addrStr = addr != null ? addr.getHostAddress() : "127.0.0.1";
        ServerSocket sock = ServerSocketFactory.getDefault().createServerSocket(0, 1, addr);
        sock.setSoTimeout(10000);

        Pattern pattern = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)$");
        Matcher matcher = pattern.matcher(addrStr);
        if (!matcher.matches()) {
            throw new IOException("Invalid address: " + addrStr);
        }
        int p1 = (sock.getLocalPort() >>> FtpConstants.BYTE_LENGTH) & FtpConstants.BYTE_MASK;
        int p2 = sock.getLocalPort() & FtpConstants.BYTE_MASK;

        StringBuffer sb = new StringBuffer();
        sb.append("PORT ");
        sb.append(matcher.group(1));
        sb.append(",");
        sb.append(matcher.group(2));
        sb.append(",");
        sb.append(matcher.group(3));
        sb.append(",");
        sb.append(matcher.group(4));
        sb.append(",");
        sb.append(p1);
        sb.append(",");
        sb.append(p2);

        resetDataSockets();
        activeModeServerSocket = sock;

        sendCommand(sb.toString());

        return getResponse();

    }

    public String openExtendedPassiveMode() throws IOException {
        sendCommand("EPSV");
        String response = getResponse();

        Pattern pattern = Pattern.compile("^.*\\(\\|\\|\\|([0-9]+)\\|\\).*$");
        Matcher matcher = pattern.matcher(response);
        int port = 0;
        if (matcher.matches()) {
            port = Integer.parseInt(matcher.group(1));
        }

        resetDataSockets();
        passiveModeSocket = new Socket(server, port);
        return response;
    }

    private void initializeIOStreams() throws IOException {
        if (passiveModeSocket != null) {
            transIs = passiveModeSocket.getInputStream();
            transOut = passiveModeSocket.getOutputStream();
        } else if (activeModeServerSocket != null) {
            Socket socket = activeModeServerSocket.accept();
            transIs = socket.getInputStream();
            transOut = socket.getOutputStream();
        } else {
            throw new IOException("IO streams have not been initialized");
        }
    }

    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    private boolean isServerSocketAvailable() {
        return passiveModeSocket != null || activeModeServerSocket != null;
    }

    private void resetDataSockets() throws IOException {
        if (passiveModeSocket != null) {
            passiveModeSocket.close();
            passiveModeSocket = null;
        }
        if (activeModeServerSocket != null) {
            activeModeServerSocket.close();
            activeModeServerSocket = null;
        }
    }

    public String openExtendedActiveMode() throws IOException {
        StringBuffer params = new StringBuffer();

        params.append("|1|");
        InetAddress addr = NetUtils.getMachineAddress(true);
        ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0, 1, addr);
        params.append(addr.getHostAddress());
        params.append("|");
        params.append(serverSocket.getLocalPort());
        params.append("|");

        sendCommand("EPRT " + params.toString());
        String response = getResponse();

        if (passiveModeSocket != null) {
            passiveModeSocket.close();
        }
        activeModeServerSocket = serverSocket;
        return response;
    }

    /**
     * Lists the content of the passed path.
     * 
     * @param f The path.
     * @return The content as string.
     * @throws IOException Error on data transfer.
     */
    public String list(String f) throws IOException {
        String response;
        if (!isServerSocketAvailable()) {
            openPassiveMode();
        }
        textBuffer = new StringBuffer();
        if (f == null) {
            sendCommand("LIST");
        } else {
            sendCommand("LIST " + f);
        }
        String res = getResponse();

        if (res.startsWith("150")) {
            initializeIOStreams();
        } else {
            return res;
        }

        BufferedReader in = new BufferedReader(new InputStreamReader(transIs, "ISO-8859-1"));
        TextReceiver l = new TextReceiver(in, false);
        executeReceive(l);
        resetDataSockets();
        response = getResponse();
        return response;

    }

    /**
     * Lists the current folder.
     * 
     * @return The content.
     * @throws IOException Error on data transfer.
     */
    public String list() throws IOException {
        return list(null);
    }

    /**
     * Retrieves a text file.
     * 
     * @param filename The filename.
     * @return The content of the text file.
     * @throws IOException Error on data transfer.
     */
    public String retrieveText(String filename) throws IOException {
        String response;
        sendAndReceive("TYPE A");
        if (!isServerSocketAvailable()) {
            openPassiveMode();
        }
        textBuffer = new StringBuffer();
        response = sendAndReceive("RETR " + filename);
        if (!response.startsWith("150")) {
            return response;
        }
        initializeIOStreams();
        BufferedReader in = new BufferedReader(new InputStreamReader(transIs, "ISO-8859-1"));
        TextReceiver l = new TextReceiver(in, false);
        executeReceive(l);

        response = getResponse();
        resetDataSockets();
        return response;
    }

    /**
     * Retrieves a text file.
     * 
     * @param filename The filename.
     * @return Size of file.
     * @throws IOException Error on data transfer.
     */
    public int retrieveBigText(String filename) throws IOException {

        String response;
        sendAndReceive("TYPE A");
        if (!isServerSocketAvailable()) {
            openPassiveMode();
        }
        textBuffer = new StringBuffer();
        response = sendAndReceive("RETR " + filename);
        if (response.startsWith("150")) {
            initializeIOStreams();
        } else {
            return 0;
        }

        BufferedReader in = new BufferedReader(new InputStreamReader(transIs, "ISO-8859-1"));
        TextReceiver l = new TextReceiver(in, true);
        executeReceive(l);

        getResponse();
        resetDataSockets();
        return l.getCount();
    }

    private void executeReceive(TextReceiver l) {
        Thread t = new Thread(l);
        t.start();
        while (t.isAlive()) {
            Thread.yield();
        }
    }

    /**
     * Retrieves a raw data file.
     * 
     * @param filename The filename.
     * @return The content of the data file.
     * @throws IOException Error on data transfer.
     */
    public String retrieveRaw(String filename) throws IOException {
        String response;
        if (!isServerSocketAvailable()) {
            openPassiveMode();
        }
        rawBuffer = null;
        response = sendAndReceive("RETR " + filename);
        if (!response.startsWith("150")) {
            return response;
        }
        initializeIOStreams();
        RawReceiver l = new RawReceiver(transIs);
        executeReceive(l);

        response = getResponse();
        resetDataSockets();
        return response;
    }

    private void executeReceive(RawReceiver l) {
        Thread t = new Thread(l);
        t.start();
        while (t.isAlive()) {
            Thread.yield();
        }
    }

    /**
     * Stores a text file on the remote system.
     * 
     * @param filename The filename.
     * @param textToStore The text to be stored.
     * @return The response.
     * @throws IOException Error on data transfer.
     */
    public String storeText(String filename, String textToStore) throws IOException {
        return storeText(filename, textToStore, false);
    }

    /**
     * Appends text to an text file.
     * 
     * @param filename The filename.
     * @param textToStore The text to append.
     * @return The server response.
     * @throws IOException Error on data transfer.
     */
    public String appendText(String filename, String textToStore) throws IOException {
        return storeText(filename, textToStore, true);
    }

    private String storeText(String filename, String textToStore, boolean append) throws IOException {
        String response;

        sendAndReceive("TYPE A");
        if (!isServerSocketAvailable()) {
            openPassiveMode();
        }

        textBuffer = new StringBuffer();
        if (append) {
            sendCommand("APPE" + filename);
        } else {
            sendCommand("STOR " + filename);
        }
        response = getResponse();
        if (!response.startsWith("150")) {
            return response;
        }
        initializeIOStreams();
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(transOut, "ISO-8859-1"));
        TextSender l = new TextSender(out, textToStore);
        executeSend(l);

        response = getResponse();

        // lab

        resetDataSockets();
        return response;

    }

    /**
     * Stores a file of a given size. The content is arbitrary.
     * 
     * @param filename Filename.
     * @param size Size of the file.
     * @return Response.
     * @throws IOException
     */
    public String storeBigText(String filename, int size) throws IOException {
        String response;
        // openPassiveMode();
        response = sendAndReceive("TYPE A");
        if (!isServerSocketAvailable()) {
            openPassiveMode();
        }
        textBuffer = new StringBuffer();
        sendCommand("STOR " + filename);

        response = getResponse();
        if (!response.startsWith("150")) {
            return response;
        }
        initializeIOStreams();
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(transOut, "ISO-8859-1"));
        TextSender l = new TextSender(out, size);
        executeSend(l);
        response = getResponse();
        resetDataSockets();
        return response;

    }

    private void executeSend(TextSender l) {
        Thread t = new Thread(l);
        t.start();
        while (t.isAlive()) {
            Thread.yield();
        }
    }

    /**
     * Stores a data file on the remote system.
     * 
     * @param filename The filename.
     * @param data The Data to be stored.
     * @return The response.
     * @throws IOException Error on data transfer.
     */
    public String storeRaw(String filename, byte[] data) throws IOException {
        return storeRaw(filename, data, false);
    }

    /**
     * Appends text to an data file.
     * 
     * @param filename The filename.
     * @param data The data to append.
     * @return The server response.
     * @throws IOException Error on data transfer.
     */
    public String appendRaw(String filename, byte[] data) throws IOException {
        return storeRaw(filename, data, true);
    }

    private String storeRaw(String filename, byte[] data, boolean append) throws IOException {
        String response;
        //response = sendAndReceive("TYPE I");
        if (!isServerSocketAvailable()) {
            openPassiveMode();
        }
        if (append) {
            sendCommand("APPE" + filename);
        } else {
            sendCommand("STOR " + filename);
        }
        response = getResponse();
        if (!response.startsWith("150")) {
            return response;
        }
        initializeIOStreams();

        BufferedOutputStream out = new BufferedOutputStream(transOut);
        RawSender l = new RawSender(out, data);
        executeSend(l);
        response = getResponse();
        resetDataSockets();
        return response;

    }

    private void executeSend(RawSender l) {
        Thread t = new Thread(l);
        t.start();
        while (t.isAlive()) {
            Thread.yield();
        }
    }

    /**
     * Sends a command string to the server.
     * 
     * @param cmd The command.
     * @return The server response.
     * @throws IOException Error on data transfer.
     */
    public String sendAndReceive(String cmd) throws IOException {
        sendCommand(cmd);
        return getResponse();
    }

    private String getResponse() throws IOException {
        return getResponse(in);
    }

    private String getResponse(BufferedReader in) throws IOException {
        StringBuffer sb = new StringBuffer();
        boolean done;
        do {
            String line = in.readLine();
            sb.append(line).append("\n");
            int idx = 0;
            done = Character.isDigit(line.charAt(idx++)) && Character.isDigit(line.charAt(idx++))
                    && Character.isDigit(line.charAt(idx++)) && line.charAt(idx++) == ' ';
        } while (!done);
        return sb.toString().trim();
    }

    /**
     * Send command and wait for resonse.
     */
    private void sendCommand(String command) throws IOException {
        // log.info("-> " + command);
        out.println(command);
    }

    /**
     * Listens to server socket.
     * 
     * @author Lars Behnke
     */
    private class TextReceiver implements Runnable {

        private BufferedReader reader;

        private int count;

        private boolean countOnly;

        /**
         * Constructor.
         * 
         * @param reader The reader.
         * @param countOnly True, if only the text size matters.
         */
        public TextReceiver(BufferedReader reader, boolean countOnly) {
            this.countOnly = countOnly;
            this.reader = reader;
        }

        /**
         * {@inheritDoc}
         */
        public void run() {
            synchronized (lock) {
                try {
                    char[] buffer = new char[4096];
                    int count;
                    while ((count = reader.read(buffer)) != -1) {
                        if (!countOnly) {
                            String logLine = new String(buffer, 0, count);
                            if (textBuffer.length() > 0) {
                                textBuffer.append(System.getProperty("line.separator"));
                            }
                            log.info(logLine);
                            textBuffer.append(logLine);
                            if (log.isTraceEnabled()) {
                                if (count >= LOG_LINE_LENGTH) {

                                    logLine = logLine.substring(0, LOG_LINE_LENGTH) + " ["
                                            + (logLine.length() - LOG_LINE_LENGTH) + " chars more]";
                                }

                                log.trace("<==: " + logLine.trim());
                            }
                        }
                        this.count += count;
                    }
                    if (countOnly) {
                        log.trace("<==: " + this.count + " characters read");
                    }
                    reader.close();

                } catch (IOException e) {
                    log.error(e);
                }
                lock.notifyAll();
            }
        }

        /**
         * @return Size of file.
         */
        public int getCount() {
            return count;
        }
    }

    /**
     * Sends data to server socket.
     * 
     * @author Lars Behnke
     */
    private class TextSender implements Runnable {

        private BufferedWriter writer;

        private String textToSend;

        private int textSize;

        /**
         * Constructor.
         * 
         * @param writer The writer.
         * @param textToSend Text to send.
         */
        public TextSender(BufferedWriter writer, String textToSend) {
            this.writer = writer;
            this.textToSend = textToSend;
            this.textSize = -1;
        }

        /**
         * Constructor.
         * 
         * @param writer The writer.
         * @param size Size of the test string.
         */
        public TextSender(BufferedWriter writer, int size) {
            this.writer = writer;
            this.textSize = size;
        }

        /**
         * @see java.lang.Runnable#run()
         */
        public void run() {
            synchronized (lock) {
                try {
                    if (textSize == -1 && textToSend != null) {
                        if (log.isTraceEnabled()) {
                            String x;
                            if (textToSend.length() >= LOG_LINE_LENGTH) {
                                x = textToSend.substring(0, LOG_LINE_LENGTH) + " ["
                                        + (textToSend.length() - LOG_LINE_LENGTH) + " chars more]";
                            } else {
                                x = textToSend;
                            }
                            log.trace("==>: " + x.trim());
                        }
                        writer.write(textToSend);
                        writer.flush();
                    } else if (textSize >= 0) {
                        if (log.isTraceEnabled()) {
                            log.trace("==>: Sending test text, length: " + textSize);
                        }
                        for (int i = 0; i < textSize; i++) {
                            writer.write('X');
                        }

                    }
                    writer.flush();
                    //if (passiveModeSocket != null) {
                    //    passiveModeSocket.shutdownOutput();
                    //}
                    writer.close();
                } catch (IOException e) {
                    log.error(e);
                }
                lock.notifyAll();
            }
        }
    }

    /**
     * Sends data to server socket.
     * 
     * @author Lars Behnke
     */
    private class RawSender implements Runnable {

        private BufferedOutputStream os;

        private byte[] dataToSend;

        /**
         * Constructor.
         * 
         * @param os The output stream.
         * @param dataToSend Data to send.
         */
        public RawSender(BufferedOutputStream os, byte[] dataToSend) {
            this.os = os;
            this.dataToSend = dataToSend;
        }

        /**
         * @see java.lang.Runnable#run()
         */
        public void run() {
            synchronized (lock) {
                try {
                    log.trace("==>: " + dataToSend.length + " bytes");
                    os.write(dataToSend);
                    os.flush();
                    os.close();
                } catch (IOException e) {
                    log.error(e);
                } finally {
                    lock.notifyAll();
                }
            }
        }
    }

    /**
     * Raw data receiver that listens to server socket.
     * 
     * @author Lars Behnke
     */
    private class RawReceiver implements Runnable {

        private InputStream is;

        /**
         * Constructor.
         * 
         * @param is The input stream.
         */
        public RawReceiver(InputStream is) {
            this.is = is;
        }

        /**
         * {@inheritDoc}
         */
        public void run() {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int count;
            synchronized (lock) {
                try {

                    while ((count = is.read(buffer)) >= 0) {
                        log.trace("<==: " + count + " bytes");
                        baos.write(buffer, 0, count);
                    }
                    rawBuffer = baos.toByteArray();
                } catch (IOException e) {
                    log.error(e);
                } finally {
                    lock.notifyAll();
                }
            }
        }
    }

}

// CHECKSTYLE:ON