FtpClient.java :  » 6.0-JDK-Modules-sun » net » sun » net » ftp » Java Open Source

Java Open Source » 6.0 JDK Modules sun » net 
net » sun » net » ftp » FtpClient.java
/*
 * Copyright 1994-2004 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.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.
 *
 * @version  1.74, 05/05/07
 * @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 */
    private String  serverName = null;

    /** 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.
     */
    private static RegexpPool nonProxyHostsPool = null;

    /** The string soucre of nonProxyHostsPool
     */
    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;


    /* 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
     */
    public static boolean getUseFtpProxy() {
  // if the ftp.proxyHost is set, use it!
  return (getFtpProxyHost() != null);
    }

    /**
     * @return the host to use, or null if none has been specified
     */
    public static String getFtpProxyHost() {
  return (String) java.security.AccessController.doPrivileged(
      new java.security.PrivilegedAction() {
      public Object 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;
      }
  });
    }

    /**
     * @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 Object 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 = (String) 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.
     *
     * @exception  FtpProtocolException if an error occured
     */
    public void closeServer() throws IOException {
  if (serverIsOpen()) {
      issueCommand("QUIT");
      super.closeServer();
  }
    }

    /**
     * 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");
  }
  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.
     *
     * @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.
     *
     * @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
     *
     * @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 = (Socket) AccessController.doPrivileged(
              new PrivilegedAction() {
            public Object 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 
     *
     * @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 <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 <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 <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
     *
     * @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 
     *
     * @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 
     *
     * @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 
     *
     * @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 
     *
     * @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 
     *
     * @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 
     *
     */
    public void cdUp() throws IOException {
  issueCommandCheck("CDUP");
    }

    /**
     * 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'
     *
     * @exception FtpProtocolException if the command fails
     */
    public void binary() throws IOException {
  issueCommandCheck("TYPE I");
  binaryMode = true;
    }

    /**
     * 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
     *
     * @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
     *
     * @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
     *
     * @exception FtpProtocolException if the command fails
     */
    public void noop() throws IOException {
  issueCommandCheck("NOOP");
    }

    /**
     * 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 <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 <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. */
    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();
    }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.