diff --git a/jdk/make/sun/net/FILES_java.gmk b/jdk/make/sun/net/FILES_java.gmk
index 19dd2d6d7f57330b1415b68dc8ff7160267ed218..4f2a5165a7a44c2453c5713c8c15c44c2e229d04 100644
--- a/jdk/make/sun/net/FILES_java.gmk
+++ b/jdk/make/sun/net/FILES_java.gmk
@@ -45,8 +45,14 @@ FILES_java = \
sun/net/dns/ResolverConfiguration.java \
sun/net/dns/ResolverConfigurationImpl.java \
sun/net/ftp/FtpClient.java \
+ sun/net/ftp/FtpClientProvider.java \
+ sun/net/ftp/FtpDirEntry.java \
+ sun/net/ftp/FtpReplyCode.java \
+ sun/net/ftp/FtpDirParser.java \
sun/net/ftp/FtpLoginException.java \
sun/net/ftp/FtpProtocolException.java \
+ sun/net/ftp/impl/FtpClient.java \
+ sun/net/ftp/impl/DefaultFtpClientProvider.java \
sun/net/spi/DefaultProxySelector.java \
sun/net/spi/nameservice/NameServiceDescriptor.java \
sun/net/spi/nameservice/NameService.java \
diff --git a/jdk/src/share/classes/sun/net/ftp/FtpClient.java b/jdk/src/share/classes/sun/net/ftp/FtpClient.java
index 2cd51192cef56b1ed4c7c1e479b327c8c386351f..35e6b6561ed20461b0edd097e2ef3b06587a1349 100644
--- a/jdk/src/share/classes/sun/net/ftp/FtpClient.java
+++ b/jdk/src/share/classes/sun/net/ftp/FtpClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright 1994-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -22,794 +22,922 @@
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
-
package sun.net.ftp;
-import java.util.StringTokenizer;
-import java.util.regex.*;
-import java.io.*;
import java.net.*;
-import sun.net.TransferProtocolClient;
-import sun.net.TelnetInputStream;
-import sun.net.TelnetOutputStream;
-import sun.misc.RegexpPool;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
+import java.io.*;
+import java.util.Date;
+import java.util.List;
+import java.util.Iterator;
/**
- * This class implements the FTP client.
+ * A class that implements the FTP protocol according to
+ * RFCs 959,
+ * 2228,
+ * 2389,
+ * 2428,
+ * 3659,
+ * 4217.
+ * Which includes support for FTP over SSL/TLS (aka ftps).
*
- * @author Jonathan Payne
+ * {@code FtpClient} provides all the functionalities of a typical FTP
+ * client, like storing or retrieving files, listing or creating directories.
+ * A typical usage would consist of connecting the client to the server,
+ * log in, issue a few commands then logout.
+ * Here is a code example:
+ *
Error reporting: There are, mostly, two families of errors that
+ * can occur during an FTP session. The first kind are the network related issues
+ * like a connection reset, and they are usually fatal to the session, meaning,
+ * in all likelyhood the connection to the server has been lost and the session
+ * should be restarted from scratch. These errors are reported by throwing an
+ * {@link IOException}. The second kind are the errors reported by the FTP server,
+ * like when trying to download a non-existing file for example. These errors
+ * are usually non fatal to the session, meaning more commands can be sent to the
+ * server. In these cases, a {@link FtpProtocolException} is thrown.
+ *
+ * It should be noted that this is not a thread-safe API, as it wouldn't make
+ * too much sense, due to the very sequential nature of FTP, to provide a
+ * client able to be manipulated from multiple threads.
+ *
+ * @since 1.7
*/
+public abstract class FtpClient implements java.io.Closeable {
-public class FtpClient extends TransferProtocolClient {
- public static final int FTP_PORT = 21;
-
- static int FTP_SUCCESS = 1;
- static int FTP_TRY_AGAIN = 2;
- static int FTP_ERROR = 3;
+ private static final int FTP_PORT = 21;
- /** remember the ftp server name because we may need it */
- private String serverName = null;
+ public static enum TransferType {
- /** socket for data transfer */
- private boolean replyPending = false;
- private boolean binaryMode = false;
- private boolean loggedIn = false;
+ ASCII, BINARY, EBCDIC
+ };
- /** regexp pool of hosts for which we should connect directly, not Proxy
- * these are intialized from a property.
+ /**
+ * Returns the default FTP port number.
+ *
+ * @return the port number.
*/
- private static RegexpPool nonProxyHostsPool = null;
+ public static final int defaultPort() {
+ return FTP_PORT;
+ }
- /** The string soucre of nonProxyHostsPool
+ /**
+ * Creates an instance of FtpClient. The client is not connected to any
+ * server yet.
+ *
*/
- private static String nonProxyHostsSource = null;
-
- /** last command issued */
- String command;
-
- /** The last reply code from the ftp daemon. */
- int lastReplyCode;
-
- /** Welcome message from the server, if any. */
- public String welcomeMsg;
-
+ protected FtpClient() {
+ }
- /* these methods are used to determine whether ftp urls are sent to */
- /* an http server instead of using a direct connection to the */
- /* host. They aren't used directly here. */
/**
- * @return if the networking layer should send ftp connections through
- * a proxy
+ * Creates an instance of {@code FtpClient}. The client is not connected to any
+ * server yet.
+ *
+ * @return the created {@code FtpClient}
*/
- public static boolean getUseFtpProxy() {
- // if the ftp.proxyHost is set, use it!
- return (getFtpProxyHost() != null);
+ public static FtpClient create() {
+ FtpClientProvider provider = FtpClientProvider.provider();
+ return provider.createFtpClient();
}
/**
- * @return the host to use, or null if none has been specified
- */
- public static String getFtpProxyHost() {
- return java.security.AccessController.doPrivileged(
- new java.security.PrivilegedAction() {
- public String run() {
- String result = System.getProperty("ftp.proxyHost");
- if (result == null) {
- result = System.getProperty("ftpProxyHost");
- }
- if (result == null) {
- // as a last resort we use the general one if ftp.useProxy
- // is true
- if (Boolean.getBoolean("ftp.useProxy")) {
- result = System.getProperty("proxyHost");
- }
- }
- return result;
- }
- });
+ * Creates an instance of FtpClient and connects it to the specified
+ * address.
+ *
+ * @param dest the {@code InetSocketAddress} to connect to.
+ * @return The created {@code FtpClient}
+ * @throws IOException if the connection fails
+ * @see #connect(java.net.SocketAddress)
+ */
+ public static FtpClient create(InetSocketAddress dest) throws FtpProtocolException, IOException {
+ FtpClient client = create();
+ if (dest != null) {
+ client.connect(dest);
+ }
+ return client;
}
/**
- * @return the proxy port to use. Will default reasonably if not set.
- */
- public static int getFtpProxyPort() {
- final int result[] = {80};
- java.security.AccessController.doPrivileged(
- new java.security.PrivilegedAction() {
- public Void run() {
-
- String tmp = System.getProperty("ftp.proxyPort");
- if (tmp == null) {
- // for compatibility with 1.0.2
- tmp = System.getProperty("ftpProxyPort");
- }
- if (tmp == null) {
- // as a last resort we use the general one if ftp.useProxy
- // is true
- if (Boolean.getBoolean("ftp.useProxy")) {
- tmp = System.getProperty("proxyPort");
- }
- }
- if (tmp != null) {
- result[0] = Integer.parseInt(tmp);
- }
- return null;
- }
- });
- return result[0];
+ * Creates an instance of {@code FtpClient} and connects it to the
+ * specified host on the default FTP port.
+ *
+ * @param dest the {@code String} containing the name of the host
+ * to connect to.
+ * @return The created {@code FtpClient}
+ * @throws IOException if the connection fails.
+ * @throws FtpProtocolException if the server rejected the connection
+ */
+ public static FtpClient create(String dest) throws FtpProtocolException, IOException {
+ return create(new InetSocketAddress(dest, FTP_PORT));
}
- public static boolean matchNonProxyHosts(String host) {
- synchronized (FtpClient.class) {
- String rawList = java.security.AccessController.doPrivileged(
- new sun.security.action.GetPropertyAction("ftp.nonProxyHosts"));
- if (rawList == null) {
- nonProxyHostsPool = null;
- } else {
- if (!rawList.equals(nonProxyHostsSource)) {
- RegexpPool pool = new RegexpPool();
- StringTokenizer st = new StringTokenizer(rawList, "|", false);
- try {
- while (st.hasMoreTokens()) {
- pool.add(st.nextToken().toLowerCase(), Boolean.TRUE);
- }
- } catch (sun.misc.REException ex) {
- System.err.println("Error in http.nonProxyHosts system property: " + ex);
- }
- nonProxyHostsPool = pool;
- }
- }
- nonProxyHostsSource = rawList;
- }
-
- if (nonProxyHostsPool == null) {
- return false;
- }
-
- if (nonProxyHostsPool.match(host) != null) {
- return true;
- } else {
- return false;
- }
- }
+ /**
+ * Enables, or disables, the use of the passive mode. In that mode,
+ * data connections are established by having the client connect to the server.
+ * This is the recommended default mode as it will work best through
+ * firewalls and NATs. If set to {@code false} the mode is said to be
+ * active which means the server will connect back to the client
+ * after a PORT command to establish a data connection.
+ *
+ *
Note: Since the passive mode might not be supported by all
+ * FTP servers, enabling it means the client will try to use it. If the
+ * server rejects it, then the client will attempt to fall back to using
+ * the active mode by issuing a {@code PORT} command instead.
+ *
+ * @param passive {@code true} to force passive mode.
+ * @return This FtpClient
+ * @see #isPassiveModeEnabled()
+ */
+ public abstract FtpClient enablePassiveMode(boolean passive);
/**
- * issue the QUIT command to the FTP server and close the connection.
+ * Tests whether passive mode is enabled.
*
- * @exception FtpProtocolException if an error occured
+ * @return {@code true} if the passive mode has been enabled.
+ * @see #enablePassiveMode(boolean)
*/
- public void closeServer() throws IOException {
- if (serverIsOpen()) {
- issueCommand("QUIT");
- super.closeServer();
- }
- }
+ public abstract boolean isPassiveModeEnabled();
/**
- * Send a command to the FTP server.
+ * Sets the default timeout value to use when connecting to the server,
*
- * @param cmd String containing the command
- * @return reply code
+ * @param timeout the timeout value, in milliseconds, to use for the connect
+ * operation. A value of zero or less, means use the default timeout.
*
- * @exception FtpProtocolException if an error occured
+ * @return This FtpClient
*/
- protected int issueCommand(String cmd) throws IOException {
- command = cmd;
-
- int reply;
-
- while (replyPending) {
- replyPending = false;
- if (readReply() == FTP_ERROR)
- throw new FtpProtocolException("Error reading FTP pending reply\n");
- }
- do {
- sendServer(cmd + "\r\n");
- reply = readReply();
- } while (reply == FTP_TRY_AGAIN);
- return reply;
- }
+ public abstract FtpClient setConnectTimeout(int timeout);
/**
- * Send a command to the FTP server and check for success.
+ * Returns the current default connection timeout value.
*
- * @param cmd String containing the command
+ * @return the value, in milliseconds, of the current connect timeout.
+ * @see #setConnectTimeout(int)
+ */
+ public abstract int getConnectTimeout();
+
+ /**
+ * Sets the timeout value to use when reading from the server,
*
- * @exception FtpProtocolException if an error occured
+ * @param timeout the timeout value, in milliseconds, to use for the read
+ * operation. A value of zero or less, means use the default timeout.
+ * @return This FtpClient
*/
- protected void issueCommandCheck(String cmd) throws IOException {
- if (issueCommand(cmd) != FTP_SUCCESS)
- throw new FtpProtocolException(cmd + ":" + getResponseString());
- }
+ public abstract FtpClient setReadTimeout(int timeout);
/**
- * Read the reply from the FTP server.
+ * Returns the current read timeout value.
*
- * @return FTP_SUCCESS or FTP_ERROR depending on success
- * @exception FtpProtocolException if an error occured
+ * @return the value, in milliseconds, of the current read timeout.
+ * @see #setReadTimeout(int)
*/
- protected int readReply() throws IOException {
- lastReplyCode = readServerResponse();
+ public abstract int getReadTimeout();
- switch (lastReplyCode / 100) {
- case 1:
- replyPending = true;
- /* falls into ... */
+ /**
+ * Set the {@code Proxy} to be used for the next connection.
+ * If the client is already connected, it doesn't affect the current
+ * connection. However it is not recommended to change this during a session.
+ *
+ * @param p the {@code Proxy} to use, or {@code null} for no proxy.
+ * @return This FtpClient
+ */
+ public abstract FtpClient setProxy(Proxy p);
- case 2:
- case 3:
- return FTP_SUCCESS;
+ /**
+ * Get the proxy of this FtpClient
+ *
+ * @return the {@code Proxy}, this client is using, or {@code null}
+ * if none is used.
+ * @see #setProxy(Proxy)
+ */
+ public abstract Proxy getProxy();
- case 5:
- if (lastReplyCode == 530) {
- if (!loggedIn) {
- throw new FtpLoginException("Not logged in");
- }
- return FTP_ERROR;
- }
- if (lastReplyCode == 550) {
- throw new FileNotFoundException(command + ": " + getResponseString());
- }
- }
+ /**
+ * Tests whether this client is connected or not to a server.
+ *
+ * @return {@code true} if the client is connected.
+ */
+ public abstract boolean isConnected();
- /* this statement is not reached */
- return FTP_ERROR;
- }
+ /**
+ * Connects the {@code FtpClient} to the specified destination server.
+ *
+ * @param dest the address of the destination server
+ * @return this FtpClient
+ * @throws IOException if connection failed.
+ * @throws SecurityException if there is a SecurityManager installed and it
+ * denied the authorization to connect to the destination.
+ * @throws FtpProtocolException
+ */
+ public abstract FtpClient connect(SocketAddress dest) throws FtpProtocolException, IOException;
/**
- * Tries to open a Data Connection in "PASSIVE" mode by issuing a EPSV or
- * PASV command then opening a Socket to the specified address & port
- *
- * @return the opened socket
- * @exception FtpProtocolException if an error occurs when issuing the
- * PASV command to the ftp server.
- */
- protected Socket openPassiveDataConnection() throws IOException {
- String serverAnswer;
- int port;
- InetSocketAddress dest = null;
-
- /**
- * Here is the idea:
- *
- * - First we want to try the new (and IPv6 compatible) EPSV command
- * But since we want to be nice with NAT software, we'll issue the
- * EPSV ALL cmd first.
- * EPSV is documented in RFC2428
- * - If EPSV fails, then we fall back to the older, yet OK PASV command
- * - If PASV fails as well, then we throw an exception and the calling method
- * will have to try the EPRT or PORT command
- */
- if (issueCommand("EPSV ALL") == FTP_SUCCESS) {
- // We can safely use EPSV commands
- if (issueCommand("EPSV") == FTP_ERROR)
- throw new FtpProtocolException("EPSV Failed: " + getResponseString());
- serverAnswer = getResponseString();
-
- // The response string from a EPSV command will contain the port number
- // the format will be :
- // 229 Entering Extended Passive Mode (|||58210|)
- //
- // So we'll use the regular expresions package to parse the output.
-
- Pattern p = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)");
- Matcher m = p.matcher(serverAnswer);
- if (! m.find())
- throw new FtpProtocolException("EPSV failed : " + serverAnswer);
- // Yay! Let's extract the port number
- String s = m.group(1);
- port = Integer.parseInt(s);
- InetAddress add = serverSocket.getInetAddress();
- if (add != null) {
- dest = new InetSocketAddress(add, port);
- } else {
- // This means we used an Unresolved address to connect in
- // the first place. Most likely because the proxy is doing
- // the name resolution for us, so let's keep using unresolved
- // address.
- dest = InetSocketAddress.createUnresolved(serverName, port);
- }
- } else {
- // EPSV ALL failed, so Let's try the regular PASV cmd
- if (issueCommand("PASV") == FTP_ERROR)
- throw new FtpProtocolException("PASV failed: " + getResponseString());
- serverAnswer = getResponseString();
-
- // Let's parse the response String to get the IP & port to connect to
- // the String should be in the following format :
- //
- // 227 Entering Passive Mode (A1,A2,A3,A4,p1,p2)
- //
- // Note that the two parenthesis are optional
- //
- // The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2
- //
- // The regular expression is a bit more complex this time, because the
- // parenthesis are optionals and we have to use 3 groups.
-
- Pattern p = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?");
- Matcher m = p.matcher(serverAnswer);
- if (! m.find())
- throw new FtpProtocolException("PASV failed : " + serverAnswer);
- // Get port number out of group 2 & 3
- port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8);
- // IP address is simple
- String s = m.group(1).replace(',','.');
- dest = new InetSocketAddress(s, port);
- }
- // Got everything, let's open the socket!
- Socket s;
- if (proxy != null) {
- if (proxy.type() == Proxy.Type.SOCKS) {
- s = AccessController.doPrivileged(
- new PrivilegedAction() {
- public Socket run() {
- return new Socket(proxy);
- }});
- } else
- s = new Socket(Proxy.NO_PROXY);
- } else
- s = new Socket();
- // Bind the socket to the same address as the control channel. This
- // is needed in case of multi-homed systems.
- s.bind(new InetSocketAddress(serverSocket.getLocalAddress(),0));
- if (connectTimeout >= 0) {
- s.connect(dest, connectTimeout);
- } else {
- if (defaultConnectTimeout > 0) {
- s.connect(dest, defaultConnectTimeout);
- } else {
- s.connect(dest);
- }
- }
- if (readTimeout >= 0)
- s.setSoTimeout(readTimeout);
- else
- if (defaultSoTimeout > 0) {
- s.setSoTimeout(defaultSoTimeout);
- }
- return s;
- }
+ * Connects the FtpClient to the specified destination server.
+ *
+ * @param dest the address of the destination server
+ * @param timeout the value, in milliseconds, to use as a connection timeout
+ * @return this FtpClient
+ * @throws IOException if connection failed.
+ * @throws SecurityException if there is a SecurityManager installed and it
+ * denied the authorization to connect to the destination.
+ * @throws FtpProtocolException
+ */
+ public abstract FtpClient connect(SocketAddress dest, int timeout) throws FtpProtocolException, IOException;
/**
- * Tries to open a Data Connection with the server. It will first try a passive
- * mode connection, then, if it fails, a more traditional PORT command
+ * Retrieves the address of the FTP server this client is connected to.
*
- * @param cmd the command to execute (RETR, STOR, etc...)
- * @return the opened socket
- *
- * @exception FtpProtocolException if an error occurs when issuing the
- * PORT command to the ftp server.
+ * @return the {@link SocketAddress} of the server, or {@code null} if this
+ * client is not connected yet.
*/
- protected Socket openDataConnection(String cmd) throws IOException {
- ServerSocket portSocket;
- Socket clientSocket = null;
- String portCmd;
- InetAddress myAddress;
- IOException e;
-
- // Let's try passive mode first
- try {
- clientSocket = openPassiveDataConnection();
- } catch (IOException ex) {
- clientSocket = null;
- }
- if (clientSocket != null) {
- // We did get a clientSocket, so the passive mode worked
- // Let's issue the command (GET, DIR, ...)
- try {
- if (issueCommand(cmd) == FTP_ERROR) {
- clientSocket.close();
- throw new FtpProtocolException(getResponseString());
- } else
- return clientSocket;
- } catch (IOException ioe) {
- clientSocket.close();
- throw ioe;
- }
- }
+ public abstract SocketAddress getServerAddress();
- assert(clientSocket == null);
+ /**
+ * Attempts to log on the server with the specified user name and password.
+ *
+ * @param user The user name
+ * @param password The password for that user
+ * @return this FtpClient
+ * @throws IOException if an error occured during the transmission
+ * @throws FtpProtocolException if the login was refused by the server
+ */
+ public abstract FtpClient login(String user, char[] password) throws FtpProtocolException, IOException;
- // Passive mode failed, let's fall back to the good old "PORT"
+ /**
+ * Attempts to log on the server with the specified user name, password and
+ * account name.
+ *
+ * @param user The user name
+ * @param password The password for that user.
+ * @param account The account name for that user.
+ * @return this FtpClient
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the login was refused by the server
+ */
+ public abstract FtpClient login(String user, char[] password, String account) throws FtpProtocolException, IOException;
- if (proxy != null && proxy.type() == Proxy.Type.SOCKS) {
- // We're behind a firewall and the passive mode fail,
- // since we can't accept a connection through SOCKS (yet)
- // throw an exception
- throw new FtpProtocolException("Passive mode failed");
- }
- // Bind the ServerSocket to the same address as the control channel
- // This is needed for multi-homed systems
- portSocket = new ServerSocket(0, 1, serverSocket.getLocalAddress());
- try {
- myAddress = portSocket.getInetAddress();
- if (myAddress.isAnyLocalAddress())
- myAddress = getLocalAddress();
- // Let's try the new, IPv6 compatible EPRT command
- // See RFC2428 for specifics
- // Some FTP servers (like the one on Solaris) are bugged, they
- // will accept the EPRT command but then, the subsequent command
- // (e.g. RETR) will fail, so we have to check BOTH results (the
- // EPRT cmd then the actual command) to decide wether we should
- // fall back on the older PORT command.
- portCmd = "EPRT |" +
- ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" +
- myAddress.getHostAddress() +"|" +
- portSocket.getLocalPort()+"|";
- if (issueCommand(portCmd) == FTP_ERROR ||
- issueCommand(cmd) == FTP_ERROR) {
- // The EPRT command failed, let's fall back to good old PORT
- portCmd = "PORT ";
- byte[] addr = myAddress.getAddress();
-
- /* append host addr */
- for (int i = 0; i < addr.length; i++) {
- portCmd = portCmd + (addr[i] & 0xFF) + ",";
- }
-
- /* append port number */
- portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + ","
- + (portSocket.getLocalPort() & 0xff);
- if (issueCommand(portCmd) == FTP_ERROR) {
- e = new FtpProtocolException("PORT :" + getResponseString());
- throw e;
- }
- if (issueCommand(cmd) == FTP_ERROR) {
- e = new FtpProtocolException(cmd + ":" + getResponseString());
- throw e;
- }
- }
- // Either the EPRT or the PORT command was successful
- // Let's create the client socket
- if (connectTimeout >= 0) {
- portSocket.setSoTimeout(connectTimeout);
- } else {
- if (defaultConnectTimeout > 0)
- portSocket.setSoTimeout(defaultConnectTimeout);
- }
- clientSocket = portSocket.accept();
- if (readTimeout >= 0)
- clientSocket.setSoTimeout(readTimeout);
- else {
- if (defaultSoTimeout > 0)
- clientSocket.setSoTimeout(defaultSoTimeout);
- }
- } finally {
- portSocket.close();
- }
+ /**
+ * Closes the current connection. Logs out the current user, if any, by
+ * issuing the QUIT command to the server.
+ * This is in effect terminates the current
+ * session and the connection to the server will be closed.
+ *
After a close, the client can then be connected to another server
+ * to start an entirely different session.
+ *
+ * @throws IOException if an error occurs during transmission
+ */
+ public abstract void close() throws IOException;
- return clientSocket;
- }
+ /**
+ * Checks whether the client is logged in to the server or not.
+ *
+ * @return {@code true} if the client has already completed a login.
+ */
+ public abstract boolean isLoggedIn();
- /* public methods */
+ /**
+ * Changes to a specific directory on a remote FTP server
+ *
+ * @param remoteDirectory path of the directory to CD to.
+ * @return this FtpClient
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the command was refused by the server
+ */
+ public abstract FtpClient changeDirectory(String remoteDirectory) throws FtpProtocolException, IOException;
/**
- * Open a FTP connection to host host.
+ * Changes to the parent directory, sending the CDUP command to the server.
*
- * @param host The hostname of the ftp server
+ * @return this FtpClient
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the command was refused by the server
+ */
+ public abstract FtpClient changeToParentDirectory() throws FtpProtocolException, IOException;
+
+ /**
+ * Retrieve the server current working directory using the PWD command.
*
- * @exception FtpProtocolException if connection fails
+ * @return a {@code String} containing the current working directory
+ * @throws IOException if an error occurs during transmission
+ * @throws FtpProtocolException if the command was refused by the server,
*/
- public void openServer(String host) throws IOException {
- openServer(host, FTP_PORT);
- }
+ public abstract String getWorkingDirectory() throws FtpProtocolException, IOException;
/**
- * Open a FTP connection to host host on port port.
+ * Sets the restart offset to the specified value. That value will be
+ * sent through a {@code REST} command to server before the next file
+ * transfer and has the effect of resuming a file transfer from the
+ * specified point. After the transfer the restart offset is set back to
+ * zero.
+ *
+ * @param offset the offset in the remote file at which to start the next
+ * transfer. This must be a value greater than or equal to zero.
+ * @return this FtpClient
+ * @throws IllegalArgumentException if the offset is negative.
+ */
+ public abstract FtpClient setRestartOffset(long offset);
+
+ /**
+ * Retrieves a file from the ftp server and writes its content to the specified
+ * {@code OutputStream}.
+ *
If the restart offset was set, then a {@code REST} command will be
+ * sent before the {@code RETR} in order to restart the tranfer from the specified
+ * offset.
+ *
The {@code OutputStream} is not closed by this method at the end
+ * of the transfer.
+ *
This method will block until the transfer is complete or an exception
+ * is thrown.
+ *
+ * @param name a {@code String} containing the name of the file to
+ * retreive from the server.
+ * @param local the {@code OutputStream} the file should be written to.
+ * @return this FtpClient
+ * @throws IOException if the transfer fails.
+ * @throws FtpProtocolException if the command was refused by the server
+ * @see #setRestartOffset(long)
+ */
+ public abstract FtpClient getFile(String name, OutputStream local) throws FtpProtocolException, IOException;
+
+ /**
+ * Retrieves a file from the ftp server, using the {@code RETR} command, and
+ * returns the InputStream from the established data connection.
+ * {@link #completePending()} has to be called once the application
+ * is done reading from the returned stream.
+ *
If the restart offset was set, then a {@code REST} command will be
+ * sent before the {@code RETR} in order to restart the tranfer from the specified
+ * offset.
+ *
+ * @param name the name of the remote file
+ * @return the {@link java.io.InputStream} from the data connection
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was refused by the server
+ * @see #setRestartOffset(long)
+ */
+ public abstract InputStream getFileStream(String name) throws FtpProtocolException, IOException;
+
+ /**
+ * Transfers a file from the client to the server (aka a put)
+ * by sending the STOR command, and returns the {@code OutputStream}
+ * from the established data connection.
+ *
+ * A new file is created at the server site if the file specified does
+ * not already exist.
*
- * @param host the hostname of the ftp server
- * @param port the port to connect to (usually 21)
+ * {@link #completePending()} has to be called once the application
+ * is finished writing to the returned stream.
*
- * @exception FtpProtocolException if connection fails
+ * @param name the name of the remote file to write.
+ * @return the {@link java.io.OutputStream} from the data connection or
+ * {@code null} if the command was unsuccessful.
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
*/
- public void openServer(String host, int port) throws IOException {
- this.serverName = host;
- super.openServer(host, port);
- if (readReply() == FTP_ERROR)
- throw new FtpProtocolException("Welcome message: " +
- getResponseString());
+ public OutputStream putFileStream(String name) throws FtpProtocolException, IOException {
+ return putFileStream(name, false);
}
+ /**
+ * Transfers a file from the client to the server (aka a put)
+ * by sending the STOR or STOU command, depending on the
+ * {@code unique} argument, and returns the {@code OutputStream}
+ * from the established data connection.
+ * {@link #completePending()} has to be called once the application
+ * is finished writing to the stream.
+ *
+ * A new file is created at the server site if the file specified does
+ * not already exist.
+ *
+ * If {@code unique} is set to {@code true}, the resultant file
+ * is to be created under a name unique to that directory, meaning
+ * it will not overwrite an existing file, instead the server will
+ * generate a new, unique, file name.
+ * The name of the remote file can be retrieved, after completion of the
+ * transfer, by calling {@link #getLastFileName()}.
+ *
+ * @param name the name of the remote file to write.
+ * @param unique {@code true} if the remote files should be unique,
+ * in which case the STOU command will be used.
+ * @return the {@link java.io.OutputStream} from the data connection.
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract OutputStream putFileStream(String name, boolean unique) throws FtpProtocolException, IOException;
/**
- * login user to a host with username user and password
- * password
- *
- * @param user Username to use at login
- * @param password Password to use at login or null of none is needed
- *
- * @exception FtpLoginException if login is unsuccesful
- */
- public void login(String user, String password) throws IOException {
- if (!serverIsOpen())
- throw new FtpLoginException("not connected to host");
- if (user == null || user.length() == 0)
- return;
- if (issueCommand("USER " + user) == FTP_ERROR)
- throw new FtpLoginException("user " + user + " : " + getResponseString());
- /*
- * Checks for "331 User name okay, need password." answer
- */
-
- if (lastReplyCode == 331)
- if ((password == null) || (password.length() == 0) ||
- (issueCommand("PASS " + password) == FTP_ERROR))
- throw new FtpLoginException("password: " + getResponseString());
-
- // keep the welcome message around so we can
- // put it in the resulting HTML page.
- String l;
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < serverResponse.size(); i++) {
- l = (String)serverResponse.elementAt(i);
- if (l != null) {
- if (l.length() >= 4 && l.startsWith("230")) {
- // get rid of the "230-" prefix
- l = l.substring(4);
- }
- sb.append(l);
- }
- }
- welcomeMsg = sb.toString();
- loggedIn = true;
+ * Transfers a file from the client to the server (aka a put)
+ * by sending the STOR or STOU command, depending on the
+ * {@code unique} argument. The content of the {@code InputStream}
+ * passed in argument is written into the remote file, overwriting any
+ * existing data.
+ *
+ * A new file is created at the server site if the file specified does
+ * not already exist.
+ *
+ * If {@code unique} is set to {@code true}, the resultant file
+ * is to be created under a name unique to that directory, meaning
+ * it will not overwrite an existing file, instead the server will
+ * generate a new, unique, file name.
+ * The name of the remote file can be retrieved, after completion of the
+ * transfer, by calling {@link #getLastFileName()}.
+ *
+ *
This method will block until the transfer is complete or an exception
+ * is thrown.
+ *
+ * @param name the name of the remote file to write.
+ * @param local the {@code InputStream} that points to the data to
+ * transfer.
+ * @return this FtpClient
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public FtpClient putFile(String name, InputStream local) throws FtpProtocolException, IOException {
+ return putFile(name, local, false);
}
/**
- * GET a file from the FTP server
- *
- * @param filename name of the file to retrieve
- * @return the InputStream to read the file from
- *
- * @exception FileNotFoundException if the file can't be opened
- */
- public TelnetInputStream get(String filename) throws IOException {
- Socket s;
-
- try {
- s = openDataConnection("RETR " + filename);
- } catch (FileNotFoundException fileException) {
- /* Well, "/" might not be the file delimitor for this
- particular ftp server, so let's try a series of
- "cd" commands to get to the right place. */
- /* But don't try this if there are no '/' in the path */
- if (filename.indexOf('/') == -1)
- throw fileException;
-
- StringTokenizer t = new StringTokenizer(filename, "/");
- String pathElement = null;
-
- while (t.hasMoreElements()) {
- pathElement = t.nextToken();
-
- if (!t.hasMoreElements()) {
- /* This is the file component. Look it up now. */
- break;
- }
- try {
- cd(pathElement);
- } catch (FtpProtocolException e) {
- /* Giving up. */
- throw fileException;
- }
- }
- if (pathElement != null) {
- s = openDataConnection("RETR " + pathElement);
- } else {
- throw fileException;
- }
- }
+ * Transfers a file from the client to the server (aka a put)
+ * by sending the STOR command. The content of the {@code InputStream}
+ * passed in argument is written into the remote file, overwriting any
+ * existing data.
+ *
+ * A new file is created at the server site if the file specified does
+ * not already exist.
+ *
+ *
This method will block until the transfer is complete or an exception
+ * is thrown.
+ *
+ * @param name the name of the remote file to write.
+ * @param local the {@code InputStream} that points to the data to
+ * transfer.
+ * @param unique {@code true} if the remote file should be unique
+ * (i.e. not already existing), {@code false} otherwise.
+ * @return this FtpClient
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ * @see #getLastFileName()
+ */
+ public abstract FtpClient putFile(String name, InputStream local, boolean unique) throws FtpProtocolException, IOException;
- return new TelnetInputStream(s.getInputStream(), binaryMode);
- }
+ /**
+ * Sends the APPE command to the server in order to transfer a data stream
+ * passed in argument and append it to the content of the specified remote
+ * file.
+ *
+ *
This method will block until the transfer is complete or an exception
+ * is thrown.
+ *
+ * @param name A {@code String} containing the name of the remote file
+ * to append to.
+ * @param local The {@code InputStream} providing access to the data
+ * to be appended.
+ * @return this FtpClient
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract FtpClient appendFile(String name, InputStream local) throws FtpProtocolException, IOException;
/**
- * PUT a file to the FTP server
- *
- * @param filename name of the file to store
- * @return the OutputStream to write the file to
+ * Renames a file on the server.
*
+ * @param from the name of the file being renamed
+ * @param to the new name for the file
+ * @return this FtpClient
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
*/
- public TelnetOutputStream put(String filename) throws IOException {
- Socket s = openDataConnection("STOR " + filename);
- TelnetOutputStream out = new TelnetOutputStream(s.getOutputStream(), binaryMode);
- if (!binaryMode)
- out.setStickyCRLF(true);
- return out;
- }
+ public abstract FtpClient rename(String from, String to) throws FtpProtocolException, IOException;
/**
- * Append to a file on the FTP server
- *
- * @param filename name of the file to append to
- * @return the OutputStream to write the file to
+ * Deletes a file on the server.
*
+ * @param name a {@code String} containing the name of the file
+ * to delete.
+ * @return this FtpClient
+ * @throws IOException if an error occured during the exchange
+ * @throws FtpProtocolException if the command was rejected by the server
*/
- public TelnetOutputStream append(String filename) throws IOException {
- Socket s = openDataConnection("APPE " + filename);
- TelnetOutputStream out = new TelnetOutputStream(s.getOutputStream(), binaryMode);
- if (!binaryMode)
- out.setStickyCRLF(true);
+ public abstract FtpClient deleteFile(String name) throws FtpProtocolException, IOException;
- return out;
- }
+ /**
+ * Creates a new directory on the server.
+ *
+ * @param name a {@code String} containing the name of the directory
+ * to create.
+ * @return this FtpClient
+ * @throws IOException if an error occured during the exchange
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract FtpClient makeDirectory(String name) throws FtpProtocolException, IOException;
/**
- * LIST files in the current directory on a remote FTP server
+ * Removes a directory on the server.
*
- * @return the InputStream to read the list from
+ * @param name a {@code String} containing the name of the directory
+ * to remove.
*
+ * @return this FtpClient
+ * @throws IOException if an error occured during the exchange.
+ * @throws FtpProtocolException if the command was rejected by the server
*/
- public TelnetInputStream list() throws IOException {
- Socket s = openDataConnection("LIST");
-
- return new TelnetInputStream(s.getInputStream(), binaryMode);
- }
+ public abstract FtpClient removeDirectory(String name) throws FtpProtocolException, IOException;
/**
- * List (NLST) file names on a remote FTP server
+ * Sends a No-operation command. It's useful for testing the connection
+ * status or as a keep alive mechanism.
*
- * @param path pathname to the directory to list, null for current
- * directory
- * @return the InputStream to read the list from
- * @exception FtpProtocolException
+ * @return this FtpClient
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
*/
- public TelnetInputStream nameList(String path) throws IOException {
- Socket s;
+ public abstract FtpClient noop() throws FtpProtocolException, IOException;
- if (path != null)
- s = openDataConnection("NLST " + path);
- else
- s = openDataConnection("NLST");
- return new TelnetInputStream(s.getInputStream(), binaryMode);
- }
+ /**
+ * Sends the {@code STAT} command to the server.
+ * This can be used while a data connection is open to get a status
+ * on the current transfer, in that case the parameter should be
+ * {@code null}.
+ * If used between file transfers, it may have a pathname as argument
+ * in which case it will work as the LIST command except no data
+ * connection will be created.
+ *
+ * @param name an optional {@code String} containing the pathname
+ * the STAT command should apply to.
+ * @return the response from the server
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract String getStatus(String name) throws FtpProtocolException, IOException;
/**
- * CD to a specific directory on a remote FTP server
+ * Sends the {@code FEAT} command to the server and returns the list of supported
+ * features in the form of strings.
*
- * @param remoteDirectory path of the directory to CD to
+ * The features are the supported commands, like AUTH TLS, PROT or PASV.
+ * See the RFCs for a complete list.
*
- * @exception FtpProtocolException
+ * Note that not all FTP servers support that command, in which case
+ * a {@link FtpProtocolException} will be thrown.
+ *
+ * @return a {@code List} of {@code Strings} describing the
+ * supported additional features
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the command is rejected by the server
*/
- public void cd(String remoteDirectory) throws IOException {
- if (remoteDirectory == null ||
- "".equals(remoteDirectory))
- return;
- issueCommandCheck("CWD " + remoteDirectory);
- }
+ public abstract List getFeatures() throws FtpProtocolException, IOException;
+
+ /**
+ * Sends the {@code ABOR} command to the server.
+ *
It tells the server to stop the previous command or transfer. No action
+ * will be taken if the previous command has already been completed.
+ *
This doesn't abort the current session, more commands can be issued
+ * after an abort.
+ *
+ * @return this FtpClient
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract FtpClient abort() throws FtpProtocolException, IOException;
/**
- * CD to the parent directory on a remote FTP server
+ * Some methods do not wait until completion before returning, so this
+ * method can be called to wait until completion. This is typically the case
+ * with commands that trigger a transfer like {@link #getFileStream(String)}.
+ * So this method should be called before accessing information related to
+ * such a command.
+ *
This method will actually block reading on the command channel for a
+ * notification from the server that the command is finished. Such a
+ * notification often carries extra information concerning the completion
+ * of the pending action (e.g. number of bytes transfered).
+ *
Note that this will return immediately if no command or action
+ * is pending
+ *
It should be also noted that most methods issuing commands to the ftp
+ * server will call this method if a previous command is pending.
+ *
Example of use:
+ *
+ * InputStream in = cl.getFileStream("file");
+ * ...
+ * cl.completePending();
+ * long size = cl.getLastTransferSize();
+ *
+ * On the other hand, it's not necessary in a case like:
+ *
Since {@link #close()} will call completePending() if necessary.
+ * @return this FtpClient
+ * @throws IOException if an error occured during the transfer
+ * @throws FtpProtocolException if the command didn't complete successfully
+ */
+ public abstract FtpClient completePending() throws FtpProtocolException, IOException;
+
+ /**
+ * Reinitializes the USER parameters on the FTP server
*
+ * @return this FtpClient
+ * @throws IOException if an error occurs during transmission
+ * @throws FtpProtocolException if the command fails
*/
- public void cdUp() throws IOException {
- issueCommandCheck("CDUP");
- }
+ public abstract FtpClient reInit() throws FtpProtocolException, IOException;
/**
- * Print working directory of remote FTP server
+ * Changes the transfer type (binary, ascii, ebcdic) and issue the
+ * proper command (e.g. TYPE A) to the server.
*
- * @exception FtpProtocolException if the command fails
+ * @param type the {@code TransferType} to use.
+ * @return This FtpClient
+ * @throws IOException if an error occurs during transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
*/
- public String pwd() throws IOException {
- String answ;
+ public abstract FtpClient setType(TransferType type) throws FtpProtocolException, IOException;
- issueCommandCheck("PWD");
- /*
- * answer will be of the following format :
- *
- * 257 "/" is current directory.
- */
- answ = getResponseString();
- if (!answ.startsWith("257"))
- throw new FtpProtocolException("PWD failed. " + answ);
- return answ.substring(5, answ.lastIndexOf('"'));
+ /**
+ * Changes the current transfer type to binary.
+ * This is a convenience method that is equivalent to
+ * {@code setType(TransferType.BINARY)}
+ *
+ * @return This FtpClient
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ * @see #setType(TransferType)
+ */
+ public FtpClient setBinaryType() throws FtpProtocolException, IOException {
+ setType(TransferType.BINARY);
+ return this;
}
/**
- * Set transfer type to 'I'
- *
- * @exception FtpProtocolException if the command fails
+ * Changes the current transfer type to ascii.
+ * This is a convenience method that is equivalent to
+ * {@code setType(TransferType.ASCII)}
+ *
+ * @return This FtpClient
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ * @see #setType(TransferType)
*/
- public void binary() throws IOException {
- issueCommandCheck("TYPE I");
- binaryMode = true;
+ public FtpClient setAsciiType() throws FtpProtocolException, IOException {
+ setType(TransferType.ASCII);
+ return this;
}
/**
- * Set transfer type to 'A'
- *
- * @exception FtpProtocolException if the command fails
+ * Issues a {@code LIST} command to the server to get the current directory
+ * listing, and returns the InputStream from the data connection.
+ *
+ *
{@link #completePending()} has to be called once the application
+ * is finished reading from the stream.
+ *
+ * @param path the pathname of the directory to list, or {@code null}
+ * for the current working directory.
+ * @return the {@code InputStream} from the resulting data connection
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ * @see #changeDirectory(String)
+ * @see #listFiles(String)
*/
- public void ascii() throws IOException {
- issueCommandCheck("TYPE A");
- binaryMode = false;
- }
+ public abstract InputStream list(String path) throws FtpProtocolException, IOException;
/**
- * Rename a file on the ftp server
- *
- * @exception FtpProtocolException if the command fails
+ * Issues a {@code NLST path} command to server to get the specified directory
+ * content. It differs from {@link #list(String)} method by the fact that
+ * it will only list the file names which would make the parsing of the
+ * somewhat easier.
+ *
+ *
{@link #completePending()} has to be called once the application
+ * is finished reading from the stream.
+ *
+ * @param path a {@code String} containing the pathname of the
+ * directory to list or {@code null} for the current working directory.
+ * @return the {@code InputStream} from the resulting data connection
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
*/
- public void rename(String from, String to) throws IOException {
- issueCommandCheck("RNFR " + from);
- issueCommandCheck("RNTO " + to);
- }
+ public abstract InputStream nameList(String path) throws FtpProtocolException, IOException;
/**
- * Get the "System string" from the FTP server
- *
- * @exception FtpProtocolException if it fails
+ * Issues the {@code SIZE [path]} command to the server to get the size of a
+ * specific file on the server.
+ * Note that this command may not be supported by the server. In which
+ * case -1 will be returned.
+ *
+ * @param path a {@code String} containing the pathname of the
+ * file.
+ * @return a {@code long} containing the size of the file or -1 if
+ * the server returned an error, which can be checked with
+ * {@link #getLastReplyCode()}.
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
*/
- public String system() throws IOException {
- String answ;
- issueCommandCheck("SYST");
- answ = getResponseString();
- if (!answ.startsWith("215"))
- throw new FtpProtocolException("SYST failed." + answ);
- return answ.substring(4); // Skip "215 "
- }
+ public abstract long getSize(String path) throws FtpProtocolException, IOException;
+
+ /**
+ * Issues the {@code MDTM [path]} command to the server to get the modification
+ * time of a specific file on the server.
+ * Note that this command may not be supported by the server, in which
+ * case {@code null} will be returned.
+ *
+ * @param path a {@code String} containing the pathname of the file.
+ * @return a {@code Date} representing the last modification time
+ * or {@code null} if the server returned an error, which
+ * can be checked with {@link #getLastReplyCode()}.
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract Date getLastModified(String path) throws FtpProtocolException, IOException;
/**
- * Send a No-operation command. It's usefull for testing the connection status
+ * Sets the parser used to handle the directory output to the specified
+ * one. By default the parser is set to one that can handle most FTP
+ * servers output (Unix base mostly). However it may be necessary for
+ * and application to provide its own parser due to some uncommon
+ * output format.
+ *
+ * @param p The {@code FtpDirParser} to use.
+ * @return this FtpClient
+ * @see #listFiles(String)
+ */
+ public abstract FtpClient setDirParser(FtpDirParser p);
+
+ /**
+ * Issues a {@code MLSD} command to the server to get the specified directory
+ * listing and applies the internal parser to create an Iterator of
+ * {@link java.net.FtpDirEntry}. Note that the Iterator returned is also a
+ * {@link java.io.Closeable}.
+ *
If the server doesn't support the MLSD command, the LIST command is used
+ * instead and the parser set by {@link #setDirParser(java.net.FtpDirParser) }
+ * is used instead.
+ *
+ * {@link #completePending()} has to be called once the application
+ * is finished iterating through the files.
+ *
+ * @param path the pathname of the directory to list or {@code null}
+ * for the current working directoty.
+ * @return a {@code Iterator} of files or {@code null} if the
+ * command failed.
+ * @throws IOException if an error occured during the transmission
+ * @see #setDirParser(FtpDirParser)
+ * @see #changeDirectory(String)
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract Iterator listFiles(String path) throws FtpProtocolException, IOException;
+
+ /**
+ * Attempts to use Kerberos GSSAPI as an authentication mechanism with the
+ * ftp server. This will issue an {@code AUTH GSSAPI} command, and if
+ * it is accepted by the server, will followup with {@code ADAT}
+ * command to exchange the various tokens until authentication is
+ * successful. This conforms to Appendix I of RFC 2228.
+ *
+ * @return this FtpClient
+ * @throws IOException if an error occurs during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract FtpClient useKerberos() throws FtpProtocolException, IOException;
+
+ /**
+ * Returns the Welcome string the server sent during initial connection.
*
- * @exception FtpProtocolException if the command fails
+ * @return a {@code String} containing the message the server
+ * returned during connection or {@code null}.
*/
- public void noop() throws IOException {
- issueCommandCheck("NOOP");
- }
+ public abstract String getWelcomeMsg();
/**
- * Reinitialize the USER parameters on the FTp server
+ * Returns the last reply code sent by the server.
*
- * @exception FtpProtocolException if the command fails
+ * @return the lastReplyCode or {@code null} if none were received yet.
*/
- public void reInit() throws IOException {
- issueCommandCheck("REIN");
- loggedIn = false;
- }
+ public abstract FtpReplyCode getLastReplyCode();
/**
- * New FTP client connected to host host.
+ * Returns the last response string sent by the server.
*
- * @param host Hostname of the FTP server
+ * @return the message string, which can be quite long, last returned
+ * by the server, or {@code null} if no response were received yet.
+ */
+ public abstract String getLastResponseString();
+
+ /**
+ * Returns, when available, the size of the latest started transfer.
+ * This is retreived by parsing the response string received as an initial
+ * response to a {@code RETR} or similar request.
*
- * @exception FtpProtocolException if the connection fails
+ * @return the size of the latest transfer or -1 if either there was no
+ * transfer or the information was unavailable.
*/
- public FtpClient(String host) throws IOException {
- super();
- openServer(host, FTP_PORT);
- }
+ public abstract long getLastTransferSize();
/**
- * New FTP client connected to host host, port port.
+ * Returns, when available, the remote name of the last transfered file.
+ * This is mainly useful for "put" operation when the unique flag was
+ * set since it allows to recover the unique file name created on the
+ * server which may be different from the one submitted with the command.
*
- * @param host Hostname of the FTP server
- * @param port port number to connect to (usually 21)
+ * @return the name the latest transfered file remote name, or
+ * {@code null} if that information is unavailable.
+ */
+ public abstract String getLastFileName();
+
+ /**
+ * Attempts to switch to a secure, encrypted connection. This is done by
+ * sending the {@code AUTH TLS} command.
+ *
+ * If successful this will establish a secure command channel with the
+ * server, it will also make it so that all other transfers (e.g. a RETR
+ * command) will be done over an encrypted channel as well unless a
+ * {@link #reInit()} command or a {@link #endSecureSession()} command is issued.
+ *
This method should be called after a successful {@link #connect(java.net.InetSocketAddress) }
+ * but before calling {@link #login(java.lang.String, char[]) }.
+ *
+ * @return this FtpCLient
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ * @see #endSecureSession()
+ */
+ public abstract FtpClient startSecureSession() throws FtpProtocolException, IOException;
+
+ /**
+ * Sends a {@code CCC} command followed by a {@code PROT C}
+ * command to the server terminating an encrypted session and reverting
+ * back to a non encrypted transmission.
+ *
+ * @return this FtpClient
+ * @throws IOException if an error occured during transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ * @see #startSecureSession()
+ */
+ public abstract FtpClient endSecureSession() throws FtpProtocolException, IOException;
+
+ /**
+ * Sends the "Allocate" ({@code ALLO}) command to the server telling it to
+ * pre-allocate the specified number of bytes for the next transfer.
*
- * @exception FtpProtocolException if the connection fails
+ * @param size The number of bytes to allocate.
+ * @return this FtpClient
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
*/
- public FtpClient(String host, int port) throws IOException {
- super();
- openServer(host, port);
- }
+ public abstract FtpClient allocate(long size) throws FtpProtocolException, IOException;
- /** Create an uninitialized FTP client. */
- public FtpClient() {}
+ /**
+ * Sends the "Structure Mount" ({@code SMNT}) command to the server. This let the
+ * user mount a different file system data structure without altering his
+ * login or accounting information.
+ *
+ * @param struct a {@code String} containing the name of the
+ * structure to mount.
+ * @return this FtpClient
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract FtpClient structureMount(String struct) throws FtpProtocolException, IOException;
- public FtpClient(Proxy p) {
- proxy = p;
- }
+ /**
+ * Sends a System ({@code SYST}) command to the server and returns the String
+ * sent back by the server describing the operating system at the
+ * server.
+ *
+ * @return a {@code String} describing the OS, or {@code null}
+ * if the operation was not successful.
+ * @throws IOException if an error occured during the transmission.
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract String getSystem() throws FtpProtocolException, IOException;
- protected void finalize() throws IOException {
- /**
- * Do not call the "normal" closeServer() as we want finalization
- * to be as efficient as possible
- */
- if (serverIsOpen())
- super.closeServer();
- }
+ /**
+ * Sends the {@code HELP} command to the server, with an optional command, like
+ * SITE, and returns the text sent back by the server.
+ *
+ * @param cmd the command for which the help is requested or
+ * {@code null} for the general help
+ * @return a {@code String} containing the text sent back by the
+ * server, or {@code null} if the command failed.
+ * @throws IOException if an error occured during transmission
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract String getHelp(String cmd) throws FtpProtocolException, IOException;
+ /**
+ * Sends the {@code SITE} command to the server. This is used by the server
+ * to provide services specific to his system that are essential
+ * to file transfer.
+ *
+ * @param cmd the command to be sent.
+ * @return this FtpClient
+ * @throws IOException if an error occured during transmission
+ * @throws FtpProtocolException if the command was rejected by the server
+ */
+ public abstract FtpClient siteCmd(String cmd) throws FtpProtocolException, IOException;
}
diff --git a/jdk/src/share/classes/sun/net/ftp/FtpClientProvider.java b/jdk/src/share/classes/sun/net/ftp/FtpClientProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..0061fcd219bbbd25ff6e3a9af4161f7174b056f2
--- /dev/null
+++ b/jdk/src/share/classes/sun/net/ftp/FtpClientProvider.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.net.ftp;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ServiceConfigurationError;
+//import sun.misc.Service;
+
+/**
+ * Service provider class for FtpClient.
+ * Sub-classes of FtpClientProvider provide an implementation of {@link FtpClient}
+ * and associated classes. Applications do not normally use this class directly.
+ * See {@link #provider() } for how providers are found and loaded.
+ *
+ * @since 1.7
+ */
+public abstract class FtpClientProvider {
+
+ /**
+ * Creates a FtpClient from this provider.
+ *
+ * @return The created {@link FtpClient}.
+ */
+ public abstract FtpClient createFtpClient();
+ private static final Object lock = new Object();
+ private static FtpClientProvider provider = null;
+
+ /**
+ * Initializes a new instance of this class.
+ *
+ * @throws SecurityException if a security manager is installed and it denies
+ * {@link RuntimePermission}("ftpClientProvider")
+ */
+ protected FtpClientProvider() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new RuntimePermission("ftpClientProvider"));
+ }
+ }
+
+ private static boolean loadProviderFromProperty() {
+ String cm = System.getProperty("sun.net.ftpClientProvider");
+ if (cm == null) {
+ return false;
+ }
+ try {
+ Class c = Class.forName(cm, true, null);
+ provider = (FtpClientProvider) c.newInstance();
+ return true;
+ } catch (ClassNotFoundException x) {
+ throw new ServiceConfigurationError(x.toString());
+ } catch (IllegalAccessException x) {
+ throw new ServiceConfigurationError(x.toString());
+ } catch (InstantiationException x) {
+ throw new ServiceConfigurationError(x.toString());
+ } catch (SecurityException x) {
+ throw new ServiceConfigurationError(x.toString());
+ }
+ }
+
+ private static boolean loadProviderAsService() {
+ // Iterator i = Service.providers(FtpClientProvider.class,
+ // ClassLoader.getSystemClassLoader());
+ // while (i.hasNext()) {
+ // try {
+ // provider = (FtpClientProvider) i.next();
+ // return true;
+ // } catch (ServiceConfigurationError sce) {
+ // if (sce.getCause() instanceof SecurityException) {
+ // // Ignore, try next provider, if any
+ // continue;
+ // }
+ // throw sce;
+ // }
+ // }
+ return false;
+ }
+
+ /**
+ * Returns the system wide default FtpClientProvider for this invocation of
+ * the Java virtual machine.
+ *
+ *
The first invocation of this method locates the default provider
+ * object as follows:
+ *
+ *
+ *
+ *
If the system property
+ * java.net.FtpClientProvider is defined then it is
+ * taken to be the fully-qualified name of a concrete provider class.
+ * The class is loaded and instantiated; if this process fails then an
+ * unspecified unchecked error or exception is thrown.
+ *
+ *
If a provider class has been installed in a jar file that is
+ * visible to the system class loader, and that jar file contains a
+ * provider-configuration file named
+ * java.net.FtpClientProvider in the resource
+ * directory META-INF/services, then the first class name
+ * specified in that file is taken. The class is loaded and
+ * instantiated; if this process fails then an unspecified unchecked error or exception is
+ * thrown.
+ *
+ *
Finally, if no provider has been specified by any of the above
+ * means then the system-default provider class is instantiated and the
+ * result is returned.
+ *
+ *
+ *
+ *
Subsequent invocations of this method return the provider that was
+ * returned by the first invocation.
+ *
+ * @return The system-wide default FtpClientProvider
+ */
+ public static FtpClientProvider provider() {
+ synchronized (lock) {
+ if (provider != null) {
+ return provider;
+ }
+ return (FtpClientProvider) AccessController.doPrivileged(
+ new PrivilegedAction