All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.enterprisedt.net.ftp.FTPClient Maven / Gradle / Ivy

Go to download

edtFTPj is the first choice of Java developers worldwide for incorporating FTP functionality into their applications.

The newest version!
/**
 *
 *  edtFTPj
 *
 *  Copyright (C) 2000-2003  Enterprise Distributed Technologies Ltd
 *
 *  www.enterprisedt.com
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Bug fixes, suggestions and comments should be should posted on 
 *  http://www.enterprisedt.com/forums/index.php
 *
 *  Change Log:
 *
 *        $Log: FTPClient.java,v $
 *        Revision 1.56  2006/07/27 14:12:01  bruceb
 *        IPV6 changes and fixed bug re control channel messages after unexpected close on data connection
 *
 *        Revision 1.55  2006/05/22 01:54:42  hans
 *        Made remoteHost protected.
 *
 *        Revision 1.54  2006/02/16 19:47:09  hans
 *        Added comment
 *
 *        Revision 1.53  2005/11/15 21:02:32  bruceb
 *        more debug
 *
 *        Revision 1.52  2005/11/10 19:46:13  bruceb
 *        delegate resume comments to FTPClientInterface
 *
 *        Revision 1.51  2005/11/10 13:40:28  bruceb
 *        more elaborate versioning info to debug
 *
 *        Revision 1.50  2005/11/09 21:15:38  bruceb
 *        autodetect file types
 *
 *        Revision 1.49  2005/10/10 20:42:56  bruceb
 *        append now in FTPClientInterface
 *
 *        Revision 1.48  2005/09/29 16:03:06  bruceb
 *        permit 350 return from STOR
 *
 *        Revision 1.47  2005/09/21 10:38:06  bruceb
 *        fix for LIST error re empty dir (proFTPD/TLS)
 *
 *        Revision 1.46  2005/09/20 09:44:36  bruceb
 *        extra no files found string, SYST accepts 213
 *
 *        Revision 1.45  2005/09/02 21:03:04  bruceb
 *        no abort() with cancel
 *
 *        Revision 1.44  2005/08/26 17:48:16  bruceb
 *        passive ip address setting + ASCII optimisation
 *
 *        Revision 1.43  2005/06/17 18:25:56  bruceb
 *        fix javadoc
 *
 *        Revision 1.42  2005/06/16 21:39:49  hans
 *        deprecated ControlPort accessors and removed comments for FTPClientInterface methods
 *
 *        Revision 1.41  2005/06/10 15:44:38  bruceb
 *        added noOperation() and connected()
 *
 *        Revision 1.40  2005/06/03 11:25:17  bruceb
 *        ascii fixes, setActivePortRange
 *
 *        Revision 1.41  2005/05/24 11:32:28  bruceb
 *        version + timestamp info in static block
 *
 *        Revision 1.40  2005/05/15 19:46:28  bruceb
 *        changes for testing setActivePortRange + STOR accepting 350 nonstrict
 *
 *        Revision 1.39  2005/04/01 13:58:15  bruceb
 *        restructured dir() exception handling + quote() change
 *
 *        Revision 1.38  2005/03/18 11:04:32  bruceb
 *        deprecated constructors
 *
 *        Revision 1.37  2005/03/11 14:40:11  bruceb
 *        added cdup() and changed buffer defaults
 *
 *        Revision 1.36  2005/03/03 21:07:14  bruceb
 *        implement interface & augment login doco
 *
 *        Revision 1.35  2005/02/04 12:40:35  bruceb
 *        tidied javadoc
 *
 *        Revision 1.34  2005/02/04 12:28:51  bruceb
 *        when getting, if file exists and is readonly, exception is thrown
 *
 *        Revision 1.33  2005/01/28 13:55:39  bruceb
 *        added ACCT handling
 *
 *        Revision 1.32  2005/01/14 20:27:02  bruceb
 *        exception restructuring + ABOR
 *
 *        Revision 1.31  2004/11/19 08:28:10  bruceb
 *        added setPORTIP()
 *
 *        Revision 1.30  2004/10/18 15:54:48  bruceb
 *        clearSOCKS added, set encoding for control sock, locale for parser
 *
 *        Revision 1.29  2004/09/21 21:28:28  bruceb
 *        fixed javadoc comment
 *
 *        Revision 1.28  2004/09/18 14:27:57  bruceb
 *        features() throw exception if not supported
 *
 *        Revision 1.27  2004/09/18 09:33:47  bruceb
 *        1.1.8 tweaks
 *
 *        Revision 1.26  2004/09/17 14:12:38  bruceb
 *        fixed javadoc re filemasks
 *
 *        Revision 1.25  2004/09/14 06:24:03  bruceb
 *        fixed javadoc comment
 *
 *        Revision 1.24  2004/08/31 13:48:29  bruceb
 *        resume,features,restructure
 *
 *        Revision 1.23  2004/07/23 08:34:32  bruceb
 *        strict replies or not, better tfr monitor reporting
 *
 *        Revision 1.22  2004/06/25 11:47:46  bruceb
 *        made 1.1.x compatible
 *
 *        Revision 1.21  2004/06/11 10:20:35  bruceb
 *        permit 200 to be returned from various cmds
 *
 *        Revision 1.20  2004/05/22 16:52:57  bruceb
 *        message listener
 *
 *        Revision 1.19  2004/05/15 22:37:22  bruceb
 *        put debugResponses back in
 *
 *        Revision 1.18  2004/05/13 23:00:34  hans
 *        changed comment
 *
 *        Revision 1.17  2004/05/08 21:14:41  bruceb
 *        checkConnection stuff
 *
 *        Revision 1.14  2004/04/19 21:54:06  bruceb
 *        final tweaks to dirDetails() re caching
 *
 *        Revision 1.13  2004/04/18 11:16:44  bruceb
 *        made validateTransfer() public
 *
 *        Revision 1.12  2004/04/17 18:37:38  bruceb
 *        new parse functionality
 *
 *        Revision 1.11  2004/03/23 20:26:49  bruceb
 *        tweak to size(), catch exceptions on puts()
 *
 *        Revision 1.10  2003/11/15 11:23:55  bruceb
 *        changes required for ssl subclasses
 *
 *        Revision 1.6  2003/05/31 14:53:44  bruceb
 *        1.2.2 changes
 *
 *        Revision 1.5  2003/01/29 22:46:08  bruceb
 *        minor changes
 *
 *        Revision 1.4  2002/11/19 22:01:25  bruceb
 *        changes for 1.2
 *
 *        Revision 1.3  2001/10/09 20:53:46  bruceb
 *        Active mode changes
 *
 *        Revision 1.1  2001/10/05 14:42:03  bruceb
 *        moved from old project
 *
 */

package com.enterprisedt.net.ftp;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import java.util.TimeZone;
import java.util.Vector;

import com.enterprisedt.util.debug.Level;
import com.enterprisedt.util.debug.Logger;

/**
 *  Supports client-side FTP. Most common
 *  FTP operations are present in this class.
 *
 *  @author      Bruce Blackshaw
 *  @version     $Revision: 1.56 $
 */
public class FTPClient implements FTPClientInterface {

    /**
     *  Revision control id
     */
    public static String cvsId = "@(#)$Id: FTPClient.java,v 1.56 2006/07/27 14:12:01 bruceb Exp $";
    
    /**
     * Default byte interval for transfer monitor
     */
    final private static int DEFAULT_MONITOR_INTERVAL = 65535;
    
    /**
     * Default transfer buffer size
     */
    final private static int DEFAULT_BUFFER_SIZE = 16384;
    
    /**
     * Maximum port number
     */
    final private static int MAX_PORT = 65535;
    
    /**
     * Short value for a timeout
     */
    final private static int SHORT_TIMEOUT = 500;
    
    /**
     * Default encoding used for control data
     */
    final public static String DEFAULT_ENCODING = "US-ASCII";
    
    /**
     * SOCKS port property name
     */
    final private static String SOCKS_PORT = "socksProxyPort";

    /**
     * SOCKS host property name
     */
    final private static String SOCKS_HOST = "socksProxyHost";
    
