package ftpfs.ftp; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.*; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Socket; import java.net.ServerSocket; import java.net.SocketException; import java.util.ResourceBundle; import java.util.MissingResourceException; import java.util.StringTokenizer; import java.util.Vector; /* * FtpBean Version 1.4.4 * Copyright 1999 Calvin Tai * E-mail: calvin_tai2000@yahoo.com.hk * URL: http://www.geocities.com/SiliconValley/Code/9129/javabean/ftpbean * * COPYRIGHT NOTICE * Copyright 1999 Calvin Tai All Rights Reserved. * * FtpBean may be modified and used in any application free of charge by * anyone so long as this copyright notice and the comments above remain * intact. By using this code you agree to indemnify Calvin Tai from any * liability that might arise from it's use. * * Selling the code for this java bean alone is expressly forbidden. * In other words, please ask first before you try and make money off of * this java bean as a standalone application. * * Obtain permission before redistributing this software over the Internet or * in any other medium. In all cases copyright and header must remain intact. */ /* * Class Name: FtpBean * Author: Calvin Tai * Date: 20 Aug 1999 * Last Updated: 28Mar2002 * * 20120612--add cancel support where the observer can cancel the download. * * Note: * 1) To turn on debug mode, change the field DEBUG to true, * then re-compile the class file. * * Updates: * version 1.4.5 - 28 Mar 2002 * 1) Check the reply code and login procedures according to the state diagram specified in RFC959, * added class FtpReplyResourceBundle. * 2) Added property account information (acctInfo). * 3) Public methods ftpConnect(String, String) and ftpConnect(String, String, String, String) added. * 4) Use US-ASCII encoding for both the input, output stream and String to fix the problem where * some system is not using US-ASCII as the default encoding.(e.g. EBCDIC) * 5) Handle the string is null in method checkReply * * version 1.4.4 - 10 March 2001 * 1) Fixed bug on the getPassiveSocket method which pass incorrect server and port information * when using the SocketOpener class for openning new socket. * 2) Fixed bug on the getDataSocket method where the ServerSocket is not closed when using * active mode transfer. This may cause problem if there are too many ServerSocket opened. * 3) Fixed bug on most of the get/put file methods where the setTransferType is not enclosed * in the "try" block. * 4) Replaced those depreciated methods for multi-thread. * * version 1.4.3 - 16, Nov 2000 * 1) Increase code efficiency for private method getBytes(BufferedInputStream, FtpObserver). * This increase the of those get file methods that return a byte array or String. * 2) Private method getPassiveDataSocket() changed, so that it read the ip address returned * from the PASV command rather than using the getServerName method as the host. * 3) Added the SocketOpener class to deal with add timeout feature for openning Socket. * 4) Changed the way to get new Socket in ftpConnect and getPassiveSocket methods to support * the timeout feature if it is being set. * 5) Public method getSocketTimeout() added. * 6) Public method setSocketTimeout(int) added. * 7) Public method getAsciiFile(String, String, String) added. * 8) Public method getAsciiFile(String, String, String, FtpObserver) added. * * version 1.4.2 - 1, Aug 2000 * 1) Updated dead lock in close() method when using putBinaryFile(String, String) to upload a * non-existing local file. * * version 1.4.1 - 19, May 2000 * 1) Fixed bug on deadlock may caused by using active transfer mode to get data connection. * 2) Fixed bug on deadlock may caused by using passive transfer mode to get data connection. * 3) Private method getDataSocket(String[]) changed to getDataSocet(String, long). * 4) Public method execute() added. * 5) Public method getSystemType() added. * * version 1.4 - 28, March 2000 * 1) Private method aquire() changed to acquire(). * 2) All get/put file methods changed to support the feature of FtpObserver class. * * version 1.3.1 - 1, Feb 2000 * 1) Fixed bug on deadlock during incorrect login. * 2) Private method closeSocket() added. * * version 1.3 - 20, Jan 2000 * 1) Public method getDirectoryContent() changed to getDirectoryContentAsString(). * 2) Public method getPassiveModeTransfer() changed to isPassiveModeTransfer(). * 3) Public method getDirectoryContent() added. * 4) Thread safe. * 5) Private method aquire added. * 6) Private method release added. * * version 1.2 - 5, Nov 1999 * 1) Debug mode added. * 2) Public method putBinaryFile(String, String) added. * 3) Public method putBinaryFile(String, String, long) added. * 4) Public method getReply() added. * 5) Public method getAsciiFile(String) changed to getAsciiFile(String, String). * 6) Public method putAsciiFile(String, String) changed to putAsciiFile(String, String, String). * 7) Public method getPassive() changed to getPassiveModeTransfer(). * 8) Public method setPassive(boolean) changed to setPassiveModeTransfer(boolean). * * version 1.1 - 21, Aug 1999 * 1) Provide active data connection (Using PORT command). * 2) Public method setPassive(boolean) added. * 3) Public method getPassive() added. * 4) Fixed bug on getting file with a restarting point as an byte array. */ /** * This bean provide some basic FTP functions.
* You can use this bean in some visual development tools (eg. IBM VA) * or just include its files and use its methods.
* This class is thread safe, only one thread can acquire the object and do some ftp operation at a time.

* How to use it?
* To start a new instance of this FTP bean
* * FtpBean ftp = new FtpBean(); *
* // To connect to a FTP server
* * ftp.ftpConnect("servername", "username", "password"); *
* // Some methods return null if you call them without establish connection first. *
* //Then just call the functions provided by this bean.
* //After you end the ftp section. Just close the connection.
* * ftp.close(); *

* Remarks:
* 1) Whenever a ftp command failed (eg. Permission denied), the methods throw an FtpException.
* So you need to catch the FtpException wherever you invoke those methods of this class.
* 2) This bean use passive mode to establish data connection by default. * If this cause problem from firewall of the network, try using active mode:
* ftp.setPassiveModeTransfer(false);
* 3) To turn on debug mode, you need to change the source of this class. Then re-compile it.
* 4) For timeout on creating Socket. if a timeout is being set and operation timeout, a * java.io.InterruptedIOException is throw. This is the case for both passive transfer mode and * establishment of connection to the server at the beginning. For active transfer mode, timeout * is set in the servers ftpd. If there is timeout, the servers ftp software return an error * code which causing the bean to throw a ftp.FtpException.

* * * IMPORTANT for using in an Applet:
* 1) If you use this bean in an applet and the applet is open to the public, * please don't include the user name and password in * the source code of your applet. As anyone who can get your class files can get your * user name and password. It is reasonable to ask the user for user name and password * if you are going to use FTP in the applet.
* 2) If you use it in an applet, please be aware of the security restriction from the browser. * As an unsigned applet can ONLY connect to the host which serves it. Also, some methods in this bean * will write/read to the local file system. These methods are also restricted by the browser.

* * If you find any bugs in this bean or any comment, please give me a notice at
* Calvin(calvin_tai2000@yahoo.com.hk)
* */ public class FtpBean { protected final static String FTP_INIT = "FTP_INIT"; // Ftp commands set protected final static String CMD_ACCT = "ACCT "; protected final static String CMD_APPE = "APPE "; protected final static String CMD_CWD = "CWD "; protected final static String CMD_CDUP = "CDUP"; protected final static String CMD_DELE = "DELE "; protected final static String CMD_MKD = "MKD "; protected final static String CMD_PASV = "PASV"; protected final static String CMD_PASS = "PASS "; protected final static String CMD_PORT = "PORT "; protected final static String CMD_PWD = "PWD"; protected final static String CMD_QUIT = "QUIT"; protected final static String CMD_RMD = "RMD "; protected final static String CMD_REST = "REST "; protected final static String CMD_RETR = "RETR "; protected final static String CMD_RNTO = "RNTO "; protected final static String CMD_RNFR = "RNFR "; protected final static String CMD_SITE = "SITE "; protected final static String CMD_STOR = "STOR "; protected final static String CMD_SYST = "SYST"; protected final static String CMD_USER = "USER "; protected final static String CMD_TYPE = "TYPE "; protected final static String CMD_LIST = "LIST"; // Reply code type, determined by the first digit of the reply code protected final static String REPLY_POS_PRE = "1"; // Positive Preliminary Reply protected final static String REPLY_POS_CMP = "2"; // Positive Completion Reply protected final static String REPLY_POS_INT = "3"; // Positive Intermediate Reply protected final static String REPLY_TRA_NEG = "4"; // Transient Negative Completion Reply protected final static String REPLY_PER_NEG = "5"; // Permanent Negative Completion Reply protected final static String REPLY_UNDEF = "0"; // Undefined reply, should not be exist. private final static String TF_MOD_ASCII = "A"; private final static String TF_MOD_BIN = "I"; private final static String FTP_ENCODING = "US-ASCII"; private final FtpReplyResourceBundle ftpReplies = new FtpReplyResourceBundle(); private String acctInfo = ""; // account information private String server = ""; // server name private String user = ""; // user name private String replymessage = ""; // reply message from server private String reply = ""; // reply to the command private Socket socket; // Socket for FTP connection private BufferedReader in; // Input for FTP connection private PrintWriter out; // Output for FTP connection private int port = 21; // FTP port number, default 21 private boolean passive = true; // Passive mode transfer, default true private int timeout; // Timeout to open socket // Needed for thread safety private int[] lock = new int[0]; // For synchronized locking private boolean acquired = false; // Acquired by a thread or not private Vector thread_spool = new Vector(); // Spool for the waiting threads // Needed for some Visual tools private PropertyChangeSupport pcs; // PropertyChangeSupport for visual tools private static final boolean DEBUG = false; // True to turn on debug mode /** * Constructor */ public FtpBean() { pcs = new PropertyChangeSupport(this); } /* * Add PropertyChangeListener */ public void addPropertyChangeListener(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); } /* * removePropertyChangeListener */ public void removePropertyChangeListener(PropertyChangeListener listener) { pcs.removePropertyChangeListener(listener); } /** * Connect to Ftp server and login. * @param server Name of server * @param user User name for login * @exception FtpException if a ftp error occur (eg. Login fail in this case). * @exception IOException if an I/O error occur */ public void ftpConnect(String server, String user) throws IOException, FtpException { ftpConnect(server, user, "", ""); } /** * Connect to Ftp server and login. * @param server Name of server * @param user User name for login * @param password Password for login * @exception FtpException if a ftp error occur (eg. Login fail in this case). * @exception IOException if an I/O error occur */ public void ftpConnect(String server, String user, String password) throws IOException, FtpException { ftpConnect(server, user, password, ""); } /** * Connect to FTP server and login. * @param server Name of server * @param user User name for login * @param password Password for login * @param acct account information * @exception FtpException if a ftp error occur (eg. Login fail in this case). * @exception IOException if an I/O error occur */ public void ftpConnect(String server, String user, String password, String acct) throws IOException, FtpException { if(DEBUG) // Debug message System.out.println("FtpBean: Connecting to server " + server); acquire(); // Acquire the object // Set server name & user name setServerName(server); setUserName(user); setAcctInfo(acct); Proxy proxy= Proxy.NO_PROXY; String proxyHost= System.getProperty("ftp.proxyHost"); if ( proxyHost!=null ) { String proxyPort= System.getProperty("ftp.proxyPort","21"); String nonProxyHosts= System.getProperty("ftp.nonProxyHosts","" ); if ( !nonProxyHosts.equals("") ) { throw new IllegalArgumentException("ftp.nonProxyHosts is not supported, please submit this error"); } proxy= new Proxy( Proxy.Type.valueOf("ftp"), new InetSocketAddress(proxyHost,Integer.parseInt(proxyPort)) ); } // Create socket, get input & output stream try { if (timeout == 0) { if ( proxy==Proxy.NO_PROXY ) { socket = new Socket(server, port); } else { //untested socket = new Socket(proxy); } } else { /* If a timeout has been set before opening the socket, * use the SocketOpener. Note that if the thread times out, * it is forcibly terminated. */ if ( proxy==Proxy.NO_PROXY ) { socket = new SocketOpener (server, port).makeSocket(timeout); } else { //untested socket = new SocketOpener ( proxy ).makeSocket(timeout); } } in = new BufferedReader( new InputStreamReader(socket.getInputStream(), FTP_ENCODING)); out = new PrintWriter( new OutputStreamWriter(socket.getOutputStream(), FTP_ENCODING), true); // Read reply code when get connected getRespond(FTP_INIT); if(DEBUG) // Debug message System.out.println("FtpBean: Connected"); // Login ftpLogin(user, password, acct); // check if login success } finally { release(); // Release the object } } /** * Close FTP connection. * @exception IOException if an I/O error occur * @exception FtpException if a ftp error occur */ public void close() throws IOException, FtpException { acquire(); // Acquire the object try { ftpCommand(CMD_QUIT); closeSocket(); // Set account information, server name & user name to "" setServerName(""); setUserName(""); setAcctInfo(""); } finally { release(); // Release the object } } /** * Delete a file at the FTP server. * @param filename Name of the file to be deleted. * @exception FtpException if a ftp error occur. (eg. no such file in this case) * @exception IOException if an I/O error occur. */ public void fileDelete(String filename) throws IOException, FtpException { if(out == null) return; acquire(); // Acquire the object try { ftpCommand(CMD_DELE, filename); } finally { release(); // Release the object } } /** * Rename a file at the FTP server. * @param oldfilename The name of the file to be renamed * @param newfilename The new name of the file * @exception FtpException if a ftp error occur. (eg. A file named the new file name already in this case.) * @exception IOException if an I/O error occur. */ public void fileRename(String oldfilename, String newfilename) throws IOException, FtpException { if(out == null) return; acquire(); // Acquire this object try { ftpCommand(CMD_RNFR, oldfilename); ftpCommand(CMD_RNTO, newfilename); } finally { release(); // Release the object } } /** * Get an ASCII file from the server and return as String. * @param filename Name of ASCII file to be getted. * @param separator The line separator you want in the return String (eg. "\r\n", "\n", "\r") * @return The Ascii content of the file. It uses parameter 'separator' as the line separator. * @exception FtpException if a ftp error occur. (eg. no such file in this case) * @exception IOException if an I/O error occur. */ public String getAsciiFile(String filename, String separator) throws IOException, FtpException { return getAsciiFile(filename, separator, (FtpObserver)null); } /** * Get an ASCII file from the server and return as String. * @param filename Name of ASCII file to be getted. * @param separator The line separator you want in the return String (eg. "\r\n", "\n", "\r"). * @param observer The observer of the downloading progress * @return The Ascii content of the file. It uses parameter 'separator' as the line separator. * @exception FtpException if a ftp error occur. (eg. no such file in this case) * @exception IOException if an I/O error occur. * @see FtpObserver */ public String getAsciiFile(String filename, String separator, FtpObserver observer) throws IOException, FtpException { if(out == null) return null; String str_content; acquire(); // Acquire the object try { setTransferType(true); str_content = new String(getFile(filename, 0, observer), FTP_ENCODING); } finally { release(); // Release the object } str_content = changeLineSeparator(str_content, "\r\n", separator); return str_content; } /** * Get an ascii file from the server and write to local file system. * @param ftpfile Name of ascii file in the server side. * @param localfile Name of ascii file in the local file system. * @param separator The line separator you want in the local ascii file (eg. "\r\n", "\n", "\r"). * @exception FtpException if a ftp error occur. (eg. no such file in this case) * @exception IOException if an I/O error occur. */ public void getAsciiFile(String ftpfile, String localfile, String separator) throws IOException, FtpException { getAsciiFile(ftpfile, localfile, separator, null); } /** * Get an ascii file from the server and write to local file system. * @param ftpfile Name of ascii file in the server side. * @param localfile Name of ascii file in the local file system. * @param separator The line separator you want in the local ascii file (eg. "\r\n", "\n", "\r"). * @param observer The observer of the downloading progress. * @exception FtpException if a ftp error occur. (eg. no such file in this case) * @exception IOException if an I/O error occur. * @see FtpObserver */ public void getAsciiFile(String ftpfile, String localfile, String separator, FtpObserver observer) throws IOException, FtpException { final int BUF = 1024; if(out == null) return; acquire(); Socket sock= null; BufferedInputStream reader = null; RandomAccessFile out= null; try { sock = getDataSocket(CMD_RETR, ftpfile, 0); // Read bytes from server reader = new BufferedInputStream( sock.getInputStream()); // File to write to out = new RandomAccessFile(localfile, "rw"); int offset; byte[] data = new byte[BUF + 1]; boolean cont= true; // Loop to read file while( cont && (offset = reader.read(data, 0, BUF)) != -1) { // Last character is '\r', read one more character. // It is because the next character may be '\n'. Where "\r\n" is the // line separactor in ASCII transmission. Then it can be replaced. if(((char)data[offset - 1] == '\r')) { data[offset] = (byte)reader.read(); if(data[offset] != -1) offset++; } String content = new String(data, 0, offset, FTP_ENCODING); content = changeLineSeparator(content, "\r\n", separator); out.writeBytes(content); if(observer != null) { cont= observer.byteRead(offset); } } if ( cont==false ) { throw new IOException("Operation cancelled"); } getRespond(CMD_RETR); if(!reply.substring(0, 3).equals("226")) { throw new FtpException(reply); // transfer incomplete } } finally { if ( out!=null ) out.close(); if ( reader!=null ) reader.close(); if ( sock!=null ) sock.close(); release(); } } /** * Append an ascii file to the server. *
Remark:
* this method convert the line separator of the String content to
* NVT-ASCII format line separator "\r\n". Then the ftp daemon will
* convert the NVT-ASCII format line separator into its system line separator. * @param filename The name of file * @param content The String content of the file * @param separator Line separator of the content * @exception FtpException if a ftp error occur. (eg. permission denied in this case) * @exception IOException if an I/O error occur. */ public void appendAsciiFile(String filename, String content, String separator) throws IOException, FtpException { if(out == null) return; content = changeLineSeparator(content, separator, "\r\n"); byte[] byte_content = content.getBytes(FTP_ENCODING); acquire(); // Acquire the object try { setTransferType(true); appendFile(filename, byte_content); } finally { release(); // Release the object } } /** * Save an ascii file to the server. *
Remark:
* this method convert the line separator of the String content to
* NVT-ASCII format line separator "\r\n". Then the ftp daemon will
* convert the NVT-ASCII format line separator into its system line separator. * @param filename The name of file * @param content The String content of the file * @param separator Line separator of the content * @exception FtpException if a ftp error occur. (eg. permission denied in this case) * @exception IOException if an I/O error occur. */ public void putAsciiFile(String filename, String content, String separator) throws IOException, FtpException { if(out == null) return; content = changeLineSeparator(content, separator, "\r\n"); byte[] byte_content = content.getBytes(FTP_ENCODING); acquire(); // Acquire the object try { setTransferType(true); putFile(filename, byte_content, 0); } finally { release(); // Release the object } } /** * Get a binary file and return a byte array. * @param filename The name of the binary file to be got. * @return An array of byte of the content of the binary file. * @exception FtpException if a ftp error occur. (eg. No such file in this case) * @exception IOException if an I/O error occur. */ public byte[] getBinaryFile(String filename) throws IOException, FtpException { if(out == null) return null; return getBinaryFile(filename, 0, null); } /** * Get a binary file and return a byte array. * @param filename The name of the binary file to be got. * @param observer The observer of the downloading progress. * @return An array of byte of the content of the binary file. * @exception FtpException if a ftp error occur. (eg. No such file in this case) * @exception IOException if an I/O error occur. */ public byte[] getBinaryFile(String filename, FtpObserver observer) throws IOException, FtpException { if(out == null) return null; return getBinaryFile(filename, 0, observer); } /** * Get a binary file at a restarting point. * Return null if restarting point is less than zero. * @param filename Name of binary file to be getted. * @param restart Restarting point, ignored if less than or equal to zero. * @return An array of byte of the content of the binary file. * @exception FtpException if a ftp error occur. (eg. No such file in this case) * @exception IOException if an I/O error occur. */ public byte[] getBinaryFile(String filename, long restart) throws IOException, FtpException { if(out == null) return null; return getBinaryFile(filename, restart, null); } /** * Get a binary file at a restarting point. * Return null if restarting point is less than zero. * @param filename Name of binary file to be getted. * @param restart Restarting point, ignored if less than or equal to zero. * @param observer The FtpObserver which monitor this downloading progress * @return An array of byte of the content of the binary file. * @exception FtpException if a ftp error occur. (eg. No such file in this case) * @exception IOException if an I/O error occur. * @see FtpObserver */ public byte[] getBinaryFile(String filename, long restart, FtpObserver observer) throws IOException, FtpException { if(out == null) return null; byte[] content; acquire(); // Acquire the object try { setTransferType(false); content = getFile(filename, restart, observer); } finally { release(); // Release the object } return content; } /** * Read file from ftp server and write to a file in local hard disk. * This method is much faster than those method which return a byte array
* if the network is fast enough.
*
Remark:
* Cannot be used in unsigned applet. * @param ftpfile Name of file to be get from the ftp server, can be in full path. * @param localfile Name of local file to be write, can be in full path. * @exception FtpException if a ftp error occur. (eg. No such file in this case) * @exception IOException if an I/O error occur. */ public void getBinaryFile(String ftpfile, String localfile) throws IOException, FtpException { if(out == null) return; getBinaryFile(ftpfile, localfile, 0, null); } /** * Read file from ftp server and write to a file in local hard disk. * This method is much faster than those method which return a byte array
* if the network is fast enough.
*
Remark:
* Cannot be used in unsigned applet. * @param ftpfile Name of file to be get from the ftp server, can be in full path. * @param localfile Name of local file to be write, can be in full path. * @param restart Restarting point * @exception FtpException if a ftp error occur. (eg. No such file in this case) * @exception IOException if an I/O error occur. */ public void getBinaryFile(String ftpfile, String localfile, long restart) throws IOException, FtpException { if(out == null) return; getBinaryFile(ftpfile, localfile, restart, null); } /** * Read file from ftp server and write to a file in local hard disk. * This method is much faster than those method which return a byte array
* if the network is fast enough.
*
Remark:
* Cannot be used in unsigned applet. * @param ftpfile Name of file to be get from the ftp server, can be in full path. * @param localfile Name of local file to be write, can be in full path. * @param observer The FtpObserver which monitor this downloading progress * @exception FtpException if a ftp error occur. (eg. No such file in this case) * @exception IOException if an I/O error occur. * @see FtpObserver */ public void getBinaryFile(String ftpfile, String localfile, FtpObserver observer) throws IOException, FtpException { if(out == null) return; getBinaryFile(ftpfile, localfile, 0, observer); } /** * Read from a ftp file and restart at a specific point. * This method is much faster than those method which return a byte array
* if the network is fast enough.
* Remark:
* Cannot be used in unsigned applet. * @param ftpfile Name of file to be get from the ftp server, can be in full path. * @param localfile File name of local file * @param restart Restarting point, ignored if equal or less than zero. * @param observer The FtpObserver which monitor this downloading progress * @exception FtpException if a ftp error occur. (eg. No such file in this case) * @exception IOException if an I/O error occur. * @see FtpObserver */ public void getBinaryFile(String ftpfile, String localfile, long restart, FtpObserver observer) throws IOException, FtpException { if(out == null) return; acquire(); // Acquire the object Socket sock = null; RandomAccessFile out=null; BufferedInputStream reader= null; try { setTransferType(false); // Set transfer type to binary sock = getDataSocket(CMD_RETR ,ftpfile, restart); // Read bytes from server and write to file. reader = new BufferedInputStream( sock.getInputStream()); out = new RandomAccessFile(localfile, "rw"); out.seek(restart); readData(reader, out, observer); reader.close(); reader=null; out.close(); out=null; sock.close(); sock=null; getRespond(CMD_RETR); } finally { if (reader!=null ) reader.close(); if ( out!=null ) out.close(); if ( sock!=null ) sock.close(); release(); // Release the object } } /** * Put a binary file to the server from an array of byte. * @param filename The name of file. * @param content The byte array to be written to the server. * @exception FtpException if a ftp error occur. (eg. permission denied in this case) * @exception IOException if an I/O error occur. */ public void putBinaryFile(String filename, byte[] content) throws IOException, FtpException { if(out == null) return; putBinaryFile(filename, content, -1); } /** * Put a binary file to the server from an array of byte with a restarting point * @param filename The name of file. * @param content The byte array to be write to the server. * @param restart The restarting point, ingored if less than or equal to zero. * @exception FtpException if a ftp error occur. (eg. permission denied in this case) * @exception IOException if an I/O error occur. */ public void putBinaryFile(String filename, byte[] content, long restart) throws IOException, FtpException { if(out == null) return; acquire(); // Acquire the object try { setTransferType(false); putFile(filename, content, restart); } finally { release(); // Release the object } } /** * Read a file from local hard disk and write to the server. *
Remark:
*
Cannot be used in unsigned applet. * @param local_file Name of local file, can be in full path. * @param remote_file Name of file in the ftp server, can be in full path. * @exception FtpException if a ftp error occur. (eg. permission denied) * @exception IOException if an I/O error occur. */ public void putBinaryFile(String local_file, String remote_file) throws IOException, FtpException { if(out == null) return; putBinaryFile(local_file, remote_file, 0, null); } /** * Read a file from local hard disk and write to the server. *
Remark:
*
Cannot be used in unsigned applet. * @param local_file Name of local file, can be in full path. * @param remote_file Name of file in the ftp server, can be in full path. * @param observer The FtpObserver which monitor this uploading progress. * @exception FtpException if a ftp error occur. (eg. permission denied) * @exception IOException if an I/O error occur. */ public void putBinaryFile(String local_file, String remote_file, FtpObserver observer) throws IOException, FtpException { if(out == null) return; putBinaryFile(local_file, remote_file, 0, observer); } /** * Read a file from local hard disk and write to the server with restarting point. * Remark:
* Cannot be used in unsigned applet. * @param local_file Name of local file, can be in full path. * @param remote_file Name of file in the ftp server, can be in full path. * @param restart The restarting point, ignored if less than or greater than zero. * @exception FtpException if a ftp error occur. (eg. permission denied) * @exception IOException if an I/O error occur. */ public void putBinaryFile(String local_file, String remote_file, long restart) throws IOException, FtpException { if(out == null) return; putBinaryFile(local_file, remote_file, restart, null); } /** * Read a file from local hard disk and write to the server with restarting point. * Remark:
* Cannot be used in unsigned applet. * @param local_file Name of local file, can be in full path. * @param remote_file Name of file in the ftp server, can be in full path. * @param observer The FtpObserver which monitor this uploading progress * @exception FtpException if a ftp error occur. (eg. permission denied) * @exception IOException if an I/O error occur. */ public void putBinaryFile(String local_file, String remote_file, long restart, FtpObserver observer) throws IOException, FtpException { if(out == null) return; acquire(); // Acquire the object try { Socket sock = null; setTransferType(false); RandomAccessFile fin = new RandomAccessFile(local_file, "r"); sock = getDataSocket(CMD_STOR, remote_file, restart); if(restart > 0) fin.seek(restart); DataOutputStream dout = new DataOutputStream(sock.getOutputStream()); writeData(fin, dout, observer); fin.close(); dout.close(); getRespond(CMD_STOR); } finally { release(); // Release the object } } /** * Read a file from local hard disk and append a file on the server. * Remark:
* Cannot be used in unsigned applet. * @param filename Name of local file and remote file. * @exception FtpException if a ftp error occur. (eg. permission denied) * @exception IOException if an I/O error occur. */ public void appendBinaryFile(String filename) throws IOException, FtpException { appendBinaryFile (filename, filename, null); } /** * Read a file from local hard disk and append to the server with restarting point. * Remark:
* Cannot be used in unsigned applet. * @param local_file Name of local file, can be in full path. * @param remote_file Name of file in the ftp server, can be in full path. * @exception FtpException if a ftp error occur. (eg. permission denied) * @exception IOException if an I/O error occur. */ public void appendBinaryFile(String local_file, String remote_file) throws IOException, FtpException { appendBinaryFile (local_file, remote_file, null); } /** * Read a file from local hard disk and append to the server with restarting point. * Remark:
* Cannot be used in unsigned applet. * @param local_file Name of local file, can be in full path. * @param remote_file Name of file in the ftp server, can be in full path. * @param observer The FtpObserver which monitor this uploading progress * @exception FtpException if a ftp error occur. (eg. permission denied) * @exception IOException if an I/O error occur. */ public void appendBinaryFile(String local_file, String remote_file, FtpObserver observer) throws IOException, FtpException { if(out == null) return; acquire(); // Acquire the object try { Socket sock = null; setTransferType(false); RandomAccessFile fin = new RandomAccessFile(local_file, "r"); sock = getDataSocket(CMD_APPE, remote_file, 0); DataOutputStream dout = new DataOutputStream(sock.getOutputStream()); writeData(fin, dout, observer); fin.close(); dout.close(); getRespond(CMD_APPE); } finally { release(); // Release the object } } /** * Get current directory name. * @return The name of the current directory. * @exception FtpException if a ftp error occur. * @exception IOException if an I/O error occur. */ public String getDirectory() throws IOException, FtpException { if(out == null) return null; acquire(); // Acquire the object try { ftpCommand(CMD_PWD); } finally { release(); // Release the object } int first = reply.indexOf('\"'); int last = reply.lastIndexOf('\"'); return reply.substring(first + 1, last); } /** * Change directory. * @param directory Name of directory * @exception FtpException if a ftp error occur. (eg. permission denied in this case) * @exception IOException if an I/O error occur. */ public void setDirectory(String directory) throws IOException, FtpException { if(out == null) return; acquire(); // Acquire the object try { ftpCommand(CMD_CWD, directory); } finally { release(); // Release the object } } /** * Change to parent directory. * @exception FtpException if a ftp error occur. (eg. permission denied in this case) * @exception IOException if an I/O error occur. */ public void toParentDirectory() throws IOException, FtpException { if(out == null) return; acquire(); // Acquire the object try { ftpCommand(CMD_CDUP); } finally { release(); // Release the object } } /** * Get the content of current directory * @return A FtpListResult object, return null if it is not connected. * @exception FtpException if a ftp error occur. (eg. permission denied in this case) * @exception IOException if an I/O error occur. * @see FtpListResult */ public FtpListResult getDirectoryContent() throws IOException, FtpException { if(out == null) return null; String str_list = getDirectoryContentAsString(); FtpListResult ftplist = new FtpListResult(); ftplist.parseList(str_list, getSystemType()); return ftplist; } /** * Get the content of current directory. * @return A list of directories, files and links in the current directory. * @exception FtpException if a ftp error occur. (eg. permission denied in this case) * @exception IOException if an I/O error occur. */ public String getDirectoryContentAsString() throws IOException, FtpException { if(out == null) return null; StringBuffer list = new StringBuffer(""); // Directory list Socket sock = null; // Socket to establish data connection acquire(); // Acquire the object try { // get DataSocket for the LIST command. // As no restarting point, send 0. sock = getDataSocket(CMD_LIST, 0); BufferedReader listen = new BufferedReader( new InputStreamReader( sock.getInputStream(), FTP_ENCODING)); // Read bytes from server. String line; while((line = listen.readLine()) != null) list.append(line).append("\n"); listen.close(); sock.close(); getRespond(CMD_LIST); } finally { release(); // Release the object } return list.toString(); } /** * Make a directory in the server. * @param directory The name of directory to be made. * @exception FtpException if a ftp error occur. (eg. permission denied in this case) * @exception IOException if an I/O error occur. */ public void makeDirectory(String directory) throws IOException, FtpException { if(out == null) return; acquire(); // Acquire the object try { ftpCommand(CMD_MKD ,directory); } finally { release(); // Release the object } } /** * Remove a directory in the server * @param directory The name of directory to be removed * @exception FtpException if a ftp error occur. (eg. permission denied in this case) * @exception IOException if an I/O error occur. */ public void removeDirectory(String directory) throws IOException, FtpException { if(out == null) return; acquire(); // Acquire the object try { ftpCommand(CMD_RMD ,directory); } finally { release(); // Release the object } } /** * Execute a command using ftp. * e.g. chmod 700 file * @param exec The command to execute. * @exception FtpException if a ftp error occur. (eg. command not understood) * @exception IOException if an I/O error occur. */ public void execute(String exec) throws IOException, FtpException { if(out == null) return; acquire(); // Acquire the object try { ftpCommand(CMD_SITE, exec); } finally { release(); // Release the object } } /** * Get the type of operating system of the server. * Return null if it is not currently connected to any ftp server. * @return Name of the operating system. */ public String getSystemType() throws IOException, FtpException { if(out == null) return null; acquire(); // Acquire the object try { ftpCommand(CMD_SYST); } finally { release(); // Release the object } return reply.substring(4); } /** * Return the port number */ public int getPort() { return port; } /** * Set port number if the port number of ftp is not 21 */ public void setPort(int port) { acquire(); // Acquire the object pcs.firePropertyChange("port", Integer.valueOf(this.port), Integer.valueOf(port)); this.port = port; release(); // Release the object } /** * Set timeout when creating a socket. * This include trying to connect to the ftp server at the beginnig and * trying to connect to the server in order to establish a data connection. * @param timeout Timeout in milliseconds, 0 means infinity. */ public void setSocketTimeout(int timeout) throws SocketException { acquire(); // Acquire the object pcs.firePropertyChange("socketTimeout", Integer.valueOf(this.timeout), Integer.valueOf(timeout)); this.timeout = timeout; release(); // Release the object } /** * Get timeout when creating socket. * @return Timeout for creating socket in milliseconds, 0 means infinity. */ public int getSocketTimeout() throws SocketException { return timeout; // default is 0 } /** * Return the account information. Return "" if it is not connected to any server. */ public String getAcctInfo() { return acctInfo; } /** * Return the server name. Return "" if it is not connected to any server. */ public String getServerName() { return server; } /** * Return the user name. Return "" if it is not connected to any server. */ public String getUserName() { return user; } /** * Get reply of the last command. * @return Reply of the last comomand
for example: 250 CWD command successful */ public String getReply() { return reply; } /** * Get reply message of the last command. * @return Reply message of the last command
for example:
* 250-Please read the file README
* 250-it was last modified on Wed Feb 10 21:51:00 1999 - 268 days ago */ public String getReplyMessage() { return replymessage; } /** * Return true if it is using passive transfer mode. */ public boolean isPassiveModeTransfer() { return passive; } /** * Set passive transfer mode. Default is true. * @param passive Using passive transfer if true. */ public void setPassiveModeTransfer(boolean passive) { acquire(); // Acquire the object pcs.firePropertyChange("passiveModeTransfer", Boolean.valueOf(this.passive), Boolean.valueOf(passive)); this.passive = passive; if(DEBUG) // debug message System.out.println("FtpBean: Set passive transfer - " + passive); release(); // Release the object } /* * Close the Socket, input and output stream */ private void closeSocket() throws IOException { in.close(); out.close(); socket.close(); in = null; out = null; socket = null; } /* * Get the reply type by identifying the first digit of the reply code. */ private String getReplyType(String reply_code) { if(reply_code == null || reply_code.length() <= 0) return REPLY_UNDEF; String reply_type = reply_code.substring(0, 1); if(!(reply_type.equals(REPLY_POS_PRE) || reply_type.equals(REPLY_POS_CMP) || reply_type.equals(REPLY_POS_INT) || reply_type.equals(REPLY_TRA_NEG) || reply_type.equals(REPLY_PER_NEG))) return REPLY_UNDEF; return reply_type; } /* * Read the respond message from the server's inputstream and assign to replymessage */ private void getRespond(String cmd) throws IOException, FtpException { String line = ""; String replymessage = ""; do { line = in.readLine(); //System.err.println("FTP Responds: "+line); if(!checkReply(line)) break; if ( line.contains("Transfer complete") ) { break; } replymessage = replymessage.concat(line).concat("\n"); } while(true); setReplyMessage(replymessage); setReply(line); String reply_type = getReplyType(reply); String[] valid_replies = null; try { valid_replies = ftpReplies.getStringArray(cmd); } catch(MissingResourceException e) { throw new FtpException("Valid reply for command '" + cmd + "' not found in reply resource bundle"); } boolean valid = false; for(int i = 0; i < valid_replies.length; i++) { if(reply_type.equals(valid_replies[i])) { valid = true; break; } } if(!valid) throw new FtpException(reply); } /* * Login to server, using FTP commands "USER" and "PASS" * @param user FTP username * @param password FTP Password */ private void ftpLogin(String user, String password, String acct) throws IOException, FtpException { ftpCommand(CMD_USER ,user); // send user name if(getReplyType(reply).equals(REPLY_POS_CMP)) return; ftpCommand(CMD_PASS, password); // send password if(getReplyType(reply).equals(REPLY_POS_CMP)) return; ftpCommand(CMD_ACCT, acct); } /* * Send FTP command to the server. * @param command The command to be sent */ private void ftpCommand(String cmd) throws IOException, FtpException { ftpCommand(cmd, ""); } /* * Send FTP command to the server. * @param command The command to be sent * @param Expected code return from the command. */ private void ftpCommand(String cmd, String param) throws IOException, FtpException { if(out == null) return; if(DEBUG) // Debug message { if(cmd.equals(CMD_PASS)) System.out.println("FtpBean: Send password"); else System.out.println("FtpBean: Send command \"" + cmd + param + "\""); } out.print(cmd + param + "\r\n"); // Send cmd out.flush(); getRespond(cmd); } /* * Get a file, return a byte array. * @param filename Name of binary file * @param restart The restarting point * @param observer Observer of the downloading progress */ private byte[] getFile(String filename, long restart, FtpObserver observer) throws IOException, FtpException { if(out == null) return null; Socket sock = null; sock = getDataSocket(CMD_RETR, filename, restart); // Read bytes from server BufferedInputStream reader = new BufferedInputStream( sock.getInputStream()); byte[] data = getBytes(reader, observer); reader.close(); sock.close(); getRespond(CMD_RETR); if(!reply.substring(0, 3).equals("226")) { throw new FtpException(reply); // transfer incomplete } return data; } /* * Read bytes continuously from the BufferedInputStream. * Add the new read bytes to an byte array. * Return the byte array after all bytes are read. * If the FtpObserver is not null, it invokes its byteRead method * every time there are new bytes read. */ private byte[] getBytes(BufferedInputStream reader, FtpObserver observer) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(1024); int offset; byte[] data = new byte[1024]; boolean cont= true; while( cont && (offset = reader.read(data)) != -1) { out.write(data, 0, offset); if(observer != null) { cont= observer.byteRead(offset); } } if ( !cont ) { throw new IOException("Operation cancelled"); } return out.toByteArray(); } private void appendFile(String filename, byte[] content) throws IOException, FtpException { Socket sock = null; sock = getDataSocket(CMD_APPE, filename, 0); // Write data to server DataOutputStream writer = new DataOutputStream(sock.getOutputStream()); writer.write(content, 0, content.length); writer.close(); sock.close(); getRespond(CMD_APPE); } /* * Accept a byte array and filename, then save to server * @param file Name of file to be saved. * @param content The content of the ASCII file. * @param restart Restarting point */ private void putFile(String filename, byte[] content, long restart) throws IOException, FtpException { Socket sock = null; sock = getDataSocket(CMD_STOR, filename, restart); // Write data to server DataOutputStream writer = new DataOutputStream(sock.getOutputStream()); writer.write(content, 0, content.length); writer.close(); sock.close(); getRespond(CMD_STOR); } /* * Establish data connection for transfer */ private Socket getDataSocket(String command, long restart) throws IOException, FtpException { return getDataSocket(command, "", restart); } /* * Establish data connection for transfer */ private Socket getDataSocket(String command, String param, long restart) throws IOException, FtpException { Socket sock = null; ServerSocket ssock = null; // Establish data conncetion using passive or active mode. if(passive) sock = getPassiveDataSocket(); else ssock = getActiveDataSocket(); // Send the restart command if it is greater than zero if(restart > 0) ftpCommand(CMD_REST, Long.toString(restart)); // Send commands like LIST, RETR and STOR // These commands will return 125 or 150 when success. ftpCommand(command, param); // Get Socket object for active mode. if(!passive) { sock = ssock.accept(); ssock.close(); } return sock; } /* * Establish data connection in passive mode using "PASV" command * Change the server to passive mode. * by the command "PASV", it will return its address * and port number that it will listen to. * Create a Socket object to that address and port number. * Then return the Socket object. */ private Socket getPassiveDataSocket() throws IOException, FtpException { Socket sock = null; ftpCommand(CMD_PASV); // array that holds the outputed address and port number. String[] address = new String[6]; // Extract the address & port numbers from the string like // 227 Entering Passive Mode (192.168.111.1,220,235) reply = reply.substring(reply.indexOf('(') + 1, reply.indexOf(')')); // put the 'reply' to the array 'address' StringTokenizer t = new StringTokenizer(reply, ","); for(int i = 0; i < 6; i++) address[i] = t.nextToken(); // Returned server address String SRV_IP = address[0] + '.' + address[1] + '.' + address[2] + '.' + address[3]; // Get the port number // Left shift the first number by 8 int NEW_PORT = (Integer.parseInt(address[4]) << 8) + Integer.parseInt(address[5]); if(DEBUG) System.out.println("FtpBean: Extracted Server ip - " + SRV_IP + ", Port Number - " + NEW_PORT); // Create a new socket object if(timeout == 0) { sock = new Socket(SRV_IP, NEW_PORT); } else { sock = new SocketOpener(SRV_IP, NEW_PORT).makeSocket(timeout); } return sock; } /* * Establish data connection in active mode using "PORT" command. * It create a ServerSocket object to listen for a port number in local machine. * Use port command to tell the server which port the local machine is listenning. * Return the ServerSocket object. */ private ServerSocket getActiveDataSocket() throws IOException, FtpException { int[] port_numbers = new int[6]; // Array that contains // Get ip address of local machine. ip address and port numbers String local_address = socket.getLocalAddress().getHostAddress(); // Assign the ip address of local machine to the array. StringTokenizer st = new StringTokenizer(local_address, "."); for(int i = 0; i < 4; i++) port_numbers[i] = Integer.parseInt(st.nextToken()); ServerSocket ssocket = new ServerSocket(0); // ServerSocket to listen to a random free port number int local_port = ssocket.getLocalPort(); // The port number it is listenning to // Assign port numbers the array port_numbers[4] = ((local_port & 0xff00) >> 8); port_numbers[5] = (local_port & 0x00ff); // Send "PORT" command to server String port_param = ""; for(int i = 0; i < port_numbers.length; i++) { port_param = port_param.concat(String.valueOf(port_numbers[i])); if(i < port_numbers.length - 1) port_param = port_param.concat(","); } ftpCommand(CMD_PORT, port_param); return ssocket; } /* * Set reply of the last command */ private void setReply(String reply) { pcs.firePropertyChange("reply", this.reply, reply); this.reply = reply; } /* * Set reply message and fire property change * @param reply The reply message to be set. */ private void setReplyMessage(String replymessage) { pcs.firePropertyChange("replyMessage", this.replymessage, replymessage); this.replymessage = replymessage; } /* * Set account information and fire property change * @param acctInfo The account information */ private void setAcctInfo(String acctInfo) { pcs.firePropertyChange("acctInfo", this.acctInfo, acctInfo); this.acctInfo = acctInfo; } /* * Set server name and fire property change * @param server The name of the server. */ private void setServerName(String server) { pcs.firePropertyChange("serverName", this.server, server); this.server = server; } /* * Set user name and fire property change */ private void setUserName(String user) { pcs.firePropertyChange("userName", this.user, user); this.user = user; } /* * Set the transfer type * @param ascii True for ascii transfer type */ private void setTransferType(boolean ascii) throws IOException, FtpException { if(ascii) { ftpCommand(CMD_TYPE, TF_MOD_ASCII); } else { ftpCommand(CMD_TYPE, TF_MOD_BIN); } } /* * Replace the line separator of a text */ private String changeLineSeparator(String text , String old_separator, String new_separator) { if(DEBUG) // Debug message System.out.println("FtpBean: Converting ASCII format"); if(old_separator.equals(new_separator)) return text; StringBuffer content= new StringBuffer(""); int index; while((index = text.indexOf(old_separator)) != -1) { content.append(text.substring(0, index)).append(new_separator); text = text.substring(index + old_separator.length()); } if(text.length() > 0) content.append(text); return content.toString(); } /* * Check the input string is a reply code or not */ private boolean checkReply(String str) { if(str == null) return true; // Return true if the fourth character is a space. if(str.length() > 3 && str.charAt(3) == ' ' && Character.isDigit(str.charAt(0)) && Character.isDigit(str.charAt(1)) && Character.isDigit(str.charAt(2))) { return false; } else { return true; } } /* * Read the data from the BufferedInputStream object and write to the RandomAccessFile object */ private void readData(BufferedInputStream reader, RandomAccessFile out, FtpObserver observer) throws IOException { int offset; byte[] data = new byte[1024]; boolean cont= true; while( cont && (offset = reader.read(data)) != -1) { out.write(data, 0, offset); if(observer != null) { cont= observer.byteRead(offset); } } if ( cont==false ) { throw new InterruptedIOException("Operation cancelled"); } } /* * Write data from the RandomAccessFile object to a DataOutputStream object * @param din File to be read from local hard disk * @param dout Output stream to write content to the server. * @param observer FtpObserver of the uploading process. */ private void writeData(RandomAccessFile din, DataOutputStream dout, FtpObserver observer) throws IOException { int offset; byte[] data = new byte[1024]; boolean cont= true; while( cont && (offset = din.read(data)) != -1) { dout.write(data, 0, offset); if(observer != null) { cont= observer.byteWrite(offset); } } if ( cont==false ) { throw new InterruptedIOException("Operation cancelled"); } } // Methods for thread safe. // All of this thread safe operation are transparent to the programmers who are using this bean. // All threads that want to do some ftp operation must acquire this object first. // Then release the object after those operation or an exception is throwed. // When the object is acquired by a thread, other threads that want to do ftp operation, // will be placed in the thread_spool. Then have rights to access this thread when the // previous thread is done. // Normally, calling the acquire() and release() methods are like this: // // acquire(); // try // { // // Do operation that may cause Exception here // } finally // { // release(); // } // // This can ensure the thread will release the object even an exception is threw. /* * Acquire this FtpBean object. * If there is a thread acquired this object already. Put itself into the thread spool. */ private void acquire() { Thread thread = Thread.currentThread(); synchronized(lock) { thread_spool.addElement(thread); // Add thread to thread_spool if(DEBUG) // Debug message System.out.println("Add thread to spool, size: " + thread_spool.size()); } try { while(acquired && !thread_spool.elementAt(0).equals(thread)) thread.sleep(10); } catch(InterruptedException e) { if(DEBUG) // Debug message System.out.println("Thread interrupted"); return; } acquired = true; if(DEBUG) // Debug message System.out.println("FtpBean: Acquired by thread."); /* Old locking synchronized(lock) { // Loop if object is acquired by a thread // or there are other threads waiting in the thread_spool loop: while(acquired || thread_spool.size() > 0) { if(thread_spool.contains(thread)) { if(thread_spool.elementAt(0).equals(thread) && !acquired) { // Object is released by previous thread // And this is the first thread in the thread_spool // Then break the loop. thread_spool.removeElement(thread); break loop; } else lock.notify(); // Notify other threads } else thread_spool.addElement(thread); // Add thread to thread_spool // Go to wait try { lock.wait(); } catch(Exception e) { System.out.println(e); } } // Acquire this object acquired= true; if(DEBUG) // Debug message System.out.println("FtpBean: Acquired by thread."); } */ } /* * Release this FtpBean object and notify other waiting threads. */ private void release() { synchronized(lock) { thread_spool.removeElementAt (0); } acquired = true; if(DEBUG) // Debug message System.out.println("FtpBean: Released by thread."); /* synchronized(lock) { if(DEBUG) // Debug message System.out.println("FtpBean: Released by thread."); // Release this object acquired = false; // Notify other threads if(thread_spool.size() > 0) lock.notify(); else lock.notifyAll(); } */ } }