//============================================================================== // FTPClient.java //============================================================================== package tribble.net.ftp; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.Exception; import java.lang.Integer; import java.lang.NullPointerException; import java.lang.String; import java.lang.System; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; /******************************************************************************* * Simple FTP client. * Allows clients to establish FTP connections, send and receive files, get * remote directory listings, etc. * *

* This is a simple, bare-bones, no-nonsense implementation, providing only the * most basic FTP capabilities, and performing only minimal error checking and * recovery. If your FTP server is well behaved, though, this implementation * should meet the basic needs of simple FTP applications. * * *

* Usage * *

* This is a simple program that connects to an FTP server, uploads a file, * downloads a file, then closes the connection to the server: * *

*    import tribble.net.ftp.*;
*
*    public class MyFtpClient
*    {
*        public static void main(String[] args)
*            throws Exception
*        {
*            {@link FTPClientI}  ftp = null;
*
*            try
*            {
*                // Connect and login to the FTP server
*                ftp = new {@link #FTPClient FTPClient}();
*                ftp.{@link #setHost setHost}("ftp.domain.net");
*                ftp.{@link #connect connect}();
*                ftp.{@link #login(String, String) login}("userid", "password");
*
*                // Upload (put) a text file
*                ftp.{@link #setTextMode setTextMode}(true);
*                ftp.{@link #setRemoteDir setRemoteDir}("incoming");
*                ftp.{@link #putFile(String, String) putFile}("file.txt", "file.txt");
*
*                // Download (get) a binary file
*                ftp.{@link #setRemoteDirUp setRemoteDirUp}();
*                ftp.{@link #setRemoteDir setRemoteDir}("outgoing");
*                ftp.{@link #setTextMode setTextMode}(false);
*                ftp.{@link #getFile(String, String) getFile}("file.dat", "file.dat");
*            }
*            catch ({@link FTPException} ex)
*            {
*                // An error occurred
*                System.out.println(ex.getMessage());
*                throw ex;
*            }
*            finally
*            {
*                // Close the FTP connection
*                if (ftp != null)
*                    ftp.{@link #disconnect disconnect}();
*            }
*        }
*    }
* * *

* References * *

* IETF RFC 959 - File Transfer Protocol (FTP)
* www.ietf.org/rfc/rfc0959.txt. * * *

*
Source code:
*
Available at: * http://david.tribble.com/src/java/tribble/net/ftp/FTPClient.java *
*
Documentation:
*
Available at: * http://david.tribble.com/docs/tribble/net/ftp/FTPClient.html *
*
* * * @version API 2.0 $Revision: 1.29 $ $Date: 2010/07/12 21:18:03 $ * @since API 1.0, 2001-04-14 * @author David R. Tribble (david@tribble.com). *

* Copyright ©2001-2010 by David R. Tribble, all rights reserved.
* Permission is granted to any person or entity except those designated * by the United States Department of State as a terrorist, or terrorist * government or agency, to use and distribute this source code provided * that the original copyright notice remains present and unaltered. */ public class FTPClient extends FTPClientAdapter implements FTPClientI { /** Revision information. */ static final String REV = "@(#)tribble/net/ftp/FTPClient.java API 2.0 $Revision: 1.29 $ $Date: 2010/07/12 21:18:03 $\n"; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constants /** FTP command line terminator (newline). */ static final String CMD_EOLN = "\r\n"; /** Local native JVM newline character sequence. */ static final byte[] LOCAL_NEWLINE = System.getProperty("line.separator").getBytes(); /** Remote newline character sequence (CR/LF pair). */ static final byte[] REMOTE_NEWLINE = { '\r', '\n' }; //-------------------------------------- // FTP commands static final String CMD_ABORT = "ABOR"; static final String CMD_ACCOUNT = "ACCT"; static final String CMD_ALLOCATE = "ALLO"; static final String CMD_APPEND = "APPE"; static final String CMD_CHDIR = "CWD"; static final String CMD_DELETE = "DELE"; static final String CMD_GET = "RETR"; static final String CMD_GETDIR = "PWD"; static final String CMD_HELP = "HELP"; static final String CMD_LISTFILES = "LIST"; static final String CMD_LISTNAMES = "NLST"; static final String CMD_LOGIN = "USER"; static final String CMD_LOGOUT = "QUIT"; static final String CMD_MKDIR = "MKD"; static final String CMD_MODE = "MODE"; static final String CMD_MODE_STREAM = "MODE S"; static final String CMD_MODE_BLOCK = "MODE B"; static final String CMD_MODE_COMPR = "MODE C"; static final String CMD_MOUNT = "SMNT"; static final String CMD_NOP = "NOOP"; static final String CMD_PARAMS = "SITE"; static final String CMD_PASSIVE = "PASV"; static final String CMD_PASSWORD = "PASS"; static final String CMD_PORT = "PORT"; static final String CMD_PUT = "STOR"; static final String CMD_PUT_UNIQ = "STOU"; static final String CMD_REINIT = "REIN"; static final String CMD_RENAME_FROM = "RNFR"; static final String CMD_RENAME_TO = "RNTO"; static final String CMD_RESTART = "REST"; static final String CMD_RMDIR = "RMD"; static final String CMD_STATUS = "STAT"; static final String CMD_STRUCT = "STRU"; static final String CMD_STRUCT_FILE = "STRU F"; static final String CMD_STRUCT_REC = "STRU R"; static final String CMD_STRUCT_PAGE = "STRU P"; static final String CMD_SYSTEM = "SYST"; static final String CMD_TYPE = "TYPE"; static final String CMD_TYPE_ASC = "TYPE A N"; static final String CMD_TYPE_BIN = "TYPE I"; static final String CMD_UPDIR = "CDUP"; //-------------------------------------- // FTP response code groups (first digit, code/100) static final short RG_OKAY_INC = 1; static final short RG_OKAY_COMPL = 2; static final short RG_OKAY_PEND = 3; static final short RG_FAIL_RETRY = 4; static final short RG_FAIL = 5; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Static methods /*************************************************************************** * Test driver. * *

* Usage *

* * java tribble.net.ftp.FTPClient host port|- * user|- password [-action...] * * * * @param args * Command line arguments. * * @see FTPClientRun * * @since 1.1, 2001-04-14 */ public static void main(String[] args) throws Exception { FTPClientRun.run(new FTPClient(), args); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Variables /** FTP command input stream. */ InputStream m_cmdIn; /** FTP command output stream. */ OutputStream m_cmdOut; /** FTP data input stream. */ InputStream m_dataIn; /** FTP data output stream. */ OutputStream m_dataOut; /** FTP command stream socket. */ Socket m_cmdSock; /** FTP data stream socket. */ Socket m_dataSock; /** FTP local connection host address. */ InetAddress m_localHost; /** Command I/O buffer. */ byte[] m_cbuf = new byte[2*1024]; /** Data I/O buffer. */ byte[] m_dbuf = new byte[4*1024]; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constructors /*************************************************************************** * Default constructor. * * @since 1.1, 2001-04-14 */ public FTPClient() { } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Methods /*************************************************************************** * Connect to the remote FTP system. * * @throws IOException * Thrown if unable to connect to the remote FTP system. * * @see #disconnect disconnect() * * @since 1.1, 2001-04-14 */ @Override public void connect() throws IOException { FTPResponse resp; // Sanity checks if (m_hostName == null || m_hostName.length() == 0) throw new FTPException("FTP host name not set"); if (m_debugOut != null) m_debugOut.println("$ connect: " + m_hostName + ":" + m_cmdPort + "/" + m_dataPort); // Close any previous connection if (m_isConnected) disconnect(); m_stop = false; // Establish a new FTP connection m_cmdSock = new Socket(m_hostName, m_cmdPort); m_cmdSock.setSoTimeout(m_timeOut*1000); m_localHost = m_cmdSock.getLocalAddress(); m_cmdIn = m_cmdSock.getInputStream(); m_cmdOut = m_cmdSock.getOutputStream(); m_isConnected = true; if (m_debugOut != null) m_debugOut.println("$ localHost: " + m_localHost.toString() + ":" + m_cmdPort); // Read connection response from the remote FTP system resp = getResponse(); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't connect to FTP system (" + resp.m_code + "): " + m_hostName + ":" + m_cmdPort); } /*************************************************************************** * Disconnect from the remote FTP system. * Note that this method does not throw {@link IOException}. * * @see #connect connect() * * @since 1.1, 2001-04-14 */ @Override public void disconnect() { FTPResponse resp; // Sanity checks if (!m_isConnected) return; // Close the data port I/O streams try { if (m_dataSock != null) closeDataPort(); } catch (IOException ex) { } // Close the FTP connection try { resp = doCommand(CMD_LOGOUT); } catch (IOException ex) { } // Close the FTP connection try { // Shut down the FTP I/O streams m_cmdOut.close(); m_cmdIn.close(); } catch (IOException ex) { } try { // Shut down the FTP I/O streams m_cmdSock.close(); } catch (IOException ex) { } // Clean up m_cmdOut = null; m_cmdIn = null; m_cmdSock = null; m_isLoggedOn = false; m_isConnected = false; m_stop = false; if (m_debugOut != null) m_debugOut.println("$ disconnected: " + m_hostName); } /*************************************************************************** * Log on to the remote FTP system. * * @param user * FTP user-ID for the remote system. * * @param pwd * User password. This can be empty (""). * * @throws IOException * Thrown if an error occurs. * * @see #login() login() * @see #connect connect() * * @since 1.2, 2006-03-15 */ @Override public void login(String user, String pwd) throws IOException { FTPResponse resp; int rc; int i, j; // Sanity checks if (user == null || user.length() == 0) throw new FTPException("Null or empty FTP user-ID"); if (pwd == null) throw new FTPException("Null FTP password"); if (!m_isConnected) throw new FTPException("FTP session not connected"); if (m_debugOut != null) m_debugOut.println("$ login: user='" + user + "'"); // Log into the remote FTP system m_userID = user; resp = doCommand(CMD_LOGIN, user); rc = resp.m_code; if (rc/100 == RG_OKAY_PEND) { // Provide a required password m_password = pwd; resp = doCommand(CMD_PASSWORD, pwd); rc = resp.m_code; if (rc/100 == RG_FAIL) throw new FTPException(rc, "Bad FTP user or password (" + rc + "): \"" + user + "\""); } if (rc/100 != RG_OKAY_COMPL && rc/100 != RG_OKAY_PEND) throw new FTPException(rc, "FTP login failed (" + rc + "): \"" + user + "\""); m_isLoggedOn = true; } /*************************************************************************** * Set the transfer mode to text (ASCII) or binary. * In text (ASCII) mode, files are transferred as text files, so that newline * sequences (CR, LF, or CR/LF) are converted into the local native newline * sequence (which is determined by the * System.getProperty("line.separator") setting). * * @param flag * If true, the transfer mode is set to text (ASCII), otherwise it is set * to binary. * * @return * The previous mode setting. * * @since 1.26, 2007-07-26 */ @Override public boolean setTextMode(boolean flag) { boolean prev; prev = m_textMode; try { FTPResponse resp; // Set the transfer mode on the remote side resp = doCommand(flag ? CMD_TYPE_ASC : CMD_TYPE_BIN); if (resp.m_code/100 == RG_OKAY_COMPL) m_textMode = flag; } catch (IOException ex) { // Mode not set, leave m_textMode unchanged } return prev; } /*************************************************************************** * Set the transfer mode to text (ASCII) or binary. * * @deprecated (since 1.26, 2007-07-26) * Use {@link #setTextMode setTextMode()} instead. * * @param flag * If true, the transfer mode is set to text (ASCII), otherwise it is set * to binary. * * @return * The previous mode setting. * * @since 1.3, 2006-03-15 */ public boolean setAsciiMode(boolean flag) { return setTextMode(flag); } /*************************************************************************** * Ping the remote FTP system. * This sends a "NOOP" FTP command to the remote system and receives its * reply. * * @throws IOException * Thrown if an error occurs, e.g., the FTP connection is broken. * * @since 1.4, 2006-03-16 */ @Override public void ping() throws IOException { FTPResponse resp; // Sanity checks if (!m_isConnected) throw new FTPException("FTP session not connected"); // Send a no-op command to the remote FTP system resp = doCommand(CMD_NOP); } /*************************************************************************** * Retrieve the identity information of the remote FTP system. * * @param out * Output stream to which the identification information is to be written. * * @throws IOException * Thrown if an I/O error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the transfer * (write) operation prematurely. * * @since API 2.0, 1.29, 2010-07-12 */ @Override public void getSystemInfo(OutputStream out) throws IOException { FTPResponse resp; // Sanity checks if (out == null) return; if (!m_isConnected) throw new FTPException("FTP session not connected"); // Get the identification of the remote FTP system resp = doCommand(CMD_SYSTEM); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't get remote system info (" + resp.m_code + ")"); transferCmd(resp, true, out); } /*************************************************************************** * Retrieve the current status of the remote FTP system. * * @param out * Output stream to which the status is to be written. * * @throws IOException * Thrown if an I/O error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the transfer * (write) operation prematurely. * * @since API 2.0, 1.29, 2010-07-12 */ @Override public void getStatus(OutputStream out) throws IOException { FTPResponse resp; // Sanity checks if (out == null) return; if (!m_isConnected) throw new FTPException("FTP session not connected"); // Get the current status of the remote FTP system resp = doCommand(CMD_STATUS); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't get remote system status (" + resp.m_code + ")"); transferCmd(resp, false, out); } /*************************************************************************** * Get (receive) a help listing of supported FTP commands from the remote FTP * system. * * @param out * Output stream to which the help listing is to be written. * * @throws IOException * Thrown if an error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the transfer * (write) operation prematurely. * * @since API 2.0, 1.29, 2010-07-12 */ @Override public void getHelp(OutputStream out) throws IOException { FTPResponse resp; // Sanity checks if (out == null) return; if (!m_isConnected) throw new FTPException("FTP session not connected"); // Get help listing resp = doCommand(CMD_HELP); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't get remote system help (" + resp.m_code + ")"); transferCmd(resp, false, out); } /*************************************************************************** * Set the working directory on the remote FTP system. * * @param dir * Remote directory name. * * @return * The new current remote directory name. * * @throws IOException * Thrown if an error occurs. * * @see #setRemoteDirUp setRemoteDirUp() * @see #getRemoteDir getRemoteDir() * @see #setLocalDir setLocalDir() * * @since 1.1, 2001-04-14 */ @Override public String setRemoteDir(String dir) throws IOException { FTPResponse resp; // Sanity checks if (dir == null) throw new NullPointerException("Null FTP directory name"); if (dir.length() == 0) throw new FTPException("Empty FTP directory name"); if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Change the remote working directory resp = doCommand(CMD_CHDIR, dir); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't change remote directory (" + resp.m_code + "): \"" + dir + "\""); m_remoteDir = dir; // Retrieve the new current remote working directory dir = getCurrentDir(resp); if (dir == null) { resp = doCommand(CMD_GETDIR); dir = getCurrentDir(resp); } if (dir != null) m_remoteDir = dir; return m_remoteDir; } /*************************************************************************** * Set the working directory on the remote FTP system to the parent directory * of the current working directory. * In other words, change the directory to be one level up from the current * setting. * * @return * The new current remote directory name. * * @throws IOException * Thrown if an error occurs. * * @see #getRemoteDir getRemoteDir() * @see #setRemoteDir setRemoteDir() * @see #setLocalDir setLocalDir() * * @since 1.9, 2006-04-01 */ @Override public String setRemoteDirUp() throws IOException { FTPResponse resp; String dir; // Sanity checks if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Change the remote working directory resp = doCommand(CMD_UPDIR); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't change remote directory up (" + resp.m_code + ")"); // Retrieve the new current remote working directory dir = getCurrentDir(resp); if (dir == null) { resp = doCommand(CMD_GETDIR); dir = getCurrentDir(resp); } if (dir != null) m_remoteDir = dir; return m_remoteDir; } /*************************************************************************** * Retrieve the current working directory of the remote FTP system. * * @return * Remote directory name. * * @throws IOException * Thrown if an error occurs. * * @see #setRemoteDir setRemoteDir() * @see #setRemoteDirUp setRemoteDirUp() * @see #getLocalDir getLocalDir() * * @since 1.1, 2001-04-15 */ @Override public String getRemoteDir() throws IOException { FTPResponse resp; String dir; // Sanity checks if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Retrieve the current remote working directory resp = doCommand(CMD_GETDIR); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't get remote directory (" + resp.m_code + ")"); dir = getCurrentDir(resp); if (dir != null) m_remoteDir = dir; return m_remoteDir; } /*************************************************************************** * Retrieve the current working directory of the remote FTP system from the * results of the previous FTP command. * * @return * Current remote directory name, or null if it cannot be retrieved. * * @throws IOException * Thrown if an error occurs. * * @see #setRemoteDir setRemoteDir() * @see #setRemoteDirUp setRemoteDirUp() * @see #getRemoteDir getRemoteDir() * * @since 1.9, 2006-04-01 */ String getCurrentDir(FTPResponse resp) throws IOException { // Retrieve the new current remote working directory // from the results of a previous 'CMD_GETDIR' command if (resp.m_code/100 == RG_OKAY_COMPL) { byte[] line; int i, j, k, o; line = resp.line(0); for (i = 0; i < line.length && line[i] != '"'; i++) continue; for (j = line.length-1; j > i && line[j] != '"'; j--) continue; for (k = i+1, o = i+1; o < j; k++, o++) { if (line[o] == '"' && line[o+1] == '"') o++; line[k] = line[o]; } if (i+1 < k) return (new String(line, i+1, k-(i+1))); } // Failed return null; } /*************************************************************************** * Get (receive) a file from the remote FTP system to the local system. * * @param src * Remote source filename. If this does not contain a directory prefix, the * current remote working directory is assumed. * * @param out * Output stream to write the contents of the file retrieved from the remote * FTP system to. Note that this stream is flushed but is not closed * after the contents have been transmitted. * * @throws IOException * Thrown if the file could not be transmitted or if any other error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the transfer * operation prematurely. * * @see #getFile(String, File) getFile() * @see #getFile(String, String) getFile() * @see #putFile(InputStream, String) putFile() * * @since 1.12, 2006-04-10 */ @Override public void getFile(String src, OutputStream out) throws IOException { FTPResponse resp; InputStream in = null; OutputStream outp; IOException exc = null; // Sanity checks if (src == null) throw new NullPointerException("Null FTP source file"); if (out == null) throw new NullPointerException("Null FTP output stream"); if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Open the target file as an output stream if (out instanceof BufferedOutputStream) outp = out; else outp = new BufferedOutputStream(out, m_bufSize); // Retrieve (get) the file from the remote FTP system resp = openDataPort(CMD_GET, src); if (resp.m_code/100 >= RG_FAIL_RETRY) throw new FTPException(resp.m_code, "Can't get remote FTP file (" + resp.m_code + "): " + src); in = new BufferedInputStream(m_dataIn, m_bufSize); transferFile(in, outp, LOCAL_NEWLINE); // Clean up try { in.close(); outp.flush(); } catch (IOException ex) { exc = ex; } // Clean up closeDataPort(); resp = getResponse(); if (exc != null) throw (exc); } /*************************************************************************** * Put (send) a file from the local system to the remote FTP system. * * @param src * Local source filename. If this does not contain a directory prefix, the * current local working directory is assumed. * * @param dst * Remote target filename. If this does not contain a directory prefix, the * current remote working directory is assumed. This may be null, in which * case the base filename of src (without the directory prefix) is * used. * * @throws IOException * Thrown if the file could not be transmitted or if any other error occurs. * * @see #putFile(String, String) putFile() * @see #putFile(InputStream, String) putFile() * @see #getFile(String, File) getFile() * * @since 1.1, 2001-04-14 */ @Override public void putFile(File src, String dst) throws IOException { InputStream in; long len; // Sanity checks if (src == null) throw new NullPointerException("Null FTP source file"); if (dst == null) throw new NullPointerException("Null FTP target file"); if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); if (!src.exists()) throw new FTPException("FTP source file does not exist: \"" + src.getPath() + "\""); // Open the source file as an input stream in = new FileInputStream(src); // Preallocate the target file on the remote system len = src.length(); if (len > 1024) { int obytes; FTPResponse resp; obytes = (len <= Integer.MAX_VALUE ? (int) len : Integer.MAX_VALUE); resp = doCommand(CMD_ALLOCATE, obytes + ""); } try { // Store (put) the file to the remote FTP system putFile(in, dst); } finally { in.close(); } } /*************************************************************************** * Put (send) a file from the local system to the remote FTP system. * * @param in * Input stream containing the contents of the file to send to the remote FTP * system. Note that this stream is not closed after the contents * have been transmitted. * * @param dst * Remote target filename. If this does not contain a directory prefix, the * current remote working directory is assumed. This may be null, in which * case the base filename of src (without the directory prefix) is * used. * * @throws IOException * Thrown if the file could not be transmitted or if any other error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the transfer * operation prematurely. * * @see #putFile(File, String) putFile() * @see #putFile(String, String) putFile() * @see #appendFile(File, String) appendFile() * @see #getFile(String, OutputStream) getFile() * * @since 1.12, 2006-04-10 */ @Override public void putFile(InputStream in, String dst) throws IOException { FTPResponse resp; InputStream inp; IOException exc = null; // Sanity checks if (in == null) throw new NullPointerException("Null FTP input stream"); if (dst == null) throw new NullPointerException("Null FTP target file"); if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Open the source file as an input stream if (in instanceof BufferedInputStream) inp = in; else inp = new BufferedInputStream(in, m_bufSize); // Store (put) the file to the remote FTP system resp = openDataPort(CMD_PUT, dst); if (resp.m_code/100 >= RG_FAIL_RETRY) throw new FTPException(resp.m_code, "Can't put file to remote FTP (" + resp.m_code + "): " + dst); try { OutputStream out; // Store (put) the file to the remote FTP system out = new BufferedOutputStream(m_dataOut, m_bufSize); transferFile(inp, out, REMOTE_NEWLINE); out.flush(); out.close(); } catch (IOException ex) { exc = ex; } // Clean up closeDataPort(); resp = getResponse(); if (exc != null) throw (exc); } /*************************************************************************** * Append (send) a file from the local system to a file on the remote FTP * system. * * @param in * Input stream containing the contents of the file to send to the remote FTP * system. Note that this stream is not closed after the contents * have been transmitted. * * @param dst * Remote target filename. If this does not contain a directory prefix, the * current remote working directory is assumed. This may be null, in which * case the base filename of src (without the directory prefix) is * used. * * @throws IOException * Thrown if the file could not be transmitted or if any other error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the transfer * operation prematurely. * * @see #appendFile(File, String) appendFile() * @see #appendFile(String, String) appendFile() * @see #putFile(InputStream, String) putFile() * * @since 1.25, 2007-06-30 */ @Override public void appendFile(InputStream in, String dst) throws IOException { FTPResponse resp; InputStream inp; IOException exc = null; // Sanity checks if (in == null) throw new NullPointerException("Null FTP input stream"); if (dst == null) throw new NullPointerException("Null FTP target file"); if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Open the source file as an input stream if (in instanceof BufferedInputStream) inp = in; else inp = new BufferedInputStream(in, m_bufSize); // Append (put) the file to the remote FTP system resp = openDataPort(CMD_APPEND, dst); if (resp.m_code/100 >= RG_FAIL_RETRY) throw new FTPException(resp.m_code, "Can't append file to remote FTP (" + resp.m_code + "): " + dst); try { OutputStream out; // Append (put) the file to the remote FTP system out = new BufferedOutputStream(m_dataOut, m_bufSize); transferFile(inp, out, REMOTE_NEWLINE); out.flush(); out.close(); } catch (IOException ex) { exc = ex; } // Clean up closeDataPort(); resp = getResponse(); if (exc != null) throw (exc); } /*************************************************************************** * Transfer (send or receive) a file from one system (local or remote) to the * other system (remote or local). * *

* When transferring text-mode files (i.e., {@link #m_textMode} is true), * this method handles the translation of newlines, translating CR, LF, and * CR/LF input sequences into CR/LF output sequences. * * @param in * Input source stream. This may be for a local file or a remote file. * * @param out * Output target stream. This may be for a remote file or a local file. * * @param nl * Newline sequence for the target system. This only applies to text (ASCII) * mode file transfers. * * @return * Number of bytes written to the output stream. * * @throws IOException * Thrown if the file could not be transmitted or if any other error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the transfer * operation prematurely. * * @see #getFile(String, File) getFile() * @see #putFile(File, String) putFile() * * @since 1.5, 2006-03-18 */ long transferFile(InputStream in, OutputStream out, byte[] nl) throws IOException { long nlines = 0; // Copy the source file to the target file try { m_inBytes = 0; m_outBytes = 0; if (m_textMode) { int ungetCh = -1; // Text (ASCII) data transfer for (;;) { int ch; // Check for an interrupt signal if (m_stop) throw new FTPStoppedException("FTP transfer stopped"); // Read the next byte from the local/remote source file if (ungetCh < 0) ch = in.read(); else ch = ungetCh; ungetCh = -1; if (ch < 0) break; m_inBytes++; // Write the data byte to the remote/local target file if (ch == '\r') { // Translate a CR or CR/LF newline sequence ch = in.read(); m_inBytes++; if (ch != '\n') { ungetCh = ch; m_inBytes--; } ch = '\n'; } if (ch == '\n') { // Translate a LF, CR, or CR/LF into a CR/LF for (int i = 0; i < nl.length; i++) { out.write(nl[i]); m_outBytes++; } nlines++; } else { out.write(ch); m_outBytes++; } } return m_outBytes; } else { // Binary data transfer for (;;) { int ch; // Check for an interrupt signal if (m_stop) throw new FTPStoppedException("FTP transfer stopped"); // Read the next byte from the local/remote source file ch = in.read(); if (ch < 0) break; m_inBytes++; // Write the data byte to the remote/local target file out.write(ch); m_outBytes++; } return m_outBytes; } } finally { if (m_debugOut != null) m_debugOut.println("$ bytes read:" + m_inBytes + ", wrote:" + m_outBytes + ", lines:" + nlines); } } /*************************************************************************** * Transfer the contents of the command stream to a local output stream. * Leading spaces are stripped from the command response lines. * * @param resp * Command response received from the remote FTP system. * * @param all * If true, all response lines including the last one are written, otherwise * all but the last line are written. * * @param out * Local output stream. * * @throws IOException * Thrown if an I/O error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the transfer * (write) operation prematurely. * * @since API 2.0, 1.29, 2010-07-12 */ void transferCmd(FTPResponse resp, boolean all, OutputStream out) throws IOException { int len; // Write the command response to the output stream len = resp.m_lines.size(); len = (all ? len : len-1); for (int i = 0; i < len; i++) { byte[] line; int j; // Check for an interrupt signal if (m_stop) throw new FTPStoppedException("FTP transfer stopped"); // Write the next response line line = resp.line(i); j = (i == 0 ? 4 : 0); for ( ; j < line.length && line[j] == ' '; j++) continue; for ( ; j < line.length; j++) out.write((char) (line[j] & 0xFF)); for (j = 0; j < LOCAL_NEWLINE.length; j++) out.write((char) (LOCAL_NEWLINE[j] & 0xFF)); } out.flush(); } /*************************************************************************** * Get (receive) a directory listing from the remote FTP system. * * @param path * The remote directory or filename to list. If this is empty * (""), the current remote working directory is assumed. * * @param max * Maximum number of filenames (output lines) to list. A value of zero (0) * specifies that there is no maximum. * * @param out * Output stream to which the directory listing is to be written. * * @throws IOException * Thrown if an error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the listing * (output) operation prematurely. * * @since API 2.0, 1.29, 2010-07-12 */ @Override public void getDirectoryList(String path, int max, OutputStream out) throws IOException { // Get the directory listing of the remote directory doDirectoryCmd(path, true, max, out); } /*************************************************************************** * Get (receive) a list of filenames in a directory on the remote FTP system. * * @param path * The remote directory or filename to list. If this is empty * (""), the current remote working directory is assumed. * * @param max * Maximum number of filenames to get. A value of zero (0) specifies that * there is no maximum. * * @param out * Output stream to which the directory listing is to be written. * * @throws IOException * Thrown if an error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the listing * (output) operation prematurely. * * @since API 2.0, 1.29, 2010-07-12 */ @Override public void getDirectoryNames(String path, int max, OutputStream out) throws IOException { // Get the directory listing of the remote directory doDirectoryCmd(path, false, max, out); } /*************************************************************************** * Get (receive) a directory listing for the current working directory on the * remote FTP system. * * @param path * The remote directory or filename to list. If this is empty * (""), the current remote working directory is assumed. * * @param full * If true, a full directory listing is produced, otherwise only filenames * are received. * * @param max * Maximum number of filenames (or output lines) to get. A value of zero (0) * specifies that there is no maximum. * * @param out * Output stream to which the directory listing is to be written. The stream * is flushed but not closed after the filenames are written to it. * * @throws IOException * Thrown if an error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the listing * (output) operation prematurely. * * @since API 2.0, 1.29, 2010-07-12 */ void doDirectoryCmd(String path, boolean full, int max, OutputStream out) throws IOException { FTPResponse resp; int n; // Sanity checks if (out == null) return; if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Get the directory listing of the remote directory if (path == null) path = ""; resp = openDataPort((full ? CMD_LISTFILES : CMD_LISTNAMES), path); if (resp.m_code/100 != RG_OKAY_INC && resp.m_code/100 != RG_OKAY_PEND) { closeDataPort(); if (resp.m_code/100 == RG_FAIL_RETRY) { // Assume that the remote directory is empty out.flush(); return; } throw new FTPException(resp.m_code, "Can't get remote directory list (" + resp.m_code + "): \"" + path + "\""); } // Read the directory listing results n = 0; for (;;) { int len; // Check for an interrupt signal if (m_stop) throw new FTPStoppedException("FTP listing stopped"); // Read the next line from the directory listing len = readDataLine(); if (len < 0) break; // Write the directory entry to the output stream if (len > 0) { for (int j = 0; j < len; j++) out.write((char) (m_dbuf[j] & 0xFF)); for (int j = 0; j < LOCAL_NEWLINE.length; j++) out.write((char) (LOCAL_NEWLINE[j] & 0xFF)); n++; if (n >= max && max > 0) break; } } out.flush(); // Get the transfer completion response closeDataPort(); resp = getResponse(); } /*************************************************************************** * Get (receive) a list of filenames in a directory on the remote FTP system. * * @param path * The remote directory or filename to list. If this is empty * (""), the current remote working directory is assumed. * * @param filt * Filer to apply to the filenames. Only filenames that are accepted by the * filter will appear in the returned vector. If this is null, no filtering * is applied to the filenames. This object's accept() method is * called for each filename in the directory, being passed a null directory * (first argument) and the found filename (second argument). * * @param max * Maximum number of filenames to list. A value of zero (0) specifies that * there is no maximum. * * @return * A vector of Strings containing the filenames in the remote * directory. Note that this may contain zero entries. * * @throws IOException * Thrown if an error occurs. * * @throws FTPStoppedException * Thrown if {@link #stop stop()} is called, which terminates the listing * operation prematurely. * * @since 1.22, 2007-04-15 */ //@Override public ArrayList getDirectoryNames(String path, FilenameFilter filt, int max) throws IOException { ArrayList list; FTPResponse resp; // Sanity checks if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Get the directory listing of the remote directory if (path == null) path = ""; resp = openDataPort(CMD_LISTNAMES, path); if (resp.m_code/100 != RG_OKAY_INC && resp.m_code/100 != RG_OKAY_PEND) { closeDataPort(); if (resp.m_code/100 == RG_FAIL_RETRY) { // Assume that the remote directory is empty return (new ArrayList(1)); } throw new FTPException(resp.m_code, "Can't get remote directory names (" + resp.m_code + "): \"" + path + "\""); } // Read the directory listing results list = new ArrayList(); for (;;) { int len; // Check for an interrupt signal if (m_stop) throw new FTPStoppedException("FTP listing stopped"); // Read the next line from the directory listing len = readDataLine(); if (len < 0) break; // Found a directory entry if (len > 0) { String fname; fname = new String(m_dbuf, 0, len); if (filt == null || filt.accept(null, fname)) { // Add the filename to the list list.add(fname); if (max > 0 && list.size() >= max) break; } } } // Get the transfer completion response closeDataPort(); resp = getResponse(); return list; } /*************************************************************************** * Rename a file or directory on the remote FTP system. * * @param from * Old (existing) remote file or directory name to rename. * * @param to * New name to rename the remote file or directory to. * * @throws IOException * Thrown if an error occurs. * * @since 1.4, 2006-03-17 */ @Override public void rename(String from, String to) throws IOException { FTPResponse resp; // Sanity checks if (from == null) throw new NullPointerException("Null FTP from-file"); if (to == null) throw new NullPointerException("Null FTP to-file"); if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Rename an existing remote file or directory resp = doCommand(CMD_RENAME_FROM, from); if (resp.m_code/100 != RG_OKAY_PEND) throw new FTPException(resp.m_code, "Can't rename remote file (" + resp.m_code + "): from \"" + from + "\""); resp = doCommand(CMD_RENAME_TO, to); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't rename remote file (" + resp.m_code + "): to \"" + to + "\""); } /*************************************************************************** * Remove a file on the remote FTP system. * * @param file * Name of the file to delete. If this specifies a relative filename, the * file is assumed to be located in the current remote working directory. * * @throws IOException * Thrown if an error occurs. * * @since 1.4, 2006-03-17 */ @Override public void removeFile(String file) throws IOException { FTPResponse resp; // Sanity checks if (file == null) throw new NullPointerException("Null FTP filename"); if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Remove an existing remote file or directory resp = doCommand(CMD_DELETE, file); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't delete remote file (" + resp.m_code + "): \"" + file + "\""); } /*************************************************************************** * Create a directory on the remote FTP system. * * @param dir * Name of the directory to create. If this specifies a relative directory * name, the directory is created in the current remote working directory. * * @throws IOException * Thrown if an error occurs. * * @since 1.4, 2006-03-17 */ @Override public void createDirectory(String dir) throws IOException { FTPResponse resp; // Sanity checks if (dir == null) throw new NullPointerException("Null FTP directory name"); if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Create a new remote directory resp = doCommand(CMD_MKDIR, dir); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't create remote directory (" + resp.m_code + "): \"" + dir + "\""); } /*************************************************************************** * Remove a directory on the remote FTP system. * * @param dir * Name of the directory to delete. If this specifies a relative directory * name, the directory is assumed to be located in the current remote working * directory. * * @throws IOException * Thrown if an error occurs. * * @since 1.4, 2006-03-17 */ @Override public void removeDirectory(String dir) throws IOException { FTPResponse resp; // Sanity checks if (dir == null) throw new NullPointerException("Null FTP directory name"); if (!m_isConnected) throw new FTPException("FTP session not connected"); if (!m_isLoggedOn) throw new FTPException("Not logged into FTP session"); // Remove an existing remote directory resp = doCommand(CMD_RMDIR, dir); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't remove remote directory (" + resp.m_code + "): \"" + dir + "\""); } /*************************************************************************** * Send a command to the remote FTP system and receive its response. * * @param cmd * FTP command. * * @param args * Command arguments. This can be null. * * @return * Command response. * * @throws IOException * Thrown if the command could not be transmitted or if any other error * occurs. * * @since 1.1, 2001-04-14 */ FTPResponse doCommand(String cmd, String args) throws IOException { // Sanity checks if (!m_isConnected) throw new FTPException("FTP session not connected"); if (m_debugOut != null) m_debugOut.println("$> " + cmd + (args != null ? " " + (cmd.equals(CMD_PASSWORD) ? "***" : args) : "")); // Send the command to the remote FTP system writeCmd(cmd); if (args != null && args.length() > 0) { writeCmd(" "); writeCmd(args); } writeCmd(CMD_EOLN); // Receive the response from the FTP system return getResponse(); } /*************************************************************************** * Send a command to the remote FTP system and receive its response. * * @param cmd * FTP command to execute. * * @return * Command response. * * @throws IOException * Thrown if the command could not be transmitted or if any other error * occurs. * * @since 1.1, 2001-04-14 */ FTPResponse doCommand(String cmd) throws IOException { return doCommand(cmd, null); } /*************************************************************************** * Receive the response to a command sent to the remote FTP system. * * @return * Command response. * * @throws IOException * Thrown if the command response could not be received or if any other error * occurs. * * @since 1.1, 2001-04-14 */ FTPResponse getResponse() throws IOException { FTPResponse resp; ArrayList lines; // Set up resp = new FTPResponse(); lines = new ArrayList(2); resp.m_lines = lines; resp.m_code = -1; // Receive the command response line(s) readLines: for (;;) { int len; byte[] line; // Receive the next response line len = readCmdLine(); if (len < 0) break readLines; // Skip blank lines if (len == 0) continue readLines; // Add the line to the list of response lines line = new byte[len]; System.arraycopy(m_cbuf, 0, line, 0, len); lines.add(line); if (m_debugOut != null) m_debugOut.println("$< [" + new String(line, 0, len) + "]"); if (line.length > 3 && line[0] >= '0' && line[0] <= '9' && line[1] >= '0' && line[1] <= '9' && line[2] >= '0' && line[2] <= '9' && (line[3] == ' ' || line[3] == '-')) { int code; // Parse the response code code = ((line[0]-'0')*10 + line[1]-'0')*10 + line[2]-'0'; // Check for the final command line of the group if (resp.m_code < 0) resp.m_code = code; if (code == resp.m_code && line[3] == ' ') break readLines; } } // Done return resp; } /*************************************************************************** * Write a string to the remote FTP command port. * * @param s * String to write. * * @throws IOException * Thrown if the command could not be transmitted or if any other error * occurs. * * @since 1.1, 2001-04-14 */ void writeCmd(String s) throws IOException { int len; // Write data to the command port of the remote FTP system len = s.length(); for (int i = 0; i < len; i++) m_cmdOut.write(s.charAt(i) & 0xFF); } /*************************************************************************** * Read a response text line from the remote FTP command port. * * @return * Length of the text line read from the FTP system (which is stored in * {@link #m_cbuf}. * * @throws IOException * Thrown if the line could not be received or if any other error occurs. * * @since 1.1, 2001-04-14 */ int readCmdLine() throws IOException { return readLine(m_cmdIn, m_cbuf); } /*************************************************************************** * Read a text line from the remote FTP data port. * * @return * Length of the text line read from the FTP system (which is stored in * {@link #m_dbuf}. * * @throws IOException * Thrown if the line could not be received or if any other error occurs. * * @since 1.4, 2006-03-16 */ int readDataLine() throws IOException { return readLine(m_dataIn, m_dbuf); } /*************************************************************************** * Read a text line from the remote FTP command or data port. * * @param in * Command or data port connected to the remote FTP system. * * @param buf * Buffer into which the received text line is written. * * @return * Length of the text line read from the FTP system. * * @throws IOException * Thrown if the line could not be received or if any other FTP error occurs. * * @since 1.4, 2006-03-17 */ int readLine(InputStream in, byte[] buf) throws IOException { int len; int ch; // Read a text line from the FTP command port ch = in.read(); if (ch < 0) return -1; m_inBytes++; len = 0; for (;;) { // Handle end of line if (ch == '\r') { // Look for a CR/LF newline sequence while (ch == '\r') { ch = in.read(); m_inBytes++; } if (ch == '\n') break; if (len < buf.length) buf[len++] = '\n'; } else if (ch == '\n') break; // Add the next character to the command line if (len < buf.length) buf[len++] = (byte) ch; // Read the next command character ch = in.read(); if (ch < 0) break; m_inBytes++; } return (len <= buf.length ? len : buf.length); } /*************************************************************************** * Open and connect a data port socket to the remote FTP system. * * @param cmd * FTP command to execute which results in a stream being opened on the data * port. * * @param args * Command arguments. This can be null. * * @return * Command response. * * @throws IOException * Thrown if a socket error occurs. * * @since 1.7, 2006-03-22 */ FTPResponse openDataPort(String cmd, String args) throws IOException { if (m_passiveMode) return openRemoteDataPort(cmd, args); else return openLocalDataPort(cmd, args); } /*************************************************************************** * Open and connect to a passive data port socket originating on the remote * FTP system. * * @param cmd * FTP command to execute which results in a stream being opened on the data * port. * * @param args * Command arguments. This can be null. * * @return * Command response. * * @throws IOException * Thrown if a socket error occurs. * * @since 1.7, 2006-03-22 */ FTPResponse openRemoteDataPort(String cmd, String args) throws IOException { FTPResponse resp; byte[] line; int[] parms; Socket sock; int dport; int i, j; // Initiate a passive socket port on the remote FTP system resp = doCommand(CMD_PASSIVE); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't open passive FTP data port (" + resp.m_code + "): " + m_hostName); // Extract the remote passive data port // Response is "3xx ...h4,h3,h2,h1,p2,p1..." parms = new int[4+2]; line = resp.line(0); for (j = line.length-1; j > 3 && (line[j] < '0' || line[j] > '9'); j--) continue; for (i = j; i > 3 && ((line[i] >= '0' && line[i] <= '9') || line[i] == ','); i--) continue; for (int n = 0; n < 6; n++) { int d = 0; for (i++; i <= j && (line[i] >= '0' && line[i] <= '9'); i++) d = d*10 + line[i]-'0'; parms[n] = d; } dport = (parms[4]<<8) + parms[5]; if (m_debugOut != null) m_debugOut.println("$ remoteHost: " + parms[0] + "." + parms[1] + "." + parms[2] + "." + parms[3] + ":" + dport); // Open the remote data port socket m_dataSock = new Socket(m_hostName, dport); m_dataSock.setSoTimeout(m_timeOut*1000); m_dataIn = m_dataSock.getInputStream(); m_dataOut = m_dataSock.getOutputStream(); // Send the command to the remote FTP system resp = doCommand(cmd, args); return resp; } /*************************************************************************** * Open a non-passive data port socket originating on the local system, and * wait for a connection to it from the remote FTP system. * * @param cmd * FTP command to execute which results in a stream being opened on the data * port. * * @param args * Command arguments. This can be null. * * @return * Command response. * * @throws IOException * Thrown if a socket error occurs. * * @since 1.7, 2006-03-22 */ FTPResponse openLocalDataPort(String cmd, String args) throws IOException { ServerSocket sock = null; try { FTPResponse resp; byte[] haddr; int p; String port; // Create a local socket to listen on sock = new ServerSocket(m_dataPort); sock.setSoTimeout(m_timeOut*1000); // Notify the remote FTP system of the port haddr = m_localHost.getAddress(); p = sock.getLocalPort(); port = ""; for (int i = 0; i < haddr.length; i++) port += (haddr[i] & 0xFF) + ","; port += ((p >>> 8) & 0xFF) + "," + (p & 0xFF); if (m_debugOut != null) m_debugOut.println("$ localHost: " + m_localHost.toString() + ":" + p); resp = doCommand(CMD_PORT, port); if (resp.m_code/100 != RG_OKAY_COMPL) throw new FTPException(resp.m_code, "Can't set FTP data port (" + resp.m_code + "): " + p); // Send the command to the remote FTP system resp = doCommand(cmd, args); if (resp.m_code/100 != RG_OKAY_INC) throw new FTPException(resp.m_code, "Can't execute FTP command (" + resp.m_code + "): " + cmd); // Listen (wait) for a connection from the remote FTP system m_dataSock = sock.accept(); m_dataSock.setSoTimeout(m_timeOut*1000); m_dataIn = m_dataSock.getInputStream(); m_dataOut = m_dataSock.getOutputStream(); return resp; } finally { // Clean up try { if (sock != null) sock.close(); } catch (IOException ex) { } } } /*************************************************************************** * Close the data port socket connected to the remote FTP system. * * @throws IOException * Thrown if a socket error occurs. * * @since 1.4, 2006-03-16 */ void closeDataPort() throws IOException { // Shut down the data socket connection if (m_dataSock != null) { try { if (m_dataIn != null) m_dataIn.close(); if (m_dataOut != null) m_dataOut.close(); m_dataSock.close(); } finally { m_dataIn = null; m_dataOut = null; m_dataSock = null; } } } } // End FTPClient.java