/*
 * Copyright (c) 1994, 2004, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.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;

This class implements the FTP client.
Author: Jonathan Payne
/** * This class implements the FTP client. * * @author Jonathan Payne */
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;
remember the ftp server name because we may need it
/** remember the ftp server name because we may need it */
private String serverName = null;
socket for data transfer
/** socket for data transfer */
private boolean replyPending = false; private boolean binaryMode = false; private boolean loggedIn = false;
regexp pool of hosts for which we should connect directly, not Proxy these are intialized from a property.
/** regexp pool of hosts for which we should connect directly, not Proxy * these are intialized from a property. */
private static RegexpPool nonProxyHostsPool = null;
The string soucre of nonProxyHostsPool
/** The string soucre of nonProxyHostsPool */
private static String nonProxyHostsSource = null;
last command issued
/** last command issued */
String command;
The last reply code from the ftp daemon.
/** The last reply code from the ftp daemon. */
int lastReplyCode;
Welcome message from the server, if any.
/** Welcome message from the server, if any. */
public String welcomeMsg; /* 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. */
Returns:if the networking layer should send ftp connections through a proxy
/** * @return if the networking layer should send ftp connections through * a proxy */
public static boolean getUseFtpProxy() { // if the ftp.proxyHost is set, use it! return (getFtpProxyHost() != null); }
Returns:the host to use, or null if none has been specified
/** * @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<String>() { 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; } }); }
Returns:the proxy port to use. Will default reasonably if not set.
/** * @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<Void>() { 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]; } 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; } }
issue the QUIT command to the FTP server and close the connection.
Throws:
  • FtpProtocolException – if an error occured
/** * issue the QUIT command to the FTP server and close the connection. * * @exception FtpProtocolException if an error occured */
public void closeServer() throws IOException { if (serverIsOpen()) { issueCommand("QUIT"); super.closeServer(); } }
Send a command to the FTP server.
Params:
  • cmd – String containing the command
Throws:
Returns: reply code
/** * Send a command to the FTP server. * * @param cmd String containing the command * @return reply code * * @exception FtpProtocolException if an error occured */
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"); } if (cmd.indexOf('\n') != -1) { FtpProtocolException ex = new FtpProtocolException("Illegal FTP command"); ex.initCause(new IllegalArgumentException("Illegal carriage return")); throw ex; } do { sendServer(cmd + "\r\n"); reply = readReply(); } while (reply == FTP_TRY_AGAIN); return reply; }
Send a command to the FTP server and check for success.
Params:
  • cmd – String containing the command
Throws:
/** * Send a command to the FTP server and check for success. * * @param cmd String containing the command * * @exception FtpProtocolException if an error occured */
protected void issueCommandCheck(String cmd) throws IOException { if (issueCommand(cmd) != FTP_SUCCESS) throw new FtpProtocolException(cmd + ":" + getResponseString()); }
Read the reply from the FTP server.
Throws:
Returns: FTP_SUCCESS or FTP_ERROR depending on success
/** * Read the reply from the FTP server. * * @return FTP_SUCCESS or FTP_ERROR depending on success * @exception FtpProtocolException if an error occured */
protected int readReply() throws IOException { lastReplyCode = readServerResponse(); switch (lastReplyCode / 100) { case 1: replyPending = true; /* falls into ... */ case 2: case 3: return FTP_SUCCESS; case 5: if (lastReplyCode == 530) { if (!loggedIn) { throw new FtpLoginException("Not logged in"); } return FTP_ERROR; } if (lastReplyCode == 550) { throw new FileNotFoundException(command + ": " + getResponseString()); } } /* this statement is not reached */ return FTP_ERROR; }
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
Throws:
Returns: the opened socket
/** * 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<Socket>() { public Socket run() { return new Socket(proxy); }}); } else s = new Socket(Proxy.NO_PROXY); } else s = new Socket(); 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; }
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
Params:
  • cmd – the command to execute (RETR, STOR, etc...)
Throws:
Returns: the opened socket
/** * 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 * * @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. */
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; } } assert(clientSocket == null); // Passive mode failed, let's fall back to the good old "PORT" 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"); } else portSocket = new ServerSocket(0, 1); 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(); } return clientSocket; } /* public methods */
Open a FTP connection to host host.
Params:
  • host – The hostname of the ftp server
Throws:
/** * Open a FTP connection to host <i>host</i>. * * @param host The hostname of the ftp server * * @exception FtpProtocolException if connection fails */
public void openServer(String host) throws IOException { openServer(host, FTP_PORT); }
Open a FTP connection to host host on port port.
Params:
  • host – the hostname of the ftp server
  • port – the port to connect to (usually 21)
Throws:
/** * Open a FTP connection to host <i>host</i> on port <i>port</i>. * * @param host the hostname of the ftp server * @param port the port to connect to (usually 21) * * @exception FtpProtocolException if connection fails */
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()); }
login user to a host with username user and password password
Params:
  • user – Username to use at login
  • password – Password to use at login or null of none is needed
