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