    /**
     * Line separator
     */
    final private static byte[] LINE_SEPARATOR = System.getProperty("line.separator").getBytes();
    
    /**
     * Used for ASCII translation
     */
    final private static byte CARRIAGE_RETURN = 13;
    
    /**
     * Used for ASCII translation
     */
    final private static byte LINE_FEED = 10;
    
    /**
     * Server string indicating no files found
     */
    final private static String NO_FILES = "NO FILES";
        
    /**
     * Server string indicating no files found (wu-ftpd)
     */
    final private static String NO_SUCH_FILE_OR_DIR = "NO SUCH FILE OR DIRECTORY";
    
    /**  
     * Server string indicating no files found
     */
    final private static String EMPTY_DIR = "EMPTY";
    
    /**
     * Server string for OS/390 indicating no files found
     */
    final private static String NO_DATA_SETS_FOUND = "NO DATA SETS FOUND";
    
    /**
     * Array of empty directory messages
     */
    final private static String[] EMPTY_DIR_MSGS = {NO_FILES, NO_SUCH_FILE_OR_DIR, EMPTY_DIR, NO_DATA_SETS_FOUND};
    
    /**
     * Server string transfer complete (proFTPD/TLS)
     */
    final private static String TRANSFER_COMPLETE = "TRANSFER COMPLETE";
       
    /**
     * Logging object
     */
    private static Logger log = Logger.getLogger(FTPClient.class);
    
    /**
     *  Format to interpret MTDM timestamp
     */
    private SimpleDateFormat tsFormat =
        new SimpleDateFormat("yyyyMMddHHmmss");
    
    /**
     *  Socket responsible for controlling
     *  the connection
     */
	protected FTPControlSocket control = null;

    /**
     *  Socket responsible for transferring
     *  the data
     */
    protected FTPDataSocket data = null;

    /**
     *  Socket timeout for both data and control. In
     *  milliseconds
     */
    protected int timeout = 0;
    
    /**
     * Address of the remote server.
     */
    protected InetAddress remoteAddr;
    
    /**
     * Name/IP of remote host
     */
    protected String remoteHost;

    /**
     * Control port number.
     */
    protected int controlPort = FTPControlSocket.CONTROL_PORT;
    
    /**
     * If true, uses the original host IP if an internal IP address
     * is returned by the server in PASV mode
     */
    private boolean autoPassiveIPSubstitution = false;
    
    /**
     * Encoding used on control socket
     */
    protected String controlEncoding = DEFAULT_ENCODING;
      
    /**
     * Use strict return codes if true
     */
    private boolean strictReturnCodes = true;
    
    /**
     *  Can be used to cancel a transfer
     */
    private boolean cancelTransfer = false;
    
    /**
     * If true, a file transfer is being resumed
     */
    private boolean resume = false;
    
    /**
     * Resume byte marker point
     */
    private long resumeMarker = 0;
    
    /**
     * If true, filetypes are autodetected and transfer mode changed to binary/ASCII as 
     * required
     */
    private boolean detectTransferMode = false;
    
    /**
     * Bytes transferred in between monitor callbacks
     */
    private long monitorInterval = DEFAULT_MONITOR_INTERVAL;
    
    /**
     * Size of transfer buffers
     */
    private int transferBufferSize = DEFAULT_BUFFER_SIZE;
    
    /**
     * Parses LIST output
     */
    private FTPFileFactory fileFactory = null;
    
    /**
     * Locale for date parsing
     */
    private Locale listingLocale = Locale.getDefault();
    
    /**
     *  Progress monitor
     */
    private FTPProgressMonitor monitor = null;  
    
    /**
     * Message listener
     */
    protected FTPMessageListener messageListener = null;

    /**
     *  Record of the transfer type - make the default ASCII
     */
    private FTPTransferType transferType = FTPTransferType.ASCII;

    /**
     *  Record of the connect mode - make the default PASV (as this was
     *  the original mode supported)
     */
    private FTPConnectMode connectMode = FTPConnectMode.PASV;

    /**
     *  Holds the last valid reply from the server on the control socket
     */
	protected FTPReply lastValidReply;    
    
    /**
     *  Instance initializer. Sets formatter to GMT.
     */
    {
        tsFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
    }  
    
    
    /**
     * Get the version of edtFTPj
     * 
     * @return int array of {major,middle,minor} version numbers 
     */
    public static int[] getVersion() {
        return VersionDetails.getVersion();
    }
    
    /**
     * Get the build timestamp
     * 
     * @return d-MMM-yyyy HH:mm:ss z build timestamp 
     */
    public static String getBuildTimestamp() {
        return VersionDetails.getBuildTimestamp();
    }

    /**
     *  Constructor. Creates the control
     *  socket
     *
     *  @param   remoteHost  the remote hostname
     *  @deprecated  use setter methods to set properties
     */
    public FTPClient(String remoteHost)
        throws IOException, FTPException {

		this(remoteHost, FTPControlSocket.CONTROL_PORT, 0);
    }

    /**
     *  Constructor. Creates the control
     *  socket
     *
     *  @param   remoteHost  the remote hostname
     *  @param   controlPort  port for control stream (-1 for default port)
     *  @deprecated  use setter methods to set properties
     */
    public FTPClient(String remoteHost, int controlPort)
        throws IOException, FTPException {

		this(remoteHost, controlPort, 0);
    }
    
    
    /**
     *  Constructor. Creates the control
     *  socket
     *
     *  @param   remoteHost  the remote hostname
     *  @param   controlPort  port for control stream (use -1 for the default port)
     *  @param  timeout       the length of the timeout, in milliseconds
     *                        (pass in 0 for no timeout)
     *  @deprecated  use setter methods to set properties
     */
    public FTPClient(String remoteHost, int controlPort, int timeout)
    throws IOException, FTPException {

        this(InetAddress.getByName(remoteHost), controlPort, timeout);
    }

    /**
     *  Constructor. Creates the control
     *  socket
     *
     *  @param   remoteHost  the remote hostname
     *  @param   controlPort  port for control stream (use -1 for the default port)
     *  @param  timeout       the length of the timeout, in milliseconds
     *                        (pass in 0 for no timeout)
     *  @param   encoding         character encoding used for data
     *  @deprecated  use setter methods to set properties
     */
    public FTPClient(String remoteHost, int controlPort, int timeout, String encoding)
        throws IOException, FTPException {

        this(InetAddress.getByName(remoteHost), controlPort, timeout, encoding);
    }

    /**
     *  Constructor. Creates the control
     *  socket
     *
     *  @param   remoteAddr  the address of the
     *                       remote host
     *  @deprecated  use setter methods to set properties
     */
    public FTPClient(InetAddress remoteAddr)
        throws IOException, FTPException {

		this(remoteAddr, FTPControlSocket.CONTROL_PORT, 0);
    }
    

    /**
     *  Constructor. Creates the control
     *  socket. Allows setting of control port (normally
     *  set by default to 21).
     *
     *  @param   remoteAddr  the address of the
     *                       remote host
     *  @param   controlPort  port for control stream
     *  @deprecated  use setter methods to set properties
     */
    public FTPClient(InetAddress remoteAddr, int controlPort)
        throws IOException, FTPException {

		this(remoteAddr, controlPort, 0);
    }

    /**
     *  Constructor. Creates the control
     *  socket. Allows setting of control port (normally
     *  set by default to 21).
     *
     *  @param   remoteAddr    the address of the
     *                          remote host
     *  @param   controlPort   port for control stream (-1 for default port)
     *  @param  timeout        the length of the timeout, in milliseconds 
     *                         (pass in 0 for no timeout)
     *  @deprecated  use setter methods to set properties
     */
    public FTPClient(InetAddress remoteAddr, int controlPort, int timeout)
        throws IOException, FTPException {
        if (controlPort < 0)
            controlPort = FTPControlSocket.CONTROL_PORT;
		initialize(new FTPControlSocket(remoteAddr, controlPort, timeout, DEFAULT_ENCODING, null));
    }
    