Throws:
/** * login user to a host with username <i>user</i> and password * <i>password</i> * * @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; }
GET a file from the FTP server
Params:
  • filename – name of the file to retrieve
Throws:
Returns: the InputStream to read the file from
/** * GET a file from the FTP server * * @param filename name of the file to retrieve * @return the <code>InputStream</code> 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; } } return new TelnetInputStream(s.getInputStream(), binaryMode); }
PUT a file to the FTP server
Params:
  • filename – name of the file to store
Returns: the OutputStream to write the file to
/** * PUT a file to the FTP server * * @param filename name of the file to store * @return the <code>OutputStream</code> to write the file to * */
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; }
Append to a file on the FTP server
Params:
  • filename – name of the file to append to
Returns: the OutputStream to write the file to
/** * Append to a file on the FTP server * * @param filename name of the file to append to * @return the <code>OutputStream</code> to write the file to * */
public TelnetOutputStream append(String filename) throws IOException { Socket s = openDataConnection("APPE " + filename); TelnetOutputStream out = new TelnetOutputStream(s.getOutputStream(), binaryMode); if (!binaryMode) out.setStickyCRLF(true); return out; }
LIST files in the current directory on a remote FTP server
Returns: the InputStream to read the list from
/** * LIST files in the current directory on a remote FTP server * * @return the <code>InputStream</code> to read the list from * */
public TelnetInputStream list() throws IOException { Socket s = openDataConnection("LIST"); return new TelnetInputStream(s.getInputStream(), binaryMode); }
List (NLST) file names on a remote FTP server
Params:
  • path – pathname to the directory to list, null for current directory
Throws:
  • FtpProtocolException
Returns: the InputStream to read the list from
/** * List (NLST) file names on a remote FTP server * * @param path pathname to the directory to list, null for current * directory * @return the <code>InputStream</code> to read the list from * @exception <code>FtpProtocolException</code> */
public TelnetInputStream nameList(String path) throws IOException { Socket s; if (path != null) s = openDataConnection("NLST " + path); else s = openDataConnection("NLST"); return new TelnetInputStream(s.getInputStream(), binaryMode); }
CD to a specific directory on a remote FTP server
Params:
  • remoteDirectory – path of the directory to CD to
Throws:
  • FtpProtocolException
/** * CD to a specific directory on a remote FTP server * * @param remoteDirectory path of the directory to CD to * * @exception <code>FtpProtocolException</code> */
public void cd(String remoteDirectory) throws IOException { if (remoteDirectory == null || "".equals(remoteDirectory)) return; issueCommandCheck("CWD " + remoteDirectory); }
CD to the parent directory on a remote FTP server
/** * CD to the parent directory on a remote FTP server * */
public void cdUp() throws IOException { issueCommandCheck("CDUP"); }
Print working directory of remote FTP server
Throws:
  • FtpProtocolException – if the command fails
/** * Print working directory of remote FTP server * * @exception FtpProtocolException if the command fails */
public String pwd() throws IOException { String answ; 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('"')); }
Set transfer type to 'I'
Throws:
  • FtpProtocolException – if the command fails
/** * Set transfer type to 'I' * * @exception FtpProtocolException if the command fails */
public void binary() throws IOException { issueCommandCheck("TYPE I"); binaryMode = true; }
Set transfer type to 'A'
Throws:
  • FtpProtocolException – if the command fails
/** * Set transfer type to 'A' * * @exception FtpProtocolException if the command fails */
public void ascii() throws IOException { issueCommandCheck("TYPE A"); binaryMode = false; }
Rename a file on the ftp server
Throws:
  • FtpProtocolException – if the command fails
/** * Rename a file on the ftp server * * @exception FtpProtocolException if the command fails */
public void rename(String from, String to) throws IOException { issueCommandCheck("RNFR " + from); issueCommandCheck("RNTO " + to); }
Get the "System string" from the FTP server
Throws:
  • FtpProtocolException – if it fails
/** * Get the "System string" from the FTP server * * @exception FtpProtocolException if it fails */
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 " }
Send a No-operation command. It's usefull for testing the connection status
Throws:
  • FtpProtocolException – if the command fails
/** * Send a No-operation command. It's usefull for testing the connection status * * @exception FtpProtocolException if the command fails */
public void noop() throws IOException { issueCommandCheck("NOOP"); }
Reinitialize the USER parameters on the FTp server
Throws:
  • FtpProtocolException – if the command fails
/** * Reinitialize the USER parameters on the FTp server * * @exception FtpProtocolException if the command fails */
public void reInit() throws IOException { issueCommandCheck("REIN"); loggedIn = false; }
New FTP client connected to host host.
Params:
  • host – Hostname of the FTP server
Throws:
/** * New FTP client connected to host <i>host</i>. * * @param host Hostname of the FTP server * * @exception FtpProtocolException if the connection fails */
public FtpClient(String host) throws IOException { super(); openServer(host, FTP_PORT); }
New FTP client connected to host host, port port.
Params:
  • host – Hostname of the FTP server
  • port – port number to connect to (usually 21)
Throws:
/** * New FTP client connected to host <i>host</i>, port <i>port</i>. * * @param host Hostname of the FTP server * @param port port number to connect to (usually 21) * * @exception FtpProtocolException if the connection fails */
public FtpClient(String host, int port) throws IOException { super(); openServer(host, port); }
Create an uninitialized FTP client.
/** Create an uninitialized FTP client. */
public FtpClient() {} public FtpClient(Proxy p) { proxy = p; } 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(); } }