Save This Page
Home » openjdk-7 » sun.net.www.protocol » ftp » [javadoc | source]
    1   /*
    2    * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   
   26   /**
   27    * FTP stream opener.
   28    */
   29   
   30   package sun.net.www.protocol.ftp;
   31   
   32   import java.io.IOException;
   33   import java.io.InputStream;
   34   import java.io.OutputStream;
   35   import java.io.BufferedInputStream;
   36   import java.io.FilterInputStream;
   37   import java.io.FilterOutputStream;
   38   import java.io.FileNotFoundException;
   39   import java.net.URL;
   40   import java.net.SocketPermission;
   41   import java.net.UnknownHostException;
   42   import java.net.InetSocketAddress;
   43   import java.net.URI;
   44   import java.net.Proxy;
   45   import java.net.ProxySelector;
   46   import java.util.StringTokenizer;
   47   import java.util.Iterator;
   48   import java.security.Permission;
   49   import sun.net.NetworkClient;
   50   import sun.net.www.MessageHeader;
   51   import sun.net.www.MeteredStream;
   52   import sun.net.www.URLConnection;
   53   import sun.net.www.protocol.http.HttpURLConnection;
   54   import sun.net.ftp.FtpClient;
   55   import sun.net.ftp.FtpProtocolException;
   56   import sun.net.ProgressSource;
   57   import sun.net.ProgressMonitor;
   58   import sun.net.www.ParseUtil;
   59   import sun.security.action.GetPropertyAction;
   60   
   61   
   62   /**
   63    * This class Opens an FTP input (or output) stream given a URL.
   64    * It works as a one shot FTP transfer :
   65    * <UL>
   66    * <LI>Login</LI>
   67    * <LI>Get (or Put) the file</LI>
   68    * <LI>Disconnect</LI>
   69    * </UL>
   70    * You should not have to use it directly in most cases because all will be handled
   71    * in a abstract layer. Here is an example of how to use the class :
   72    * <P>
   73    * <code>URL url = new URL("ftp://ftp.sun.com/pub/test.txt");<p>
   74    * UrlConnection con = url.openConnection();<p>
   75    * InputStream is = con.getInputStream();<p>
   76    * ...<p>
   77    * is.close();</code>
   78    *
   79    * @see sun.net.ftp.FtpClient
   80    */
   81   public class FtpURLConnection extends URLConnection {
   82   
   83       // In case we have to use proxies, we use HttpURLConnection
   84       HttpURLConnection http = null;
   85       private Proxy instProxy;
   86   
   87       InputStream is = null;
   88       OutputStream os = null;
   89   
   90       FtpClient ftp = null;
   91       Permission permission;
   92   
   93       String password;
   94       String user;
   95   
   96       String host;
   97       String pathname;
   98       String filename;
   99       String fullpath;
  100       int port;
  101       static final int NONE = 0;
  102       static final int ASCII = 1;
  103       static final int BIN = 2;
  104       static final int DIR = 3;
  105       int type = NONE;
  106       /* Redefine timeouts from java.net.URLConnection as we need -1 to mean
  107        * not set. This is to ensure backward compatibility.
  108        */
  109       private int connectTimeout = NetworkClient.DEFAULT_CONNECT_TIMEOUT;;
  110       private int readTimeout = NetworkClient.DEFAULT_READ_TIMEOUT;;
  111   
  112       /**
  113        * For FTP URLs we need to have a special InputStream because we
  114        * need to close 2 sockets after we're done with it :
  115        *  - The Data socket (for the file).
  116        *   - The command socket (FtpClient).
  117        * Since that's the only class that needs to see that, it is an inner class.
  118        */
  119       protected class FtpInputStream extends FilterInputStream {
  120           FtpClient ftp;
  121           FtpInputStream(FtpClient cl, InputStream fd) {
  122               super(new BufferedInputStream(fd));
  123               ftp = cl;
  124           }
  125   
  126           @Override
  127           public void close() throws IOException {
  128               super.close();
  129               if (ftp != null) {
  130                   ftp.close();
  131               }
  132           }
  133       }
  134   
  135       /**
  136        * For FTP URLs we need to have a special OutputStream because we
  137        * need to close 2 sockets after we're done with it :
  138        *  - The Data socket (for the file).
  139        *   - The command socket (FtpClient).
  140        * Since that's the only class that needs to see that, it is an inner class.
  141        */
  142       protected class FtpOutputStream extends FilterOutputStream {
  143           FtpClient ftp;
  144           FtpOutputStream(FtpClient cl, OutputStream fd) {
  145               super(fd);
  146               ftp = cl;
  147           }
  148   
  149           @Override
  150           public void close() throws IOException {
  151               super.close();
  152               if (ftp != null) {
  153                   ftp.close();
  154               }
  155           }
  156       }
  157   
  158       /**
  159        * Creates an FtpURLConnection from a URL.
  160        *
  161        * @param   url     The <code>URL</code> to retrieve or store.
  162        */
  163       public FtpURLConnection(URL url) {
  164           this(url, null);
  165       }
  166   
  167       /**
  168        * Same as FtpURLconnection(URL) with a per connection proxy specified
  169        */
  170       FtpURLConnection(URL url, Proxy p) {
  171           super(url);
  172           instProxy = p;
  173           host = url.getHost();
  174           port = url.getPort();
  175           String userInfo = url.getUserInfo();
  176   
  177           if (userInfo != null) { // get the user and password
  178               int delimiter = userInfo.indexOf(':');
  179               if (delimiter == -1) {
  180                   user = ParseUtil.decode(userInfo);
  181                   password = null;
  182               } else {
  183                   user = ParseUtil.decode(userInfo.substring(0, delimiter++));
  184                   password = ParseUtil.decode(userInfo.substring(delimiter));
  185               }
  186           }
  187       }
  188   
  189       private void setTimeouts() {
  190           if (ftp != null) {
  191               if (connectTimeout >= 0) {
  192                   ftp.setConnectTimeout(connectTimeout);
  193               }
  194               if (readTimeout >= 0) {
  195                   ftp.setReadTimeout(readTimeout);
  196               }
  197           }
  198       }
  199   
  200       /**
  201        * Connects to the FTP server and logs in.
  202        *
  203        * @throws  FtpLoginException if the login is unsuccessful
  204        * @throws  FtpProtocolException if an error occurs
  205        * @throws  UnknownHostException if trying to connect to an unknown host
  206        */
  207   
  208       public synchronized void connect() throws IOException {
  209           if (connected) {
  210               return;
  211           }
  212   
  213           Proxy p = null;
  214           if (instProxy == null) { // no per connection proxy specified
  215               /**
  216                * Do we have to use a proxy?
  217                */
  218               ProxySelector sel = java.security.AccessController.doPrivileged(
  219                       new java.security.PrivilegedAction<ProxySelector>() {
  220                           public ProxySelector run() {
  221                               return ProxySelector.getDefault();
  222                           }
  223                       });
  224               if (sel != null) {
  225                   URI uri = sun.net.www.ParseUtil.toURI(url);
  226                   Iterator<Proxy> it = sel.select(uri).iterator();
  227                   while (it.hasNext()) {
  228                       p = it.next();
  229                       if (p == null || p == Proxy.NO_PROXY ||
  230                           p.type() == Proxy.Type.SOCKS) {
  231                           break;
  232                       }
  233                       if (p.type() != Proxy.Type.HTTP ||
  234                               !(p.address() instanceof InetSocketAddress)) {
  235                           sel.connectFailed(uri, p.address(), new IOException("Wrong proxy type"));
  236                           continue;
  237                       }
  238                       // OK, we have an http proxy
  239                       InetSocketAddress paddr = (InetSocketAddress) p.address();
  240                       try {
  241                           http = new HttpURLConnection(url, p);
  242                           http.setDoInput(getDoInput());
  243                           http.setDoOutput(getDoOutput());
  244                           if (connectTimeout >= 0) {
  245                               http.setConnectTimeout(connectTimeout);
  246                           }
  247                           if (readTimeout >= 0) {
  248                               http.setReadTimeout(readTimeout);
  249                           }
  250                           http.connect();
  251                           connected = true;
  252                           return;
  253                       } catch (IOException ioe) {
  254                           sel.connectFailed(uri, paddr, ioe);
  255                           http = null;
  256                       }
  257                   }
  258               }
  259           } else { // per connection proxy specified
  260               p = instProxy;
  261               if (p.type() == Proxy.Type.HTTP) {
  262                   http = new HttpURLConnection(url, instProxy);
  263                   http.setDoInput(getDoInput());
  264                   http.setDoOutput(getDoOutput());
  265                   if (connectTimeout >= 0) {
  266                       http.setConnectTimeout(connectTimeout);
  267                   }
  268                   if (readTimeout >= 0) {
  269                       http.setReadTimeout(readTimeout);
  270                   }
  271                   http.connect();
  272                   connected = true;
  273                   return;
  274               }
  275           }
  276   
  277           if (user == null) {
  278               user = "anonymous";
  279               String vers = java.security.AccessController.doPrivileged(
  280                       new GetPropertyAction("java.version"));
  281               password = java.security.AccessController.doPrivileged(
  282                       new GetPropertyAction("ftp.protocol.user",
  283                                             "Java" + vers + "@"));
  284           }
  285           try {
  286               ftp = FtpClient.create();
  287               if (p != null) {
  288                   ftp.setProxy(p);
  289               }
  290               setTimeouts();
  291               if (port != -1) {
  292                   ftp.connect(new InetSocketAddress(host, port));
  293               } else {
  294                   ftp.connect(new InetSocketAddress(host, FtpClient.defaultPort()));
  295               }
  296           } catch (UnknownHostException e) {
  297               // Maybe do something smart here, like use a proxy like iftp.
  298               // Just keep throwing for now.
  299               throw e;
  300           } catch (FtpProtocolException fe) {
  301               throw new IOException(fe);
  302           }
  303           try {
  304               ftp.login(user, password.toCharArray());
  305           } catch (sun.net.ftp.FtpProtocolException e) {
  306               ftp.close();
  307               // Backward compatibility
  308               throw new sun.net.ftp.FtpLoginException("Invalid username/password");
  309           }
  310           connected = true;
  311       }
  312   
  313   
  314       /*
  315        * Decodes the path as per the RFC-1738 specifications.
  316        */
  317       private void decodePath(String path) {
  318           int i = path.indexOf(";type=");
  319           if (i >= 0) {
  320               String s1 = path.substring(i + 6, path.length());
  321               if ("i".equalsIgnoreCase(s1)) {
  322                   type = BIN;
  323               }
  324               if ("a".equalsIgnoreCase(s1)) {
  325                   type = ASCII;
  326               }
  327               if ("d".equalsIgnoreCase(s1)) {
  328                   type = DIR;
  329               }
  330               path = path.substring(0, i);
  331           }
  332           if (path != null && path.length() > 1 &&
  333                   path.charAt(0) == '/') {
  334               path = path.substring(1);
  335           }
  336           if (path == null || path.length() == 0) {
  337               path = "./";
  338           }
  339           if (!path.endsWith("/")) {
  340               i = path.lastIndexOf('/');
  341               if (i > 0) {
  342                   filename = path.substring(i + 1, path.length());
  343                   filename = ParseUtil.decode(filename);
  344                   pathname = path.substring(0, i);
  345               } else {
  346                   filename = ParseUtil.decode(path);
  347                   pathname = null;
  348               }
  349           } else {
  350               pathname = path.substring(0, path.length() - 1);
  351               filename = null;
  352           }
  353           if (pathname != null) {
  354               fullpath = pathname + "/" + (filename != null ? filename : "");
  355           } else {
  356               fullpath = filename;
  357           }
  358       }
  359   
  360       /*
  361        * As part of RFC-1738 it is specified that the path should be
  362        * interpreted as a series of FTP CWD commands.
  363        * This is because, '/' is not necessarly the directory delimiter
  364        * on every systems.
  365        */
  366       private void cd(String path) throws FtpProtocolException, IOException {
  367           if (path == null || path.isEmpty()) {
  368               return;
  369           }
  370           if (path.indexOf('/') == -1) {
  371               ftp.changeDirectory(ParseUtil.decode(path));
  372               return;
  373           }
  374   
  375           StringTokenizer token = new StringTokenizer(path, "/");
  376           while (token.hasMoreTokens()) {
  377               ftp.changeDirectory(ParseUtil.decode(token.nextToken()));
  378           }
  379       }
  380   
  381       /**
  382        * Get the InputStream to retreive the remote file. It will issue the
  383        * "get" (or "dir") command to the ftp server.
  384        *
  385        * @return  the <code>InputStream</code> to the connection.
  386        *
  387        * @throws  IOException if already opened for output
  388        * @throws  FtpProtocolException if errors occur during the transfert.
  389        */
  390       @Override
  391       public InputStream getInputStream() throws IOException {
  392           if (!connected) {
  393               connect();
  394           }
  395   
  396           if (http != null) {
  397               return http.getInputStream();
  398           }
  399   
  400           if (os != null) {
  401               throw new IOException("Already opened for output");
  402           }
  403   
  404           if (is != null) {
  405               return is;
  406           }
  407   
  408           MessageHeader msgh = new MessageHeader();
  409   
  410           boolean isAdir = false;
  411           try {
  412               decodePath(url.getPath());
  413               if (filename == null || type == DIR) {
  414                   ftp.setAsciiType();
  415                   cd(pathname);
  416                   if (filename == null) {
  417                       is = new FtpInputStream(ftp, ftp.list(null));
  418                   } else {
  419                       is = new FtpInputStream(ftp, ftp.nameList(filename));
  420                   }
  421               } else {
  422                   if (type == ASCII) {
  423                       ftp.setAsciiType();
  424                   } else {
  425                       ftp.setBinaryType();
  426                   }
  427                   cd(pathname);
  428                   is = new FtpInputStream(ftp, ftp.getFileStream(filename));
  429               }
  430   
  431               /* Try to get the size of the file in bytes.  If that is
  432               successful, then create a MeteredStream. */
  433               try {
  434                   long l = ftp.getLastTransferSize();
  435                   msgh.add("content-length", Long.toString(l));
  436                   if (l > 0) {
  437   
  438                       // Wrap input stream with MeteredStream to ensure read() will always return -1
  439                       // at expected length.
  440   
  441                       // Check if URL should be metered
  442                       boolean meteredInput = ProgressMonitor.getDefault().shouldMeterInput(url, "GET");
  443                       ProgressSource pi = null;
  444   
  445                       if (meteredInput) {
  446                           pi = new ProgressSource(url, "GET", l);
  447                           pi.beginTracking();
  448                       }
  449   
  450                       is = new MeteredStream(is, pi, l);
  451                   }
  452               } catch (Exception e) {
  453                   e.printStackTrace();
  454               /* do nothing, since all we were doing was trying to
  455               get the size in bytes of the file */
  456               }
  457   
  458               if (isAdir) {
  459                   msgh.add("content-type", "text/plain");
  460                   msgh.add("access-type", "directory");
  461               } else {
  462                   msgh.add("access-type", "file");
  463                   String ftype = guessContentTypeFromName(fullpath);
  464                   if (ftype == null && is.markSupported()) {
  465                       ftype = guessContentTypeFromStream(is);
  466                   }
  467                   if (ftype != null) {
  468                       msgh.add("content-type", ftype);
  469                   }
  470               }
  471           } catch (FileNotFoundException e) {
  472               try {
  473                   cd(fullpath);
  474                   /* if that worked, then make a directory listing
  475                   and build an html stream with all the files in
  476                   the directory */
  477                   ftp.setAsciiType();
  478   
  479                   is = new FtpInputStream(ftp, ftp.list(null));
  480                   msgh.add("content-type", "text/plain");
  481                   msgh.add("access-type", "directory");
  482               } catch (IOException ex) {
  483                   throw new FileNotFoundException(fullpath);
  484               } catch (FtpProtocolException ex2) {
  485                   throw new FileNotFoundException(fullpath);
  486               }
  487           } catch (FtpProtocolException ftpe) {
  488               throw new IOException(ftpe);
  489           }
  490           setProperties(msgh);
  491           return is;
  492       }
  493   
  494       /**
  495        * Get the OutputStream to store the remote file. It will issue the
  496        * "put" command to the ftp server.
  497        *
  498        * @return  the <code>OutputStream</code> to the connection.
  499        *
  500        * @throws  IOException if already opened for input or the URL
  501        *          points to a directory
  502        * @throws  FtpProtocolException if errors occur during the transfert.
  503        */
  504       @Override
  505       public OutputStream getOutputStream() throws IOException {
  506           if (!connected) {
  507               connect();
  508           }
  509   
  510           if (http != null) {
  511               OutputStream out = http.getOutputStream();
  512               // getInputStream() is neccessary to force a writeRequests()
  513               // on the http client.
  514               http.getInputStream();
  515               return out;
  516           }
  517   
  518           if (is != null) {
  519               throw new IOException("Already opened for input");
  520           }
  521   
  522           if (os != null) {
  523               return os;
  524           }
  525   
  526           decodePath(url.getPath());
  527           if (filename == null || filename.length() == 0) {
  528               throw new IOException("illegal filename for a PUT");
  529           }
  530           try {
  531               if (pathname != null) {
  532                   cd(pathname);
  533               }
  534               if (type == ASCII) {
  535                   ftp.setAsciiType();
  536               } else {
  537                   ftp.setBinaryType();
  538               }
  539               os = new FtpOutputStream(ftp, ftp.putFileStream(filename, false));
  540           } catch (FtpProtocolException e) {
  541               throw new IOException(e);
  542           }
  543           return os;
  544       }
  545   
  546       String guessContentTypeFromFilename(String fname) {
  547           return guessContentTypeFromName(fname);
  548       }
  549   
  550       /**
  551        * Gets the <code>Permission</code> associated with the host & port.
  552        *
  553        * @return  The <code>Permission</code> object.
  554        */
  555       @Override
  556       public Permission getPermission() {
  557           if (permission == null) {
  558               int urlport = url.getPort();
  559               urlport = urlport < 0 ? FtpClient.defaultPort() : urlport;
  560               String urlhost = this.host + ":" + urlport;
  561               permission = new SocketPermission(urlhost, "connect");
  562           }
  563           return permission;
  564       }
  565   
  566       /**
  567        * Sets the general request property. If a property with the key already
  568        * exists, overwrite its value with the new value.
  569        *
  570        * @param   key     the keyword by which the request is known
  571        *                  (e.g., "<code>accept</code>").
  572        * @param   value   the value associated with it.
  573        * @throws IllegalStateException if already connected
  574        * @see #getRequestProperty(java.lang.String)
  575        */
  576       @Override
  577       public void setRequestProperty(String key, String value) {
  578           super.setRequestProperty(key, value);
  579           if ("type".equals(key)) {
  580               if ("i".equalsIgnoreCase(value)) {
  581                   type = BIN;
  582               } else if ("a".equalsIgnoreCase(value)) {
  583                   type = ASCII;
  584               } else if ("d".equalsIgnoreCase(value)) {
  585                   type = DIR;
  586               } else {
  587                   throw new IllegalArgumentException(
  588                           "Value of '" + key +
  589                           "' request property was '" + value +
  590                           "' when it must be either 'i', 'a' or 'd'");
  591               }
  592           }
  593       }
  594   
  595       /**
  596        * Returns the value of the named general request property for this
  597        * connection.
  598        *
  599        * @param key the keyword by which the request is known (e.g., "accept").
  600        * @return  the value of the named general request property for this
  601        *           connection.
  602        * @throws IllegalStateException if already connected
  603        * @see #setRequestProperty(java.lang.String, java.lang.String)
  604        */
  605       @Override
  606       public String getRequestProperty(String key) {
  607           String value = super.getRequestProperty(key);
  608   
  609           if (value == null) {
  610               if ("type".equals(key)) {
  611                   value = (type == ASCII ? "a" : type == DIR ? "d" : "i");
  612               }
  613           }
  614   
  615           return value;
  616       }
  617   
  618       @Override
  619       public void setConnectTimeout(int timeout) {
  620           if (timeout < 0) {
  621               throw new IllegalArgumentException("timeouts can't be negative");
  622           }
  623           connectTimeout = timeout;
  624       }
  625   
  626       @Override
  627       public int getConnectTimeout() {
  628           return (connectTimeout < 0 ? 0 : connectTimeout);
  629       }
  630   
  631       @Override
  632       public void setReadTimeout(int timeout) {
  633           if (timeout < 0) {
  634               throw new IllegalArgumentException("timeouts can't be negative");
  635           }
  636           readTimeout = timeout;
  637       }
  638   
  639       @Override
  640       public int getReadTimeout() {
  641           return readTimeout < 0 ? 0 : readTimeout;
  642       }
  643   }

Save This Page
Home » openjdk-7 » sun.net.www.protocol » ftp » [javadoc | source]