/* * ftp4j - A pure Java FTP client library * * Copyright (C) 2008-2010 Carlo Pelliccia (www.sauronsoftware.it) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version * 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License 2.1 for more details. * * You should have received a copy of the GNU Lesser General Public * License version 2.1 along with this program. * If not, see . */ package it.sauronsoftware.ftp4j; import it.sauronsoftware.ftp4j.connectors.DirectConnector; import it.sauronsoftware.ftp4j.extrecognizers.DefaultTextualExtensionRecognizer; import it.sauronsoftware.ftp4j.extrecognizers.ParametricTextualExtensionRecognizer; import it.sauronsoftware.ftp4j.listparsers.DOSListParser; import it.sauronsoftware.ftp4j.listparsers.EPLFListParser; import it.sauronsoftware.ftp4j.listparsers.MLSDListParser; import it.sauronsoftware.ftp4j.listparsers.NetWareListParser; import it.sauronsoftware.ftp4j.listparsers.UnixListParser; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.net.InetAddress; import java.net.Socket; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import javax.net.ssl.SSLSocketFactory; /** * This class implements a FTP client. * * You can use an instance of this class to connect to a remote FTP site and do * FTP operations like directory listing, file upload and download, resume a * broken upload/download and so on. * * The common flow is: create the object, connect to a remote FTP site with the * connect() method, authenticate with login(), do anything you need with the * contents of the remote site, quit the site with disconnect(). * * A FTPClient object can handle a connection per time. Once you have used and * disconnected a FTPClient object you can use it again to connect another FTP * server. * * @author Carlo Pelliccia * @version 1.7.1 */ public class FTPClient { private static final Logger logger= org.das2.util.LoggerManager.getLogger("das2.filesystem"); /** * The constant for the FTP security level. * * @since 1.4 */ public static final int SECURITY_FTP = 0; /** * The constant for the FTPS (FTP over implicit TLS/SSL) security level. * * @since 1.4 */ public static final int SECURITY_FTPS = 1; /** * The constant for the FTPES (FTP over explicit TLS/SSL) security level. * * @since 1.4 */ public static final int SECURITY_FTPES = 2; /** * The constant for the AUTO file transfer type. It lets the client pick * between textual and binary types, depending on the extension of the file * exchanged through a textual extension recognizer. */ public static final int TYPE_AUTO = 0; /** * The constant for the TEXTUAL file transfer type. It means that the data * sent or received is treated as textual information. This implies charset * conversion during the transfer. */ public static final int TYPE_TEXTUAL = 1; /** * The constant for the BINARY file transfer type. It means that the data * sent or received is treated as a binary stream. The data is taken "as * is", without any charset conversion. */ public static final int TYPE_BINARY = 2; /** * The constant for the MLSD policy that causes the client to use the MLSD * command instead of LIST, but only if the MLSD command is explicitly * supported by the server (the support is tested with the FEAT command). * * @since 1.5 */ public static final int MLSD_IF_SUPPORTED = 0; /** * The constant for the MLSD policy that causes the client to use always the * MLSD command instead of LIST, also if the MLSD command is not explicitly * supported by the server (the support is tested with the FEAT command). * * @since 1.5 */ public static final int MLSD_ALWAYS = 1; /** * The constant for the MLSD policy that causes the client to use always the * LIST command, also if the MLSD command is explicitly supported by the * server (the support is tested with the FEAT command). * * @since 1.5 */ public static final int MLSD_NEVER = 2; /** * The size of the buffer used when sending or receiving data. * * @since 1.6 */ private static final int SEND_AND_RECEIVE_BUFFER_SIZE = 64 * 1024; /** * The DateFormat object used to parse the reply to a MDTM command. */ private static final DateFormat MDTM_DATE_FORMAT = new SimpleDateFormat( "yyyyMMddHHmmss"); /** * The RegExp Pattern object used to parse the reply to a PASV command. */ private static final Pattern PASV_PATTERN = Pattern .compile("\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}"); /** * The RegExp Pattern object used to parse the reply to a PWD command. */ private static final Pattern PWD_PATTERN = Pattern.compile("\"/.*\""); /** * The connector used to connect the remote host. */ private FTPConnector connector = new DirectConnector(); /** * The SSL socket factory used to negotiate SSL connections. */ private SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory .getDefault(); /** * The FTPCommunicationListener objects registered on the client. */ private ArrayList communicationListeners = new ArrayList(); /** * The FTPListParser objects registered on the client. */ private ArrayList listParsers = new ArrayList(); /** * The textual extension recognizer used by the client. */ private FTPTextualExtensionRecognizer textualExtensionRecognizer = DefaultTextualExtensionRecognizer.getInstance(); /** * The FTPListParser used successfully during previous connection-scope list * operations. */ private FTPListParser parser = null; /** * If the client is connected, it reports the remote host name or address. */ private String host = null; /** * If the client is connected, it reports the remote port number. */ private int port = 0; /** * The security level. The value should be one of SECURITY_FTP, * SECURITY_FTPS and SECURITY_FTPES constants. Default value is * SECURITY_FTP. */ private int security = SECURITY_FTP; /** * If the client is authenticated, it reports the authentication username. */ private String username; /** * If the client is authenticated, it reports the authentication password. */ private String password; /** * The flag reporting the connection status. */ private boolean connected = false; /** * The flag reporting the authentication status. */ private boolean authenticated = false; /** * The flag for the passive FTP data transfer mode. Default value is true, * cause it's usually the preferred FTP operating mode. */ private boolean passive = true; /** * The type of the data transfer contents (auto, textual, binary). The value * should be one of {@link FTPClient#TYPE_AUTO}, * {@link FTPClient#TYPE_TEXTUAL} and {@link FTPClient#TYPE_BINARY} * constants. Default value is TYPE_AUTO. */ private int type = TYPE_AUTO; /** * The MLSD command policy. The value should be one of * {@link FTPClient#MLSD_IF_SUPPORTED}, {@link FTPClient#MLSD_ALWAYS} and * {@link FTPClient#MLSD_NEVER} constants. Default value is * MLSD_IF_SUPPORTED. */ private int mlsdPolicy = MLSD_IF_SUPPORTED; /** * If this value is greater than 0, the auto-noop feature is enabled. If * positive, the field is used as a timeout value (expressed in * milliseconds). If autoNoopDelay milliseconds has passed without any * communication between the client and the server, a NOOP command is * automaticaly sent to the server by the client. */ private long autoNoopTimeout = 0; /** * The auto noop timer thread. */ private AutoNoopTimer autoNoopTimer; /** * The system time (in millis) of the moment when the next auto noop command * should be issued. */ private long nextAutoNoopTime; /** * A flag used to mark whether the connected server supports the resume of * broken transfers. */ private boolean restSupported = false; /** * The name of the charset used to establish textual communications. If not * null the client will use always the given charset. If null the client * tries to auto-detect the server charset. If this attempt fails the client * will use the machine current charset. */ private String charset = null; /** * This flag enables and disables the use of compression (ZLIB) during data * transfers. Compression is enabled when both this flag is true and the * server supports compressed transfers. */ private boolean compressionEnabled = false; /** * A flag used to mark whether the connected server supports UTF-8 pathnames * encoding. */ private boolean utf8Supported = false; /** * A flag used to mark whether the connected server supports the MLSD * command (RFC 3659). */ private boolean mlsdSupported = false; /** * A flag used to mark whether the connected server supports the MODE Z * command. */ private boolean modezSupported = false; /** * A flag used to mark whether MODE Z is enabled. */ private boolean modezEnabled = false; /** * This flag indicates whether the data channel is encrypted. */ private boolean dataChannelEncrypted = false; /** * This flag reports if there's any ongoing abortable data transfer * operation. Its value should be accessed only under the eye of the * abortLock synchronization object. */ private boolean ongoingDataTransfer = false; /** * The InputStream used for data transfer operations. */ private InputStream dataTransferInputStream = null; /** * The OutputStream used for data transfer operations. */ private OutputStream dataTransferOutputStream = null; /** * This flag turns to true when any data transfer stream is closed due to an * abort request. */ private boolean aborted = false; /** * This flags tells if the reply to an ABOR command waits to be consumed. */ private boolean consumeAborCommandReply = false; /** * Lock object used for synchronization. */ private Object lock = new Object(); /** * Lock object used for synchronization in abort operations. */ private Object abortLock = new Object(); /** * The communication channel established with the server. */ private FTPCommunicationChannel communication = null; /** * Builds and initializes the client. */ public FTPClient() { // The built-in parsers. addListParser(new UnixListParser()); addListParser(new DOSListParser()); addListParser(new EPLFListParser()); addListParser(new NetWareListParser()); addListParser(new MLSDListParser()); } /** * This method returns the connector used to connect the remote host. * * @return The connector used to connect the remote host. */ public FTPConnector getConnector() { synchronized (lock) { return connector; } } /** * This method sets the connector used to connect the remote host. * * Default one is a * it.sauronsoftware.ftp4j.connectors.direct.DirectConnector instance. * * @param connector * The connector used to connect the remote host. * @see DirectConnector */ public void setConnector(FTPConnector connector) { synchronized (lock) { this.connector = connector; } } /** * Sets the SSL socket factory used to negotiate SSL connections. * * @param sslSocketFactory * The SSL socket factory used to negotiate SSL connections. * * @since 1.4 */ public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { synchronized (lock) { this.sslSocketFactory = sslSocketFactory; } } /** * Returns the SSL socket factory used to negotiate SSL connections. * * @return The SSL socket factory used to negotiate SSL connections. * * @since 1.4 */ public SSLSocketFactory getSSLSocketFactory() { synchronized (lock) { return sslSocketFactory; } } /** * Sets the security level for the connection. This method should be called * before starting a connection with a server. The security level must be * expressed using one of the SECURITY_FTP, SECURITY_FTPS and SECURITY_FTPES * costants. * * SECURITY_FTP, which is the default value, applies the basic FTP security * level. * * SECURITY_FTPS applies the FTPS security level, which is FTP over implicit * TLS/SSL. * * SECURITY_FTPES applies the FTPES security level, which is FTP over * explicit TLS/SSL. * * @param security * The security level. * @throws IllegalStateException * If the client is already connected to a server. * @throws IllegalArgumentException * If the supplied security level is not valid. * @since 1.4 */ public void setSecurity(int security) throws IllegalStateException, IllegalArgumentException { if (security != SECURITY_FTP && security != SECURITY_FTPS && security != SECURITY_FTPES) { throw new IllegalArgumentException("Invalid security"); } synchronized (lock) { if (connected) { throw new IllegalStateException( "The security level of the connection can't be " + "changed while the client is connected"); } this.security = security; } } /** * Returns the security level used by the client in the connection. * * @return The security level, which could be one of the SECURITY_FTP, * SECURITY_FTPS and SECURITY_FTPES costants. * * @since 1.4 */ public int getSecurity() { return security; } /** * Applies SSL encryption to an already open socket. * * @param socket * The already established socket. * @param host * The logical destination host. * @param port * The logical destination port. * @return The SSL socket. * @throws IOException * If the SSL negotiation fails. */ private Socket ssl(Socket socket, String host, int port) throws IOException { return sslSocketFactory.createSocket(socket, host, port, true); } /** * This method enables/disables the use of the passive mode. * * @param passive * If true the passive mode is enabled. */ public void setPassive(boolean passive) { synchronized (lock) { this.passive = passive; } } /** * This methods sets how to treat the contents during a file transfer. * * The type supplied should be one of TYPE_AUTO, TYPE_TEXTUAL or TYPE_BINARY * constants. Default value is TYPE_AUTO. * * {@link FTPClient#TYPE_TEXTUAL} means that the data sent or received is * treated as textual information. This implies charset conversion during * the transfer. * * {@link FTPClient#TYPE_BINARY} means that the data sent or received is * treated as a binary stream. The data is taken "as is", without any * charset conversion. * * {@link FTPClient#TYPE_AUTO} lets the client pick between textual and * binary types, depending on the extension of the file exchanged, using a * FTPTextualExtensionRecognizer instance, which could be set through the * setTextualExtensionRecognizer method. The default recognizer is an * instance of {@link DefaultTextualExtensionRecognizer}. * * @param type * The type. * @throws IllegalArgumentException * If the supplied type is not valid. * @see FTPClient#setTextualExtensionRecognizer(FTPTextualExtensionRecognizer) * @see DefaultTextualExtensionRecognizer */ public void setType(int type) throws IllegalArgumentException { if (type != TYPE_AUTO && type != TYPE_BINARY && type != TYPE_TEXTUAL) { throw new IllegalArgumentException("Invalid type"); } synchronized (lock) { this.type = type; } } /** * This method returns the value suggesting how the client encode and decode * the contents during a data transfer. * * @return The type as a numeric value. The value could be compared to the * constants {@link FTPClient#TYPE_AUTO}, * {@link FTPClient#TYPE_BINARY} and {@link FTPClient#TYPE_TEXTUAL}. */ public int getType() { synchronized (lock) { return type; } } /** * This method lets the user control how the client chooses whether to use * or not the MLSD command (RFC 3659) instead of the base LIST command. * * The type supplied should be one of MLSD_IF_SUPPORTED, MLSD_ALWAYS or * MLSD_NEVER constants. Default value is MLSD_IF_SUPPORTED. * * {@link FTPClient#MLSD_IF_SUPPORTED} means that the client should use the * MLSD command only if it is explicitly supported by the server. * * {@link FTPClient#MLSD_ALWAYS} means that the client should use always the * MLSD command, also if the MLSD command is not explicitly supported by the * server * * {@link FTPClient#MLSD_NEVER} means that the client should use always only * the LIST command, also if the MLSD command is explicitly supported by the * server. * * The support for the MLSD command is tested by the client after the * connection to the remote server, with the FEAT command. * * @param mlsdPolicy * The MLSD policy. * @throws IllegalArgumentException * If the supplied MLSD policy value is not valid. * @since 1.5 */ public void setMLSDPolicy(int mlsdPolicy) throws IllegalArgumentException { if (type != MLSD_IF_SUPPORTED && type != MLSD_ALWAYS && type != MLSD_NEVER) { throw new IllegalArgumentException("Invalid MLSD policy"); } synchronized (lock) { this.mlsdPolicy = mlsdPolicy; } } /** * This method returns the value suggesting how the client chooses whether * to use or not the MLSD command (RFC 3659) instead of the base LIST * command. * * @return The MLSD policy as a numeric value. The value could be compared * to the constants {@link FTPClient#MLSD_IF_SUPPORTED}, * {@link FTPClient#MLSD_ALWAYS} and {@link FTPClient#MLSD_NEVER}. * @since 1.5 */ public int getMLSDPolicy() { synchronized (lock) { return mlsdPolicy; } } /** * Returns the name of the charset used to establish textual communications. * If not null the client will use always the given charset. If null the * client tries to auto-detect the server charset. If this attempt fails the * client will use the machine current charset. * * @return The name of the charset used to establish textual communications. * @since 1.1 */ public String getCharset() { synchronized (lock) { return charset; } } /** * Sets the name of the charset used to establish textual communications. If * not null the client will use always the given charset. If null the client * tries to auto-detect the server charset. If this attempt fails the client * will use the machine current charset. * * @param charset * The name of the charset used to establish textual * communications. * @since 1.1 */ public void setCharset(String charset) { synchronized (lock) { this.charset = charset; if (connected) { try { communication.changeCharset(pickCharset()); } catch (IOException e) { logger.log( Level.SEVERE, e.getMessage(), e ); } } } } /** * Checks whether the connected server explicitly supports resuming of * broken data transfers. * * @return true if the server supports resuming, false otherwise. * @since 1.5.1 */ public boolean isResumeSupported() { synchronized (lock) { return restSupported; } } /** * Checks whether the connected remote FTP server supports compressed data * transfers (uploads, downloads, list operations etc.). If so, the * compression of any subsequent data transfer (upload, download, list etc.) * can be compressed, saving bandwidth. To enable compression call * {@link FTPClient#setCompressionEnabled(boolean)} . * * The returned value is not significant if the client is not connected and * authenticated. * * @return true if compression of data transfers is supported on * the server-side, false otherwise. * @see FTPClient#isCompressionEnabled() * @since 1.5 */ public boolean isCompressionSupported() { return modezSupported; } /** * Enables or disables the use of compression during any subsequent data * transfer. Compression is enabled when both the supplied value and the * {@link FTPClient#isCompressionSupported()}) returned value are * true. * * The default value is false. * * @param compressionEnabled * true to enable the use of compression during any * subsequent file transfer, false to disable the * feature. * @see FTPClient#isCompressionSupported() * @since 1.5 */ public void setCompressionEnabled(boolean compressionEnabled) { this.compressionEnabled = compressionEnabled; } /** * Checks whether the use of compression is enabled on the client-side. * * Please note that compressed transfers are actually enabled only if both * this method and {@link FTPClient#isCompressionSupported()} return * true. * * @return true if compression is enabled, false * otherwise. * @see FTPClient#isCompressionSupported() * @since 1.5 */ public boolean isCompressionEnabled() { return compressionEnabled; } /** * This method returns the textual extension recognizer used by the client. * * Default one is {@link DefaultTextualExtensionRecognizer}. * * @return The textual extension recognizer used by the client. * @see DefaultTextualExtensionRecognizer */ public FTPTextualExtensionRecognizer getTextualExtensionRecognizer() { synchronized (lock) { return textualExtensionRecognizer; } } /** * This method sets the textual extension recognizer used by the client. * * The default one is {@link DefaultTextualExtensionRecognizer}. * * You can plug your own by implementing the * {@link FTPTextualExtensionRecognizer} interface. For your convenience the * ftp4j gives you another FTPTextualExtensionRecognizer implementation, * which is {@link ParametricTextualExtensionRecognizer}. * * @param textualExtensionRecognizer * The textual extension recognizer used by the client. * @see DefaultTextualExtensionRecognizer * @see ParametricTextualExtensionRecognizer */ public void setTextualExtensionRecognizer(FTPTextualExtensionRecognizer textualExtensionRecognizer) { synchronized (lock) { this.textualExtensionRecognizer = textualExtensionRecognizer; } } /** * This method tests if this client is authenticated. * * @return true if this client is authenticated, false otherwise. */ public boolean isAuthenticated() { synchronized (lock) { return authenticated; } } /** * This method tests if this client is connected to a remote FTP server. * * @return true if this client is connected to a remote FTP server, false * otherwise. */ public boolean isConnected() { synchronized (lock) { return connected; } } /** * This method tests if this client works in passive FTP mode. * * @return true if this client is configured to work in passive FTP mode. */ public boolean isPassive() { synchronized (lock) { return passive; } } /** * If the client is connected, it reports the remote host name or address. * * @return The remote host name or address. */ public String getHost() { synchronized (lock) { return host; } } /** * If the client is connected, it reports the remote port number. * * @return The remote port number. */ public int getPort() { synchronized (lock) { return port; } } /** * If the client is authenticated, it reports the authentication password. * * @return The authentication password. */ public String getPassword() { synchronized (lock) { return password; } } /** * If the client is authenticated, it reports the authentication username. * * @return The authentication username. */ public String getUsername() { synchronized (lock) { return username; } } /** * Enable and disable the auto-noop feature. * * If the supplied value is greater than 0, the auto-noop feature is * enabled, otherwise it is disabled. If positive, the field is used as a * timeout value (expressed in milliseconds). If autoNoopDelay milliseconds * has passed without any communication between the client and the server, a * NOOP command is automaticaly sent to the server by the client. * * The default value for the auto noop delay is 0 (disabled). * * @param autoNoopTimeout * The duration of the auto-noop timeout, in milliseconds. If 0 * or less, the auto-noop feature is disabled. * * @since 1.5 */ public void setAutoNoopTimeout(long autoNoopTimeout) { synchronized (lock) { if (connected && authenticated) { stopAutoNoopTimer(); } long oldValue = this.autoNoopTimeout; long newValue = autoNoopTimeout; this.autoNoopTimeout = autoNoopTimeout; if (oldValue != 0 && newValue != 0 && nextAutoNoopTime > 0) { nextAutoNoopTime = nextAutoNoopTime - (oldValue - newValue); } if (connected && authenticated) { startAutoNoopTimer(); } } } /** * Returns the duration of the auto-noop timeout, in milliseconds. If 0 or * less, the auto-noop feature is disabled. * * @return The duration of the auto-noop timeout, in milliseconds. If 0 or * less, the auto-noop feature is disabled. * * @since 1.5 */ public long getAutoNoopTimeout() { synchronized (lock) { return autoNoopTimeout; } } /** * This method adds a FTPCommunicationListener to the object. * * @param listener * The listener. */ public void addCommunicationListener(FTPCommunicationListener listener) { synchronized (lock) { communicationListeners.add(listener); if (communication != null) { communication.addCommunicationListener(listener); } } } /** * This method removes a FTPCommunicationListener previously added to the * object. * * @param listener * The listener to be removed. */ public void removeCommunicationListener(FTPCommunicationListener listener) { synchronized (lock) { communicationListeners.remove(listener); if (communication != null) { communication.removeCommunicationListener(listener); } } } /** * This method returns a list with all the {@link FTPCommunicationListener} * used by the client. * * @return A list with all the FTPCommunicationListener used by the client. */ public FTPCommunicationListener[] getCommunicationListeners() { synchronized (lock) { int size = communicationListeners.size(); FTPCommunicationListener[] ret = new FTPCommunicationListener[size]; for (int i = 0; i < size; i++) { ret[i] = (FTPCommunicationListener) communicationListeners.get(i); } return ret; } } /** * This method adds a {@link FTPListParser} to the object. * * @param listParser * The list parser. */ public void addListParser(FTPListParser listParser) { synchronized (lock) { listParsers.add(listParser); } } /** * This method removes a {@link FTPListParser} previously added to the * object. * * @param listParser * The list parser to be removed. */ public void removeListParser(FTPListParser listParser) { synchronized (lock) { listParsers.remove(listParser); } } /** * This method returns a list with all the {@link FTPListParser} used by the * client. * * @return A list with all the FTPListParsers used by the client. */ public FTPListParser[] getListParsers() { synchronized (lock) { int size = listParsers.size(); FTPListParser[] ret = new FTPListParser[size]; for (int i = 0; i < size; i++) { ret[i] = (FTPListParser) listParsers.get(i); } return ret; } } /** * This method connects the client to the remote FTP host, using the default * port value 21 (990 if security level is set to FTPS, see * {@link FTPClient#setSecurity(int)}). * * @param host * The hostname of the remote server. * @return The server welcome message, one line per array element. * @throws IllegalStateException * If the client is already connected to a remote host. * @throws IOException * If an I/O occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the server refuses the connection. */ public String[] connect(String host) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { int def; if (security == SECURITY_FTPS) { def = 990; } else { def = 21; } return connect(host, def); } /** * This method connects the client to the remote FTP host. * * @param host * The host name or address of the remote server. * @param port * The port listened by the remote server. * @return The server welcome message, one line per array element. * @throws IllegalStateException * If the client is already connected to a remote host. * @throws IOException * If an I/O occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the server refuses the connection. */ public String[] connect(String host, int port) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client already connected to any host? if (connected) { throw new IllegalStateException("Client already connected to " + host + " on port " + port); } // Ok, it's connection time. Let's try! Socket connection = null; try { // Open the connection. connection = connector.connectForCommunicationChannel(host, port); if (security == SECURITY_FTPS) { connection = ssl(connection, host, port); } // Open the communication channel. communication = new FTPCommunicationChannel(connection, pickCharset()); for (Iterator i = communicationListeners.iterator(); i.hasNext();) { communication.addCommunicationListener((FTPCommunicationListener) i.next()); } // Welcome message. FTPReply wm = communication.readFTPReply(); // Does this reply mean "ok"? if (!wm.isSuccessCode()) { // Mmmmm... it seems no! throw new FTPException(wm); } // Flag this object as connected to the remote host. this.connected = true; this.authenticated = false; this.parser = null; this.host = host; this.port = port; this.username = null; this.password = null; this.utf8Supported = false; this.restSupported = false; this.mlsdSupported = false; this.modezSupported = false; this.dataChannelEncrypted = false; // Returns the welcome message. return wm.getMessages(); } catch (IOException e) { // D'oh! throw e; } finally { // If connection has failed... if (!connected) { if (connection != null) { // Close the connection, 'cause it should be open. try { connection.close(); } catch (Throwable t) { ; } } } } } } /** * Aborts the current connection attempt. It can be called by a secondary * thread while the client is blocked in a connect() call. The * connect() method will exit with an {@link IOException}. * * @since 1.7 */ public void abortCurrentConnectionAttempt() { connector.abortConnectForCommunicationChannel(); } /** * This method disconnects from the remote server, optionally performing the * QUIT procedure. * * @param sendQuitCommand * If true the QUIT procedure with the server will be performed, * otherwise the connection is abruptly closed by the client * without sending any advice to the server. * @throws IllegalStateException * If the client is not connected to a remote host. * @throws IOException * If an I/O occurs (can be thrown only if sendQuitCommand is * true). * @throws FTPIllegalReplyException * If the server replies in an illegal way (can be thrown only * if sendQuitCommand is true). * @throws FTPException * If the server refuses the QUIT command (can be thrown only if * sendQuitCommand is true). */ public void disconnect(boolean sendQuitCommand) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Stops the auto noop timer (if started). if (authenticated) { stopAutoNoopTimer(); } // Send QUIT? if (sendQuitCommand) { // Call the QUIT command. communication.sendFTPCommand("QUIT"); FTPReply r = communication.readFTPReply(); if (!r.isSuccessCode()) { throw new FTPException(r); } } // Close the communication. communication.close(); communication = null; // Reset the connection flag. connected = false; } } /** * This method causes the communication channel to be abruptly closed. Use * it carefully, since this one is not thread-safe. It is given as an * "emergency brake" to close the control connection when it is blocked. A * thread-safe solution for the same purpose is a call to disconnect(false). * * @see FTPClient#disconnect(boolean) */ public void abruptlyCloseCommunication() { // Close the communication. if (communication != null) { communication.close(); communication = null; } // Reset the connection flag. connected = false; // Stops the auto noop timer. stopAutoNoopTimer(); } /** * This method authenticates the user against the server. * * @param username * The username. * @param password * The password (if none set it to null). * @throws IllegalStateException * If the client is not connected. Call the connect() method * before! * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If login fails. */ public void login(String username, String password) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { login(username, password, null); } /** * This method authenticates the user against the server. * * @param username * The username. * @param password * The password (if none set it to null). * @param account * The account (if none set it to null). Be careful: some servers * don't implement this feature. * @throws IllegalStateException * If the client is not connected. Call the connect() method * before! * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If login fails. */ public void login(String username, String password, String account) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // AUTH TLS command if security is FTPES if (security == SECURITY_FTPES) { communication.sendFTPCommand("AUTH TLS"); FTPReply r = communication.readFTPReply(); if (r.isSuccessCode()) { communication.ssl(sslSocketFactory); } else { communication.sendFTPCommand("AUTH SSL"); r = communication.readFTPReply(); if (r.isSuccessCode()) { communication.ssl(sslSocketFactory); } else { throw new FTPException(r.getCode(), "SECURITY_FTPES cannot be applied: " + "the server refused both AUTH TLS and AUTH SSL commands"); } } } // Reset the authentication flag. authenticated = false; // Usefull flags. boolean passwordRequired; boolean accountRequired; // Send the user and read the reply. communication.sendFTPCommand("USER " + username); FTPReply r = communication.readFTPReply(); switch (r.getCode()) { case 230: // Password and account aren't required. passwordRequired = false; accountRequired = false; break; case 331: // Password is required. passwordRequired = true; // Account... maybe! More information later... accountRequired = false; break; case 332: // Password is not required, but account is required. passwordRequired = false; accountRequired = true; default: // User validation failed. throw new FTPException(r); } // Password. if (passwordRequired) { if (password == null) { throw new FTPException(331); } // Send the password. communication.sendFTPCommand("PASS " + password); r = communication.readFTPReply(); switch (r.getCode()) { case 230: // Account is not required. accountRequired = false; break; case 332: // Account is required. accountRequired = true; break; default: // Authentication failed. throw new FTPException(r); } } // Account. if (accountRequired) { if (account == null) { throw new FTPException(332); } // Send the account. communication.sendFTPCommand("ACCT " + account); r = communication.readFTPReply(); switch (r.getCode()) { case 230: // Well done! break; default: // Something goes wrong. throw new FTPException(r); } } // Well, if this point is reached the client could consider itself // as authenticated. this.authenticated = true; this.username = username; this.password = password; } // Post-login operations. postLoginOperations(); // Starts the auto noop timer. startAutoNoopTimer(); } /** * Performs some post-login operations, such trying to detect server support * for utf8. * * @throws IllegalStateException * If the client is not connected. Call the connect() method * before! * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If login fails. */ private void postLoginOperations() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { utf8Supported = false; restSupported = false; mlsdSupported = false; modezSupported = false; dataChannelEncrypted = false; communication.sendFTPCommand("FEAT"); FTPReply r = communication.readFTPReply(); if (r.getCode() == 211) { String[] lines = r.getMessages(); for (int i = 1; i < lines.length - 1; i++) { String feat = lines[i].trim().toUpperCase(); // REST STREAM supported? if ("REST STREAM".equalsIgnoreCase(feat)) { restSupported = true; continue; } // UTF8 supported? if ("UTF8".equalsIgnoreCase(feat)) { utf8Supported = true; communication.changeCharset("UTF-8"); continue; } // MLSD supported? if ("MLSD".equalsIgnoreCase(feat)) { mlsdSupported = true; continue; } // MODE Z supported? if ("MODE Z".equalsIgnoreCase(feat) || feat.startsWith("MODE Z ")) { modezSupported = true; continue; } } } // Turn UTF 8 on (if supported). if (utf8Supported) { communication.sendFTPCommand("OPTS UTF8 ON"); communication.readFTPReply(); } // Data channel security. if (security == SECURITY_FTPS || security == SECURITY_FTPES) { communication.sendFTPCommand("PBSZ 0"); communication.readFTPReply(); communication.sendFTPCommand("PROT P"); FTPReply reply = communication.readFTPReply(); if (reply.isSuccessCode()) { dataChannelEncrypted = true; } } } } /** * This method performs a logout operation for the current user, leaving the * connection open, thus it can be used to start a new user session. Be * careful with this: some FTP servers don't implement this feature, even * though it is a standard FTP one. * * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public void logout() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Send the REIN command. communication.sendFTPCommand("REIN"); FTPReply r = communication.readFTPReply(); if (!r.isSuccessCode()) { throw new FTPException(r); } else { // Stops the auto noop timer. stopAutoNoopTimer(); // Ok. Not authenticated, now. authenticated = false; username = null; password = null; } } } /** * This method performs a "noop" operation with the server. * * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If login fails. */ public void noop() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Safe code try { // Send the noop. communication.sendFTPCommand("NOOP"); FTPReply r = communication.readFTPReply(); if (!r.isSuccessCode()) { throw new FTPException(r); } } finally { // Resets auto noop timer. touchAutoNoopTimer(); } } } /** * This method sends a custom command to the server. Don't use this method * to send standard commands already supported by the client: this should * cause unexpected results. * * @param command * The command line. * @return The reply supplied by the server, parsed and served in an object * way mode. * @throws IllegalStateException * If this client is not connected. * @throws IOException * If a I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. */ public FTPReply sendCustomCommand(String command) throws IllegalStateException, IOException, FTPIllegalReplyException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Sends the command. communication.sendFTPCommand(command); // Resets auto noop timer. touchAutoNoopTimer(); // Returns the reply. return communication.readFTPReply(); } } /** * This method sends a SITE specific command to the server. * * @param command * The site command. * @return The reply supplied by the server, parsed and served in an object * way mode. * @throws IllegalStateException * If this client is not connected. * @throws IOException * If a I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. */ public FTPReply sendSiteCommand(String command) throws IllegalStateException, IOException, FTPIllegalReplyException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Sends the command. communication.sendFTPCommand("SITE " + command); // Resets auto noop timer. touchAutoNoopTimer(); // Returns the reply. return communication.readFTPReply(); } } /** * Call this method to switch the user current account. Be careful with * this: some FTP servers don't implement this feature, even though it is a * standard FTP one. * * @param account * The account. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If login fails. */ public void changeAccount(String account) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Send the ACCT command. communication.sendFTPCommand("ACCT " + account); // Gets the reply. FTPReply r = communication.readFTPReply(); // Resets auto noop timer. touchAutoNoopTimer(); // Evaluates the response. if (!r.isSuccessCode()) { throw new FTPException(r); } } } /** * This method asks and returns the current working directory. * * @return path The path to the current working directory. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public String currentDirectory() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Send the PWD command. communication.sendFTPCommand("PWD"); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } // Parse the response. String[] messages = r.getMessages(); if (messages.length != 1) { throw new FTPIllegalReplyException(); } Matcher m = PWD_PATTERN.matcher(messages[0]); if (m.find()) { return messages[0].substring(m.start() + 1, m.end() - 1); } else { throw new FTPIllegalReplyException(); } } } /** * This method changes the current working directory. * * @param path * The path to the new working directory. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public void changeDirectory(String path) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Send the CWD command. communication.sendFTPCommand("CWD " + path); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } } } /** * This method changes the current working directory to the parent one. * * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public void changeDirectoryUp() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Sends the CWD command. communication.sendFTPCommand("CDUP"); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } } } /** * This method asks and returns the last modification date of a file or * directory. * * @param path * The path to the file or the directory. * @return The file/directory last modification date. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public Date modifiedDate(String path) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Sends the MDTM command. communication.sendFTPCommand("MDTM " + path); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } String[] messages = r.getMessages(); if (messages.length != 1) { throw new FTPIllegalReplyException(); } else { try { return MDTM_DATE_FORMAT.parse(messages[0]); } catch (ParseException e) { throw new FTPIllegalReplyException(); } } } } /** * This method asks and returns a file size in bytes. * * @param path * The path to the file. * @return The file size in bytes. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public long fileSize(String path) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Sends the TYPE I command. communication.sendFTPCommand("TYPE I"); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } // Sends the SIZE command. communication.sendFTPCommand("SIZE " + path); r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } String[] messages = r.getMessages(); if (messages.length != 1) { throw new FTPIllegalReplyException(); } else { try { return Long.parseLong(messages[0]); } catch (Throwable t) { throw new FTPIllegalReplyException(); } } } } /** * This method renames a remote file or directory. It can also be used to * move a file or a directory. * * In example: * *
	 * client.rename("oldname", "newname"); // This one renames
	 * 
* *
	 * client.rename("the/old/path/oldname", "/a/new/path/newname"); // This one moves
	 * 
* * @param oldPath * The current path of the file (or directory). * @param newPath * The new path for the file (or directory). * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public void rename(String oldPath, String newPath) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Sends the RNFR command. communication.sendFTPCommand("RNFR " + oldPath); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (r.getCode() != 350) { throw new FTPException(r); } // Sends the RNFR command. communication.sendFTPCommand("RNTO " + newPath); r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } } } /** * This method deletes a remote file. * * @param path * The path to the file. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public void deleteFile(String path) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Sends the DELE command. communication.sendFTPCommand("DELE " + path); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } } } /** * This method deletes a remote directory. * * @param path * The path to the directory. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public void deleteDirectory(String path) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Sends the RMD command. communication.sendFTPCommand("RMD " + path); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } } } /** * This method creates a new remote directory in the current working one. * * @param directoryName * The name of the new directory. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public void createDirectory(String directoryName) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Sends the MKD command. communication.sendFTPCommand("MKD " + directoryName); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } } } /** * This method calls the HELP command on the remote server, returning a list * of lines with the help contents. * * @return The help contents, splitted by line. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public String[] help() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Sends the HELP command. communication.sendFTPCommand("HELP"); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } return r.getMessages(); } } /** * This method returns the remote server status, as the result of a FTP STAT * command. * * @return The remote server status, splitted by line. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. */ public String[] serverStatus() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Sends the STAT command. communication.sendFTPCommand("STAT"); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } return r.getMessages(); } } /** * This method lists the entries of the current working directory parsing * the reply to a FTP LIST command. * * The response to the LIST command is parsed through the FTPListParser * objects registered on the client. The distribution of ftp4j contains some * standard parsers already registered on every FTPClient object created. If * they don't work in your case (a FTPListParseException is thrown), you can * build your own parser implementing the FTPListParser interface and add it * to the client by calling its addListParser() method. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The list() method will break with a * FTPAbortedException. * * @param fileSpec * A file filter string. Depending on the server implementation, * wildcard characters could be accepted. * @return The list of the files (and directories) in the current working * directory. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @throws FTPListParseException * If none of the registered parsers can handle the response * sent by the server. * @see FTPListParser * @see FTPClient#addListParser(FTPListParser) * @see FTPClient#getListParsers() * @see FTPClient#abortCurrentDataTransfer(boolean) * @see FTPClient#listNames() * @since 1.2 */ public FTPFile[] list(String fileSpec) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException, FTPListParseException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // ASCII, please! communication.sendFTPCommand("TYPE A"); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } // Prepares the connection for the data transfer. FTPDataTransferConnectionProvider provider = openDataTransferChannel(); // MLSD or LIST command? boolean mlsdCommand; if (mlsdPolicy == MLSD_IF_SUPPORTED) { mlsdCommand = mlsdSupported; } else if (mlsdPolicy == MLSD_ALWAYS) { mlsdCommand = true; } else { mlsdCommand = false; } String command = mlsdCommand ? "MLSD" : "LIST"; // Adds the file/directory selector. if (fileSpec != null && fileSpec.length() > 0) { command += " " + fileSpec; } // Prepares the lines array. ArrayList lines = new ArrayList(); // Local abort state. boolean wasAborted = false; // Sends the command. communication.sendFTPCommand(command); try { Socket dtConnection; try { dtConnection = provider.openDataTransferConnection(); } finally { provider.dispose(); } // Change the operation status. synchronized (abortLock) { ongoingDataTransfer = true; aborted = false; consumeAborCommandReply = false; } // Fetch the list from the data transfer connection. NVTASCIIReader dataReader = null; try { // Opens the data transfer connection. dataTransferInputStream = dtConnection.getInputStream(); // MODE Z enabled? if (modezEnabled) { dataTransferInputStream = new InflaterInputStream(dataTransferInputStream); } // Let's do it! dataReader = new NVTASCIIReader(dataTransferInputStream, mlsdCommand ? "UTF-8" : pickCharset()); String line; while ((line = dataReader.readLine()) != null) { if (line.length() > 0) { lines.add(line); } } } catch (IOException e) { synchronized (abortLock) { if (aborted) { throw new FTPAbortedException(); } else { throw new FTPDataTransferException( "I/O error in data transfer", e); } } } finally { if (dataReader != null) { try { dataReader.close(); } catch (Throwable t) { ; } } try { dtConnection.close(); } catch (Throwable t) { ; } // Set to null the instance-level input stream. dataTransferInputStream = null; // Change the operation status. synchronized (abortLock) { wasAborted = aborted; ongoingDataTransfer = false; aborted = false; } } } finally { r = communication.readFTPReply(); touchAutoNoopTimer(); if (r.getCode() != 150 && r.getCode() != 125) { throw new FTPException(r); } // Consumes the result reply of the transfer. r = communication.readFTPReply(); if (!wasAborted && r.getCode() != 226) { throw new FTPException(r); } // ABOR command response (if needed). if (consumeAborCommandReply) { communication.readFTPReply(); consumeAborCommandReply = false; } } // Build an array of lines. int size = lines.size(); String[] list = new String[size]; for (int i = 0; i < size; i++) { list[i] = (String) lines.get(i); } // Parse the list. FTPFile[] ret = null; if (mlsdCommand) { // Forces the MLSDListParser. MLSDListParser parser = new MLSDListParser(); ret = parser.parse(list); } else { // Is there any already successful parser? if (parser != null) { // Yes, let's try with it. try { ret = parser.parse(list); } catch (FTPListParseException e) { // That parser doesn't work anymore. parser = null; } } // Is there an available result? if (ret == null) { // Try to parse the list with every available parser. for (Iterator i = listParsers.iterator(); i.hasNext();) { FTPListParser aux = (FTPListParser) i.next(); try { // Let's try! ret = aux.parse(list); // This parser smells good! parser = aux; // Leave the loop. break; } catch (FTPListParseException e) { // Let's try the next one. continue; } } } } if (ret == null) { // None of the parsers can handle the list response. throw new FTPListParseException(); } else { // Return the parsed list. return ret; } } } /** * This method lists the entries of the current working directory parsing * the reply to a FTP LIST command. * * The response to the LIST command is parsed through the FTPListParser * objects registered on the client. The distribution of ftp4j contains some * standard parsers already registered on every FTPClient object created. If * they don't work in your case (a FTPListParseException is thrown), you can * build your own parser implementing the FTPListParser interface and add it * to the client by calling its addListParser() method. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The list() method will break with a * FTPAbortedException. * * @return The list of the files (and directories) in the current working * directory. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @throws FTPListParseException * If none of the registered parsers can handle the response * sent by the server. * @see FTPListParser * @see FTPClient#addListParser(FTPListParser) * @see FTPClient#getListParsers() * @see FTPClient#abortCurrentDataTransfer(boolean) * @see FTPClient#listNames() */ public FTPFile[] list() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException, FTPListParseException { return list(null); } /** * This method lists the entries of the current working directory with a FTP * NLST command. * * The response consists in an array of string, each one reporting the name * of a file or a directory placed in the current working directory. For a * more detailed directory listing procedure look at the list() method. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The listNames() method will break with a * FTPAbortedException. * * @return The list of the files (and directories) in the current working * directory. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @throws FTPListParseException * If none of the registered parsers can handle the response * sent by the server. * @see FTPClient#abortCurrentDataTransfer(boolean) * @see FTPClient#list() */ public String[] listNames() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException, FTPListParseException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // ASCII, please! communication.sendFTPCommand("TYPE A"); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } // Prepares the lines array. ArrayList lines = new ArrayList(); // Local abort state. boolean wasAborted = false; // Prepares the connection for the data transfer. FTPDataTransferConnectionProvider provider = openDataTransferChannel(); // Send the NLST command. communication.sendFTPCommand("NLST"); try { Socket dtConnection; try { dtConnection = provider.openDataTransferConnection(); } finally { provider.dispose(); } // Change the operation status. synchronized (abortLock) { ongoingDataTransfer = true; aborted = false; consumeAborCommandReply = false; } // Fetch the list from the data transfer connection. NVTASCIIReader dataReader = null; try { // Opens the data transfer connection. dataTransferInputStream = dtConnection.getInputStream(); // MODE Z enabled? if (modezEnabled) { dataTransferInputStream = new InflaterInputStream(dataTransferInputStream); } // Let's do it! dataReader = new NVTASCIIReader(dataTransferInputStream, pickCharset()); String line; while ((line = dataReader.readLine()) != null) { if (line.length() > 0) { lines.add(line); } } } catch (IOException e) { synchronized (abortLock) { if (aborted) { throw new FTPAbortedException(); } else { throw new FTPDataTransferException( "I/O error in data transfer", e); } } } finally { if (dataReader != null) { try { dataReader.close(); } catch (Throwable t) { ; } } try { dtConnection.close(); } catch (Throwable t) { ; } // Set to null the instance-level input stream. dataTransferInputStream = null; // Change the operation status. synchronized (abortLock) { wasAborted = aborted; ongoingDataTransfer = false; aborted = false; } } } finally { r = communication.readFTPReply(); if (r.getCode() != 150 && r.getCode() != 125) { throw new FTPException(r); } // Consumes the result reply of the transfer. r = communication.readFTPReply(); if (!wasAborted && r.getCode() != 226) { throw new FTPException(r); } // ABOR command response (if needed). if (consumeAborCommandReply) { communication.readFTPReply(); consumeAborCommandReply = false; } } // Build an array. int size = lines.size(); String[] list = new String[size]; for (int i = 0; i < size; i++) { list[i] = (String) lines.get(i); } return list; } } /** * This method uploads a file to the remote server. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param file * The file to upload. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws FileNotFoundException * If the supplied file cannot be found. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) */ public void upload(File file) throws IllegalStateException, FileNotFoundException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { upload(file, 0, null); } /** * This method uploads a file to the remote server. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param file * The file to upload. * @param listener * The listener for the operation. Could be null. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws FileNotFoundException * If the supplied file cannot be found. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) */ public void upload(File file, FTPDataTransferListener listener) throws IllegalStateException, FileNotFoundException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { upload(file, 0, listener); } /** * This method uploads a file to the remote server. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param file * The file to upload. * @param restartAt * The restart point (number of bytes already uploaded). Use * {@link FTPClient#isResumeSupported()} to check if the server * supports resuming of broken data transfers. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws FileNotFoundException * If the supplied file cannot be found. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) */ public void upload(File file, long restartAt) throws IllegalStateException, FileNotFoundException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { upload(file, restartAt, null); } /** * This method uploads a file to the remote server. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param file * The file to upload. * @param restartAt * The restart point (number of bytes already uploaded). Use * {@link FTPClient#isResumeSupported()} to check if the server * supports resuming of broken data transfers. * @param listener * The listener for the operation. Could be null. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws FileNotFoundException * If the supplied file cannot be found. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) */ public void upload(File file, long restartAt, FTPDataTransferListener listener) throws IllegalStateException, FileNotFoundException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { if (!file.exists()) { throw new FileNotFoundException(file.getAbsolutePath()); } InputStream inputStream = null; try { inputStream = new FileInputStream(file); } catch (IOException e) { throw new FTPDataTransferException(e); } try { upload(file.getName(), inputStream, restartAt, restartAt, listener); } catch (IllegalStateException e) { throw e; } catch (IOException e) { throw e; } catch (FTPIllegalReplyException e) { throw e; } catch (FTPException e) { throw e; } catch (FTPDataTransferException e) { throw e; } catch (FTPAbortedException e) { throw e; } finally { if (inputStream != null) { try { inputStream.close(); } catch (Throwable t) { ; } } } } /** * This method uploads a content to the remote server. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param fileName * The name of the remote file. * @param inputStream * The source of data. * @param restartAt * The restart point (number of bytes already uploaded). Use * {@link FTPClient#isResumeSupported()} to check if the server * supports resuming of broken data transfers. * @param streamOffset * The offset to skip in the stream. * @param listener * The listener for the operation. Could be null. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) */ public void upload(String fileName, InputStream inputStream, long restartAt, long streamOffset, FTPDataTransferListener listener) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Select the type of contents. int tp = type; if (tp == TYPE_AUTO) { tp = detectType(fileName); } if (tp == TYPE_TEXTUAL) { communication.sendFTPCommand("TYPE A"); } else if (tp == TYPE_BINARY) { communication.sendFTPCommand("TYPE I"); } FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } // Prepares the connection for the data transfer. FTPDataTransferConnectionProvider provider = openDataTransferChannel(); // REST command (if supported and/or requested). if (restSupported || restartAt > 0) { boolean done = false; try { communication.sendFTPCommand("REST " + restartAt); r = communication.readFTPReply(); touchAutoNoopTimer(); if (r.getCode() != 350 && ((r.getCode() != 501 && r.getCode() != 502) || restartAt > 0)) { throw new FTPException(r); } done = true; } finally { if (!done) { provider.dispose(); } } } // Local abort state. boolean wasAborted = false; // Send the STOR command. communication.sendFTPCommand("STOR " + fileName); try { Socket dtConnection; try { dtConnection = provider.openDataTransferConnection(); } finally { provider.dispose(); } // Change the operation status. synchronized (abortLock) { ongoingDataTransfer = true; aborted = false; consumeAborCommandReply = false; } // Upload the stream. try { // Skips. inputStream.skip(streamOffset); // Opens the data transfer connection. dataTransferOutputStream = dtConnection.getOutputStream(); // MODE Z enabled? if (modezEnabled) { dataTransferOutputStream = new DeflaterOutputStream(dataTransferOutputStream); } // Listeners. if (listener != null) { listener.started(); } // Let's do it! if (tp == TYPE_TEXTUAL) { Reader reader = new InputStreamReader(inputStream); Writer writer = new OutputStreamWriter( dataTransferOutputStream, pickCharset()); char[] buffer = new char[SEND_AND_RECEIVE_BUFFER_SIZE]; int l; while ((l = reader.read(buffer)) != -1) { writer.write(buffer, 0, l); writer.flush(); if (listener != null) { listener.transferred(l); } } } else if (tp == TYPE_BINARY) { byte[] buffer = new byte[SEND_AND_RECEIVE_BUFFER_SIZE]; int l; while ((l = inputStream.read(buffer)) != -1) { dataTransferOutputStream.write(buffer, 0, l); dataTransferOutputStream.flush(); if (listener != null) { listener.transferred(l); } } } } catch (IOException e) { synchronized (abortLock) { if (aborted) { if (listener != null) { listener.aborted(); } throw new FTPAbortedException(); } else { if (listener != null) { listener.failed(); } throw new FTPDataTransferException( "I/O error in data transfer", e); } } } finally { // Closing stream and data connection. if (dataTransferOutputStream != null) { try { dataTransferOutputStream.close(); } catch (Throwable t) { ; } } try { dtConnection.close(); } catch (Throwable t) { ; } // Set to null the instance-level input stream. dataTransferOutputStream = null; // Change the operation status. synchronized (abortLock) { wasAborted = aborted; ongoingDataTransfer = false; aborted = false; } } } finally { // Data transfer command reply. r = communication.readFTPReply(); touchAutoNoopTimer(); if (r.getCode() != 150 && r.getCode() != 125) { throw new FTPException(r); } // Consumes the result reply of the transfer. r = communication.readFTPReply(); if (!wasAborted && r.getCode() != 226) { throw new FTPException(r); } // ABOR command response (if needed). if (consumeAborCommandReply) { communication.readFTPReply(); consumeAborCommandReply = false; } } // Listener notification. if (listener != null) { listener.completed(); } } } /** * This method appends the contents of a local file to an existing file on * the remote server. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param file * The local file whose contents will be appended to the remote * file. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws FileNotFoundException * If the supplied file cannot be found. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) * @since 1.6 */ public void append(File file) throws IllegalStateException, FileNotFoundException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { append(file, null); } /** * This method uploads a file to the remote server. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param file * The local file whose contents will be appended to the remote * file. * @param listener * The listener for the operation. Could be null. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws FileNotFoundException * If the supplied file cannot be found. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) * @since 1.6 */ public void append(File file, FTPDataTransferListener listener) throws IllegalStateException, FileNotFoundException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { if (!file.exists()) { throw new FileNotFoundException(file.getAbsolutePath()); } InputStream inputStream = null; try { inputStream = new FileInputStream(file); } catch (IOException e) { throw new FTPDataTransferException(e); } try { append(file.getName(), inputStream, 0, listener); } catch (IllegalStateException e) { throw e; } catch (IOException e) { throw e; } catch (FTPIllegalReplyException e) { throw e; } catch (FTPException e) { throw e; } catch (FTPDataTransferException e) { throw e; } catch (FTPAbortedException e) { throw e; } finally { if (inputStream != null) { try { inputStream.close(); } catch (Throwable t) { ; } } } } /** * This method appends data to an existing file on the remote server. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param fileName * The name of the remote file. * @param inputStream * The source of data. * @param streamOffset * The offset to skip in the stream. * @param listener * The listener for the operation. Could be null. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) * @since 1.6 */ public void append(String fileName, InputStream inputStream, long streamOffset, FTPDataTransferListener listener) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Select the type of contents. int tp = type; if (tp == TYPE_AUTO) { tp = detectType(fileName); } if (tp == TYPE_TEXTUAL) { communication.sendFTPCommand("TYPE A"); } else if (tp == TYPE_BINARY) { communication.sendFTPCommand("TYPE I"); } FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } // Local abort state. boolean wasAborted = false; // Prepares the connection for the data transfer. FTPDataTransferConnectionProvider provider = openDataTransferChannel(); // Send the STOR command. communication.sendFTPCommand("APPE " + fileName); try { Socket dtConnection; try { dtConnection = provider.openDataTransferConnection(); } finally { provider.dispose(); } // Change the operation status. synchronized (abortLock) { ongoingDataTransfer = true; aborted = false; consumeAborCommandReply = false; } // Upload the stream. try { // Skips. inputStream.skip(streamOffset); // Opens the data transfer connection. dataTransferOutputStream = dtConnection.getOutputStream(); // MODE Z enabled? if (modezEnabled) { dataTransferOutputStream = new DeflaterOutputStream(dataTransferOutputStream); } // Listeners. if (listener != null) { listener.started(); } // Let's do it! if (tp == TYPE_TEXTUAL) { Reader reader = new InputStreamReader(inputStream); Writer writer = new OutputStreamWriter( dataTransferOutputStream, pickCharset()); char[] buffer = new char[SEND_AND_RECEIVE_BUFFER_SIZE]; int l; while ((l = reader.read(buffer)) != -1) { writer.write(buffer, 0, l); writer.flush(); if (listener != null) { listener.transferred(l); } } } else if (tp == TYPE_BINARY) { byte[] buffer = new byte[SEND_AND_RECEIVE_BUFFER_SIZE]; int l; while ((l = inputStream.read(buffer)) != -1) { dataTransferOutputStream.write(buffer, 0, l); dataTransferOutputStream.flush(); if (listener != null) { listener.transferred(l); } } } } catch (IOException e) { synchronized (abortLock) { if (aborted) { if (listener != null) { listener.aborted(); } throw new FTPAbortedException(); } else { if (listener != null) { listener.failed(); } throw new FTPDataTransferException( "I/O error in data transfer", e); } } } finally { // Closing stream and data connection. if (dataTransferOutputStream != null) { try { dataTransferOutputStream.close(); } catch (Throwable t) { ; } } try { dtConnection.close(); } catch (Throwable t) { ; } // Set to null the instance-level input stream. dataTransferOutputStream = null; // Change the operation status. synchronized (abortLock) { wasAborted = aborted; ongoingDataTransfer = false; aborted = false; } } } finally { r = communication.readFTPReply(); touchAutoNoopTimer(); if (r.getCode() != 150 && r.getCode() != 125) { throw new FTPException(r); } // Consumes the result reply of the transfer. r = communication.readFTPReply(); if (!wasAborted && r.getCode() != 226) { throw new FTPException(r); } // ABOR command response (if needed). if (consumeAborCommandReply) { communication.readFTPReply(); consumeAborCommandReply = false; } } // Notifies the listener. if (listener != null) { listener.completed(); } } } /** * This method downloads a remote file from the server to a local file. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param remoteFileName * The name of the file to download. * @param localFile * The local file. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws FileNotFoundException * If the supplied file cannot be found. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) */ public void download(String remoteFileName, File localFile) throws IllegalStateException, FileNotFoundException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { download(remoteFileName, localFile, 0, null); } /** * This method downloads a remote file from the server to a local file. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param remoteFileName * The name of the file to download. * @param localFile * The local file. * @param listener * The listener for the operation. Could be null. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws FileNotFoundException * If the supplied file cannot be found. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) */ public void download(String remoteFileName, File localFile, FTPDataTransferListener listener) throws IllegalStateException, FileNotFoundException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { download(remoteFileName, localFile, 0, listener); } /** * This method resumes a download operation from the remote server to a * local file. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param remoteFileName * The name of the file to download. * @param localFile * The local file. * @param restartAt * The restart point (number of bytes already downloaded). Use * {@link FTPClient#isResumeSupported()} to check if the server * supports resuming of broken data transfers. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws FileNotFoundException * If the supplied file cannot be found. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) */ public void download(String remoteFileName, File localFile, long restartAt) throws IllegalStateException, FileNotFoundException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { download(remoteFileName, localFile, restartAt, null); } /** * This method resumes a download operation from the remote server to a * local file. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param remoteFileName * The name of the file to download. * @param localFile * The local file. * @param restartAt * The restart point (number of bytes already downloaded). Use * {@link FTPClient#isResumeSupported()} to check if the server * supports resuming of broken data transfers. * @param listener * The listener for the operation. Could be null. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws FileNotFoundException * If the supplied file cannot be found. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) */ public void download(String remoteFileName, File localFile, long restartAt, FTPDataTransferListener listener) throws IllegalStateException, FileNotFoundException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { OutputStream outputStream = null; try { outputStream = new FileOutputStream(localFile, restartAt > 0); } catch (IOException e) { throw new FTPDataTransferException(e); } try { download(remoteFileName, outputStream, restartAt, listener); } catch (IllegalStateException e) { throw e; } catch (IOException e) { throw e; } catch (FTPIllegalReplyException e) { throw e; } catch (FTPException e) { throw e; } catch (FTPDataTransferException e) { throw e; } catch (FTPAbortedException e) { throw e; } finally { if (outputStream != null) { try { outputStream.close(); } catch (Throwable t) { ; } } } } /** * This method resumes a download operation from the remote server. * * Calling this method blocks the current thread until the operation is * completed. The operation could be interrupted by another thread calling * abortCurrentDataTransfer(). The method will break with a * FTPAbortedException. * * @param fileName * The name of the remote file. * @param outputStream * The destination stream of data read during the download. * @param restartAt * The restart point (number of bytes already downloaded). Use * {@link FTPClient#isResumeSupported()} to check if the server * supports resuming of broken data transfers. * @param listener * The listener for the operation. Could be null. * @throws IllegalStateException * If the client is not connected or not authenticated. * @throws IOException * If an I/O error occurs. * @throws FTPIllegalReplyException * If the server replies in an illegal way. * @throws FTPException * If the operation fails. * @throws FTPDataTransferException * If a I/O occurs in the data transfer connection. If you * receive this exception the transfer failed, but the main * connection with the remote FTP server is in theory still * working. * @throws FTPAbortedException * If operation is aborted by another thread. * @see FTPClient#abortCurrentDataTransfer(boolean) */ public void download(String fileName, OutputStream outputStream, long restartAt, FTPDataTransferListener listener) throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException, FTPAbortedException { synchronized (lock) { // Is this client connected? if (!connected) { throw new IllegalStateException("Client not connected"); } // Is this client authenticated? if (!authenticated) { throw new IllegalStateException("Client not authenticated"); } // Select the type of contents. int tp = type; if (tp == TYPE_AUTO) { tp = detectType(fileName); } if (tp == TYPE_TEXTUAL) { communication.sendFTPCommand("TYPE A"); } else if (tp == TYPE_BINARY) { communication.sendFTPCommand("TYPE I"); } FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } // Prepares the connection for the data transfer. FTPDataTransferConnectionProvider provider = openDataTransferChannel(); // REST command (if supported and/or requested). if (restSupported || restartAt > 0) { boolean done = false; try { communication.sendFTPCommand("REST " + restartAt); r = communication.readFTPReply(); touchAutoNoopTimer(); if (r.getCode() != 350 && ((r.getCode() != 501 && r.getCode() != 502) || restartAt > 0)) { throw new FTPException(r); } done = true; } finally { if (!done) { provider.dispose(); } } } // Local abort state. boolean wasAborted = false; // Send the RETR command. communication.sendFTPCommand("RETR " + fileName); try { Socket dtConnection; try { dtConnection = provider.openDataTransferConnection(); } finally { provider.dispose(); } // Change the operation status. synchronized (abortLock) { ongoingDataTransfer = true; aborted = false; consumeAborCommandReply = false; } // Download the stream. try { // Opens the data transfer connection. dataTransferInputStream = dtConnection.getInputStream(); // MODE Z enabled? if (modezEnabled) { dataTransferInputStream = new InflaterInputStream(dataTransferInputStream); } // Listeners. if (listener != null) { listener.started(); } // Let's do it! if (tp == TYPE_TEXTUAL) { Reader reader = new InputStreamReader( dataTransferInputStream, pickCharset()); Writer writer = new OutputStreamWriter(outputStream); char[] buffer = new char[SEND_AND_RECEIVE_BUFFER_SIZE]; int l; while ((l = reader.read(buffer, 0, buffer.length)) != -1) { writer.write(buffer, 0, l); writer.flush(); if (listener != null) { listener.transferred(l); } } } else if (tp == TYPE_BINARY) { byte[] buffer = new byte[SEND_AND_RECEIVE_BUFFER_SIZE]; int l; while ((l = dataTransferInputStream.read(buffer, 0, buffer.length)) != -1) { outputStream.write(buffer, 0, l); if (listener != null) { listener.transferred(l); } } } } catch (IOException e) { synchronized (abortLock) { if (aborted) { if (listener != null) { listener.aborted(); } throw new FTPAbortedException(); } else { if (listener != null) { listener.failed(); } throw new FTPDataTransferException( "I/O error in data transfer", e); } } } finally { // Closing stream and data connection. if (dataTransferInputStream != null) { try { dataTransferInputStream.close(); } catch (Throwable t) { ; } } try { dtConnection.close(); } catch (Throwable t) { ; } // Set to null the instance-level input stream. dataTransferInputStream = null; // Change the operation status. synchronized (abortLock) { wasAborted = aborted; ongoingDataTransfer = false; aborted = false; } } } finally { r = communication.readFTPReply(); touchAutoNoopTimer(); if (r.getCode() != 150 && r.getCode() != 125) { throw new FTPException(r); } // Consumes the result reply of the transfer. r = communication.readFTPReply(); if (!wasAborted && r.getCode() != 226) { throw new FTPException(r); } // ABOR command response (if needed). if (consumeAborCommandReply) { communication.readFTPReply(); consumeAborCommandReply = false; } } // Notifies the listener. if (listener != null) { listener.completed(); } } } /** * This method detects the type for a file transfer. */ private int detectType(String fileName) throws IOException, FTPIllegalReplyException, FTPException { int start = fileName.lastIndexOf('.') + 1; int stop = fileName.length(); if (start > 0 && start < stop - 1) { String ext = fileName.substring(start, stop); ext = ext.toLowerCase(); if (textualExtensionRecognizer.isTextualExt(ext)) { return TYPE_TEXTUAL; } else { return TYPE_BINARY; } } else { return TYPE_BINARY; } } /** * This method opens a data transfer channel. */ private FTPDataTransferConnectionProvider openDataTransferChannel() throws IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException { // MODE Z? if (modezSupported && compressionEnabled) { if (!modezEnabled) { // Sends the MODE Z command. communication.sendFTPCommand("MODE Z"); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (r.isSuccessCode()) { modezEnabled = true; } } } else { if (modezEnabled) { // Sends the MODE S command. communication.sendFTPCommand("MODE S"); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (r.isSuccessCode()) { modezEnabled = false; } } } // Active or passive? if (passive) { return openPassiveDataTransferChannel(); } else { return openActiveDataTransferChannel(); } } /** * This method opens a data transfer channel in active mode. */ private FTPDataTransferConnectionProvider openActiveDataTransferChannel() throws IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException { // Create a FTPDataTransferServer object. FTPDataTransferServer server = new FTPDataTransferServer() { public Socket openDataTransferConnection() throws FTPDataTransferException { Socket socket = super.openDataTransferConnection(); if (dataChannelEncrypted) { try { socket = ssl(socket, socket.getInetAddress().getHostName(), socket.getPort()); } catch (IOException e) { try { socket.close(); } catch (Throwable t) { } throw new FTPDataTransferException(e); } } return socket; } }; int port = server.getPort(); int p1 = port >>> 8; int p2 = port & 0xff; int[] addr = pickLocalAddress(); // Send the port command. communication.sendFTPCommand("PORT " + addr[0] + "," + addr[1] + "," + addr[2] + "," + addr[3] + "," + p1 + "," + p2); FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { // Disposes. server.dispose(); // Closes the already open connection (if any). try { Socket aux = server.openDataTransferConnection(); aux.close(); } catch (Throwable t) { ; } // Throws the exception. throw new FTPException(r); } return server; } /** * This method opens a data transfer channel in passive mode. */ private FTPDataTransferConnectionProvider openPassiveDataTransferChannel() throws IOException, FTPIllegalReplyException, FTPException, FTPDataTransferException { // Send the PASV command. communication.sendFTPCommand("PASV"); // Read the reply. FTPReply r = communication.readFTPReply(); touchAutoNoopTimer(); if (!r.isSuccessCode()) { throw new FTPException(r); } // Use a regexp to extract the remote address and port. String addressAndPort = null; String[] messages = r.getMessages(); for (int i = 0; i < messages.length; i++) { Matcher m = PASV_PATTERN.matcher(messages[i]); if (m.find()) { int start = m.start(); int end = m.end(); addressAndPort = messages[i].substring(start, end); break; } } if (addressAndPort == null) { // The remote server has not sent the coordinates for the // data transfer connection. throw new FTPIllegalReplyException(); } // Parse the string extracted from the reply. StringTokenizer st = new StringTokenizer(addressAndPort, ","); int b1 = Integer.parseInt(st.nextToken()); int b2 = Integer.parseInt(st.nextToken()); int b3 = Integer.parseInt(st.nextToken()); int b4 = Integer.parseInt(st.nextToken()); int p1 = Integer.parseInt(st.nextToken()); int p2 = Integer.parseInt(st.nextToken()); final String pasvHost = b1 + "." + b2 + "." + b3 + "." + b4; final int pasvPort = (p1 << 8) | p2; FTPDataTransferConnectionProvider provider = new FTPDataTransferConnectionProvider() { public Socket openDataTransferConnection() throws FTPDataTransferException { // Establish the connection. Socket dtConnection; try { String selectedHost = connector.getUseSuggestedAddressForDataConnections() ? pasvHost : host; dtConnection = connector.connectForDataTransferChannel(selectedHost, pasvPort); if (dataChannelEncrypted) { dtConnection = ssl(dtConnection, selectedHost, pasvPort); } } catch (IOException e) { throw new FTPDataTransferException("Cannot connect to the remote server", e); } return dtConnection; } public void dispose() { // nothing to do } }; return provider; } /** * If there's any ongoing data transfer operation, this method aborts it. * * @param sendAborCommand * If true the client will negotiate the abort procedure with the * server, through the standard FTP ABOR command. Otherwise the * open data transfer connection will be closed without any * advise has sent to the server. * @throws IOException * If the ABOR command cannot be sent due to any I/O error. This * could happen only if force is false. * @throws FTPIllegalReplyException * If the server reply to the ABOR command is illegal. This * could happen only if force is false. */ public void abortCurrentDataTransfer(boolean sendAborCommand) throws IOException, FTPIllegalReplyException { synchronized (abortLock) { if (ongoingDataTransfer && !aborted) { if (sendAborCommand) { communication.sendFTPCommand("ABOR"); touchAutoNoopTimer(); consumeAborCommandReply = true; } if (dataTransferInputStream != null) { try { dataTransferInputStream.close(); } catch (Throwable t) { ; } } if (dataTransferOutputStream != null) { try { dataTransferOutputStream.close(); } catch (Throwable t) { ; } } aborted = true; } } } /** * Returns the name of the charset that should be used in textual * transmissions. * * @return The name of the charset that should be used in textual * transmissions. */ private String pickCharset() { if (charset != null) { return charset; } else if (utf8Supported) { return "UTF-8"; } else { return System.getProperty("file.encoding"); } } /** * Picks the local address for an active data transfer operation. * * @return The local address as a 4 integer values array. * @throws IOException * If an unexpected I/O error occurs while trying to resolve the * local address. */ private int[] pickLocalAddress() throws IOException { // Forced address? int[] ret = pickForcedLocalAddress(); // Auto-detect? if (ret == null) { ret = pickAutoDetectedLocalAddress(); } // Returns. return ret; } /** * If a local address for active data transfers has been supplied through * the {@link FTPKeys#ACTIVE_DT_HOST_ADDRESS}, it returns it as a 4 elements * integer array; otherwise it returns null. * * @return The forced local address, or null. */ private int[] pickForcedLocalAddress() { int[] ret = null; String aux = System.getProperty(FTPKeys.ACTIVE_DT_HOST_ADDRESS); if (aux != null) { boolean valid = false; StringTokenizer st = new StringTokenizer(aux, "."); if (st.countTokens() == 4) { valid = true; int[] arr = new int[4]; for (int i = 0; i < 4; i++) { String tk = st.nextToken(); try { arr[i] = Integer.parseInt(tk); } catch (NumberFormatException e) { arr[i] = -1; } if (arr[i] < 0 || arr[i] > 255) { valid = false; break; } } if (valid) { ret = arr; } } if (!valid) { // warning to the developer logger.warning("WARNING: invalid value \"" + aux + "\" for the " + FTPKeys.ACTIVE_DT_HOST_ADDRESS + " system property. The value should " + "be in the x.x.x.x form."); } } return ret; } /** * Auto-detects the local network address, and returns it in the form of a 4 * elements integer array. * * @return The detected local address. * @throws IOException * If an unexpected I/O error occurs while trying to resolve the * local address. */ private int[] pickAutoDetectedLocalAddress() throws IOException { InetAddress addressObj = InetAddress.getLocalHost(); byte[] addr = addressObj.getAddress(); int b1 = addr[0] & 0xff; int b2 = addr[1] & 0xff; int b3 = addr[2] & 0xff; int b4 = addr[3] & 0xff; int[] ret = { b1, b2, b3, b4 }; return ret; } public String toString() { synchronized (lock) { StringBuffer buffer = new StringBuffer(); buffer.append(getClass().getName()); buffer.append(" [connected="); buffer.append(connected); if (connected) { buffer.append(", host="); buffer.append(host); buffer.append(", port="); buffer.append(port); } buffer.append(", connector="); buffer.append(connector); buffer.append(", security="); switch (security) { case SECURITY_FTP: buffer.append("SECURITY_FTP"); break; case SECURITY_FTPS: buffer.append("SECURITY_FTPS"); break; case SECURITY_FTPES: buffer.append("SECURITY_FTPES"); break; } buffer.append(", authenticated="); buffer.append(authenticated); if (authenticated) { buffer.append(", username="); buffer.append(username); buffer.append(", password="); StringBuffer buffer2 = new StringBuffer(); for (int i = 0; i < password.length(); i++) { buffer2.append('*'); } buffer.append(buffer2); buffer.append(", restSupported="); buffer.append(restSupported); buffer.append(", utf8supported="); buffer.append(utf8Supported); buffer.append(", mlsdSupported="); buffer.append(mlsdSupported); buffer.append(", mode=modezSupported"); buffer.append(modezSupported); buffer.append(", mode=modezEnabled"); buffer.append(modezEnabled); } buffer.append(", transfer mode="); buffer.append(passive ? "passive" : "active"); buffer.append(", transfer type="); switch (type) { case TYPE_AUTO: buffer.append("TYPE_AUTO"); break; case TYPE_BINARY: buffer.append("TYPE_BINARY"); break; case TYPE_TEXTUAL: buffer.append("TYPE_TEXTUAL"); break; } buffer.append(", textualExtensionRecognizer="); buffer.append(textualExtensionRecognizer); FTPListParser[] listParsers = getListParsers(); if (listParsers.length > 0) { buffer.append(", listParsers="); for (int i = 0; i < listParsers.length; i++) { if (i > 0) { buffer.append(", "); } buffer.append(listParsers[i]); } } FTPCommunicationListener[] communicationListeners = getCommunicationListeners(); if (communicationListeners.length > 0) { buffer.append(", communicationListeners="); for (int i = 0; i < communicationListeners.length; i++) { if (i > 0) { buffer.append(", "); } buffer.append(communicationListeners[i]); } } buffer.append(", autoNoopTimeout="); buffer.append(autoNoopTimeout); buffer.append("]"); return buffer.toString(); } } /** * Starts the auto-noop timer thread. */ private void startAutoNoopTimer() { if (autoNoopTimeout > 0) { autoNoopTimer = new AutoNoopTimer(); autoNoopTimer.start(); } } /** * Stops the auto-noop timer thread. * * @since 1.5 */ private void stopAutoNoopTimer() { if (autoNoopTimer != null) { autoNoopTimer.interrupt(); autoNoopTimer = null; } } /** * Resets the auto noop timer. */ private void touchAutoNoopTimer() { if (autoNoopTimer != null) { nextAutoNoopTime = System.currentTimeMillis() + autoNoopTimeout; } } /** * The auto noop timer thread. */ private class AutoNoopTimer extends Thread { public void run() { synchronized (lock) { if (nextAutoNoopTime <= 0 && autoNoopTimeout > 0) { nextAutoNoopTime = System.currentTimeMillis() + autoNoopTimeout; } while (!Thread.interrupted() && autoNoopTimeout > 0) { // Sleep till the next NOOP. long delay = nextAutoNoopTime - System.currentTimeMillis(); if (delay > 0) { try { lock.wait(delay); } catch (InterruptedException e) { break; } } // Is it really time to NOOP? if (System.currentTimeMillis() >= nextAutoNoopTime) { // Yes! try { noop(); } catch (Throwable t) { ; // ignore... } } } } } } }