    /**
     *  Constructor. Creates the control
     *  socket. Allows setting of control port (normally
     *  set by default to 21).
     *
     *  @param   remoteAddr    the address of the
     *                          remote host
     *  @param   controlPort   port for control stream (-1 for default port)
     *  @param   timeout        the length of the timeout, in milliseconds 
     *                         (pass in 0 for no timeout)
     *  @param   encoding         character encoding used for data
     *  @deprecated  use setter methods to set properties
     */
    public FTPClient(InetAddress remoteAddr, int controlPort, int timeout, String encoding)
        throws IOException, FTPException {
        if (controlPort < 0)
            controlPort = FTPControlSocket.CONTROL_PORT;
        initialize(new FTPControlSocket(remoteAddr, controlPort, timeout, encoding, null));
    }
    
    /**
     *  Default constructor should now always be used together with setter methods
     *  in preference to other constructors (now deprecated). The {@link #connect()}
     *  method is used to perform the actual connection to the remote host - but only
     *  for this constructor. Deprecated constructors connect in the constructor and
     *  connect() is not required (and cannot be called).
     */
    public FTPClient() {
        log.debug(VersionDetails.report(this));
    }
    
    /**
     * Connects to the server at the address and port number defined
     * in the constructor. Must be performed before login() or user() is
     * called.
     * 
     * @throws IOException Thrown if there is a TCP/IP-related error.
     * @throws FTPException Thrown if there is an error related to the FTP protocol. 
     */
    public void connect() throws IOException, FTPException {

        checkConnection(false);
       
        log.debug("Connecting to " + remoteAddr + ":" + controlPort);
        
        initialize(new FTPControlSocket(remoteAddr, controlPort, timeout, 
                                         controlEncoding, messageListener));
    }
    
    /**
     * Is this client connected?
     * 
     * @return true if connected, false otherwise
     */
    public boolean connected() {
        return control != null;
    }
    
    /**
     * Checks if the client has connected to the server and throws an exception if it hasn't.
     * This is only intended to be used by subclasses
     * 
     * @throws FTPException Thrown if the client has not connected to the server.
     */
    protected void checkConnection(boolean shouldBeConnected) throws FTPException {
    	if (shouldBeConnected && !connected())
    		throw new FTPException("The FTP client has not yet connected to the server.  "
    				+ "The requested action cannot be performed until after a connection has been established.");
    	else if (!shouldBeConnected && connected())
    		throw new FTPException("The FTP client has already been connected to the server.  "
    				+"The requested action must be performed before a connection is established.");
    }
    	
    /**
     * Set the control socket explicitly
     * 
     * @param control   control socket reference
     */
	protected void initialize(FTPControlSocket control) throws IOException {
		this.control = control;
        control.setMessageListener(messageListener);
        control.setStrictReturnCodes(strictReturnCodes);
        control.setTimeout(timeout);
        control.setAutoPassiveIPSubstitution(autoPassiveIPSubstitution);
	}
    
    /**
     *  Switch debug of responses on or off
     *
     *  @param  on  true if you wish to have responses to
     *              the log stream, false otherwise
     *  @deprecated  use the Logger class to switch debugging on and off
     */
    public void debugResponses(boolean on) {
        if (on)
            Logger.setLevel(Level.DEBUG);
        else
            Logger.setLevel(Level.OFF);
    }    
    
    /**
     * Set strict checking of FTP return codes. If strict 
     * checking is on (the default) code must exactly match the expected 
     * code. If strict checking is off, only the first digit must match.
     * 
     * @param strict    true for strict checking, false for loose checking
     */
    public void setStrictReturnCodes(boolean strict) {
        this.strictReturnCodes = strict;
        if (control != null)
            control.setStrictReturnCodes(strict);
    }
    
    /**
     * Determine if strict checking of return codes is switched on. If it is 
     * (the default), all return codes must exactly match the expected code.  
     * If strict checking is off, only the first digit must match.
     * 
     * @return  true if strict return code checking, false if non-strict.
     */
    public boolean isStrictReturnCodes() {
        return strictReturnCodes;
    }
    
    /* (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#setDetectTransferMode(boolean)
     */
    public void setDetectTransferMode(boolean detectTransferMode) {
        this.detectTransferMode = detectTransferMode;
    }

    /* (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#getDetectTransferMode()
     */
    public boolean getDetectTransferMode() {
         return detectTransferMode;
    }
    
    /**
     * Switch the transfer mode if requested and if necessary
     * 
     * @param filename      filename of file to be transferred
     */
    private void chooseTransferMode(String filename) {
        if (detectTransferMode) {
            if (FileTypes.ASCII.matches(filename) && 
                transferType.equals(FTPTransferType.BINARY)) {
                transferType = FTPTransferType.ASCII;
                log.debug("Autodetect on - changed transfer type to ASCII");
            }
            else if (FileTypes.BINARY.matches(filename) && 
                      transferType.equals(FTPTransferType.ASCII)) {
                transferType = FTPTransferType.BINARY;
                log.debug("Autodetect on - changed transfer type to binary");
            }
        }
    }

    /**
     *   Set the TCP timeout (in milliseconds) on the underlying socket.
     *
     *   If a timeout is set, then any operation which
     *   takes longer than the timeout value will be
     *   killed with a java.io.InterruptedException. We
     *   set both the control and data connections
     *
     *   @param millis The length of the timeout, in milliseconds
     */
    public void setTimeout(int millis)
        throws IOException {

        this.timeout = millis;
        if (control != null)
            control.setTimeout(millis);
    }
    
 
    /**
     *  Get the TCP timeout 
     *  
     *  @return timeout that is used, in milliseconds
     */
    public int getTimeout() {
        return timeout;
    }
    
    /**
     * Returns the control-port being connected to on the remote server. 
     * 
     * Note that this method replaces {@link #getControlPort()}.
     * 
     * @return Returns the port being connected to on the remote server. 
     */
    public int getRemotePort() {
        return controlPort;
    }
    
    /** 
     * Set the control to connect to on the remote server. Can only do this if
     * not already connected.
     * 
     * Note that this method replaces {@link #setControlPort(int)}.
     * 
     * @param remotePort The port to use. 
     * @throws FTPException Thrown if the client is already connected to the server.
     */
    public void setRemotePort(int remotePort) throws FTPException {
        checkConnection(false);
        this.controlPort = remotePort;
    }

    /**
     * Returns the control-port being connected to on the remote server. 
     * @return Returns the port being connected to on the remote server. 
     * @deprecated Use {@link com.enterprisedt.net.ftp.FTPClientInterface#getRemotePort()} instead.
     */
    public int getControlPort() {
        return controlPort;
    }
    
    /** 
     * Set the control to connect to on the remote server. Can only do this if
     * not already connected.
     * 
     * @param controlPort The port to use. 
     * @throws FTPException Thrown if the client is already connected to the server.
     * @deprecated Use {@link com.enterprisedt.net.ftp.FTPClientInterface#setRemotePort(int)} instead.
     */
    public void setControlPort(int controlPort) throws FTPException {
        checkConnection(false);
        this.controlPort = controlPort;
    }
    
    /**
     * @return Returns the remoteAddr.
     */
    public InetAddress getRemoteAddr() {
        return remoteAddr;
    }
    
