/* * Copyright 2012 jMethods, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.myjavaworld.ftp; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.Proxy; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.swing.event.EventListenerList; import com.myjavaworld.util.Filter; /** * The default implementation of <code>FTPClient</code>. Works well with most of * the UNIX type FTP servers. * * @author Sai Pullabhotla, psai [at] jMethods [dot] com * @version 2.0 */ public class DefaultFTPClient implements FTPClient, FTPConstants { /** * Timeout for this <code>FTPClient</code>. */ protected int timeout = 0; /** * Buffer size for transferring data over the network. */ protected int bufferSize = 0; /** * Data representation type. For e.g. ASCII, Binary etc. */ protected int type = 0; /** * Data transfer mode. For e.g. Stream, Block etc. */ protected int mode = 0; /** * Structure. For e.g. File, Record, Page etc. */ protected int structure = 0; /** * Whether or not to open data connections in passive mode. */ protected boolean passive = false; /** * A flag that tells we are still connected to the remote host. */ protected boolean connected = false; /** * A flag that tells if we are logged in to the remote host. */ protected boolean loggedIn = false; /** * Keep track of the current working directory on the remote host. */ protected RemoteFile workingDirectory = null; /** * Stores that last reply received from the remote host. */ protected String reply = null; /** * A Control Connection object for sending commands and receiving replies. */ protected ControlConnection controlConnection = null; /** * A DataConnection object used for transferring data to/from the remote * system. */ protected DataConnection dataConnection = null; /** * A ListParser object used to parse the directory listing produced by the * remote host. */ protected ListParser listParser = null; /** * SSL usage of this ftp client. */ protected int sslUsage = 0; /** * SSL context */ protected SSLContext sslContext = null; /** * A flag to determine if the data channel will be encrypted or not. */ protected boolean dataChannelUnencrypted = false; /** * The SSL protocol to use for negotiating Explicit SSL connections. */ protected String explicitSSLProtocol = null; /** * List of registered listeners. */ protected EventListenerList listenerList = null; /** * Flag that determines if passive connections IP address should be * substituted with the original server's IP address. */ protected boolean passiveIPSubstitutionEnabled = false; /** * The proxy server to use when connecting to the FTP server */ protected Proxy proxy = null; /** * Constructs an <code>DefaultFTPClient</code> object that is not connected * to any host. */ public DefaultFTPClient() { this.timeout = DEFAULT_TIMEOUT; this.bufferSize = DEFAULT_BUFFER_SIZE; this.type = DEFAULT_TYPE; this.mode = DEFAULT_MODE; this.structure = DEFAULT_STRUCTURE; this.reply = ""; listenerList = new EventListenerList(); sslUsage = USE_NO_SSL; dataChannelUnencrypted = false; explicitSSLProtocol = "SSL"; passiveIPSubstitutionEnabled = false; } public void setTimeout(int timeout) { this.timeout = timeout; } public int getTimeout() { return timeout; } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } public int getBufferSize() { return bufferSize; } public void setListParser(ListParser listParser) { this.listParser = listParser; } public ListParser getListParser() { return listParser; } public void setSSLUsage(int sslUsage) { this.sslUsage = sslUsage; } public int getSSLUsage() { return sslUsage; } public void setSSLContext(SSLContext sslContext) { this.sslContext = sslContext; } public SSLContext getSSLContext() { return sslContext; } public void setDataChannelUnencrypted(boolean dataChannelUnencrypted) { this.dataChannelUnencrypted = dataChannelUnencrypted; } public boolean isDataChannelUnencrypted() { return dataChannelUnencrypted; } public boolean isSecured() { if (!loggedIn) { return false; } return controlConnection.isSecured(); } public void setExplicitSSLProtocol(String protocol) { if (protocol == null) { throw new NullPointerException(); } if (protocol.trim().length() == 0) { throw new IllegalArgumentException("protocol cannot be empty"); } this.explicitSSLProtocol = protocol; } public String getExplicitSSLProtocol() { return explicitSSLProtocol; } public synchronized void connect(String host) throws FTPException, ConnectionException { connect(host, DEFAULT_PORT); } public synchronized void connect(String host, int port) throws FTPException, ConnectionException { if (sslUsage == USE_IMPLICIT_SSL) { controlConnection = new ImplicitSSLControlConnection(this); } else if (sslUsage == USE_EXPLICIT_SSL || sslUsage == USE_SSL_IF_AVAILABLE) { controlConnection = new ExplicitSSLControlConnection(this); } else { controlConnection = new ControlConnection(this); } controlConnection.connect(host, port); // reply = controlConnection.getReply(); // if (reply.charAt(0) == '5' || reply.charAt(0) == '4') // throw new FTPException(reply); connected = true; } public synchronized void connect(FTPHost ftpHost) throws FTPException, ConnectionException { connect(ftpHost.getHostName(), ftpHost.getPort()); login(ftpHost.getUserName(), ftpHost.getPassword(), ftpHost.getAccount()); } public boolean isConnected() { return connected; } public synchronized void login(String user, String password) throws FTPException, ConnectionException { login(user, password, ""); } public synchronized void login(String user, String password, String account) throws FTPException, ConnectionException { executeCommand("USER " + user); if (reply.charAt(0) == '3') { executeCommand("PASS " + password); } if (reply.charAt(0) == '3') { if (account.trim().length() > 0) { executeCommand("ACCT " + account); } else { throw new FTPException( "Account information required to login. "); } } loggedIn = true; String connectionMessage = "Connected to " + getRemoteHost() + "/" + getRemoteIPAddress() + "\n"; // fireConnectionOpened( // new FTPConnectionEvent( // this, // "Connected to " // + getRemoteHost() // + "/" // + getRemoteIPAddress())); if (controlConnection.isSecured()) { SSLSession session = controlConnection.getSSLSession(); connectionMessage += "This is a secured FTP session \n" + "Protocol: " + session.getProtocol() + "\n" + "Cipher Suite: " + session.getCipherSuite() + "\n" + "Data Channel Encryption: " + (isDataChannelUnencrypted() ? "OFF" : "ON") + "\n"; } fireConnectionOpened(new FTPConnectionEvent(this, connectionMessage)); setType(TYPE_ASCII); } public boolean isLoggedIn() { return loggedIn; } public synchronized RemoteFile setWorkingDirectory(RemoteFile dir) throws FTPException, ConnectionException { executeCommand("CWD " + dir.getNormalizedPath()); // Though RFC 959 says that response to CWD command must include // The new working directory name, some FTP servers like personal // web server do not include the path. So execute PWD command to // update the current working directory. executeCommand("PWD"); workingDirectory = listParser .createRemoteFile(FTPUtil.parsePath(reply)); return workingDirectory; } public synchronized RemoteFile setToParentDirectory() throws FTPException, ConnectionException { executeCommand("CDUP"); // Though RFC 959 says that response to CDUP command must include // The new working directory name, some FTP servers like personal // web server do not include the path. So execute PWD command to // update the current working directory. executeCommand("PWD"); workingDirectory = listParser .createRemoteFile(FTPUtil.parsePath(reply)); return workingDirectory; } public synchronized RemoteFile getWorkingDirectory() throws FTPException, ConnectionException { // This happens for the first time after login. if (workingDirectory == null) { executeCommand("PWD"); workingDirectory = listParser.createRemoteFile(FTPUtil .parsePath(reply)); } // Otherwise, we always have the updated working directory. return workingDirectory; } public synchronized void setType(int type) throws FTPException, ConnectionException { // Send TYPE command only if the current type is not same as // the requested type. if (this.type != type) { executeCommand("TYPE " + FTPUtil.getType(type)); this.type = type; } } public int getType() { return type; } public synchronized void setStructure(int structure) throws FTPException, ConnectionException { if (this.structure != structure) { executeCommand("STRU " + FTPUtil.getStructure(structure)); this.structure = structure; } } public int getStructure() { return structure; } public synchronized void setMode(int mode) throws FTPException, ConnectionException { if (this.mode != mode) { executeCommand("MODE " + FTPUtil.getMode(mode)); this.mode = mode; } } public int getMode() { return mode; } public void setPassive(boolean passive) { this.passive = passive; } public boolean isPassive() { return passive; } public synchronized void createDirectory(RemoteFile dir) throws FTPException, ConnectionException { executeCommand("MKD " + dir.getPath()); } public synchronized void deleteDirectory(RemoteFile dir) throws FTPException, ConnectionException { executeCommand("RMD " + dir.getPath()); } public synchronized void deleteFile(RemoteFile file) throws FTPException, ConnectionException { executeCommand("DELE " + file.getPath()); } public synchronized void delete(RemoteFile path) throws FTPException, ConnectionException { if (path.isFile()) { deleteFile(path); } else { deleteDirectory(path); } } public synchronized void rename(RemoteFile from, RemoteFile to) throws FTPException, ConnectionException { executeCommand("RNFR " + from.getPath()); executeCommand("RNTO " + to.getPath()); } public synchronized void noop() throws FTPException, ConnectionException { executeCommand("NOOP"); } public void abort() throws FTPException, ConnectionException { if (dataConnection != null) { dataConnection.abort(); } } public synchronized void reinitialize() throws FTPException, ConnectionException { executeCommand("REIN"); loggedIn = false; } public void disconnect() throws FTPException, ConnectionException { try { if (dataConnection != null) { dataConnection.abort(); } if (controlConnection != null) { executeCommand("QUIT"); controlConnection.close(); } } catch (Exception exp) { // Ignore this. } finally { connected = false; loggedIn = false; controlConnection = null; dataConnection = null; fireConnectionClosed(new FTPConnectionEvent(this, "Connection Closed. ")); } } public void close() { try { if (dataConnection != null) { dataConnection.abort(); } if (controlConnection != null) { controlConnection.close(); } } catch (Exception exp) { // Ignore this. } finally { connected = false; loggedIn = false; controlConnection = null; dataConnection = null; fireConnectionClosed(new FTPConnectionEvent(this, "Connection Closed. ")); } } public synchronized void allocate(long bytes) throws FTPException, ConnectionException { executeCommand("ALLO " + bytes); } public synchronized void restart(long bytes) throws FTPException, ConnectionException { executeCommand("REST " + bytes); } public synchronized String getSystemInfo() throws FTPException, ConnectionException { return executeCommand("SYST"); } public synchronized String getHelp() throws FTPException, ConnectionException { return executeCommand("HELP"); } public synchronized void setSiteParameter(String param) throws FTPException, ConnectionException { executeCommand("SITE " + param); } public synchronized void mountStructure(String path) throws FTPException, ConnectionException { executeCommand("SMNT " + path); } public synchronized String executeCommand(String command) throws FTPException, ConnectionException { try { reply = controlConnection.executeCommand(command); } catch (ConnectionException exp) { close(); throw exp; } if (reply.charAt(0) == '5' || reply.charAt(0) == '4') { throw new FTPException(reply); } return reply; } public InetAddress getRemoteAddress() { return controlConnection.getRemoteAddress(); } public String getRemoteHost() { return controlConnection.getRemoteHost(); } public String getRemoteIPAddress() { return controlConnection.getRemoteIPAddress(); } public int getRemotePort() { return controlConnection.getRemotePort(); } public InetAddress getLocalAddress() { return controlConnection.getLocalAddress(); } public String getLocalHost() { return controlConnection.getLocalHost(); } public String getLocalIPAddress() { return controlConnection.getLocalIPAddress(); } public int getLocalPort() { return controlConnection.getLocalPort(); } public synchronized RemoteFile[] list() throws FTPException, ParseException, ConnectionException { setType(TYPE_ASCII); if (sslUsage == USE_NO_SSL || dataChannelUnencrypted) { dataConnection = new DataConnection(this); } else { dataConnection = new SSLDataConnection(this); } if (passive) { executeCommand("PASV"); String ip = FTPUtil.parseAddress(reply); int port = FTPUtil.parsePort(reply); dataConnection.connect(ip, port); executeCommand("LIST"); } else { int port = dataConnection.bind(); String portCommand = FTPUtil.getPortCommand(getLocalIPAddress(), port); executeCommand(portCommand); executeCommand("LIST"); dataConnection.accept(); } try { RemoteFile[] list = dataConnection.list(workingDirectory); reply = controlConnection.getReply(); dataConnection = null; if (reply.charAt(0) == '5' || reply.charAt(0) == 4) { throw new FTPException(reply); } return list; } catch (ParseException exp) { if (controlConnection != null) { reply = controlConnection.getReply(); } dataConnection = null; throw exp; } } public synchronized RemoteFile[] list(Filter filter) throws FTPException, ParseException, ConnectionException { if (filter == null) { return list(); } RemoteFile[] f = list(); if (f == null) { return null; } List<RemoteFile> list = new ArrayList<RemoteFile>(f.length); for (int i = 0; i < f.length; i++) { if (filter.accept(f[i])) { list.add(f[i]); } } RemoteFile[] children = new RemoteFile[list.size()]; children = list.toArray(children); return children; } public synchronized RemoteFile[] list(RemoteFile dir) throws FTPException, ParseException, ConnectionException { return null; } public synchronized RemoteFile[] list(RemoteFile dir, Filter filter) throws FTPException, ParseException, ConnectionException { return null; } public synchronized void createFile(RemoteFile file) throws FTPException, ConnectionException { if (sslUsage == USE_NO_SSL || dataChannelUnencrypted) { dataConnection = new DataConnection(this); } else { dataConnection = new SSLDataConnection(this); } // dataConnection = new DataConnection(this); try { if (passive) { executeCommand("PASV"); String ip = FTPUtil.parseAddress(reply); int port = FTPUtil.parsePort(reply); dataConnection.connect(ip, port); executeCommand("STOR " + file.getPath()); } else { int port = dataConnection.bind(); String portCommand = FTPUtil.getPortCommand( getLocalIPAddress(), port); executeCommand(portCommand); executeCommand("STOR " + file.getPath()); dataConnection.accept(); } dataConnection.close(); reply = controlConnection.getReply(); } finally { if (dataConnection != null) { dataConnection.close(); } dataConnection = null; if (reply.charAt(0) == '5' || reply.charAt(0) == 4) { throw new FTPException(reply); } } } public synchronized void download(RemoteFile source, File destination, int type, boolean append) throws FTPException, ConnectionException { setType(type); if (sslUsage == USE_NO_SSL || dataChannelUnencrypted) { dataConnection = new DataConnection(this); } else { dataConnection = new SSLDataConnection(this); } // dataConnection = new DataConnection(this); // FTPException ftpException = null; boolean ftpException = true; String ioException = null; try { if (passive) { executeCommand("PASV"); String ip = FTPUtil.parseAddress(reply); int port = FTPUtil.parsePort(reply); dataConnection.connect(ip, port); // executeCommand("RETR " + source.getPath()); executeCommand("RETR " + source.getNormalizedPath()); } else { int port = dataConnection.bind(); String portCommand = FTPUtil.getPortCommand( getLocalIPAddress(), port); executeCommand(portCommand); // executeCommand("RETR " + source.getPath()); executeCommand("RETR " + source.getNormalizedPath()); dataConnection.accept(); } ftpException = false; try { dataConnection.download(destination, append); } catch (IOException exp) { // if (dataConnection != null) { // dataConnection.close(); // } // throw new FTPException("599 " + exp.getMessage()); ioException = exp.getMessage(); } } finally { if (dataConnection != null) { dataConnection.close(); } dataConnection = null; if (!ftpException) { if (controlConnection != null) { reply = controlConnection.getReply(); if (ioException != null) { throw new FTPException("599 " + ioException); } if (reply.charAt(0) == '5' || reply.charAt(0) == '4') { throw new FTPException(reply); } } } } } public synchronized void upload(File source, RemoteFile destination, int type, boolean append, long skip) throws FTPException, ConnectionException { setType(type); if (sslUsage == USE_NO_SSL || dataChannelUnencrypted) { dataConnection = new DataConnection(this); } else { dataConnection = new SSLDataConnection(this); } // dataConnection = new DataConnection(this); boolean ftpException = true; String ioException = null; try { if (passive) { reply = executeCommand("PASV"); String ip = FTPUtil.parseAddress(reply); int port = FTPUtil.parsePort(reply); dataConnection.connect(ip, port); String command = append ? "APPE " : "STOR "; executeCommand(command + destination.getPath()); } else { int port = dataConnection.bind(); String portCommand = FTPUtil.getPortCommand( getLocalIPAddress(), port); executeCommand(portCommand); String command = append ? "APPE " : "STOR "; executeCommand(command + destination.getPath()); dataConnection.accept(); } ftpException = false; try { dataConnection.upload(source, skip); } catch (IOException exp) { // if (dataConnection != null) { // dataConnection.close(); // } // throw new FTPException("599 " + exp.getMessage()); ioException = exp.getMessage(); } } finally { if (dataConnection != null) { dataConnection.close(); } dataConnection = null; if (!ftpException) { if (controlConnection != null) { reply = controlConnection.getReply(); if (ioException != null) { throw new FTPException("599 " + ioException); } if (reply.charAt(0) == '5' || reply.charAt(1) == '4') { throw new FTPException(reply); } } } } } public void addControlConnectionListener(ControlConnectionListener l) { listenerList.add(ControlConnectionListener.class, l); } public void removeControlConnectionListener(ControlConnectionListener l) { listenerList.remove(ControlConnectionListener.class, l); } public void addDataConnectionListener(DataConnectionListener l) { listenerList.add(DataConnectionListener.class, l); } public void removeDataConnectionListener(DataConnectionListener l) { listenerList.remove(DataConnectionListener.class, l); } public EventListenerList getListenerList() { return listenerList; } public void addFTPConnectionListener(FTPConnectionListener l) { listenerList.add(FTPConnectionListener.class, l); } public void removeFTPConnectionListener(FTPConnectionListener l) { listenerList.remove(FTPConnectionListener.class, l); } public void setPassiveIPSubstitutionEnabled(boolean enable) { this.passiveIPSubstitutionEnabled = enable; } public boolean isPassiveIPSubstitutionEnabled() { return passiveIPSubstitutionEnabled; } public void setProxy(Proxy proxy) { this.proxy = proxy; } public Proxy getProxy() { return proxy; } /** * Fires the ConnectionOpened event to all registered listeners. * * @param evt * connection opened event. */ protected void fireConnectionOpened(FTPConnectionEvent evt) { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == FTPConnectionListener.class) { ((FTPConnectionListener) listeners[i + 1]) .connectionOpened(evt); } } } /** * Fires the connection closed event to all registered listeners. * * @param evt * connection closed event. */ protected void fireConnectionClosed(FTPConnectionEvent evt) { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == FTPConnectionListener.class) { ((FTPConnectionListener) listeners[i + 1]) .connectionClosed(evt); } } } }