/* * Copyright (C) Lennart Martens * * Contact: lennart.martens AT UGent.be (' AT ' to be replaced with '@') */ /* * Created by IntelliJ IDEA. * User: Lennart * Date: 29-nov-02 * Time: 13:40:47 */ package com.compomics.util.io; import org.apache.log4j.Logger; /* * CVS information: * * $Revision: 1.3 $ * $Date: 2007/07/06 09:41:53 $ */ import java.io.*; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.util.StringTokenizer; import java.util.Vector; /** * This class was modified from a source found on the net (java.sun.com, search in * developer section on 'FtpProtocolException PORT'). * * @author Lennart Martens + someone on the net... */ public class FTP { // Class specific log4j logger for FTP instances. Logger logger = Logger.getLogger(FTP.class); /** * FTP port to use for connection. */ public static final int FTP_PORT = 21; /** * Pre-defined state. */ static int FTP_UNKNOWN = -1; /** * Pre-defined state. */ static int FTP_SUCCESS = 1; /** * Pre-defined state. */ static int FTP_TRY_AGAIN = 2; /** * Pre-defined state. */ static int FTP_ERROR = 3; /** * Pre-defined state. */ static int FTP_NOCONNECTION = 100; /** * Pre-defined state. */ static int FTP_BADUSER = 101; /** * Pre-defined state. */ static int FTP_BADPASS = 102; /** * Pre-defined state. */ public static int FILE_GET = 1; /** * Pre-defined state. */ public static int FILE_PUT = 2; /** * socket for data transfer to and from the server. */ private Socket dataSocket = null; /** * Flag that indicates that we need to process a previous commands * reply first. */ private boolean replyPending = false; /** * Boolean to indicate the use of binary mode. */ private boolean binaryMode = false; /** * Boolean to indicate the use of passive mode. */ private boolean passiveMode = false; /** * Boolena to indicite whether we are currently receiving a file. */ private boolean m_bGettingFile = false; /** * The ser name for login. */ String user = null; /** * The password for login. */ String password = null; /** * The last command issued. */ public String command; /** * The last reply code from the ftp daemon. */ int lastReplyCode; /** * Welcome message from the server, if any. */ public String welcomeMsg; /** * Array of strings (usually 1 entry) for the last reply from the server. */ protected Vector serverResponse = new Vector(1); /** * Socket for communicating commands with the server. */ protected Socket controlSocket = null; /** * Stream for printing to the server. */ public PrintWriter serverOutput; /** * Buffered stream for reading replies from server. */ public InputStream serverInput; /** * String to hold the file we are up/downloading */ protected String strFileNameAndPath; protected String m_strSource; protected String m_strDestination; /** * This method sets the name of the file to up- or download. * * @param strFile String with the filename. */ public void setFilename(String strFile) { strFileNameAndPath = strFile; } /** * This method reports on the filename used for current * transfer. * * @return String with the filename. */ String getFileName() { return strFileNameAndPath; } /** * This method allows to set the source for the file that is to be * transferred. * * @param strSourceFile String with the sourec for the file to be * transferred. */ public void setSourceFile(String strSourceFile) { m_strSource = strSourceFile; } /** * This method allows the specification of the destinationfile. * * @param strDestinationFile String with the destination file. */ public void setDestinationFile(String strDestinationFile) { m_strDestination = strDestinationFile; } /** * This method reports on the sourcefile currently used in transfer. * * @return String with the source file. */ public String getSourceFile() { return m_strSource; } /** * This method reports on the destinationfile currently used in transfer. * * @return String with the destination file. */ public String getDestinationFile() { return m_strDestination; } /** * Return server connection status * * @return boolean 'true' when connected, 'false' otherwise. */ public boolean serverIsOpen() { return controlSocket != null; } /** * Set Passive mode Transfers. This is particularly useful * when attempting to FTP via passive mode. * * @param mode boolean to indicate whether passive mode * should be used. */ public void setPassive(boolean mode) { passiveMode = mode; } /** * This method allows the caller to capture the server response. * * @return int with the server's response. * @throws IOException if an IOException occurs */ public int readServerResponse() throws IOException { StringBuffer replyBuf = new StringBuffer(32); int c; int continuingCode = -1; int code = -1; String response; try { while(true) { while((c = serverInput.read()) != -1) { if(c == '\r') { if((c = serverInput.read()) != '\n') { replyBuf.append('\r'); } } replyBuf.append((char)c); if(c == '\n') break; } response = replyBuf.toString(); replyBuf.setLength(0); try { code = Integer.parseInt(response.substring(0, 3)); } catch(NumberFormatException e) { code = -1; } catch(StringIndexOutOfBoundsException e) { // this line doesn't contain a response code, // so we just completely ignore it. continue; } serverResponse.addElement(response); if(continuingCode != -1) { // we've seen an XXX- sequence! if(code != continuingCode || (response.length() >= 4 && response.charAt(3) == '-')) { continue; } else { // We've seen the end of code sequence. continuingCode = -1; break; } } else if(response.length() >= 4 && response.charAt(3) == '-') { continuingCode = code; continue; } else { break; } } } catch(Exception e) { logger.error(e.getMessage(), e); } // Store the response for later reference. return lastReplyCode = code; } /** * Sends command <i>cmd</i> to the server. * * @param cmd String with the command to send. */ public void sendServer(String cmd) { serverOutput.println(cmd); } /** * Returns all server response strings. * It also clears the server-response buffer! * * @return String with all the server response strings. */ public String getResponseString() { StringBuffer sb = new StringBuffer(); for(int i = 0; i < serverResponse.size(); i++) { sb.append(serverResponse.elementAt(i)); } serverResponse = new Vector(1); return sb.toString(); } /** * This method allows the caller to read the response strings from * the server, without resetting the internal buffer (and thus not clearing the * messages read by this messages). * * @return String with the server responses. */ public String getResponseStringNoReset() { StringBuffer sb = new StringBuffer(); for(int i = 0; i < serverResponse.size(); i++) { sb.append(serverResponse.elementAt(i)); } return sb.toString(); } /** * Issue the QUIT command to the FTP server and close the connection. * @throws IOException if an IOException occurs */ public void closeServer() throws IOException { if(serverIsOpen()) { issueCommand("QUIT"); if(!serverIsOpen()) { return; } controlSocket.close(); controlSocket = null; serverInput = null; serverOutput = null; } } /** * This method allows the caller to issue a command to the server. * * @param cmd String with the command to issue. * @return int with the server reply status. * @exception IOException when the connection with the server failed. */ protected int issueCommand(String cmd) throws IOException { command = cmd; int reply; if(replyPending) { if(readReply() == FTP_ERROR) { logger.info("Error reading pending reply\n"); } } replyPending = false; do { sendServer(cmd); reply = readReply(); } while(reply == FTP_TRY_AGAIN); return reply; } /** * This method will issue the specified command and throw an exception * whenever the reply is not equal to success! It basically converts an * FTP error code into an FtpProtocolException. * * @param cmd String with the command to issue (and verify the response for) * @exception IOException when the connection failed OR FtpProtocolException when the * command was not understood by the server. */ protected void issueCommandCheck(String cmd) throws IOException { if(issueCommand(cmd) != FTP_SUCCESS) { throw new FtpProtocolException(cmd); } } /** * This method attempts to read a reply from the FTP server. * * @return int with the reply from the server. * @exception IOException whenever the reply could not be read. */ protected int readReply() throws IOException { // The last replycode is read from the server. lastReplyCode = readServerResponse(); // Determine the nature of the response. switch(lastReplyCode / 100) { case 1: replyPending = true; // Falls into ... case 2: //This case is for future purposes. If not properly used, it might cause an infinite loop. //Don't add any code here , unless you know what you are doing. case 3: return FTP_SUCCESS; case 5: // 530 is login exception. if(lastReplyCode == 530) { if(user == null) { throw new FtpLoginException("Not logged in"); } return FTP_ERROR; } // 550 means logged in with wrong password (no access) // OR no access because file not found! if(lastReplyCode == 550) { if(!command.startsWith("PASS")) { throw new FileNotFoundException(command); } else { throw new FtpLoginException("Error: Wrong Password!"); } } } return FTP_ERROR; } /** * This method will set up the networking for client-server data transfer and * it will send the specified command to the server. This is typically used only * for true file transfer. * * @param cmd String with the command to issue. * @return Socket with the data connection socket. * @exception IOException whenever communications could not be established. */ protected Socket openDataConnection(String cmd) throws IOException { ServerSocket portSocket = null; String portCmd; // Local address. InetAddress myAddress = InetAddress.getLocalHost(); byte addr[] = myAddress.getAddress(); int shift; String ipaddress; int port = 0; IOException e; if(this.passiveMode) { /* First let's attempt to initiate Passive transfers */ try { // PASV code // Clear the response buffer. getResponseString(); // Test the support for passive mode transfer. if(issueCommand("PASV") == FTP_ERROR) { e = new FtpProtocolException("PASV"); throw e; } // OK, passive mode supported, get the servers response. String reply = getResponseStringNoReset(); // Section between brackets contains host and port information. reply = reply.substring(reply.indexOf("(") + 1, reply.indexOf(")")); StringTokenizer st = new StringTokenizer(reply, ","); String[] nums = new String[6]; int i = 0; while(st.hasMoreElements()) { try { nums[i] = st.nextToken(); i++; } catch(Exception a) { logger.error(a.getMessage(), a); } } // Reconstruct the IP address. ipaddress = nums[0] + "." + nums[1] + "." + nums[2] + "." + nums[3]; try { int firstbits = Integer.parseInt(nums[4]) << 8; int lastbits = Integer.parseInt(nums[5]); // Reconstruct the port from the information in the header. port = firstbits + lastbits; } catch(Exception b) { logger.error(b.getMessage(), b); } // If we were successful in reconstituting IP and port information, // create a socket to this port. // Else the protocol was not understandable. if((ipaddress != null) && (port != 0)) { dataSocket = new Socket(ipaddress, port); } else { e = new FtpProtocolException("PASV"); throw e; } // Try to execute the command. if(issueCommand(cmd) == FTP_ERROR) { e = new FtpProtocolException(cmd); throw e; } } catch(FtpProtocolException fpe) { // PASV was not supported...resort to PORT portCmd = "PORT "; // Append host address to the command. for(int i = 0; i < addr.length; i++) { portCmd = portCmd + (addr[i] & 0xFF) +","; } // Append port number to the command. portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff); // Try to issue the command over the local port. if(issueCommand(portCmd) == FTP_ERROR) { e = new FtpProtocolException("PORT"); portSocket.close(); throw e; } if(issueCommand(cmd) == FTP_ERROR) { e = new FtpProtocolException(cmd); portSocket.close(); throw e; } dataSocket = portSocket.accept(); portSocket.close(); } }//end if passive else { //do a port transfer try { // Set-up a local port for the communication. portSocket = new ServerSocket(0, 1, myAddress); } catch(Exception b) { logger.error(b.getMessage(), b); } portCmd = "PORT "; // Append host address. for(int i = 0; i < addr.length; i++) { portCmd = portCmd + (addr[i] & 0xFF) +","; } // Append port number. portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff); // This should work - unless the client is locked down by a firewall, // or bypassed via a proxy. if(issueCommand(portCmd) == FTP_ERROR) { e = new FtpProtocolException("PORT"); portSocket.close(); throw e; } // Issue the command to the server. if(issueCommand(cmd) == FTP_ERROR) { e = new FtpProtocolException(cmd); portSocket.close(); throw e; } // Wait for incoming data. dataSocket = portSocket.accept(); portSocket.close(); }//end of port transfer return dataSocket; // return the dataSocket } /** * open a FTP connection to host <i>host</i>. * * @param host String with the hostname (or IP) to connect to. * @exception IOException whenever connection could not be established. * @exception UnknownHostException when the hostname cannot be resolved. */ public void openServer(String host) throws IOException, UnknownHostException { // FTP ports default to port 21. int port = FTP_PORT; // If we have a connection going, close it first. if(controlSocket != null) closeServer(); // Attempt a connection. controlSocket = new Socket(host, FTP_PORT); // Get streams for communication. serverOutput = new PrintWriter(new BufferedOutputStream(controlSocket.getOutputStream()), true); serverInput = new BufferedInputStream(controlSocket.getInputStream()); } /** * Open an FTP connection to host <i>host</i> on port <i>port</i>. * This method can be used whenever the default FTP port (21) does not apply. * * @param host String with the hostname (or IP) of the FTP server. * @param port int with the portnumber the FTP server is listening on. * @exception IOException whenever connection could not be established. * @exception UnknownHostException when the hostname cannot be resolved. */ public void openServer(String host, int port) throws IOException, UnknownHostException { // If we have a connection going, close it first. if(controlSocket != null) closeServer(); // Open the socket. controlSocket = new Socket(host, port); //controlSocket.setSoLinger(true,30000); // Get control input and output Streams. serverOutput = new PrintWriter(new BufferedOutputStream(controlSocket.getOutputStream()), true); serverInput = new BufferedInputStream(controlSocket.getInputStream()); // If the reply is not what we expect, signal this - we're probably not connected to an FTP server then! if(readReply() == FTP_ERROR) throw new FtpConnectionException("Welcome message"); } /** * Login user to a host with username <i>user</i> and password * <i>password</i>. * * @param user String with the username to use. * @param password String with the password to use (passwords are sent in plain text in FTP!) * @throws IOException if an IOException occurs */ public void login(String user, String password) throws IOException { // See if we are connected first. if(!serverIsOpen()) { throw new FtpLoginException("Error: not connected to host.\n"); } this.user = user; this.password = password; if(issueCommand("USER " + user) == FTP_ERROR) { throw new FtpLoginException("Error: User not found.\n"); } if(password != null && issueCommand("PASS " + password) == FTP_ERROR){ throw new FtpLoginException("Error: Wrong Password.\n"); } } /** * Login user to a host with username <i>user</i> and no password * such as HP server which uses the form "<username>/<password>,user.<group>. * * @param user String with the username (and possibly coded information such as password). * @throws IOException if an IOException occurs */ public void login(String user) throws IOException { // See if we are connected first. if(!serverIsOpen()) { throw new FtpLoginException("not connected to host"); } this.user = user; if(issueCommand("USER " + user) == FTP_ERROR) { throw new FtpLoginException("Error: Invalid Username.\n"); } } /** * GET a file from the FTP server in Ascii mode. * * @param filename String with the filename to get from the server. * @return BufferedReader to the file on the server. * @exception IOException whenever the file could not be read. */ public BufferedReader getAscii(String filename) throws IOException { // Flag that we are processing an incoming file. m_bGettingFile = true; Socket s = null; try { // Get a connection for the retrieval ('RETR' command) of the // specified file. s = openDataConnection("RETR " + filename); } catch(FileNotFoundException fileException) { throw new FileNotFoundException(); } return new BufferedReader(new InputStreamReader(s.getInputStream())); } /** * GET a file from the FTP server in Binary mode. * * @param filename String with the filename to get from the server. * @return BufferedInputStream to the file on the server. * @exception IOException whenever the file could not be read. */ public BufferedInputStream getBinary(String filename) throws IOException { // Flag that we are processing an incoming file. m_bGettingFile = true; Socket s = null; try { // Get a connection for the retrieval ('RETR' command) of the // specified file. s = openDataConnection("RETR " + filename); } catch(FileNotFoundException fileException) { throw new FileNotFoundException(); } return new BufferedInputStream(s.getInputStream()); } /** * PUT a file on the FTP server in Ascii mode. * * @param filename String with the filename to put on the server. * @return BufferedWriter writer for completing the file on the server. * @exception IOException whenever the file could not be sent. */ public BufferedWriter putAscii(String filename) throws IOException { // Indicate that we are sending (as opposed to getting). m_bGettingFile = false; // Get a connection for the storage ('STOR' command) of the // specified file. Socket s = openDataConnection("STOR " + filename); // Writer has 4 megabyte buffer. return new BufferedWriter(new OutputStreamWriter(s.getOutputStream()), 4096); } /** * PUT a file to the FTP server in Binary mode * * @param filename String with the filename to put on the server. * @return BufferedOutputStream outputstream for completing the file on the server. * @exception IOException whenever the file could not be sent. */ public BufferedOutputStream putBinary(String filename) throws IOException { // Indicate that we are sending (as opposed to getting). m_bGettingFile = false; // Get a connection for the storage ('STOR' command) of the // specified file. Socket s = openDataConnection("STOR " + filename); return new BufferedOutputStream(s.getOutputStream()); } /** * APPEND (with create) to a file to the FTP server in Ascii mode. * * @param filename String with the name of the file to append to. * @return BufferedWriter with the stream for appending to. * @exception IOException whenever the writer fails. */ public BufferedWriter appendAscii(String filename) throws IOException { // Indicate that we are sending (as opposed to getting). m_bGettingFile = false; // Get a connection for appending ('APPE' command) the // specified file. Socket s = openDataConnection("APPE " + filename); // Writer has 4 megabyte buffer. return new BufferedWriter(new OutputStreamWriter(s.getOutputStream()), 4096); } /** * APPEND (with create) to a file to the FTP server in Binary mode. * * @param filename String with the name of the file to append to. * @return BufferedOutputStream with the stream for appending to. * @exception IOException whenever the writer fails. */ public BufferedOutputStream appendBinary(String filename) throws IOException { // Indicate that we are sending (as opposed to getting). m_bGettingFile = false; // Get a connection for appending ('APPE' command) the // specified file. Socket s = openDataConnection("APPE " + filename); return new BufferedOutputStream(s.getOutputStream()); } /** * NLIST files on a remote FTP server * * @return BufferedReader to read the listing from. * @exception IOException whenever the listing failed. */ public BufferedReader nlist() throws IOException { Socket s = openDataConnection("NLST"); return new BufferedReader(new InputStreamReader(s.getInputStream())); } /** * LIST files on a remote FTP server. * * @return BufferedReader to read the listing from. * @exception IOException whenever the listing failed. */ public BufferedReader list() throws IOException { Socket s = openDataConnection("LIST"); return new BufferedReader(new InputStreamReader(s.getInputStream())); } /** * Folder-list files on a remote FTP server. * * @return BufferedReader to read the listing from. * @exception IOException whenever the listing failed. */ public BufferedReader ls() throws IOException { Socket s = openDataConnection("LS"); return new BufferedReader(new InputStreamReader(s.getInputStream())); } /** * Folder-list files on a remote FTP server. * * @return BufferedReader to read the listing from. * @exception IOException whenever the listing failed. */ public BufferedReader dir() throws IOException { Socket s = openDataConnection("DIR"); return new BufferedReader(new InputStreamReader(s.getInputStream())); } /** * CD to a specific directory on a remote FTP server. * * @param remoteDirectory String with the directory to CD to. * @exception IOException whenever the CD failed. */ public void cd(String remoteDirectory) throws IOException { issueCommandCheck("CWD " + remoteDirectory); } /** * Change working directory to a specific directory on a remote FTP server. * * @param remoteDirectory String with the directory to CWD to. * @exception IOException whenever the CWD failed. */ public void cwd(String remoteDirectory) throws IOException { issueCommandCheck("CWD " + remoteDirectory); } /** * Rename a file on the remote server. * * @param oldFile String with the original filename for the file to rename. * @param newFile String with the filename to rename the file to. * @exception IOException when the renaming failed. */ public void rename(String oldFile, String newFile) throws IOException { issueCommandCheck("RNFR " + oldFile); issueCommandCheck("RNTO " + newFile); } /** * Site Command * * @param params String with the parameters for the SITE command. * @exception IOException when the SITE command failed. */ public void site(String params) throws IOException { issueCommandCheck("SITE " + params); } /** * Set transfer type to 'I' (binary transfer). * * @exception IOException when the binary mode could not be set up. */ public void binary() throws IOException { issueCommandCheck("TYPE I"); binaryMode = true; } /** * Set transfer type to 'A' (ascii transfer). * * @exception IOException when the ASCII mode could not be set up. */ public void ascii() throws IOException { issueCommandCheck("TYPE A"); binaryMode = false; } /** * Send Abort command. * * @exception IOException when the cancellation could not be * executed. */ public void abort() throws IOException { issueCommandCheck("ABOR"); } /** * Go up one directory on remote system. * * @exception IOException when the CDUP failed. */ public void cdup() throws IOException { issueCommandCheck("CDUP"); } /** * Create a directory on the remote system * * @param aDir String with the name for the directory to be created. * @exception IOException when the directory could not be created. */ public void mkdir(String aDir) throws IOException { issueCommandCheck("MKD " + aDir); } /** * Delete the specified directory from the ftp server file system. * * @param aDir String with the directory to delete. * @exception IOException when the deletion did not succeed. */ public void rmdir(String aDir) throws IOException { issueCommandCheck("RMD " + aDir); } /** * Delete the specified file from the ftp server file system. * * @param aFile String with the filename for the file to delete. * @exception IOException when the deletion did not succeed. */ public void delete(String aFile) throws IOException { issueCommandCheck("DELE " + aFile); } /** * Get the name of the present working directory on the ftp server file system. * * @exception IOException whenever the server did not report on the pwd. */ public void pwd() throws IOException { issueCommandCheck("PWD"); } /** * Retrieve the system type from the remote server. * * @exception IOException whenever the system type could not be determined. */ public void syst() throws IOException { issueCommandCheck("SYST"); } /** * Constructor for an FTP client connected to host <i>host</i>. * Note that this constructor automatically makes the connection. * * @param host String with the hostname (or IP) to connect to. * @exception IOException whenever a connection could not be made. */ public FTP(String host) throws IOException { openServer(host, FTP_PORT); } /** * Constructor for an FTP client connected to host <i>host</i> * and port <i>port</i>. * Note that this constructor automatically makes the connection. * * @param host String with the hostname (or IP) to connect to. * @param port int with the portnumber for the host FTP server. * @exception IOException whenever a connection could not be made. */ public FTP(String host, int port) throws IOException { openServer(host, port); } /** * This method sets the file transfer mode. * * @param nMode int with the mode (either FILE_GET for retrieval, * or any other int for sending a file). */ public void SetFileMode(int nMode) { if(nMode == FILE_GET) { m_bGettingFile = true; } else { m_bGettingFile = false; } } }