    /**
     * Set the remote address
     * 
     * @param remoteAddr The remoteAddr to set.
     * @throws FTPException
     */
    public void setRemoteAddr(InetAddress remoteAddr) throws FTPException {
        checkConnection(false);
        this.remoteAddr = remoteAddr;
        this.remoteHost = remoteAddr.getHostName();
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#getRemoteHost()
     */
    public String getRemoteHost() {
        return remoteHost;
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#setRemoteHost(java.lang.String)
     */
    public void setRemoteHost(String remoteHost) throws IOException, FTPException {
        checkConnection(false);
        this.remoteHost = remoteHost;
        this.remoteAddr = InetAddress.getByName(remoteHost);
    }
    
    /**
     * Is automatic substitution of the remote host IP set to
     * be on for passive mode connections?
     * 
     * @return true if set on, false otherwise
     */
    public boolean isAutoPassiveIPSubstitution() {
        return autoPassiveIPSubstitution;
    }

    /**
     * Set automatic substitution of the remote host IP on if
     * in passive mode
     * 
     * @param autoPassiveIPSubstitution true if set to on, false otherwise
     */
    public void setAutoPassiveIPSubstitution(boolean autoPassiveIPSubstitution) {
        this.autoPassiveIPSubstitution = autoPassiveIPSubstitution;
        if (control != null)
            control.setAutoPassiveIPSubstitution(autoPassiveIPSubstitution);
    }
    
    
    /**
     * Get the encoding used for the control connection
     * 
     * @return Returns the current controlEncoding.
     */
    public String getControlEncoding() {
        return controlEncoding;
    }
    
    /**
     * Set the control socket's encoding. Can only do this if
     * not connected
     * 
     * @param controlEncoding The controlEncoding to set, which is the name of a Charset
     * @see java.nio.charset.Charset
     * @throws FTPException
     */
    public void setControlEncoding(String controlEncoding) throws FTPException {
        checkConnection(false);
        this.controlEncoding = controlEncoding;
    }
    /**
     * @return Returns the messageListener.
     */
    public FTPMessageListener getMessageListener() {
        return messageListener;
    }
    
    /**
     * Set a listener that handles all FTP messages
     * 
     * @param listener  message listener
     */
    public void setMessageListener(FTPMessageListener listener) {
        this.messageListener = listener;
        if (control != null)
           control.setMessageListener(listener);
    }
    
    /**
     *  Set the connect mode
     *
     *  @param  mode  ACTIVE or PASV mode
     */
    public void setConnectMode(FTPConnectMode mode) {
        connectMode = mode;
    }
    
    /**
     * @return Returns the connectMode.
     */
    public FTPConnectMode getConnectMode() {
        return connectMode;
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#setProgressMonitor(com.enterprisedt.net.ftp.FTPProgressMonitor, long)
     */
    public void setProgressMonitor(FTPProgressMonitor monitor, long interval) {
        this.monitor = monitor;
        this.monitorInterval = interval;
    }    

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#setProgressMonitor(com.enterprisedt.net.ftp.FTPProgressMonitor)
     */
    public void setProgressMonitor(FTPProgressMonitor monitor) {
        this.monitor = monitor;
    }    

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#getMonitorInterval()
     */
    public long getMonitorInterval() {
        return monitorInterval;
    }
    
    /**
     * Set the size of the buffers used in writing to and reading from
     * the data sockets
     * 
     * @param size  new size of buffer in bytes
     */
    public void setTransferBufferSize(int size) {
        transferBufferSize = size;
    }
    
    /**
     * Get the size of the buffers used in writing to and reading from
     * the data sockets
     * 
     * @return  transfer buffer size
     */
    public int getTransferBufferSize() {
        return transferBufferSize;
    }
    
    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#cancelTransfer()
     */
    public void cancelTransfer() {
        cancelTransfer = true;
        log.warn("cancelTransfer() called");
    } 
    
    /**
     * We can force PORT to send a fixed IP address, which can be useful with certain
     * NAT configurations. Must be connected to the remote host to call this method.
     * 
     * @param IPAddress     IP address to force, in 192.168.1.0 form
     * @deprecated
     */
    public void setPORTIP(String IPAddress) 
        throws FTPException {
        setActiveIPAddress(IPAddress);
    }
    
    /**
     * We can force PORT to send a fixed IP address, which can be useful with certain
     * NAT configurations. Must be connected to the remote host to call this method.
     * 
     * @param forcedActiveIP     IP address to force, in 192.168.1.0 form or in IPV6 form, e.g.
     *                            1080::8:800:200C:417A
     */
    public void setActiveIPAddress(String forcedActiveIP) 
        throws FTPException {
        
        checkConnection(true);
        control.setActivePortIPAddress(forcedActiveIP);
    }
    
    
    /**
     * We can force PORT to send a fixed IP address, which can be useful with certain
     * NAT configurations. Must be connected to the remote host to call this method.
     * 
     * @param lowest     Lower limit of range.
     * @param highest    Upper limit of range.
     */
    public void setActivePortRange(int lowest, int highest) 
        throws FTPException {
        
        checkConnection(true);
        
        if (lowest < 0 || lowest > highest || highest > MAX_PORT)
            throw new FTPException("Invalid port range specified");
        
        control.setActivePortRange(lowest, highest);
    }
    
       
    /**
     *  Login into an account on the FTP server. This
     *  call completes the entire login process. Note that
     *  connect() must be called first.
     *
     *  @param   user       user name
     *  @param   password   user's password
     */
    public void login(String user, String password)
        throws IOException, FTPException {
    	
    	checkConnection(true);
        
        user(user);

        if (lastValidReply.getReplyCode().equals("230"))
            return;
        else {
            password(password);
        }
    }
    
    /**
     *  Login into an account on the FTP server. This call completes the 
     *  entire login process. This method permits additional account information 
     *  to be supplied. FTP servers can use combinations of these parameters in 
     *  many different ways, e.g. to pass in proxy details via this method, some 
     *  servers use the "user" as 'ftpUser + "@" + ftpHost + " " + ftpProxyUser', 
     *  the "password" as the FTP user's password, and the accountInfo as the proxy 
     *  password. Note that connect() must be called first.
     *
     *  @param   user           user name
     *  @param   password       user's password
     *  @param   accountInfo    account info string
     */
    public void login(String user, String password, String accountInfo)
        throws IOException, FTPException {
        
        checkConnection(true);
        
        user(user);

        if (lastValidReply.getReplyCode().equals("230")) // no pwd
            return;
        else {
            password(password);
            if (lastValidReply.getReplyCode().equals("332")) // requires acct info
                account(accountInfo);
        }
    }    
    
    /**
     *  Supply the user name to log into an account
     *  on the FTP server. Must be followed by the
     *  password() method - but we allow for no password.
     *  Note that connect() must be called first.
     *
     *  @param   user       user name
     */
    public void user(String user)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand("USER " + user);

        // we allow for a site with no password - 230 response
        String[] validCodes = {"230", "331"};
        lastValidReply = control.validateReply(reply, validCodes);
    }


    /**
     *  Supplies the password for a previously supplied
     *  username to log into the FTP server. Must be
     *  preceeded by the user() method
     *
     *  @param   password       The password.
     */
    public void password(String password)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand("PASS " + password);

        // we allow for a site with no passwords (202) or requiring
        // ACCT info (332)
        String[] validCodes = {"230", "202", "332"};
        lastValidReply = control.validateReply(reply, validCodes);
    }
    
    
    /**
     *  Supply account information string to the server. This can be
     *  used for a variety of purposes - for example, the server could
     *  indicate that a password has expired (by sending 332 in reply to
     *  PASS) and a new password automatically supplied via ACCT. It
     *  is up to the server how it uses this string.
     *
     *  @param   accountInfo    account information string
     */
    public void account(String accountInfo)
        throws IOException, FTPException {
        
        checkConnection(true);
        
        FTPReply reply = control.sendCommand("ACCT " + accountInfo);

        // ok or not implemented
        String[] validCodes = {"230", "202"};
        lastValidReply = control.validateReply(reply, validCodes);
    }


    /**
     *  Set up SOCKS v4/v5 proxy settings. This can be used if there
     *  is a SOCKS proxy server in place that must be connected thru.
     *  Note that setting these properties directs all TCP
     *  sockets in this JVM to the SOCKS proxy
     *
     *  @param  port  SOCKS proxy port
     *  @param  host  SOCKS proxy hostname
     */
    public static void initSOCKS(String port, String host) {
        Properties props = System.getProperties();
        props.put(SOCKS_PORT, port);
        props.put(SOCKS_HOST, host);
        System.setProperties(props);
    }

    /**
     *  Set up SOCKS username and password for SOCKS username/password
     *  authentication. Often, no authentication will be required
     *  but the SOCKS server may be configured to request these.
     *
     *  @param  username   the SOCKS username
     *  @param  password   the SOCKS password
     */
    public static void initSOCKSAuthentication(String username,
                                               String password) {
        Properties props = System.getProperties();
        props.put("java.net.socks.username", username);
        props.put("java.net.socks.password", password);
        System.setProperties(props);
    }
    
    /**
     * Clear SOCKS settings. Note that setting these properties affects 
     * all TCP sockets in this JVM
     */
    public static void clearSOCKS() {
        
        Properties prop = System.getProperties(); 
        prop.remove(SOCKS_HOST); 
        prop.remove(SOCKS_PORT); 
        System.setProperties(prop); 
    }

    /**
     *  Get the name of the remote host
     *
     *  @return  remote host name
     */
    String getRemoteHostName() {
        return control.getRemoteHostName();
    }

    /**
     *  Issue arbitrary ftp commands to the FTP server.
     *
     *  @param command     ftp command to be sent to server
     *  @param validCodes  valid return codes for this command. If null
     *                      is supplied no validation is performed
     * 
     *  @return  the text returned by the FTP server
     */
    public String quote(String command, String[] validCodes)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand(command);

        // allow for no validation to be supplied
        if (validCodes != null) {
            lastValidReply = control.validateReply(reply, validCodes);
        }
        else { // no validation
            lastValidReply = reply; // assume valid
        }
        return lastValidReply.getReplyText();       
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#size(java.lang.String)
     */
     public long size(String remoteFile)
         throws IOException, FTPException {
     	
     	 checkConnection(true);
     	
         FTPReply reply = control.sendCommand("SIZE " + remoteFile);
         lastValidReply = control.validateReply(reply, "213");

         // parse the reply string .
         String replyText = lastValidReply.getReplyText();
         
         // trim off any trailing characters after a space, e.g. webstar
         // responds to SIZE with 213 55564 bytes
         int spacePos = replyText.indexOf(' ');
         if (spacePos >= 0)
             replyText = replyText.substring(0, spacePos);
         
         // parse the reply
         try {
             return Long.parseLong(replyText);
         }
         catch (NumberFormatException ex) {
             throw new FTPException("Failed to parse reply: " + replyText);
         }         
     }
     
     /*
      *  (non-Javadoc)
      * @see com.enterprisedt.net.ftp.FTPClientInterface#resume()
      */
     public void resume() throws FTPException {
         if (transferType.equals(FTPTransferType.ASCII))
             throw new FTPException("Resume only supported for BINARY transfers");
         resume = true;
     }
     
     /*
      *  (non-Javadoc)
      * @see com.enterprisedt.net.ftp.FTPClientInterface#cancelResume()
      */
     public void cancelResume() 
         throws IOException, FTPException {
         restart(0);
         resume = false;
     }
     
     /**
      * Issue the RESTart command to the remote server. This indicates the byte
      * position that REST is performed at. For put, bytes start at this point, while
      * for get, bytes are fetched from this point.
      * 
      * @param size     the REST param, the mark at which the restart is 
      *                  performed on the remote file. For STOR, this is retrieved
      *                  by SIZE
      * @throws IOException
      * @throws FTPException
      */
     public void restart(long size) 
         throws IOException, FTPException {
         FTPReply reply = control.sendCommand("REST " + size);
         lastValidReply = control.validateReply(reply, "350");
     }

     /*
      *  (non-Javadoc)
      * @see com.enterprisedt.net.ftp.FTPClientInterface#put(java.lang.String, java.lang.String)
      */
    public void put(String localPath, String remoteFile)
        throws IOException, FTPException {

        put(localPath, remoteFile, false);
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#put(java.io.InputStream, java.lang.String)
     */
    public void put(InputStream srcStream, String remoteFile)
        throws IOException, FTPException {

        put(srcStream, remoteFile, false);
    }


    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#put(java.lang.String, java.lang.String, boolean)
     */
    public void put(String localPath, String remoteFile, boolean append)
        throws IOException, FTPException {
    	    	
        InputStream srcStream = new FileInputStream(localPath);
        put(srcStream, remoteFile, append);
     }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#put(java.io.InputStream, 
     *  java.lang.String, boolean)
     */
    public void put(InputStream srcStream, String remoteFile, boolean append)
        throws IOException, FTPException {

        FTPTransferType currentType = transferType;
        chooseTransferMode(remoteFile);
        try {        
            putData(srcStream, remoteFile, append);
            validateTransfer();
        }
        catch (IOException ex) {
            validateTransferOnError();
            throw ex;        
        }
        finally {
            transferType = currentType;
        }
    }

    /**
     * Validate that the put() or get() was successful.  This method is not
     * for general use.
     */
    public void validateTransfer()
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        // check the control response
        String[] validCodes = {"225", "226", "250", "426", "450"};
        FTPReply reply = control.readReply();
        
        // permit 426/450 error if we cancelled the transfer, otherwise
        // throw an exception
        String code = reply.getReplyCode();
        if ( (code.equals("426")||code.equals("450")) && !cancelTransfer )
            throw new FTPException(reply);
        
        lastValidReply = control.validateReply(reply, validCodes);
    }
    
