Home » openjdk-7 » sun.net.ftp.impl » [javadoc | source]

    1   /*
    2    * Copyright (c) 2009, 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   package sun.net.ftp.impl;
   26   
   27   import java.net;
   28   import java.io;
   29   import java.security.AccessController;
   30   import java.security.PrivilegedAction;
   31   import java.text.DateFormat;
   32   import java.text.ParseException;
   33   import java.text.SimpleDateFormat;
   34   import java.util.ArrayList;
   35   import java.util.Calendar;
   36   import java.util.Date;
   37   import java.util.Iterator;
   38   import java.util.List;
   39   import java.util.TimeZone;
   40   import java.util.Vector;
   41   import java.util.regex.Matcher;
   42   import java.util.regex.Pattern;
   43   import javax.net.ssl.SSLSocket;
   44   import javax.net.ssl.SSLSocketFactory;
   45   import sun.misc.BASE64Decoder;
   46   import sun.misc.BASE64Encoder;
   47   import sun.net.ftp;
   48   import sun.util.logging.PlatformLogger;
   49   
   50   
   51   public class FtpClient extends sun.net.ftp.FtpClient {
   52   
   53       private static int defaultSoTimeout;
   54       private static int defaultConnectTimeout;
   55       private static final PlatformLogger logger =
   56                PlatformLogger.getLogger("sun.net.ftp.FtpClient");
   57       private Proxy proxy;
   58       private Socket server;
   59       private PrintStream out;
   60       private InputStream in;
   61       private int readTimeout = -1;
   62       private int connectTimeout = -1;
   63   
   64       /* Name of encoding to use for output */
   65       private static String encoding = "ISO8859_1";
   66       /** remember the ftp server name because we may need it */
   67       private InetSocketAddress serverAddr;
   68       private boolean replyPending = false;
   69       private boolean loggedIn = false;
   70       private boolean useCrypto = false;
   71       private SSLSocketFactory sslFact;
   72       private Socket oldSocket;
   73       /** Array of strings (usually 1 entry) for the last reply from the server. */
   74       private Vector<String> serverResponse = new Vector<String>(1);
   75       /** The last reply code from the ftp daemon. */
   76       private FtpReplyCode lastReplyCode = null;
   77       /** Welcome message from the server, if any. */
   78       private String welcomeMsg;
   79       private boolean passiveMode = true;
   80       private TransferType type = TransferType.BINARY;
   81       private long restartOffset = 0;
   82       private long lastTransSize = -1; // -1 means 'unknown size'
   83       private String lastFileName;
   84       /**
   85        * Static members used by the parser
   86        */
   87       private static String[] patStrings = {
   88           // drwxr-xr-x  1 user01        ftp   512 Jan 29 23:32 prog
   89           "([\\-ld](?:[r\\-][w\\-][x\\-]){3})\\s*\\d+ (\\w+)\\s*(\\w+)\\s*(\\d+)\\s*([A-Z][a-z][a-z]\\s*\\d+)\\s*(\\d\\d:\\d\\d)\\s*(\\p{Print}*)",
   90           // drwxr-xr-x  1 user01        ftp   512 Jan 29 1997 prog
   91           "([\\-ld](?:[r\\-][w\\-][x\\-]){3})\\s*\\d+ (\\w+)\\s*(\\w+)\\s*(\\d+)\\s*([A-Z][a-z][a-z]\\s*\\d+)\\s*(\\d{4})\\s*(\\p{Print}*)",
   92           // 04/28/2006  09:12a               3,563 genBuffer.sh
   93           "(\\d{2}/\\d{2}/\\d{4})\\s*(\\d{2}:\\d{2}[ap])\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)",
   94           // 01-29-97    11:32PM <DIR> prog
   95           "(\\d{2}-\\d{2}-\\d{2})\\s*(\\d{2}:\\d{2}[AP]M)\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)"
   96       };
   97       private static int[][] patternGroups = {
   98           // 0 - file, 1 - size, 2 - date, 3 - time, 4 - year, 5 - permissions,
   99           // 6 - user, 7 - group
  100           {7, 4, 5, 6, 0, 1, 2, 3},
  101           {7, 4, 5, 0, 6, 1, 2, 3},
  102           {4, 3, 1, 2, 0, 0, 0, 0},
  103           {4, 3, 1, 2, 0, 0, 0, 0}};
  104       private static Pattern[] patterns;
  105       private static Pattern linkp = Pattern.compile("(\\p{Print}+) \\-\\> (\\p{Print}+)$");
  106       private DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, java.util.Locale.US);
  107   
  108       static {
  109           final int vals[] = {0, 0};
  110           final String encs[] = {null};
  111   
  112           AccessController.doPrivileged(
  113                   new PrivilegedAction<Object>() {
  114   
  115                       public Object run() {
  116                           vals[0] = Integer.getInteger("sun.net.client.defaultReadTimeout", 0).intValue();
  117                           vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 0).intValue();
  118                           encs[0] = System.getProperty("file.encoding", "ISO8859_1");
  119                           return null;
  120                       }
  121                   });
  122           if (vals[0] == 0) {
  123               defaultSoTimeout = -1;
  124           } else {
  125               defaultSoTimeout = vals[0];
  126           }
  127   
  128           if (vals[1] == 0) {
  129               defaultConnectTimeout = -1;
  130           } else {
  131               defaultConnectTimeout = vals[1];
  132           }
  133   
  134           encoding = encs[0];
  135           try {
  136               if (!isASCIISuperset(encoding)) {
  137                   encoding = "ISO8859_1";
  138               }
  139           } catch (Exception e) {
  140               encoding = "ISO8859_1";
  141           }
  142   
  143           patterns = new Pattern[patStrings.length];
  144           for (int i = 0; i < patStrings.length; i++) {
  145               patterns[i] = Pattern.compile(patStrings[i]);
  146           }
  147       }
  148   
  149       /**
  150        * Test the named character encoding to verify that it converts ASCII
  151        * characters correctly. We have to use an ASCII based encoding, or else
  152        * the NetworkClients will not work correctly in EBCDIC based systems.
  153        * However, we cannot just use ASCII or ISO8859_1 universally, because in
  154        * Asian locales, non-ASCII characters may be embedded in otherwise
  155        * ASCII based protocols (eg. HTTP). The specifications (RFC2616, 2398)
  156        * are a little ambiguous in this matter. For instance, RFC2398 [part 2.1]
  157        * says that the HTTP request URI should be escaped using a defined
  158        * mechanism, but there is no way to specify in the escaped string what
  159        * the original character set is. It is not correct to assume that
  160        * UTF-8 is always used (as in URLs in HTML 4.0).  For this reason,
  161        * until the specifications are updated to deal with this issue more
  162        * comprehensively, and more importantly, HTTP servers are known to
  163        * support these mechanisms, we will maintain the current behavior
  164        * where it is possible to send non-ASCII characters in their original
  165        * unescaped form.
  166        */
  167       private static boolean isASCIISuperset(String encoding) throws Exception {
  168           String chkS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
  169                   "abcdefghijklmnopqrstuvwxyz-_.!~*'();/?:@&=+$,";
  170   
  171           // Expected byte sequence for string above
  172           byte[] chkB = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72,
  173               73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99,
  174               100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
  175               115, 116, 117, 118, 119, 120, 121, 122, 45, 95, 46, 33, 126, 42, 39, 40, 41, 59,
  176               47, 63, 58, 64, 38, 61, 43, 36, 44};
  177   
  178           byte[] b = chkS.getBytes(encoding);
  179           return java.util.Arrays.equals(b, chkB);
  180       }
  181   
  182       private class DefaultParser implements FtpDirParser {
  183   
  184           /**
  185            * Possible patterns:
  186            *
  187            *  drwxr-xr-x  1 user01        ftp   512 Jan 29 23:32 prog
  188            *  drwxr-xr-x  1 user01        ftp   512 Jan 29 1997 prog
  189            *  drwxr-xr-x  1 1             1     512 Jan 29 23:32 prog
  190            *  lrwxr-xr-x  1 user01        ftp   512 Jan 29 23:32 prog -> prog2000
  191            *  drwxr-xr-x  1 username      ftp   512 Jan 29 23:32 prog
  192            *  -rw-r--r--  1 jcc      staff     105009 Feb  3 15:05 test.1
  193            *
  194            *  01-29-97    11:32PM <DIR> prog
  195            *  04/28/2006  09:12a               3,563 genBuffer.sh
  196            *
  197            *  drwxr-xr-x  folder   0       Jan 29 23:32 prog
  198            *
  199            *  0 DIR 01-29-97 23:32 PROG
  200            */
  201           private DefaultParser() {
  202           }
  203   
  204           public FtpDirEntry parseLine(String line) {
  205               String fdate = null;
  206               String fsize = null;
  207               String time = null;
  208               String filename = null;
  209               String permstring = null;
  210               String username = null;
  211               String groupname = null;
  212               boolean dir = false;
  213               Calendar now = Calendar.getInstance();
  214               int year = now.get(Calendar.YEAR);
  215   
  216               Matcher m = null;
  217               for (int j = 0; j < patterns.length; j++) {
  218                   m = patterns[j].matcher(line);
  219                   if (m.find()) {
  220                       // 0 - file, 1 - size, 2 - date, 3 - time, 4 - year,
  221                       // 5 - permissions, 6 - user, 7 - group
  222                       filename = m.group(patternGroups[j][0]);
  223                       fsize = m.group(patternGroups[j][1]);
  224                       fdate = m.group(patternGroups[j][2]);
  225                       if (patternGroups[j][4] > 0) {
  226                           fdate += (", " + m.group(patternGroups[j][4]));
  227                       } else if (patternGroups[j][3] > 0) {
  228                           fdate += (", " + String.valueOf(year));
  229                       }
  230                       if (patternGroups[j][3] > 0) {
  231                           time = m.group(patternGroups[j][3]);
  232                       }
  233                       if (patternGroups[j][5] > 0) {
  234                           permstring = m.group(patternGroups[j][5]);
  235                           dir = permstring.startsWith("d");
  236                       }
  237                       if (patternGroups[j][6] > 0) {
  238                           username = m.group(patternGroups[j][6]);
  239                       }
  240                       if (patternGroups[j][7] > 0) {
  241                           groupname = m.group(patternGroups[j][7]);
  242                       }
  243                       // Old DOS format
  244                       if ("<DIR>".equals(fsize)) {
  245                           dir = true;
  246                           fsize = null;
  247                       }
  248                   }
  249               }
  250   
  251               if (filename != null) {
  252                   Date d;
  253                   try {
  254                       d = df.parse(fdate);
  255                   } catch (Exception e) {
  256                       d = null;
  257                   }
  258                   if (d != null && time != null) {
  259                       int c = time.indexOf(":");
  260                       now.setTime(d);
  261                       now.set(Calendar.HOUR, Integer.parseInt(time.substring(0, c)));
  262                       now.set(Calendar.MINUTE, Integer.parseInt(time.substring(c + 1)));
  263                       d = now.getTime();
  264                   }
  265                   // see if it's a symbolic link, i.e. the name if followed
  266                   // by a -> and a path
  267                   Matcher m2 = linkp.matcher(filename);
  268                   if (m2.find()) {
  269                       // Keep only the name then
  270                       filename = m2.group(1);
  271                   }
  272                   boolean[][] perms = new boolean[3][3];
  273                   for (int i = 0; i < 3; i++) {
  274                       for (int j = 0; j < 3; j++) {
  275                           perms[i][j] = (permstring.charAt((i * 3) + j) != '-');
  276                       }
  277                   }
  278                   FtpDirEntry file = new FtpDirEntry(filename);
  279                   file.setUser(username).setGroup(groupname);
  280                   file.setSize(Long.parseLong(fsize)).setLastModified(d);
  281                   file.setPermissions(perms);
  282                   file.setType(dir ? FtpDirEntry.Type.DIR : (line.charAt(0) == 'l' ? FtpDirEntry.Type.LINK : FtpDirEntry.Type.FILE));
  283                   return file;
  284               }
  285               return null;
  286           }
  287       }
  288   
  289       private class MLSxParser implements FtpDirParser {
  290   
  291           private SimpleDateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");
  292   
  293           public FtpDirEntry parseLine(String line) {
  294               String name = null;
  295               int i = line.lastIndexOf(";");
  296               if (i > 0) {
  297                   name = line.substring(i + 1).trim();
  298                   line = line.substring(0, i);
  299               } else {
  300                   name = line.trim();
  301                   line = "";
  302               }
  303               FtpDirEntry file = new FtpDirEntry(name);
  304               while (!line.isEmpty()) {
  305                   String s;
  306                   i = line.indexOf(";");
  307                   if (i > 0) {
  308                       s = line.substring(0, i);
  309                       line = line.substring(i + 1);
  310                   } else {
  311                       s = line;
  312                       line = "";
  313                   }
  314                   i = s.indexOf("=");
  315                   if (i > 0) {
  316                       String fact = s.substring(0, i);
  317                       String value = s.substring(i + 1);
  318                       file.addFact(fact, value);
  319                   }
  320               }
  321               String s = file.getFact("Size");
  322               if (s != null) {
  323                   file.setSize(Long.parseLong(s));
  324               }
  325               s = file.getFact("Modify");
  326               if (s != null) {
  327                   Date d = null;
  328                   try {
  329                       d = df.parse(s);
  330                   } catch (ParseException ex) {
  331                   }
  332                   if (d != null) {
  333                       file.setLastModified(d);
  334                   }
  335               }
  336               s = file.getFact("Create");
  337               if (s != null) {
  338                   Date d = null;
  339                   try {
  340                       d = df.parse(s);
  341                   } catch (ParseException ex) {
  342                   }
  343                   if (d != null) {
  344                       file.setCreated(d);
  345                   }
  346               }
  347               s = file.getFact("Type");
  348               if (s != null) {
  349                   if (s.equalsIgnoreCase("file")) {
  350                       file.setType(FtpDirEntry.Type.FILE);
  351                   }
  352                   if (s.equalsIgnoreCase("dir")) {
  353                       file.setType(FtpDirEntry.Type.DIR);
  354                   }
  355                   if (s.equalsIgnoreCase("cdir")) {
  356                       file.setType(FtpDirEntry.Type.CDIR);
  357                   }
  358                   if (s.equalsIgnoreCase("pdir")) {
  359                       file.setType(FtpDirEntry.Type.PDIR);
  360                   }
  361               }
  362               return file;
  363           }
  364       };
  365       private FtpDirParser parser = new DefaultParser();
  366       private FtpDirParser mlsxParser = new MLSxParser();
  367       private static Pattern transPat = null;
  368   
  369       private void getTransferSize() {
  370           lastTransSize = -1;
  371           /**
  372            * If it's a start of data transfer response, let's try to extract
  373            * the size from the response string. Usually it looks like that:
  374            *
  375            * 150 Opening BINARY mode data connection for foo (6701 bytes).
  376            */
  377           String response = getLastResponseString();
  378           if (transPat == null) {
  379               transPat = Pattern.compile("150 Opening .*\\((\\d+) bytes\\).");
  380           }
  381           Matcher m = transPat.matcher(response);
  382           if (m.find()) {
  383               String s = m.group(1);
  384               lastTransSize = Long.parseLong(s);
  385           }
  386       }
  387   
  388       /**
  389        * extract the created file name from the response string:
  390        * 226 Transfer complete (unique file name:toto.txt.1).
  391        * Usually happens when a STOU (store unique) command had been issued.
  392        */
  393       private void getTransferName() {
  394           lastFileName = null;
  395           String response = getLastResponseString();
  396           int i = response.indexOf("unique file name:");
  397           int e = response.lastIndexOf(')');
  398           if (i >= 0) {
  399               i += 17; // Length of "unique file name:"
  400               lastFileName = response.substring(i, e);
  401           }
  402       }
  403   
  404       /**
  405        * Pulls the response from the server and returns the code as a
  406        * number. Returns -1 on failure.
  407        */
  408       private int readServerResponse() throws IOException {
  409           StringBuffer replyBuf = new StringBuffer(32);
  410           int c;
  411           int continuingCode = -1;
  412           int code;
  413           String response;
  414   
  415           serverResponse.setSize(0);
  416           while (true) {
  417               while ((c = in.read()) != -1) {
  418                   if (c == '\r') {
  419                       if ((c = in.read()) != '\n') {
  420                           replyBuf.append('\r');
  421                       }
  422                   }
  423                   replyBuf.append((char) c);
  424                   if (c == '\n') {
  425                       break;
  426                   }
  427               }
  428               response = replyBuf.toString();
  429               replyBuf.setLength(0);
  430               if (logger.isLoggable(PlatformLogger.FINEST)) {
  431                   logger.finest("Server [" + serverAddr + "] --> " + response);
  432               }
  433   
  434               if (response.length() == 0) {
  435                   code = -1;
  436               } else {
  437                   try {
  438                       code = Integer.parseInt(response.substring(0, 3));
  439                   } catch (NumberFormatException e) {
  440                       code = -1;
  441                   } catch (StringIndexOutOfBoundsException e) {
  442                       /* this line doesn't contain a response code, so
  443                       we just completely ignore it */
  444                       continue;
  445                   }
  446               }
  447               serverResponse.addElement(response);
  448               if (continuingCode != -1) {
  449                   /* we've seen a ###- sequence */
  450                   if (code != continuingCode ||
  451                           (response.length() >= 4 && response.charAt(3) == '-')) {
  452                       continue;
  453                   } else {
  454                       /* seen the end of code sequence */
  455                       continuingCode = -1;
  456                       break;
  457                   }
  458               } else if (response.length() >= 4 && response.charAt(3) == '-') {
  459                   continuingCode = code;
  460                   continue;
  461               } else {
  462                   break;
  463               }
  464           }
  465   
  466           return code;
  467       }
  468   
  469       /** Sends command <i>cmd</i> to the server. */
  470       private void sendServer(String cmd) {
  471           out.print(cmd);
  472           if (logger.isLoggable(PlatformLogger.FINEST)) {
  473               logger.finest("Server [" + serverAddr + "] <-- " + cmd);
  474           }
  475       }
  476   
  477       /** converts the server response into a string. */
  478       private String getResponseString() {
  479           return serverResponse.elementAt(0);
  480       }
  481   
  482       /** Returns all server response strings. */
  483       private Vector<String> getResponseStrings() {
  484           return serverResponse;
  485       }
  486   
  487       /**
  488        * Read the reply from the FTP server.
  489        *
  490        * @return <code>true</code> if the command was successful
  491        * @throws IOException if an error occured
  492        */
  493       private boolean readReply() throws IOException {
  494           lastReplyCode = FtpReplyCode.find(readServerResponse());
  495   
  496           if (lastReplyCode.isPositivePreliminary()) {
  497               replyPending = true;
  498               return true;
  499           }
  500           if (lastReplyCode.isPositiveCompletion() || lastReplyCode.isPositiveIntermediate()) {
  501               if (lastReplyCode == FtpReplyCode.CLOSING_DATA_CONNECTION) {
  502                   getTransferName();
  503               }
  504               return true;
  505           }
  506           return false;
  507       }
  508   
  509       /**
  510        * Sends a command to the FTP server and returns the error code
  511        * (which can be a "success") sent by the server.
  512        *
  513        * @param cmd
  514        * @return <code>true</code> if the command was successful
  515        * @throws IOException
  516        */
  517       private boolean issueCommand(String cmd) throws IOException {
  518           if (!isConnected()) {
  519               throw new IllegalStateException("Not connected");
  520           }
  521           if (replyPending) {
  522               try {
  523                   completePending();
  524               } catch (sun.net.ftp.FtpProtocolException e) {
  525                   // ignore...
  526               }
  527           }
  528           sendServer(cmd + "\r\n");
  529           return readReply();
  530       }
  531   
  532       /**
  533        * Send a command to the FTP server and check for success.
  534        *
  535        * @param cmd String containing the command
  536        *
  537        * @throws FtpProtocolException if an error occured
  538        */
  539       private void issueCommandCheck(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
  540           if (!issueCommand(cmd)) {
  541               throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());
  542           }
  543       }
  544       private static Pattern epsvPat = null;
  545       private static Pattern pasvPat = null;
  546   
  547       /**
  548        * Opens a "PASSIVE" connection with the server and returns the connected
  549        * <code>Socket</code>.
  550        *
  551        * @return the connected <code>Socket</code>
  552        * @throws IOException if the connection was unsuccessful.
  553        */
  554       private Socket openPassiveDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
  555           String serverAnswer;
  556           int port;
  557           InetSocketAddress dest = null;
  558   
  559           /**
  560            * Here is the idea:
  561            *
  562            * - First we want to try the new (and IPv6 compatible) EPSV command
  563            *   But since we want to be nice with NAT software, we'll issue the
  564            *   EPSV ALL command first.
  565            *   EPSV is documented in RFC2428
  566            * - If EPSV fails, then we fall back to the older, yet ok, PASV
  567            * - If PASV fails as well, then we throw an exception and the calling
  568            *   method will have to try the EPRT or PORT command
  569            */
  570           if (issueCommand("EPSV ALL")) {
  571               // We can safely use EPSV commands
  572               issueCommandCheck("EPSV");
  573               serverAnswer = getResponseString();
  574   
  575               // The response string from a EPSV command will contain the port number
  576               // the format will be :
  577               //  229 Entering Extended PASSIVE Mode (|||58210|)
  578               //
  579               // So we'll use the regular expresions package to parse the output.
  580   
  581               if (epsvPat == null) {
  582                   epsvPat = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)");
  583               }
  584               Matcher m = epsvPat.matcher(serverAnswer);
  585               if (!m.find()) {
  586                   throw new sun.net.ftp.FtpProtocolException("EPSV failed : " + serverAnswer);
  587               }
  588               // Yay! Let's extract the port number
  589               String s = m.group(1);
  590               port = Integer.parseInt(s);
  591               InetAddress add = server.getInetAddress();
  592               if (add != null) {
  593                   dest = new InetSocketAddress(add, port);
  594               } else {
  595                   // This means we used an Unresolved address to connect in
  596                   // the first place. Most likely because the proxy is doing
  597                   // the name resolution for us, so let's keep using unresolved
  598                   // address.
  599                   dest = InetSocketAddress.createUnresolved(serverAddr.getHostName(), port);
  600               }
  601           } else {
  602               // EPSV ALL failed, so Let's try the regular PASV cmd
  603               issueCommandCheck("PASV");
  604               serverAnswer = getResponseString();
  605   
  606               // Let's parse the response String to get the IP & port to connect
  607               // to. The String should be in the following format :
  608               //
  609               // 227 Entering PASSIVE Mode (A1,A2,A3,A4,p1,p2)
  610               //
  611               // Note that the two parenthesis are optional
  612               //
  613               // The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2
  614               //
  615               // The regular expression is a bit more complex this time, because
  616               // the parenthesis are optionals and we have to use 3 groups.
  617   
  618               if (pasvPat == null) {
  619                   pasvPat = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?");
  620               }
  621               Matcher m = pasvPat.matcher(serverAnswer);
  622               if (!m.find()) {
  623                   throw new sun.net.ftp.FtpProtocolException("PASV failed : " + serverAnswer);
  624               }
  625               // Get port number out of group 2 & 3
  626               port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8);
  627               // IP address is simple
  628               String s = m.group(1).replace(',', '.');
  629               dest = new InetSocketAddress(s, port);
  630           }
  631           // Got everything, let's open the socket!
  632           Socket s;
  633           if (proxy != null) {
  634               if (proxy.type() == Proxy.Type.SOCKS) {
  635                   s = AccessController.doPrivileged(
  636                           new PrivilegedAction<Socket>() {
  637   
  638                               public Socket run() {
  639                                   return new Socket(proxy);
  640                               }
  641                           });
  642               } else {
  643                   s = new Socket(Proxy.NO_PROXY);
  644               }
  645           } else {
  646               s = new Socket();
  647           }
  648           // Bind the socket to the same address as the control channel. This
  649           // is needed in case of multi-homed systems.
  650           s.bind(new InetSocketAddress(server.getLocalAddress(), 0));
  651           if (connectTimeout >= 0) {
  652               s.connect(dest, connectTimeout);
  653           } else {
  654               if (defaultConnectTimeout > 0) {
  655                   s.connect(dest, defaultConnectTimeout);
  656               } else {
  657                   s.connect(dest);
  658               }
  659           }
  660           if (readTimeout >= 0) {
  661               s.setSoTimeout(readTimeout);
  662           } else if (defaultSoTimeout > 0) {
  663               s.setSoTimeout(defaultSoTimeout);
  664           }
  665           if (useCrypto) {
  666               try {
  667                   s = sslFact.createSocket(s, dest.getHostName(), dest.getPort(), true);
  668               } catch (Exception e) {
  669                   throw new sun.net.ftp.FtpProtocolException("Can't open secure data channel: " + e);
  670               }
  671           }
  672           if (!issueCommand(cmd)) {
  673               s.close();
  674               if (getLastReplyCode() == FtpReplyCode.FILE_UNAVAILABLE) {
  675                   // Ensure backward compatibility
  676                   throw new FileNotFoundException(cmd);
  677               }
  678               throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());
  679           }
  680           return s;
  681       }
  682   
  683       /**
  684        * Opens a data connection with the server according to the set mode
  685        * (ACTIVE or PASSIVE) then send the command passed as an argument.
  686        *
  687        * @param cmd the <code>String</code> containing the command to execute
  688        * @return the connected <code>Socket</code>
  689        * @throws IOException if the connection or command failed
  690        */
  691       private Socket openDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
  692           Socket clientSocket;
  693   
  694           if (passiveMode) {
  695               try {
  696                   return openPassiveDataConnection(cmd);
  697               } catch (sun.net.ftp.FtpProtocolException e) {
  698                   // If Passive mode failed, fall back on PORT
  699                   // Otherwise throw exception
  700                   String errmsg = e.getMessage();
  701                   if (!errmsg.startsWith("PASV") && !errmsg.startsWith("EPSV")) {
  702                       throw e;
  703                   }
  704               }
  705           }
  706           ServerSocket portSocket;
  707           InetAddress myAddress;
  708           String portCmd;
  709   
  710           if (proxy != null && proxy.type() == Proxy.Type.SOCKS) {
  711               // We're behind a firewall and the passive mode fail,
  712               // since we can't accept a connection through SOCKS (yet)
  713               // throw an exception
  714               throw new sun.net.ftp.FtpProtocolException("Passive mode failed");
  715           }
  716           // Bind the ServerSocket to the same address as the control channel
  717           // This is needed for multi-homed systems
  718           portSocket = new ServerSocket(0, 1, server.getLocalAddress());
  719           try {
  720               myAddress = portSocket.getInetAddress();
  721               if (myAddress.isAnyLocalAddress()) {
  722                   myAddress = server.getLocalAddress();
  723               }
  724               // Let's try the new, IPv6 compatible EPRT command
  725               // See RFC2428 for specifics
  726               // Some FTP servers (like the one on Solaris) are bugged, they
  727               // will accept the EPRT command but then, the subsequent command
  728               // (e.g. RETR) will fail, so we have to check BOTH results (the
  729               // EPRT cmd then the actual command) to decide wether we should
  730               // fall back on the older PORT command.
  731               portCmd = "EPRT |" + ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" +
  732                       myAddress.getHostAddress() + "|" + portSocket.getLocalPort() + "|";
  733               if (!issueCommand(portCmd) || !issueCommand(cmd)) {
  734                   // The EPRT command failed, let's fall back to good old PORT
  735                   portCmd = "PORT ";
  736                   byte[] addr = myAddress.getAddress();
  737   
  738                   /* append host addr */
  739                   for (int i = 0; i < addr.length; i++) {
  740                       portCmd = portCmd + (addr[i] & 0xFF) + ",";
  741                   }
  742   
  743                   /* append port number */
  744                   portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff);
  745                   issueCommandCheck(portCmd);
  746                   issueCommandCheck(cmd);
  747               }
  748               // Either the EPRT or the PORT command was successful
  749               // Let's create the client socket
  750               if (connectTimeout >= 0) {
  751                   portSocket.setSoTimeout(connectTimeout);
  752               } else {
  753                   if (defaultConnectTimeout > 0) {
  754                       portSocket.setSoTimeout(defaultConnectTimeout);
  755                   }
  756               }
  757               clientSocket = portSocket.accept();
  758               if (readTimeout >= 0) {
  759                   clientSocket.setSoTimeout(readTimeout);
  760               } else {
  761                   if (defaultSoTimeout > 0) {
  762                       clientSocket.setSoTimeout(defaultSoTimeout);
  763                   }
  764               }
  765           } finally {
  766               portSocket.close();
  767           }
  768           if (useCrypto) {
  769               try {
  770                   clientSocket = sslFact.createSocket(clientSocket, serverAddr.getHostName(), serverAddr.getPort(), true);
  771               } catch (Exception ex) {
  772                   throw new IOException(ex.getLocalizedMessage());
  773               }
  774           }
  775           return clientSocket;
  776       }
  777   
  778       private InputStream createInputStream(InputStream in) {
  779           if (type == TransferType.ASCII) {
  780               return new sun.net.TelnetInputStream(in, false);
  781           }
  782           return in;
  783       }
  784   
  785       private OutputStream createOutputStream(OutputStream out) {
  786           if (type == TransferType.ASCII) {
  787               return new sun.net.TelnetOutputStream(out, false);
  788           }
  789           return out;
  790       }
  791   
  792       /**
  793        * Creates an instance of FtpClient. The client is not connected to any
  794        * server yet.
  795        *
  796        */
  797       protected FtpClient() {
  798       }
  799   
  800       /**
  801        * Creates an instance of FtpClient. The client is not connected to any
  802        * server yet.
  803        *
  804        */
  805       public static sun.net.ftp.FtpClient create() {
  806           return new FtpClient();
  807       }
  808   
  809       /**
  810        * Set the transfer mode to <I>passive</I>. In that mode, data connections
  811        * are established by having the client connect to the server.
  812        * This is the recommended default mode as it will work best through
  813        * firewalls and NATs.
  814        *
  815        * @return This FtpClient
  816        * @see #setActiveMode()
  817        */
  818       public sun.net.ftp.FtpClient enablePassiveMode(boolean passive) {
  819           passiveMode = passive;
  820           return this;
  821       }
  822   
  823       /**
  824        * Gets the current transfer mode.
  825        *
  826        * @return the current <code>FtpTransferMode</code>
  827        */
  828       public boolean isPassiveModeEnabled() {
  829           return passiveMode;
  830       }
  831   
  832       /**
  833        * Sets the timeout value to use when connecting to the server,
  834        *
  835        * @param timeout the timeout value, in milliseconds, to use for the connect
  836        *        operation. A value of zero or less, means use the default timeout.
  837        *
  838        * @return This FtpClient
  839        */
  840       public sun.net.ftp.FtpClient setConnectTimeout(int timeout) {
  841           connectTimeout = timeout;
  842           return this;
  843       }
  844   
  845       /**
  846        * Returns the current connection timeout value.
  847        *
  848        * @return the value, in milliseconds, of the current connect timeout.
  849        * @see #setConnectTimeout(int)
  850        */
  851       public int getConnectTimeout() {
  852           return connectTimeout;
  853       }
  854   
  855       /**
  856        * Sets the timeout value to use when reading from the server,
  857        *
  858        * @param timeout the timeout value, in milliseconds, to use for the read
  859        *        operation. A value of zero or less, means use the default timeout.
  860        * @return This FtpClient
  861        */
  862       public sun.net.ftp.FtpClient setReadTimeout(int timeout) {
  863           readTimeout = timeout;
  864           return this;
  865       }
  866   
  867       /**
  868        * Returns the current read timeout value.
  869        *
  870        * @return the value, in milliseconds, of the current read timeout.
  871        * @see #setReadTimeout(int)
  872        */
  873       public int getReadTimeout() {
  874           return readTimeout;
  875       }
  876   
  877       public sun.net.ftp.FtpClient setProxy(Proxy p) {
  878           proxy = p;
  879           return this;
  880       }
  881   
  882       /**
  883        * Get the proxy of this FtpClient
  884        *
  885        * @return the <code>Proxy</code>, this client is using, or <code>null</code>
  886        *         if none is used.
  887        * @see #setProxy(Proxy)
  888        */
  889       public Proxy getProxy() {
  890           return proxy;
  891       }
  892   
  893       /**
  894        * Connects to the specified destination.
  895        *
  896        * @param dest the <code>InetSocketAddress</code> to connect to.
  897        * @throws IOException if the connection fails.
  898        */
  899       private void tryConnect(InetSocketAddress dest, int timeout) throws IOException {
  900           if (isConnected()) {
  901               disconnect();
  902           }
  903           server = doConnect(dest, timeout);
  904           try {
  905               out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
  906                       true, encoding);
  907           } catch (UnsupportedEncodingException e) {
  908               throw new InternalError(encoding + "encoding not found");
  909           }
  910           in = new BufferedInputStream(server.getInputStream());
  911       }
  912   
  913       private Socket doConnect(InetSocketAddress dest, int timeout) throws IOException {
  914           Socket s;
  915           if (proxy != null) {
  916               if (proxy.type() == Proxy.Type.SOCKS) {
  917                   s = AccessController.doPrivileged(
  918                           new PrivilegedAction<Socket>() {
  919   
  920                               public Socket run() {
  921                                   return new Socket(proxy);
  922                               }
  923                           });
  924               } else {
  925                   s = new Socket(Proxy.NO_PROXY);
  926               }
  927           } else {
  928               s = new Socket();
  929           }
  930           // Instance specific timeouts do have priority, that means
  931           // connectTimeout & readTimeout (-1 means not set)
  932           // Then global default timeouts
  933           // Then no timeout.
  934           if (timeout >= 0) {
  935               s.connect(dest, timeout);
  936           } else {
  937               if (connectTimeout >= 0) {
  938                   s.connect(dest, connectTimeout);
  939               } else {
  940                   if (defaultConnectTimeout > 0) {
  941                       s.connect(dest, defaultConnectTimeout);
  942                   } else {
  943                       s.connect(dest);
  944                   }
  945               }
  946           }
  947           if (readTimeout >= 0) {
  948               s.setSoTimeout(readTimeout);
  949           } else if (defaultSoTimeout > 0) {
  950               s.setSoTimeout(defaultSoTimeout);
  951           }
  952           return s;
  953       }
  954   
  955       private void disconnect() throws IOException {
  956           if (isConnected()) {
  957               server.close();
  958           }
  959           server = null;
  960           in = null;
  961           out = null;
  962           lastTransSize = -1;
  963           lastFileName = null;
  964           restartOffset = 0;
  965           welcomeMsg = null;
  966           lastReplyCode = null;
  967           serverResponse.setSize(0);
  968       }
  969   
  970       /**
  971        * Tests whether this client is connected or not to a server.
  972        *
  973        * @return <code>true</code> if the client is connected.
  974        */
  975       public boolean isConnected() {
  976           return server != null;
  977       }
  978   
  979       public SocketAddress getServerAddress() {
  980           return server == null ? null : server.getRemoteSocketAddress();
  981       }
  982   
  983       public sun.net.ftp.FtpClient connect(SocketAddress dest) throws sun.net.ftp.FtpProtocolException, IOException {
  984           return connect(dest, -1);
  985       }
  986   
  987       /**
  988        * Connects the FtpClient to the specified destination.
  989        *
  990        * @param dest the address of the destination server
  991        * @throws IOException if connection failed.
  992        */
  993       public sun.net.ftp.FtpClient connect(SocketAddress dest, int timeout) throws sun.net.ftp.FtpProtocolException, IOException {
  994           if (!(dest instanceof InetSocketAddress)) {
  995               throw new IllegalArgumentException("Wrong address type");
  996           }
  997           serverAddr = (InetSocketAddress) dest;
  998           tryConnect(serverAddr, timeout);
  999           if (!readReply()) {
 1000               throw new sun.net.ftp.FtpProtocolException("Welcome message: " +
 1001                       getResponseString(), lastReplyCode);
 1002           }
 1003           welcomeMsg = getResponseString().substring(4);
 1004           return this;
 1005       }
 1006   
 1007       private void tryLogin(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {
 1008           issueCommandCheck("USER " + user);
 1009   
 1010           /*
 1011            * Checks for "331 User name okay, need password." answer
 1012            */
 1013           if (lastReplyCode == FtpReplyCode.NEED_PASSWORD) {
 1014               if ((password != null) && (password.length > 0)) {
 1015                   issueCommandCheck("PASS " + String.valueOf(password));
 1016               }
 1017           }
 1018       }
 1019   
 1020       /**
 1021        * Attempts to log on the server with the specified user name and password.
 1022        *
 1023        * @param user The user name
 1024        * @param password The password for that user
 1025        * @return <code>true</code> if the login was successful.
 1026        * @throws IOException if an error occured during the transmission
 1027        */
 1028       public sun.net.ftp.FtpClient login(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {
 1029           if (!isConnected()) {
 1030               throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
 1031           }
 1032           if (user == null || user.length() == 0) {
 1033               throw new IllegalArgumentException("User name can't be null or empty");
 1034           }
 1035           tryLogin(user, password);
 1036   
 1037           // keep the welcome message around so we can
 1038           // put it in the resulting HTML page.
 1039           String l;
 1040           StringBuffer sb = new StringBuffer();
 1041           for (int i = 0; i < serverResponse.size(); i++) {
 1042               l = serverResponse.elementAt(i);
 1043               if (l != null) {
 1044                   if (l.length() >= 4 && l.startsWith("230")) {
 1045                       // get rid of the "230-" prefix
 1046                       l = l.substring(4);
 1047                   }
 1048                   sb.append(l);
 1049               }
 1050           }
 1051           welcomeMsg = sb.toString();
 1052           loggedIn = true;
 1053           return this;
 1054       }
 1055   
 1056       /**
 1057        * Attempts to log on the server with the specified user name, password and
 1058        * account name.
 1059        *
 1060        * @param user The user name
 1061        * @param password The password for that user.
 1062        * @param account The account name for that user.
 1063        * @return <code>true</code> if the login was successful.
 1064        * @throws IOException if an error occurs during the transmission.
 1065        */
 1066       public sun.net.ftp.FtpClient login(String user, char[] password, String account) throws sun.net.ftp.FtpProtocolException, IOException {
 1067   
 1068           if (!isConnected()) {
 1069               throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
 1070           }
 1071           if (user == null || user.length() == 0) {
 1072               throw new IllegalArgumentException("User name can't be null or empty");
 1073           }
 1074           tryLogin(user, password);
 1075   
 1076           /*
 1077            * Checks for "332 Need account for login." answer
 1078            */
 1079           if (lastReplyCode == FtpReplyCode.NEED_ACCOUNT) {
 1080               issueCommandCheck("ACCT " + account);
 1081           }
 1082   
 1083           // keep the welcome message around so we can
 1084           // put it in the resulting HTML page.
 1085           StringBuffer sb = new StringBuffer();
 1086           if (serverResponse != null) {
 1087               for (String l : serverResponse) {
 1088                   if (l != null) {
 1089                       if (l.length() >= 4 && l.startsWith("230")) {
 1090                           // get rid of the "230-" prefix
 1091                           l = l.substring(4);
 1092                       }
 1093                       sb.append(l);
 1094                   }
 1095               }
 1096           }
 1097           welcomeMsg = sb.toString();
 1098           loggedIn = true;
 1099           return this;
 1100       }
 1101   
 1102       /**
 1103        * Logs out the current user. This is in effect terminates the current
 1104        * session and the connection to the server will be closed.
 1105        *
 1106        */
 1107       public void close() throws IOException {
 1108           if (isConnected()) {
 1109               issueCommand("QUIT");
 1110               loggedIn = false;
 1111           }
 1112           disconnect();
 1113       }
 1114   
 1115       /**
 1116        * Checks whether the client is logged in to the server or not.
 1117        *
 1118        * @return <code>true</code> if the client has already completed a login.
 1119        */
 1120       public boolean isLoggedIn() {
 1121           return loggedIn;
 1122       }
 1123   
 1124       /**
 1125        * Changes to a specific directory on a remote FTP server
 1126        *
 1127        * @param remoteDirectory path of the directory to CD to.
 1128        * @return <code>true</code> if the operation was successful.
 1129        * @exception <code>FtpProtocolException</code>
 1130        */
 1131       public sun.net.ftp.FtpClient changeDirectory(String remoteDirectory) throws sun.net.ftp.FtpProtocolException, IOException {
 1132           if (remoteDirectory == null || "".equals(remoteDirectory)) {
 1133               throw new IllegalArgumentException("directory can't be null or empty");
 1134           }
 1135   
 1136           issueCommandCheck("CWD " + remoteDirectory);
 1137           return this;
 1138       }
 1139   
 1140       /**
 1141        * Changes to the parent directory, sending the CDUP command to the server.
 1142        *
 1143        * @return <code>true</code> if the command was successful.
 1144        * @throws IOException
 1145        */
 1146       public sun.net.ftp.FtpClient changeToParentDirectory() throws sun.net.ftp.FtpProtocolException, IOException {
 1147           issueCommandCheck("CDUP");
 1148           return this;
 1149       }
 1150   
 1151       /**
 1152        * Returns the server current working directory, or <code>null</code> if
 1153        * the PWD command failed.
 1154        *
 1155        * @return a <code>String</code> containing the current working directory,
 1156        *         or <code>null</code>
 1157        * @throws IOException
 1158        */
 1159       public String getWorkingDirectory() throws sun.net.ftp.FtpProtocolException, IOException {
 1160           issueCommandCheck("PWD");
 1161           /*
 1162            * answer will be of the following format :
 1163            *
 1164            * 257 "/" is current directory.
 1165            */
 1166           String answ = getResponseString();
 1167           if (!answ.startsWith("257")) {
 1168               return null;
 1169           }
 1170           return answ.substring(5, answ.lastIndexOf('"'));
 1171       }
 1172   
 1173       /**
 1174        * Sets the restart offset to the specified value.  That value will be
 1175        * sent through a <code>REST</code> command to server before a file
 1176        * transfer and has the effect of resuming a file transfer from the
 1177        * specified point. After a transfer the restart offset is set back to
 1178        * zero.
 1179        *
 1180        * @param offset the offset in the remote file at which to start the next
 1181        *        transfer. This must be a value greater than or equal to zero.
 1182        * @throws IllegalArgumentException if the offset is negative.
 1183        */
 1184       public sun.net.ftp.FtpClient setRestartOffset(long offset) {
 1185           if (offset < 0) {
 1186               throw new IllegalArgumentException("offset can't be negative");
 1187           }
 1188           restartOffset = offset;
 1189           return this;
 1190       }
 1191   
 1192       /**
 1193        * Retrieves a file from the ftp server and writes it to the specified
 1194        * <code>OutputStream</code>.
 1195        * If the restart offset was set, then a <code>REST</code> command will be
 1196        * sent before the RETR in order to restart the tranfer from the specified
 1197        * offset.
 1198        * The <code>OutputStream</code> is not closed by this method at the end
 1199        * of the transfer.
 1200        *
 1201        * @param name a <code>String<code> containing the name of the file to
 1202        *        retreive from the server.
 1203        * @param local the <code>OutputStream</code> the file should be written to.
 1204        * @throws IOException if the transfer fails.
 1205        */
 1206       public sun.net.ftp.FtpClient getFile(String name, OutputStream local) throws sun.net.ftp.FtpProtocolException, IOException {
 1207           int mtu = 1500;
 1208           if (restartOffset > 0) {
 1209               Socket s;
 1210               try {
 1211                   s = openDataConnection("REST " + restartOffset);
 1212               } finally {
 1213                   restartOffset = 0;
 1214               }
 1215               issueCommandCheck("RETR " + name);
 1216               getTransferSize();
 1217               InputStream remote = createInputStream(s.getInputStream());
 1218               byte[] buf = new byte[mtu * 10];
 1219               int l;
 1220               while ((l = remote.read(buf)) >= 0) {
 1221                   if (l > 0) {
 1222                       local.write(buf, 0, l);
 1223                   }
 1224               }
 1225               remote.close();
 1226           } else {
 1227               Socket s = openDataConnection("RETR " + name);
 1228               getTransferSize();
 1229               InputStream remote = createInputStream(s.getInputStream());
 1230               byte[] buf = new byte[mtu * 10];
 1231               int l;
 1232               while ((l = remote.read(buf)) >= 0) {
 1233                   if (l > 0) {
 1234                       local.write(buf, 0, l);
 1235                   }
 1236               }
 1237               remote.close();
 1238           }
 1239           return completePending();
 1240       }
 1241   
 1242       /**
 1243        * Retrieves a file from the ftp server, using the RETR command, and
 1244        * returns the InputStream from* the established data connection.
 1245        * {@link #completePending()} <b>has</b> to be called once the application
 1246        * is done reading from the returned stream.
 1247        *
 1248        * @param name the name of the remote file
 1249        * @return the {@link java.io.InputStream} from the data connection, or
 1250        *         <code>null</code> if the command was unsuccessful.
 1251        * @throws IOException if an error occured during the transmission.
 1252        */
 1253       public InputStream getFileStream(String name) throws sun.net.ftp.FtpProtocolException, IOException {
 1254           Socket s;
 1255           if (restartOffset > 0) {
 1256               try {
 1257                   s = openDataConnection("REST " + restartOffset);
 1258               } finally {
 1259                   restartOffset = 0;
 1260               }
 1261               if (s == null) {
 1262                   return null;
 1263               }
 1264               issueCommandCheck("RETR " + name);
 1265               getTransferSize();
 1266               return createInputStream(s.getInputStream());
 1267           }
 1268   
 1269           s = openDataConnection("RETR " + name);
 1270           if (s == null) {
 1271               return null;
 1272           }
 1273           getTransferSize();
 1274           return createInputStream(s.getInputStream());
 1275       }
 1276   
 1277       /**
 1278        * Transfers a file from the client to the server (aka a <I>put</I>)
 1279        * by sending the STOR or STOU command, depending on the
 1280        * <code>unique</code> argument, and returns the <code>OutputStream</code>
 1281        * from the established data connection.
 1282        * {@link #completePending()} <b>has</b> to be called once the application
 1283        * is finished writing to the stream.
 1284        *
 1285        * A new file is created at the server site if the file specified does
 1286        * not already exist.
 1287        *
 1288        * If <code>unique</code> is set to <code>true</code>, the resultant file
 1289        * is to be created under a name unique to that directory, meaning
 1290        * it will not overwrite an existing file, instead the server will
 1291        * generate a new, unique, file name.
 1292        * The name of the remote file can be retrieved, after completion of the
 1293        * transfer, by calling {@link #getLastFileName()}.
 1294        *
 1295        * @param name the name of the remote file to write.
 1296        * @param unique <code>true</code> if the remote files should be unique,
 1297        *        in which case the STOU command will be used.
 1298        * @return the {@link java.io.OutputStream} from the data connection or
 1299        *         <code>null</code> if the command was unsuccessful.
 1300        * @throws IOException if an error occured during the transmission.
 1301        */
 1302       public OutputStream putFileStream(String name, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException {
 1303           String cmd = unique ? "STOU " : "STOR ";
 1304           Socket s = openDataConnection(cmd + name);
 1305           if (s == null) {
 1306               return null;
 1307           }
 1308           if (type == TransferType.BINARY) {
 1309               return s.getOutputStream();
 1310           }
 1311           return new sun.net.TelnetOutputStream(s.getOutputStream(), false);
 1312       }
 1313   
 1314       /**
 1315        * Transfers a file from the client to the server (aka a <I>put</I>)
 1316        * by sending the STOR command. The content of the <code>InputStream</code>
 1317        * passed in argument is written into the remote file, overwriting any
 1318        * existing data.
 1319        *
 1320        * A new file is created at the server site if the file specified does
 1321        * not already exist.
 1322        *
 1323        * @param name the name of the remote file to write.
 1324        * @param local the <code>InputStream</code> that points to the data to
 1325        *        transfer.
 1326        * @param unique <code>true</code> if the remote file should be unique
 1327        *        (i.e. not already existing), <code>false</code> otherwise.
 1328        * @return <code>true</code> if the transfer was successful.
 1329        * @throws IOException if an error occured during the transmission.
 1330        * @see #getLastFileName()
 1331        */
 1332       public sun.net.ftp.FtpClient putFile(String name, InputStream local, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException {
 1333           String cmd = unique ? "STOU " : "STOR ";
 1334           int mtu = 1500;
 1335           if (type == TransferType.BINARY) {
 1336               Socket s = openDataConnection(cmd + name);
 1337               OutputStream remote = createOutputStream(s.getOutputStream());
 1338               byte[] buf = new byte[mtu * 10];
 1339               int l;
 1340               while ((l = local.read(buf)) >= 0) {
 1341                   if (l > 0) {
 1342                       remote.write(buf, 0, l);
 1343                   }
 1344               }
 1345               remote.close();
 1346           }
 1347           return completePending();
 1348       }
 1349   
 1350       /**
 1351        * Sends the APPE command to the server in order to transfer a data stream
 1352        * passed in argument and append it to the content of the specified remote
 1353        * file.
 1354        *
 1355        * @param name A <code>String</code> containing the name of the remote file
 1356        *        to append to.
 1357        * @param local The <code>InputStream</code> providing access to the data
 1358        *        to be appended.
 1359        * @return <code>true</code> if the transfer was successful.
 1360        * @throws IOException if an error occured during the transmission.
 1361        */
 1362       public sun.net.ftp.FtpClient appendFile(String name, InputStream local) throws sun.net.ftp.FtpProtocolException, IOException {
 1363           int mtu = 1500;
 1364           Socket s = openDataConnection("APPE " + name);
 1365           OutputStream remote = createOutputStream(s.getOutputStream());
 1366           byte[] buf = new byte[mtu * 10];
 1367           int l;
 1368           while ((l = local.read(buf)) >= 0) {
 1369               if (l > 0) {
 1370                   remote.write(buf, 0, l);
 1371               }
 1372           }
 1373           remote.close();
 1374           return completePending();
 1375       }
 1376   
 1377       /**
 1378        * Renames a file on the server.
 1379        *
 1380        * @param from the name of the file being renamed
 1381        * @param to the new name for the file
 1382        * @throws IOException if the command fails
 1383        */
 1384       public sun.net.ftp.FtpClient rename(String from, String to) throws sun.net.ftp.FtpProtocolException, IOException {
 1385           issueCommandCheck("RNFR " + from);
 1386           issueCommandCheck("RNTO " + to);
 1387           return this;
 1388       }
 1389   
 1390       /**
 1391        * Deletes a file on the server.
 1392        *
 1393        * @param name a <code>String</code> containing the name of the file
 1394        *        to delete.
 1395        * @return <code>true</code> if the command was successful
 1396        * @throws IOException if an error occured during the exchange
 1397        */
 1398       public sun.net.ftp.FtpClient deleteFile(String name) throws sun.net.ftp.FtpProtocolException, IOException {
 1399           issueCommandCheck("DELE " + name);
 1400           return this;
 1401       }
 1402   
 1403       /**
 1404        * Creates a new directory on the server.
 1405        *
 1406        * @param name a <code>String</code> containing the name of the directory
 1407        *        to create.
 1408        * @return <code>true</code> if the operation was successful.
 1409        * @throws IOException if an error occured during the exchange
 1410        */
 1411       public sun.net.ftp.FtpClient makeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {
 1412           issueCommandCheck("MKD " + name);
 1413           return this;
 1414       }
 1415   
 1416       /**
 1417        * Removes a directory on the server.
 1418        *
 1419        * @param name a <code>String</code> containing the name of the directory
 1420        *        to remove.
 1421        *
 1422        * @return <code>true</code> if the operation was successful.
 1423        * @throws IOException if an error occured during the exchange.
 1424        */
 1425       public sun.net.ftp.FtpClient removeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {
 1426           issueCommandCheck("RMD " + name);
 1427           return this;
 1428       }
 1429   
 1430       /**
 1431        * Sends a No-operation command. It's useful for testing the connection
 1432        * status or as a <I>keep alive</I> mechanism.
 1433        *
 1434        * @throws FtpProtocolException if the command fails
 1435        */
 1436       public sun.net.ftp.FtpClient noop() throws sun.net.ftp.FtpProtocolException, IOException {
 1437           issueCommandCheck("NOOP");
 1438           return this;
 1439       }
 1440   
 1441       /**
 1442        * Sends the STAT command to the server.
 1443        * This can be used while a data connection is open to get a status
 1444        * on the current transfer, in that case the parameter should be
 1445        * <code>null</code>.
 1446        * If used between file transfers, it may have a pathname as argument
 1447        * in which case it will work as the LIST command except no data
 1448        * connection will be created.
 1449        *
 1450        * @param name an optional <code>String</code> containing the pathname
 1451        *        the STAT command should apply to.
 1452        * @return the response from the server or <code>null</code> if the
 1453        *         command failed.
 1454        * @throws IOException if an error occured during the transmission.
 1455        */
 1456       public String getStatus(String name) throws sun.net.ftp.FtpProtocolException, IOException {
 1457           issueCommandCheck((name == null ? "STAT" : "STAT " + name));
 1458           /*
 1459            * A typical response will be:
 1460            *  213-status of t32.gif:
 1461            * -rw-r--r--   1 jcc      staff     247445 Feb 17  1998 t32.gif
 1462            * 213 End of Status
 1463            *
 1464            * or
 1465            *
 1466            * 211-jsn FTP server status:
 1467            *     Version wu-2.6.2+Sun
 1468            *     Connected to localhost (::1)
 1469            *     Logged in as jccollet
 1470            *     TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream
 1471            *      No data connection
 1472            *     0 data bytes received in 0 files
 1473            *     0 data bytes transmitted in 0 files
 1474            *     0 data bytes total in 0 files
 1475            *     53 traffic bytes received in 0 transfers
 1476            *     485 traffic bytes transmitted in 0 transfers
 1477            *     587 traffic bytes total in 0 transfers
 1478            * 211 End of status
 1479            *
 1480            * So we need to remove the 1st and last line
 1481            */
 1482           Vector<String> resp = getResponseStrings();
 1483           StringBuffer sb = new StringBuffer();
 1484           for (int i = 1; i < resp.size() - 1; i++) {
 1485               sb.append(resp.get(i));
 1486           }
 1487           return sb.toString();
 1488       }
 1489   
 1490       /**
 1491        * Sends the FEAT command to the server and returns the list of supported
 1492        * features in the form of strings.
 1493        *
 1494        * The features are the supported commands, like AUTH TLS, PROT or PASV.
 1495        * See the RFCs for a complete list.
 1496        *
 1497        * Note that not all FTP servers support that command, in which case
 1498        * the method will return <code>null</code>
 1499        *
 1500        * @return a <code>List</code> of <code>Strings</code> describing the
 1501        *         supported additional features, or <code>null</code>
 1502        *         if the command is not supported.
 1503        * @throws IOException if an error occurs during the transmission.
 1504        */
 1505       public List<String> getFeatures() throws sun.net.ftp.FtpProtocolException, IOException {
 1506           /*
 1507            * The FEAT command, when implemented will return something like:
 1508            *
 1509            * 211-Features:
 1510            *   AUTH TLS
 1511            *   PBSZ
 1512            *   PROT
 1513            *   EPSV
 1514            *   EPRT
 1515            *   PASV
 1516            *   REST STREAM
 1517            *  211 END
 1518            */
 1519           ArrayList<String> features = new ArrayList<String>();
 1520           issueCommandCheck("FEAT");
 1521           Vector<String> resp = getResponseStrings();
 1522           // Note that we start at index 1 to skip the 1st line (211-...)
 1523           // and we stop before the last line.
 1524           for (int i = 1; i < resp.size() - 1; i++) {
 1525               String s = resp.get(i);
 1526               // Get rid of leading space and trailing newline
 1527               features.add(s.substring(1, s.length() - 1));
 1528           }
 1529           return features;
 1530       }
 1531   
 1532       /**
 1533        * sends the ABOR command to the server.
 1534        * It tells the server to stop the previous command or transfer.
 1535        *
 1536        * @return <code>true</code> if the command was successful.
 1537        * @throws IOException if an error occured during the transmission.
 1538        */
 1539       public sun.net.ftp.FtpClient abort() throws sun.net.ftp.FtpProtocolException, IOException {
 1540           issueCommandCheck("ABOR");
 1541           // TODO: Must check the ReplyCode:
 1542           /*
 1543            * From the RFC:
 1544            * There are two cases for the server upon receipt of this
 1545            * command: (1) the FTP service command was already completed,
 1546            * or (2) the FTP service command is still in progress.
 1547            * In the first case, the server closes the data connection
 1548            * (if it is open) and responds with a 226 reply, indicating
 1549            * that the abort command was successfully processed.
 1550            * In the second case, the server aborts the FTP service in
 1551            * progress and closes the data connection, returning a 426
 1552            * reply to indicate that the service request terminated
 1553            * abnormally.  The server then sends a 226 reply,
 1554            * indicating that the abort command was successfully
 1555            * processed.
 1556            */
 1557   
 1558   
 1559           return this;
 1560       }
 1561   
 1562       /**
 1563        * Some methods do not wait until completion before returning, so this
 1564        * method can be called to wait until completion. This is typically the case
 1565        * with commands that trigger a transfer like {@link #getFileStream(String)}.
 1566        * So this method should be called before accessing information related to
 1567        * such a command.
 1568        * <p>This method will actually block reading on the command channel for a
 1569        * notification from the server that the command is finished. Such a
 1570        * notification often carries extra information concerning the completion
 1571        * of the pending action (e.g. number of bytes transfered).</p>
 1572        * <p>Note that this will return true immediately if no command or action
 1573        * is pending</p>
 1574        * <p>It should be also noted that most methods issuing commands to the ftp
 1575        * server will call this method if a previous command is pending.
 1576        * <p>Example of use:
 1577        * <pre>
 1578        * InputStream in = cl.getFileStream("file");
 1579        * ...
 1580        * cl.completePending();
 1581        * long size = cl.getLastTransferSize();
 1582        * </pre>
 1583        * On the other hand, it's not necessary in a case like:
 1584        * <pre>
 1585        * InputStream in = cl.getFileStream("file");
 1586        * // read content
 1587        * ...
 1588        * cl.logout();
 1589        * </pre>
 1590        * <p>Since {@link #logout()} will call completePending() if necessary.</p>
 1591        * @return <code>true</code> if the completion was successful or if no
 1592        *         action was pending.
 1593        * @throws IOException
 1594        */
 1595       public sun.net.ftp.FtpClient completePending() throws sun.net.ftp.FtpProtocolException, IOException {
 1596           while (replyPending) {
 1597               replyPending = false;
 1598               if (!readReply()) {
 1599                   throw new sun.net.ftp.FtpProtocolException(getLastResponseString(), lastReplyCode);
 1600               }
 1601           }
 1602           return this;
 1603       }
 1604   
 1605       /**
 1606        * Reinitializes the USER parameters on the FTP server
 1607        *
 1608        * @throws FtpProtocolException if the command fails
 1609        */
 1610       public sun.net.ftp.FtpClient reInit() throws sun.net.ftp.FtpProtocolException, IOException {
 1611           issueCommandCheck("REIN");
 1612           loggedIn = false;
 1613           if (useCrypto) {
 1614               if (server instanceof SSLSocket) {
 1615                   javax.net.ssl.SSLSession session = ((SSLSocket) server).getSession();
 1616                   session.invalidate();
 1617                   // Restore previous socket and streams
 1618                   server = oldSocket;
 1619                   oldSocket = null;
 1620                   try {
 1621                       out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
 1622                               true, encoding);
 1623                   } catch (UnsupportedEncodingException e) {
 1624                       throw new InternalError(encoding + "encoding not found");
 1625                   }
 1626                   in = new BufferedInputStream(server.getInputStream());
 1627               }
 1628           }
 1629           useCrypto = false;
 1630           return this;
 1631       }
 1632   
 1633       /**
 1634        * Changes the transfer type (binary, ascii, ebcdic) and issue the
 1635        * proper command (e.g. TYPE A) to the server.
 1636        *
 1637        * @param type the <code>FtpTransferType</code> to use.
 1638        * @return This FtpClient
 1639        * @throws IOException if an error occurs during transmission.
 1640        */
 1641       public sun.net.ftp.FtpClient setType(TransferType type) throws sun.net.ftp.FtpProtocolException, IOException {
 1642           String cmd = "NOOP";
 1643   
 1644           this.type = type;
 1645           if (type == TransferType.ASCII) {
 1646               cmd = "TYPE A";
 1647           }
 1648           if (type == TransferType.BINARY) {
 1649               cmd = "TYPE I";
 1650           }
 1651           if (type == TransferType.EBCDIC) {
 1652               cmd = "TYPE E";
 1653           }
 1654           issueCommandCheck(cmd);
 1655           return this;
 1656       }
 1657   
 1658       /**
 1659        * Issues a LIST command to the server to get the current directory
 1660        * listing, and returns the InputStream from the data connection.
 1661        * {@link #completePending()} <b>has</b> to be called once the application
 1662        * is finished writing to the stream.
 1663        *
 1664        * @param path the pathname of the directory to list, or <code>null</code>
 1665        *        for the current working directory.
 1666        * @return the <code>InputStream</code> from the resulting data connection
 1667        * @throws IOException if an error occurs during the transmission.
 1668        * @see #changeDirectory(String)
 1669        * @see #listFiles(String)
 1670        */
 1671       public InputStream list(String path) throws sun.net.ftp.FtpProtocolException, IOException {
 1672           Socket s;
 1673           s = openDataConnection(path == null ? "LIST" : "LIST " + path);
 1674           if (s != null) {
 1675               return createInputStream(s.getInputStream());
 1676           }
 1677           return null;
 1678       }
 1679   
 1680       /**
 1681        * Issues a NLST path command to server to get the specified directory
 1682        * content. It differs from {@link #list(String)} method by the fact that
 1683        * it will only list the file names which would make the parsing of the
 1684        * somewhat easier.
 1685        *
 1686        * {@link #completePending()} <b>has</b> to be called once the application
 1687        * is finished writing to the stream.
 1688        *
 1689        * @param path a <code>String</code> containing the pathname of the
 1690        *        directory to list or <code>null</code> for the current working
 1691        *        directory.
 1692        * @return the <code>InputStream</code> from the resulting data connection
 1693        * @throws IOException if an error occurs during the transmission.
 1694        */
 1695       public InputStream nameList(String path) throws sun.net.ftp.FtpProtocolException, IOException {
 1696           Socket s;
 1697           s = openDataConnection("NLST " + path);
 1698           if (s != null) {
 1699               return createInputStream(s.getInputStream());
 1700           }
 1701           return null;
 1702       }
 1703   
 1704       /**
 1705        * Issues the SIZE [path] command to the server to get the size of a
 1706        * specific file on the server.
 1707        * Note that this command may not be supported by the server. In which
 1708        * case -1 will be returned.
 1709        *
 1710        * @param path a <code>String</code> containing the pathname of the
 1711        *        file.
 1712        * @return a <code>long</code> containing the size of the file or -1 if
 1713        *         the server returned an error, which can be checked with
 1714        *         {@link #getLastReplyCode()}.
 1715        * @throws IOException if an error occurs during the transmission.
 1716        */
 1717       public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOException {
 1718           if (path == null || path.length() == 0) {
 1719               throw new IllegalArgumentException("path can't be null or empty");
 1720           }
 1721           issueCommandCheck("SIZE " + path);
 1722           if (lastReplyCode == FtpReplyCode.FILE_STATUS) {
 1723               String s = getResponseString();
 1724               s = s.substring(4, s.length() - 1);
 1725               return Long.parseLong(s);
 1726           }
 1727           return -1;
 1728       }
 1729       private static String[] MDTMformats = {
 1730           "yyyyMMddHHmmss.SSS",
 1731           "yyyyMMddHHmmss"
 1732       };
 1733       private static SimpleDateFormat[] dateFormats = new SimpleDateFormat[MDTMformats.length];
 1734   
 1735       static {
 1736           for (int i = 0; i < MDTMformats.length; i++) {
 1737               dateFormats[i] = new SimpleDateFormat(MDTMformats[i]);
 1738               dateFormats[i].setTimeZone(TimeZone.getTimeZone("GMT"));
 1739           }
 1740       }
 1741   
 1742       /**
 1743        * Issues the MDTM [path] command to the server to get the modification
 1744        * time of a specific file on the server.
 1745        * Note that this command may not be supported by the server, in which
 1746        * case <code>null</code> will be returned.
 1747        *
 1748        * @param path a <code>String</code> containing the pathname of the file.
 1749        * @return a <code>Date</code> representing the last modification time
 1750        *         or <code>null</code> if the server returned an error, which
 1751        *         can be checked with {@link #getLastReplyCode()}.
 1752        * @throws IOException if an error occurs during the transmission.
 1753        */
 1754       public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException, IOException {
 1755           issueCommandCheck("MDTM " + path);
 1756           if (lastReplyCode == FtpReplyCode.FILE_STATUS) {
 1757               String s = getResponseString().substring(4);
 1758               Date d = null;
 1759               for (SimpleDateFormat dateFormat : dateFormats) {
 1760                   try {
 1761                       d = dateFormat.parse(s);
 1762                   } catch (ParseException ex) {
 1763                   }
 1764                   if (d != null) {
 1765                       return d;
 1766                   }
 1767               }
 1768           }
 1769           return null;
 1770       }
 1771   
 1772       /**
 1773        * Sets the parser used to handle the directory output to the specified
 1774        * one. By default the parser is set to one that can handle most FTP
 1775        * servers output (Unix base mostly). However it may be necessary for
 1776        * and application to provide its own parser due to some uncommon
 1777        * output format.
 1778        *
 1779        * @param p The <code>FtpDirParser</code> to use.
 1780        * @see #listFiles(String)
 1781        */
 1782       public sun.net.ftp.FtpClient setDirParser(FtpDirParser p) {
 1783           parser = p;
 1784           return this;
 1785       }
 1786   
 1787       private class FtpFileIterator implements Iterator<FtpDirEntry>, Closeable {
 1788   
 1789           private BufferedReader in = null;
 1790           private FtpDirEntry nextFile = null;
 1791           private FtpDirParser fparser = null;
 1792           private boolean eof = false;
 1793   
 1794           public FtpFileIterator(FtpDirParser p, BufferedReader in) {
 1795               this.in = in;
 1796               this.fparser = p;
 1797               readNext();
 1798           }
 1799   
 1800           private void readNext() {
 1801               nextFile = null;
 1802               if (eof) {
 1803                   return;
 1804               }
 1805               String line = null;
 1806               try {
 1807                   do {
 1808                       line = in.readLine();
 1809                       if (line != null) {
 1810                           nextFile = fparser.parseLine(line);
 1811                           if (nextFile != null) {
 1812                               return;
 1813                           }
 1814                       }
 1815                   } while (line != null);
 1816                   in.close();
 1817               } catch (IOException iOException) {
 1818               }
 1819               eof = true;
 1820           }
 1821   
 1822           public boolean hasNext() {
 1823               return nextFile != null;
 1824           }
 1825   
 1826           public FtpDirEntry next() {
 1827               FtpDirEntry ret = nextFile;
 1828               readNext();
 1829               return ret;
 1830           }
 1831   
 1832           public void remove() {
 1833               throw new UnsupportedOperationException("Not supported yet.");
 1834           }
 1835   
 1836           public void close() throws IOException {
 1837               if (in != null && !eof) {
 1838                   in.close();
 1839               }
 1840               eof = true;
 1841               nextFile = null;
 1842           }
 1843       }
 1844   
 1845       /**
 1846        * Issues a MLSD command to the server to get the specified directory
 1847        * listing and applies the current parser to create an Iterator of
 1848        * {@link java.net.ftp.FtpDirEntry}. Note that the Iterator returned is also a
 1849        * {@link java.io.Closeable}.
 1850        * If the server doesn't support the MLSD command, the LIST command is used
 1851        * instead.
 1852        *
 1853        * {@link #completePending()} <b>has</b> to be called once the application
 1854        * is finished iterating through the files.
 1855        *
 1856        * @param path the pathname of the directory to list or <code>null</code>
 1857        *        for the current working directoty.
 1858        * @return a <code>Iterator</code> of files or <code>null</code> if the
 1859        *         command failed.
 1860        * @throws IOException if an error occured during the transmission
 1861        * @see #setDirParser(FtpDirParser)
 1862        * @see #changeDirectory(String)
 1863        */
 1864       public Iterator<FtpDirEntry> listFiles(String path) throws sun.net.ftp.FtpProtocolException, IOException {
 1865           Socket s = null;
 1866           BufferedReader sin = null;
 1867           try {
 1868               s = openDataConnection(path == null ? "MLSD" : "MLSD " + path);
 1869           } catch (sun.net.ftp.FtpProtocolException FtpException) {
 1870               // The server doesn't understand new MLSD command, ignore and fall
 1871               // back to LIST
 1872           }
 1873   
 1874           if (s != null) {
 1875               sin = new BufferedReader(new InputStreamReader(s.getInputStream()));
 1876               return new FtpFileIterator(mlsxParser, sin);
 1877           } else {
 1878               s = openDataConnection(path == null ? "LIST" : "LIST " + path);
 1879               if (s != null) {
 1880                   sin = new BufferedReader(new InputStreamReader(s.getInputStream()));
 1881                   return new FtpFileIterator(parser, sin);
 1882               }
 1883           }
 1884           return null;
 1885       }
 1886   
 1887       private boolean sendSecurityData(byte[] buf) throws IOException {
 1888           BASE64Encoder encoder = new BASE64Encoder();
 1889           String s = encoder.encode(buf);
 1890           return issueCommand("ADAT " + s);
 1891       }
 1892   
 1893       private byte[] getSecurityData() {
 1894           String s = getLastResponseString();
 1895           if (s.substring(4, 9).equalsIgnoreCase("ADAT=")) {
 1896               BASE64Decoder decoder = new BASE64Decoder();
 1897               try {
 1898                   // Need to get rid of the leading '315 ADAT='
 1899                   // and the trailing newline
 1900                   return decoder.decodeBuffer(s.substring(9, s.length() - 1));
 1901               } catch (IOException e) {
 1902                   //
 1903               }
 1904           }
 1905           return null;
 1906       }
 1907   
 1908       /**
 1909        * Attempts to use Kerberos GSSAPI as an authentication mechanism with the
 1910        * ftp server. This will issue an <code>AUTH GSSAPI</code> command, and if
 1911        * it is accepted by the server, will followup with <code>ADAT</code>
 1912        * command to exchange the various tokens until authentification is
 1913        * successful. This conforms to Appendix I of RFC 2228.
 1914        *
 1915        * @return <code>true</code> if authentication was successful.
 1916        * @throws IOException if an error occurs during the transmission.
 1917        */
 1918       public sun.net.ftp.FtpClient useKerberos() throws sun.net.ftp.FtpProtocolException, IOException {
 1919           /*
 1920            * Comment out for the moment since it's not in use and would create
 1921            * needless cross-package links.
 1922            *
 1923           issueCommandCheck("AUTH GSSAPI");
 1924           if (lastReplyCode != FtpReplyCode.NEED_ADAT)
 1925           throw new sun.net.ftp.FtpProtocolException("Unexpected reply from server");
 1926           try {
 1927           GSSManager manager = GSSManager.getInstance();
 1928           GSSName name = manager.createName("SERVICE:ftp@"+
 1929           serverAddr.getHostName(), null);
 1930           GSSContext context = manager.createContext(name, null, null,
 1931           GSSContext.DEFAULT_LIFETIME);
 1932           context.requestMutualAuth(true);
 1933           context.requestReplayDet(true);
 1934           context.requestSequenceDet(true);
 1935           context.requestCredDeleg(true);
 1936           byte []inToken = new byte[0];
 1937           while (!context.isEstablished()) {
 1938           byte[] outToken
 1939           = context.initSecContext(inToken, 0, inToken.length);
 1940           // send the output token if generated
 1941           if (outToken != null) {
 1942           if (sendSecurityData(outToken)) {
 1943           inToken = getSecurityData();
 1944           }
 1945           }
 1946           }
 1947           loggedIn = true;
 1948           } catch (GSSException e) {
 1949   
 1950           }
 1951            */
 1952           return this;
 1953       }
 1954   
 1955       /**
 1956        * Returns the Welcome string the server sent during initial connection.
 1957        *
 1958        * @return a <code>String</code> containing the message the server
 1959        *         returned during connection or <code>null</code>.
 1960        */
 1961       public String getWelcomeMsg() {
 1962           return welcomeMsg;
 1963       }
 1964   
 1965       /**
 1966        * Returns the last reply code sent by the server.
 1967        *
 1968        * @return the lastReplyCode
 1969        */
 1970       public FtpReplyCode getLastReplyCode() {
 1971           return lastReplyCode;
 1972       }
 1973   
 1974       /**
 1975        * Returns the last response string sent by the server.
 1976        *
 1977        * @return the message string, which can be quite long, last returned
 1978        *         by the server.
 1979        */
 1980       public String getLastResponseString() {
 1981           StringBuffer sb = new StringBuffer();
 1982           if (serverResponse != null) {
 1983               for (String l : serverResponse) {
 1984                   if (l != null) {
 1985                       sb.append(l);
 1986                   }
 1987               }
 1988           }
 1989           return sb.toString();
 1990       }
 1991   
 1992       /**
 1993        * Returns, when available, the size of the latest started transfer.
 1994        * This is retreived by parsing the response string received as an initial
 1995        * response to a RETR or similar request.
 1996        *
 1997        * @return the size of the latest transfer or -1 if either there was no
 1998        *         transfer or the information was unavailable.
 1999        */
 2000       public long getLastTransferSize() {
 2001           return lastTransSize;
 2002       }
 2003   
 2004       /**
 2005        * Returns, when available, the remote name of the last transfered file.
 2006        * This is mainly useful for "put" operation when the unique flag was
 2007        * set since it allows to recover the unique file name created on the
 2008        * server which may be different from the one submitted with the command.
 2009        *
 2010        * @return the name the latest transfered file remote name, or
 2011        *         <code>null</code> if that information is unavailable.
 2012        */
 2013       public String getLastFileName() {
 2014           return lastFileName;
 2015       }
 2016   
 2017       /**
 2018        * Attempts to switch to a secure, encrypted connection. This is done by
 2019        * sending the "AUTH TLS" command.
 2020        * <p>See <a href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</a></p>
 2021        * If successful this will establish a secure command channel with the
 2022        * server, it will also make it so that all other transfers (e.g. a RETR
 2023        * command) will be done over an encrypted channel as well unless a
 2024        * {@link #reInit()} command or a {@link #endSecureSession()} command is issued.
 2025        *
 2026        * @return <code>true</code> if the operation was successful.
 2027        * @throws IOException if an error occured during the transmission.
 2028        * @see #endSecureSession()
 2029        */
 2030       public sun.net.ftp.FtpClient startSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {
 2031           if (!isConnected()) {
 2032               throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
 2033           }
 2034           if (sslFact == null) {
 2035               try {
 2036                   sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault();
 2037               } catch (Exception e) {
 2038                   throw new IOException(e.getLocalizedMessage());
 2039               }
 2040           }
 2041           issueCommandCheck("AUTH TLS");
 2042           Socket s = null;
 2043           try {
 2044               s = sslFact.createSocket(server, serverAddr.getHostName(), serverAddr.getPort(), true);
 2045           } catch (javax.net.ssl.SSLException ssle) {
 2046               try {
 2047                   disconnect();
 2048               } catch (Exception e) {
 2049               }
 2050               throw ssle;
 2051           }
 2052           // Remember underlying socket so we can restore it later
 2053           oldSocket = server;
 2054           server = s;
 2055           try {
 2056               out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
 2057                       true, encoding);
 2058           } catch (UnsupportedEncodingException e) {
 2059               throw new InternalError(encoding + "encoding not found");
 2060           }
 2061           in = new BufferedInputStream(server.getInputStream());
 2062   
 2063           issueCommandCheck("PBSZ 0");
 2064           issueCommandCheck("PROT P");
 2065           useCrypto = true;
 2066           return this;
 2067       }
 2068   
 2069       /**
 2070        * Sends a <code>CCC</code> command followed by a <code>PROT C</code>
 2071        * command to the server terminating an encrypted session and reverting
 2072        * back to a non crypted transmission.
 2073        *
 2074        * @return <code>true</code> if the operation was successful.
 2075        * @throws IOException if an error occured during transmission.
 2076        * @see #startSecureSession()
 2077        */
 2078       public sun.net.ftp.FtpClient endSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {
 2079           if (!useCrypto) {
 2080               return this;
 2081           }
 2082   
 2083           issueCommandCheck("CCC");
 2084           issueCommandCheck("PROT C");
 2085           useCrypto = false;
 2086           // Restore previous socket and streams
 2087           server = oldSocket;
 2088           oldSocket = null;
 2089           try {
 2090               out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
 2091                       true, encoding);
 2092           } catch (UnsupportedEncodingException e) {
 2093               throw new InternalError(encoding + "encoding not found");
 2094           }
 2095           in = new BufferedInputStream(server.getInputStream());
 2096   
 2097           return this;
 2098       }
 2099   
 2100       /**
 2101        * Sends the "Allocate" (ALLO) command to the server telling it to
 2102        * pre-allocate the specified number of bytes for the next transfer.
 2103        *
 2104        * @param size The number of bytes to allocate.
 2105        * @return <code>true</code> if the operation was successful.
 2106        * @throws IOException if an error occured during the transmission.
 2107        */
 2108       public sun.net.ftp.FtpClient allocate(long size) throws sun.net.ftp.FtpProtocolException, IOException {
 2109           issueCommandCheck("ALLO " + size);
 2110           return this;
 2111       }
 2112   
 2113       /**
 2114        * Sends the "Structure Mount" (SMNT) command to the server. This let the
 2115        * user mount a different file system data structure without altering his
 2116        * login or accounting information.
 2117        *
 2118        * @param struct a <code>String</code> containing the name of the
 2119        *        structure to mount.
 2120        * @return <code>true</code> if the operation was successful.
 2121        * @throws IOException if an error occured during the transmission.
 2122        */
 2123       public sun.net.ftp.FtpClient structureMount(String struct) throws sun.net.ftp.FtpProtocolException, IOException {
 2124           issueCommandCheck("SMNT " + struct);
 2125           return this;
 2126       }
 2127   
 2128       /**
 2129        * Sends a SYST (System) command to the server and returns the String
 2130        * sent back by the server describing the operating system at the
 2131        * server.
 2132        *
 2133        * @return a <code>String</code> describing the OS, or <code>null</code>
 2134        *         if the operation was not successful.
 2135        * @throws IOException if an error occured during the transmission.
 2136        */
 2137       public String getSystem() throws sun.net.ftp.FtpProtocolException, IOException {
 2138           issueCommandCheck("SYST");
 2139           /*
 2140            * 215 UNIX Type: L8 Version: SUNOS
 2141            */
 2142           String resp = getResponseString();
 2143           // Get rid of the leading code and blank
 2144           return resp.substring(4);
 2145       }
 2146   
 2147       /**
 2148        * Sends the HELP command to the server, with an optional command, like
 2149        * SITE, and returns the text sent back by the server.
 2150        *
 2151        * @param cmd the command for which the help is requested or
 2152        *        <code>null</code> for the general help
 2153        * @return a <code>String</code> containing the text sent back by the
 2154        *         server, or <code>null</code> if the command failed.
 2155        * @throws IOException if an error occured during transmission
 2156        */
 2157       public String getHelp(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
 2158           issueCommandCheck("HELP " + cmd);
 2159           /**
 2160            *
 2161            * HELP
 2162            * 214-The following commands are implemented.
 2163            *   USER    EPRT    STRU    ALLO    DELE    SYST    RMD     MDTM    ADAT
 2164            *   PASS    EPSV    MODE    REST    CWD     STAT    PWD     PROT
 2165            *   QUIT    LPRT    RETR    RNFR    LIST    HELP    CDUP    PBSZ
 2166            *   PORT    LPSV    STOR    RNTO    NLST    NOOP    STOU    AUTH
 2167            *   PASV    TYPE    APPE    ABOR    SITE    MKD     SIZE    CCC
 2168            * 214 Direct comments to ftp-bugs@jsn.
 2169            *
 2170            * HELP SITE
 2171            * 214-The following SITE commands are implemented.
 2172            *   UMASK           HELP            GROUPS
 2173            *   IDLE            ALIAS           CHECKMETHOD
 2174            *   CHMOD           CDPATH          CHECKSUM
 2175            * 214 Direct comments to ftp-bugs@jsn.
 2176            */
 2177           Vector<String> resp = getResponseStrings();
 2178           if (resp.size() == 1) {
 2179               // Single line response
 2180               return resp.get(0).substring(4);
 2181           }
 2182           // on multiple lines answers, like the ones above, remove 1st and last
 2183           // line, concat the the others.
 2184           StringBuffer sb = new StringBuffer();
 2185           for (int i = 1; i < resp.size() - 1; i++) {
 2186               sb.append(resp.get(i).substring(3));
 2187           }
 2188           return sb.toString();
 2189       }
 2190   
 2191       /**
 2192        * Sends the SITE command to the server. This is used by the server
 2193        * to provide services specific to his system that are essential
 2194        * to file transfer.
 2195        *
 2196        * @param cmd the command to be sent.
 2197        * @return <code>true</code> if the command was successful.
 2198        * @throws IOException if an error occured during transmission
 2199        */
 2200       public sun.net.ftp.FtpClient siteCmd(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
 2201           issueCommandCheck("SITE " + cmd);
 2202           return this;
 2203       }
 2204   }

Home » openjdk-7 » sun.net.ftp.impl » [javadoc | source]