/*
* 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 <http://www.gnu.org/licenses/>.
*/
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.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.5
*/
public class FTPClient {
/**
* 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 suggestion.
*/
private boolean aborted = 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) {
e.printStackTrace();
}
}
}
}
/**
* 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 <em>true</em> if compression of data transfers is supported on
* the server-side, <em>false</em> 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
* <em>true</em>.
*
* The default value is <em>false</em>.
*
* @param compressionEnabled
* <em>true</em> to enable the use of compression during any
* subsequent file transfer, <em>false</em> 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
* <em>true</em>.
*
* @return <em>true</em> if compression is enabled, <em>false</em>
* 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) {
;
}
}
}
}
}
}
/**
* 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.
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");
}
// Send the noop.
communication.sendFTPCommand("NOOP");
FTPReply r = communication.readFTPReply();
if (!r.isSuccessCode()) {
throw new FTPException(r);
}
// 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 SIZE command.
communication.sendFTPCommand("SIZE " + 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 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:
*
* <pre>
* client.rename("oldname", "newname"); // This one renames
* </pre>
*
* <pre>
* client.rename("the/old/path/oldname", "/a/new/path/newname"); // This one moves
* </pre>
*
* @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;
}
// Sends the command.
communication.sendFTPCommand(command);
Socket dtConnection;
try {
try {
dtConnection = provider.openDataTransferConnection();
} finally {
r = communication.readFTPReply();
touchAutoNoopTimer();
if (r.getCode() != 150 && r.getCode() != 125) {
throw new FTPException(r);
}
}
} finally {
provider.dispose();
}
// Change the operation status.
synchronized (abortLock) {
ongoingDataTransfer = true;
aborted = false;
}
// Fetch the list from the data transfer connection.
ArrayList lines = new ArrayList();
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) {
;
}
// Consume the result reply of the transfer.
communication.readFTPReply();
// Set to null the instance-level input stream.
dataTransferInputStream = null;
// Change the operation status.
synchronized (abortLock) {
ongoingDataTransfer = false;
aborted = 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 {
// Searches for the appropriate parser.
if (parser == null) {
// Try to parse the list with every parser available.
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;
}
}
} else {
ret = parser.parse(list);
}
}
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 connection for the data transfer.
FTPDataTransferConnectionProvider provider = openDataTransferChannel();
// Send the NLST command.
communication.sendFTPCommand("NLST");
Socket dtConnection;
try {
try {
dtConnection = provider.openDataTransferConnection();
} finally {
r = communication.readFTPReply();
if (r.getCode() != 150 && r.getCode() != 125) {
throw new FTPException(r);
}
}
} finally {
provider.dispose();
}
// Change the operation status.
synchronized (abortLock) {
ongoingDataTransfer = true;
aborted = false;
}
// Fetch the list from the data transfer connection.
ArrayList lines = new ArrayList();
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) {
;
}
// Consume the result reply of the transfer.
communication.readFTPReply();
// Set to null the instance-level input stream.
dataTransferInputStream = null;
// Change the operation status.
synchronized (abortLock) {
ongoingDataTransfer = false;
aborted = 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) {
throw new FTPException(r);
}
done = true;
} finally {
if (!done) {
provider.dispose();
}
}
}
// Send the STOR command.
communication.sendFTPCommand("STOR " + fileName);
Socket dtConnection;
try {
try {
dtConnection = provider.openDataTransferConnection();
} finally {
r = communication.readFTPReply();
touchAutoNoopTimer();
if (r.getCode() != 150 && r.getCode() != 125) {
throw new FTPException(r);
}
}
} finally {
provider.dispose();
}
// Change the operation status.
synchronized (abortLock) {
ongoingDataTransfer = true;
aborted = false;
}
// Upload the stream.
long done = 0;
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();
done += l;
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();
done += 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 (dataTransferOutputStream != null) {
try {
dataTransferOutputStream.close();
} catch (Throwable t) {
;
}
}
try {
dtConnection.close();
} catch (Throwable t) {
;
}
// Set to null the instance-level input stream.
dataTransferOutputStream = null;
// Consume the result reply of the transfer.
communication.readFTPReply();
// Change the operation status.
synchronized (abortLock) {
ongoingDataTransfer = false;
aborted = false;
}
}
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);
}
// Prepares the connection for the data transfer.
FTPDataTransferConnectionProvider provider = openDataTransferChannel();
// Send the STOR command.
communication.sendFTPCommand("APPE " + fileName);
Socket dtConnection;
try {
try {
dtConnection = provider.openDataTransferConnection();
} finally {
r = communication.readFTPReply();
touchAutoNoopTimer();
if (r.getCode() != 150 && r.getCode() != 125) {
throw new FTPException(r);
}
}
} finally {
provider.dispose();
}
// Change the operation status.
synchronized (abortLock) {
ongoingDataTransfer = true;
aborted = false;
}
// Upload the stream.
long done = 0;
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();
done += l;
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();
done += 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 (dataTransferOutputStream != null) {
try {
dataTransferOutputStream.close();
} catch (Throwable t) {
;
}
}
try {
dtConnection.close();
} catch (Throwable t) {
;
}
// Set to null the instance-level input stream.
dataTransferOutputStream = null;
// Consume the result reply of the transfer.
communication.readFTPReply();
// Change the operation status.
synchronized (abortLock) {
ongoingDataTransfer = false;
aborted = false;
}
}
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) {
throw new FTPException(r);
}
done = true;
} finally {
if (!done) {
provider.dispose();
}
}
}
// Send the RETR command.
communication.sendFTPCommand("RETR " + fileName);
Socket dtConnection;
try {
try {
dtConnection = provider.openDataTransferConnection();
} finally {
r = communication.readFTPReply();
touchAutoNoopTimer();
if (r.getCode() != 150 && r.getCode() != 125) {
throw new FTPException(r);
}
}
} finally {
provider.dispose();
}
// Change the operation status.
synchronized (abortLock) {
ongoingDataTransfer = true;
aborted = 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;
// Consume the result reply of the transfer.
communication.readFTPReply();
// Change the operation status.
synchronized (abortLock) {
ongoingDataTransfer = false;
aborted = false;
}
}
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 InetAddress remoteAddress;
// Ignore address?
String useSuggestedAddress = System.getProperty(FTPKeys.PASSIVE_DT_USE_SUGGESTED_ADDRESS);
if ("true".equalsIgnoreCase(useSuggestedAddress) || "yes".equalsIgnoreCase(useSuggestedAddress)
|| "1".equals(useSuggestedAddress)) {
remoteAddress = InetAddress.getByAddress(new byte[] { (byte) b1, (byte) b2, (byte) b3, (byte) b4 });
} else {
remoteAddress = InetAddress.getByName(host);
}
final int remotePort = (p1 << 8) | p2;
FTPDataTransferConnectionProvider provider = new FTPDataTransferConnectionProvider() {
public Socket openDataTransferConnection()
throws FTPDataTransferException {
// Establish the connection.
Socket dtConnection;
String remoteHost = remoteAddress.getHostAddress();
try {
dtConnection = connector.connectForDataTransferChannel(remoteHost, remotePort);
if (dataChannelEncrypted) {
dtConnection = ssl(dtConnection, remoteHost, remotePort);
}
} 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");
communication.readFTPReply();
touchAutoNoopTimer();
}
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
System.err.println("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...
}
}
}
}
}
}
}