    /**
     * Validate a transfer when an error has occurred on the data channel.
     * Set a very short transfer in case things have hung. Set it back
     * at the end.
     * 
     * @throws IOException
     * @throws FTPException
     */
    private void validateTransferOnError() 
        throws IOException, FTPException {
        
        checkConnection(true);
        control.setTimeout(SHORT_TIMEOUT);
        try {
            validateTransfer();
        }
        finally {
            control.setTimeout(timeout);
        }
    }
    
    /**
     * Close the data socket
     */
    private void closeDataSocket() {
        if (data != null) {
            try {
                data.close();
                data = null;
            }
            catch (IOException ex) {
                log.warn("Caught exception closing data socket", ex);
            }
        }
    }
    
       
    /**
     * Close stream for data socket
     * 
     * @param stream    stream reference
     */
    private void closeDataSocket(InputStream stream) {
        if (stream != null) {
            try {
                stream.close();
            } 
            catch (IOException ex) {
                log.warn("Caught exception closing data socket", ex);
            }
        }

        closeDataSocket();
    }
    
    
    /**
     * Close stream for data socket
     * 
     * @param stream    stream reference
     */
    private void closeDataSocket(OutputStream stream) {
        if (stream != null) {
            try {
                stream.close();
            } 
            catch (IOException ex) {
                log.warn("Caught exception closing data socket", ex);
            }
        }

        closeDataSocket();
    }

    /**
     * Request the server to set up the put
     * 
     * @param remoteFile
     *            name of remote file in current directory
     * @param append
     *            true if appending, false otherwise
     */
    private void initPut(String remoteFile, boolean append)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        // reset the cancel flag
        cancelTransfer = false;

        boolean close = false;
        data = null;
        try {
            // set up data channel
            data = control.createDataSocket(connectMode);
            data.setTimeout(timeout);
            
            // if resume is requested, we must obtain the size of the
            // remote file and issue REST
            if (resume) {
                if (transferType.equals(FTPTransferType.ASCII))
                    throw new FTPException("Resume only supported for BINARY transfers");
                resumeMarker = size(remoteFile);
                restart(resumeMarker);
            }
    
            // send the command to store
            String cmd = append ? "APPE " : "STOR ";
            FTPReply reply = control.sendCommand(cmd + remoteFile);
    
            // Can get a 125 or a 150, also allow 350 (for Global eXchange Services server)
            String[] validCodes = {"125", "150", "350"};
            lastValidReply = control.validateReply(reply, validCodes);
        }
        catch (IOException ex) {
            close = true;
            throw ex;
        }
        catch (FTPException ex) {
            close = true;
            throw ex;
        }
        finally {
            if (close) {
                resume = false;
                closeDataSocket();
            }
        }
    }

    /**
     *  Put as binary, i.e. read and write raw bytes
     *
     *  @param  srcStream   input stream of data to put
     *  @param  remoteFile  name of remote file we are writing to
     *  @param  append      true if appending, false otherwise
     */
    private void putData(InputStream srcStream, String remoteFile,
                           boolean append)
        throws IOException, FTPException {

        BufferedInputStream in = null;
        BufferedOutputStream out = null;
        long size = 0;
        try {
            in = new BufferedInputStream(srcStream);
    
            initPut(remoteFile, append);
    
            // get an output stream
            out = new BufferedOutputStream(
                    new DataOutputStream(data.getOutputStream()), transferBufferSize*2);
            
            // if resuming, we skip over the unwanted bytes
            if (resume) {
                in.skip(resumeMarker);
            }
    
            byte[] buf = new byte[transferBufferSize];
            byte[] prevBuf = null;
    
            // read a chunk at a time and write to the data socket            
            long monitorCount = 0;
            int count = 0;
            boolean isASCII = getType() == FTPTransferType.ASCII;
            int separatorPos = 0;
            
            while ((count = in.read(buf)) > 0 && !cancelTransfer) {
                if (isASCII) {
                    for (int i = 0; i < count; i++) {
                        // at each byte pos, check if there's a match along the line sep array  
                        boolean found = true;
                        int skip = 0;
                        for (; separatorPos < LINE_SEPARATOR.length && i+separatorPos < count; skip++,separatorPos++) {
                            if (buf[i+separatorPos] != LINE_SEPARATOR[separatorPos]) {
                                found = false;
                                break;
                            }
                        }
                        if (found) { // either found match or run out of buffer
                            if (separatorPos == LINE_SEPARATOR.length) { // found line separator
                                out.write(CARRIAGE_RETURN);
                                out.write(LINE_FEED);
                                size += 2;
                                monitorCount += 2;
                                separatorPos = 0;
                                // now must skip over bytes that match
                                i += (skip-1);  // we are already skipping current byte
                                prevBuf = null;
                            }
                            else { // reached end of buffer && are matching so far
                                   // Do nothing, we'll pick it up next time
                                // we need to save the bytes matched so far
                                prevBuf = new byte[skip];
                                for (int k = 0; k < skip; k++) {
                                    prevBuf[k] = buf[i+k];
                                }
                            }
                        }
                        else { // match failed, write out this byte && any prev ones from last buf
                            if (prevBuf != null) {
                                out.write(prevBuf);
                                size += prevBuf.length;
                                monitorCount += prevBuf.length;
                                prevBuf = null;
                            }
                            out.write(buf[i]);
                            size++;
                            monitorCount++;
                            separatorPos = 0;
                        }
                    }
                }
                else {
                    out.write(buf, 0, count);
                    size += count;
                    monitorCount += count;
                }
                
                // write out saved chunk if it exists
                if (prevBuf != null) {
                    out.write(prevBuf);
                    size += prevBuf.length;
                    monitorCount += prevBuf.length;
                }
                    
                if (monitor != null && monitorCount > monitorInterval) {
                    monitor.bytesTransferred(size); 
                    monitorCount = 0;  
                }
            }
        }
        finally {
            resume = false;
            try {
                if (in != null)
                    in.close();
            }
            catch (IOException ex) {
                log.warn("Caught exception closing input stream", ex);
            }
                
            closeDataSocket(out);
            
            // notify the final transfer size
            if (monitor != null)
                monitor.bytesTransferred(size);  
            // log bytes transferred
            log.debug("Transferred " + size + " bytes to remote host");           
        }
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#put(byte[], java.lang.String)
     */
    public void put(byte[] bytes, String remoteFile)
        throws IOException, FTPException {

        put(bytes, remoteFile, false);
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#put(byte[], java.lang.String, boolean)
     */
    public void put(byte[] bytes, String remoteFile, boolean append)
        throws IOException, FTPException {
        
        FTPTransferType currentType = transferType;
        chooseTransferMode(remoteFile);
        try {
            ByteArrayInputStream input = new ByteArrayInputStream(bytes);
            put(input, remoteFile, append);
        }
        finally {
            transferType = currentType;
        }
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#get(java.lang.String, java.lang.String)
     */
    public void get(String localPath, String remoteFile)
        throws IOException, FTPException {

        FTPTransferType currentType = transferType;
        chooseTransferMode(remoteFile);
        try {        
            getData(localPath, remoteFile);
            validateTransfer();
        }
        catch (IOException ex) {
            validateTransferOnError();
            throw ex;        
        }
        finally {
            transferType = currentType;
        }
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#get(java.io.OutputStream, java.lang.String)
     */
    public void get(OutputStream destStream, String remoteFile)
        throws IOException, FTPException {

        FTPTransferType currentType = transferType;
        chooseTransferMode(remoteFile);
        try {        
            getData(destStream, remoteFile);
            validateTransfer();
        }
        catch (IOException ex) {
            validateTransferOnError();
            throw ex;        
        }
        finally {
            transferType = currentType;
        }
    }


    /**
     *  Request to the server that the get is set up
     *
     *  @param  remoteFile  name of remote file
     */
    private void initGet(String remoteFile)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        // reset the cancel flag
        cancelTransfer = false;            

        boolean close = false;
        data = null;
        try {
            // set up data channel
            data = control.createDataSocket(connectMode);
            data.setTimeout(timeout);
            
            // if resume is requested, we must issue REST
            if (resume) {
                if (transferType.equals(FTPTransferType.ASCII))
                    throw new FTPException("Resume only supported for BINARY transfers");
                restart(resumeMarker);
            }
    
            // send the retrieve command
            FTPReply reply = control.sendCommand("RETR " + remoteFile);
    
            // Can get a 125 or a 150
            String[] validCodes1 = {"125", "150"};
            lastValidReply = control.validateReply(reply, validCodes1);
        }
        catch (IOException ex) {
            close = true;
            throw ex;
        }
        catch (FTPException ex) {
            close = true;
            throw ex;
        }
        finally {
            if (close) {
                resume = false;
                closeDataSocket();
            }
        }
    }
    

    /**
     *  Get as binary file, i.e. straight transfer of data
     *
     *  @param localPath   full path of local file to write to
     *  @param remoteFile  name of remote file
     */
    private void getData(String localPath, String remoteFile)
        throws IOException, FTPException {

        // B. McKeown: Need to store the local file name so the file can be
        // deleted if necessary.
        File localFile = new File(localPath);
        
        // if resuming, we must find the marker
        if (localFile.exists()) {
            if (!localFile.canWrite())
                throw new FTPException(localPath + " is readonly - cannot write");
            if (resume)
                resumeMarker = localFile.length();
        }           

        // B.McKeown:
        // Call initGet() before creating the FileOutputStream.
        // This will prevent being left with an empty file if a FTPException
        // is thrown by initGet().
        initGet(remoteFile);

        // create the buffered output stream for writing the file
        FileOutputStream out =
                new FileOutputStream(localPath, resume);
        
        try {
            getDataAfterInitGet(out);
        }
        catch (IOException ex) {
            localFile.delete();
            log.debug("Deleting local file '" + localFile.getAbsolutePath() + "'");
            throw ex;
        }        
    }

    /**
     *  Get as binary file, i.e. straight transfer of data
     *
     *  @param destStream  stream to write to
     *  @param remoteFile  name of remote file
     */
    private void getData(OutputStream destStream, String remoteFile)
        throws IOException, FTPException {

        initGet(remoteFile);
        
        getDataAfterInitGet(destStream);
    }
    
        
    /**
     *  Get as binary file, i.e. straight transfer of data
     *
     *  @param destStream  stream to write to
     */
    private void getDataAfterInitGet(OutputStream destStream)
        throws IOException, FTPException {

        // create the buffered output stream for writing the file
        BufferedOutputStream out =
            new BufferedOutputStream(destStream);
        
        BufferedInputStream in = null;
        long size = 0;
        IOException storedEx = null;
        try {
            // get an input stream to read data from ... AFTER we have
            // the ok to go ahead AND AFTER we've successfully opened a
            // stream for the local file
            in = new BufferedInputStream(
                    new DataInputStream(data.getInputStream()));
    
            // B. McKeown:
            // If we are in active mode we have to set the timeout of the passive
            // socket. We can achieve this by calling setTimeout() again.
            // If we are in passive mode then we are merely setting the value twice
            // which does no harm anyway. Doing this simplifies any logic changes.
            data.setTimeout(timeout);
    
            // do the retrieving
            long monitorCount = 0; 
            byte [] chunk = new byte[transferBufferSize];
            int count;
            boolean isASCII = getType() == FTPTransferType.ASCII;
            boolean crFound = false;

            // read from socket & write to file in chunks        
            while ((count = readChunk(in, chunk, transferBufferSize)) >= 0 && !cancelTransfer) {
                if (isASCII) {
                    // transform CRLF
                    boolean lfFound = false;
                    for (int i = 0; i < count; i++) {
                        lfFound = chunk[i] == LINE_FEED;
                        // if previous is a CR, write it out if current is LF, otherwise
                        // write out the previous CR
                        if (crFound) {
                            if (lfFound) {
                               out.write(LINE_SEPARATOR, 0, LINE_SEPARATOR.length);
                               size += LINE_SEPARATOR.length;
                               monitorCount += LINE_SEPARATOR.length;
                            }
                            else {
                                // not CR LF so write out previous CR
                                out.write(CARRIAGE_RETURN); 
                                size++;
                                monitorCount++;
                            }                           
                        }
                        
                        // now check if current is CR
                        crFound = chunk[i] == CARRIAGE_RETURN;
                       
                        // if we didn't find a LF this time, write current byte out
                        // unless it is a CR - in that case save it
                        if (!lfFound && !crFound) {
                            out.write(chunk[i]);
                            size++;
                            monitorCount++;
                        }
                    }
                }
                else { // binary
                    out.write(chunk, 0, count);
                    size += count;
                    monitorCount += count;
                }
                
                if (monitor != null && monitorCount > monitorInterval) {
                    monitor.bytesTransferred(size); 
                    monitorCount = 0;  
                }                                                                        
            }            
            
            // account for last byte if necessary
            if (isASCII && crFound) { 
                out.write(CARRIAGE_RETURN); 
                size++;
                monitorCount++;
            }
        }
        catch (IOException ex) {
            storedEx = ex;
        }
        finally {
            try {
                if (out != null)
                    out.close();
            }
            catch (IOException ex) {
                log.warn("Caught exception closing output stream", ex);
            }

            resume = false;
    
            // close streams
            closeDataSocket(in);
    
            // if we failed to write the file, rethrow the exception
            if (storedEx != null)
                throw storedEx;
            else if (monitor != null)
                monitor.bytesTransferred(size);  
    
            // log bytes transferred
            log.debug("Transferred " + size + " bytes from remote host");
        }
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#get(java.lang.String)
     */
    public byte[] get(String remoteFile)
        throws IOException, FTPException {

        FTPTransferType currentType = transferType;
        chooseTransferMode(remoteFile);
        try {                
            ByteArrayOutputStream result = new ByteArrayOutputStream(transferBufferSize);
            getData(result, remoteFile);
            validateTransfer();
            return result == null ? null : result.toByteArray();
        }
        catch (IOException ex) {
            validateTransferOnError();
            throw ex;        
        }
        finally {
            transferType = currentType;
        }
    }


    /**
     *  Run a site-specific command on the
     *  server. Support for commands is dependent
     *  on the server
     *
     *  @param  command   the site command to run
     *  @return true if command ok, false if
     *          command not implemented
     */
    public boolean site(String command)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        // send the retrieve command
        FTPReply reply = control.sendCommand("SITE " + command);

        // Can get a 200 (ok) or 202 (not impl). Some
        // FTP servers return 502 (not impl)
        String[] validCodes = {"200", "202", "502"};
        lastValidReply = control.validateReply(reply, validCodes);

        // return true or false? 200 is ok, 202/502 not
        // implemented
        if (reply.getReplyCode().equals("200"))
            return true;
        else
            return false;
    }


    /**
     *  List a directory's contents
     *
     *  @param  dirname  the name of the directory (not a file mask)
     *  @return a string containing the line separated
     *          directory listing
     *  @deprecated  As of FTP 1.1, replaced by {@link #dir(String)}
     */
    public String list(String dirname)
        throws IOException, FTPException {

        return list(dirname, false);
    }


    /**
     *  List a directory's contents as one string. A detailed
     *  listing is available, otherwise just filenames are provided.
     *  The detailed listing varies in details depending on OS and
     *  FTP server.
     *
     *  @param  dirname  the name of the directory(not a file mask)
     *  @param  full     true if detailed listing required
     *                   false otherwise
     *  @return a string containing the line separated
     *          directory listing
     *  @deprecated  As of FTP 1.1, replaced by {@link #dir(String,boolean)}
     */
    public String list(String dirname, boolean full)
        throws IOException, FTPException {

        String[] list = dir(dirname, full);

        StringBuffer result = new StringBuffer();
        String sep = System.getProperty("line.separator");

        // loop thru results and make into one string
        for (int i = 0; i < list.length; i++) {
            result.append(list[i]);
            result.append(sep);
        }

        return result.toString();
    }
    
    
    /**
     * Override the chosen file factory with a user created one - meaning
     * that a specific parser has been selected
     * 
     * @param fileFactory
     */
    public void setFTPFileFactory(FTPFileFactory fileFactory) {
        this.fileFactory = fileFactory;
    }
    
    /**
     * Set the locale for date parsing of dir listings
     * 
     * @param locale    new locale to use
     */
    public void setParserLocale(Locale locale) {
        listingLocale = locale;
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#dirDetails(java.lang.String)
     */
    public FTPFile[] dirDetails(String dirname) 
        throws IOException, FTPException, ParseException {
        
        // create the factory
        if (fileFactory == null)
            fileFactory = new FTPFileFactory(system());
        fileFactory.setLocale(listingLocale);
                
        // get the details and parse
        return fileFactory.parse(dir(dirname, true));
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#dir()
     */
    public String[] dir()
        throws IOException, FTPException {

        return dir(null, false);
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#dir(java.lang.String)
     */
    public String[] dir(String dirname)
        throws IOException, FTPException {

        return dir(dirname, false);
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#dir(java.lang.String, boolean)
     */
    public String[] dir(String dirname, boolean full)
        throws IOException, FTPException {
    	
    	checkConnection(true);
        
        try {
            // set up data channel
            data = control.createDataSocket(connectMode);
            data.setTimeout(timeout);
    
            // send the retrieve command
            String command = full ? "LIST ":"NLST ";
            if (dirname != null)
                command += dirname;
    
            // some FTP servers bomb out if NLST has whitespace appended
            command = command.trim();
            FTPReply reply = control.sendCommand(command);
    
            // check the control response. wu-ftp returns 550 if the
            // directory is empty, so we handle 550 appropriately. Similarly
            // proFTPD returns 450 or 226 (depending on NLST or LIST)
            String[] validCodes1 = {"125", "150", "226", "450", "550"};
            lastValidReply = control.validateReply(reply, validCodes1);  
    
            // an empty array of files for 450/550
            String[] result = new String[0];
            
            // a normal reply ... extract the file list
            String replyCode = lastValidReply.getReplyCode();
            if (!replyCode.equals("450") && !replyCode.equals("550") && !replyCode.equals("226")) {
    			// get a character input stream to read data from .
    			LineNumberReader in = null;
                Vector lines = new Vector();    
                try {
    				in = new LineNumberReader(
    					    new InputStreamReader(data.getInputStream()));
    
        			// read a line at a time
        			String line = null;
        			while ((line = readLine(in)) != null) {
                        lines.addElement(line);
        			}
                }
                finally {
                    try {
                        if (in != null)
                            in.close();
                    }
                    catch (IOException ex) {
                        log.error("Failed to close socket in dir()", ex);
                    }
                    closeDataSocket();
                }
                    
                // check the control response
                String[] validCodes2 = {"226", "250"};
                reply = control.readReply();
                lastValidReply = control.validateReply(reply, validCodes2);
    
                // empty array is default
                if (!lines.isEmpty()) {
                    result = new String[lines.size()];
                    lines.copyInto(result);
                }
            }
            else { // throw exception if not "No files" or other message
            	String replyText = lastValidReply.getReplyText().toUpperCase();
				if (!noFilesMessage(replyText)
						&& replyText.indexOf(TRANSFER_COMPLETE) < 0)
					throw new FTPException(reply);
            }
            return result;
        }
        finally {
            closeDataSocket();
        }        
    }
    
    /**
     * Check to see if this message indicates no files found in listing
     * 
     * @param msg	FTP error message to check
     * @return true if a no files message, false otherwise
     */
    private boolean noFilesMessage(String msg) {
		for (int i = 0; i < EMPTY_DIR_MSGS.length; i++) {
			if (msg.toUpperCase().indexOf(EMPTY_DIR_MSGS[i]) >= 0)
				return true;
		}
		return false;
	}

    /**
	 * Attempts to read a specified number of bytes from the given
	 * InputStream and place it in the given byte-array. The
	 * purpose of this method is to permit subclasses to execute any additional
	 * code necessary when performing this operation.
	 * 
	 * @param in
	 *            The InputStream to read from.
	 * @param chunk
	 *            The byte-array to place read bytes in.
	 * @param chunksize
	 *            Number of bytes to read.
	 * @return Number of bytes actually read.
	 * @throws IOException
	 *             Thrown if there was an error while reading.
	 */
    protected int readChunk(BufferedInputStream in, byte[] chunk, int chunksize) 
    	throws IOException {
    		
    	return in.read(chunk, 0, chunksize);
    }
    
    /**
     * Attempts to read a single character from the given InputStream. 
     * The purpose of this method is to permit subclasses to execute
     * any additional code necessary when performing this operation. 
     * @param in The LineNumberReader to read from.
     * @return The character read.
     * @throws IOException Thrown if there was an error while reading.
     */
    protected int readChar(LineNumberReader in) 
    	throws IOException {
    		
    	return in.read();
    }
    
    /**
     * Attempts to read a single line from the given InputStream. 
     * The purpose of this method is to permit subclasses to execute
     * any additional code necessary when performing this operation. 
     * @param in The LineNumberReader to read from.
     * @return The string read.
     * @throws IOException Thrown if there was an error while reading.
     */
    protected String readLine(LineNumberReader in) 
    	throws IOException {
    		
    	return in.readLine();
    }

    /**
     *  Gets the latest valid reply from the server
     *
     *  @return  reply object encapsulating last valid server response
     */
    public FTPReply getLastValidReply() {
        return lastValidReply;
    }


    /**
     *  Get the current transfer type
     *
     *  @return  the current type of the transfer,
     *           i.e. BINARY or ASCII
     */
    public FTPTransferType getType() {
        return transferType;
    }

    /**
     *  Set the transfer type
     *
     *  @param  type  the transfer type to
     *                set the server to
     */
    public void setType(FTPTransferType type)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        // determine the character to send
        String typeStr = FTPTransferType.ASCII_CHAR;
        if (type.equals(FTPTransferType.BINARY))
            typeStr = FTPTransferType.BINARY_CHAR;

        // send the command
        FTPReply reply = control.sendCommand("TYPE " + typeStr);
        lastValidReply = control.validateReply(reply, "200");

        // record the type
        transferType = type;
    }

    
    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#delete(java.lang.String)
     */
    public void delete(String remoteFile)
        throws IOException, FTPException {
    	
    	checkConnection(true);
        String[] validCodes = {"200", "250"};
        FTPReply reply = control.sendCommand("DELE " + remoteFile);
        lastValidReply = control.validateReply(reply, validCodes);
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#rename(java.lang.String, java.lang.String)
     */
    public void rename(String from, String to)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand("RNFR " + from);
        lastValidReply = control.validateReply(reply, "350");

        reply = control.sendCommand("RNTO " + to);
        lastValidReply = control.validateReply(reply, "250");
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#rmdir(java.lang.String)
     */
    public void rmdir(String dir)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand("RMD " + dir);

        // some servers return 200,257, technically incorrect but
        // we cater for it ...
        String[] validCodes = {"200", "250", "257"};
        lastValidReply = control.validateReply(reply, validCodes);
    }


    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#mkdir(java.lang.String)
     */
    public void mkdir(String dir)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand("MKD " + dir);
        
        // some servers return 200,257, technically incorrect but
        // we cater for it ...
        String[] validCodes = {"200", "250", "257"};
        lastValidReply = control.validateReply(reply, validCodes);
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#chdir(java.lang.String)
     */
    public void chdir(String dir)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand("CWD " + dir);
        lastValidReply = control.validateReply(reply, "250");
    }
    
    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#cdup()
     */
    public void cdup()
        throws IOException, FTPException {
        
        checkConnection(true);
        
        FTPReply reply = control.sendCommand("CDUP");
        String[] validCodes = {"200", "250"};
        lastValidReply = control.validateReply(reply, validCodes);
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#modtime(java.lang.String)
     */
    public Date modtime(String remoteFile)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand("MDTM " + remoteFile);
        lastValidReply = control.validateReply(reply, "213");

        // parse the reply string ...
        Date ts = tsFormat.parse(lastValidReply.getReplyText(),
                                 new ParsePosition(0));
        return ts;
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#pwd()
     */
    public String pwd()
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand("PWD");
        lastValidReply = control.validateReply(reply, "257");

        // get the reply text and extract the dir
        // listed in quotes, if we can find it. Otherwise
        // just return the whole reply string
        String text = lastValidReply.getReplyText();
        int start = text.indexOf('"');
        int end = text.lastIndexOf('"');
        if (start >= 0 && end > start)
            return text.substring(start+1, end);
        else
            return text;
    }
    
    
    /**
     *  Get the server supplied features
     *
     *  @return   string containing server features, or null if no features or not
     *             supported
     */
    public String[] features()
        throws IOException, FTPException {
        
        checkConnection(true);
        
        FTPReply reply = control.sendCommand("FEAT");
        String[] validCodes = {"211", "500", "502"};
        lastValidReply = control.validateReply(reply, validCodes);
        if (lastValidReply.getReplyCode().equals("211"))
            return lastValidReply.getReplyData();
        else
            throw new FTPException(reply);
    }
    
    /**
     *  Get the type of the OS at the server
     *
     *  @return   the type of server OS
     */
    public String system()
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand("SYST");
        String[] validCodes = {"200", "213", "215"};
        lastValidReply = control.validateReply(reply, validCodes);
        return lastValidReply.getReplyText();
    }
    
    
    /**
     *  Send a "no operation" message that does nothing. Can be
     *  called periodically to prevent the connection timing out
     */
    public void noOperation()
        throws IOException, FTPException {
        
        checkConnection(true);
        
        FTPReply reply = control.sendCommand("NOOP");
        lastValidReply = control.validateReply(reply, "200");
    }

    /**
     *  Get the help text for the specified command
     *
     *  @param  command  name of the command to get help on
     *  @return help text from the server for the supplied command
     */
    public String help(String command)
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        FTPReply reply = control.sendCommand("HELP " + command);
        String[] validCodes = {"211", "214"};
        lastValidReply = control.validateReply(reply, validCodes);
        return lastValidReply.getReplyText();
    }
    
     /**
     *  Abort the current action
     */
    protected void abort()
        throws IOException, FTPException {
        
        checkConnection(true);
        
        FTPReply reply = control.sendCommand("ABOR");
        String[] validCodes = {"426", "226"};
        lastValidReply = control.validateReply(reply, validCodes);
    }

    /*
     *  (non-Javadoc)
     * @see com.enterprisedt.net.ftp.FTPClientInterface#quit()
     */
    public void quit()
        throws IOException, FTPException {
    	
    	checkConnection(true);
    	
        fileFactory = null;
        try {
            FTPReply reply = control.sendCommand("QUIT");
            String[] validCodes = {"221", "226"};
            lastValidReply = control.validateReply(reply, validCodes);
        }
        finally { // ensure we clean up the connection
            control.logout();
            control = null;
        }
    }

}






© 2015 - 2024 Weber Informatics LLC | Privacy Policy