1 /*
   2  * Copyright (c) 2009, 2013, 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", 0).intValue();
 119                         vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 0).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.substring(0, c)));
 264                     now.set(Calendar.MINUTE, Integer.parseInt(time.substring(c + 1)));
 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         StringBuffer replyBuf = new StringBuffer(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.substring(0, 3));
 441                 } catch (NumberFormatException e) {
 442                     code = -1;
 443                 } catch (StringIndexOutOfBoundsException 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         if (!isConnected()) {
 521             throw new IllegalStateException("Not connected");
 522         }
 523         if (replyPending) {
 524             try {
 525                 completePending();
 526             } catch (sun.net.ftp.FtpProtocolException e) {
 527                 // ignore...
 528             }
 529         }
 530         sendServer(cmd + "\r\n");
 531         return readReply();
 532     }
 533 
 534     /**
 535      * Send a command to the FTP server and check for success.
 536      *
 537      * @param cmd String containing the command
 538      *
 539      * @throws FtpProtocolException if an error occurred
 540      */
 541     private void issueCommandCheck(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
 542         if (!issueCommand(cmd)) {
 543             throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());
 544         }
 545     }
 546     private static Pattern epsvPat = null;
 547     private static Pattern pasvPat = null;
 548 
 549     /**
 550      * Opens a "PASSIVE" connection with the server and returns the connected
 551      * <code>Socket</code>.
 552      *
 553      * @return the connected <code>Socket</code>
 554      * @throws IOException if the connection was unsuccessful.
 555      */
 556     private Socket openPassiveDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
 557         String serverAnswer;
 558         int port;
 559         InetSocketAddress dest = null;
 560 
 561         /**
 562          * Here is the idea:
 563          *
 564          * - First we want to try the new (and IPv6 compatible) EPSV command
 565          *   But since we want to be nice with NAT software, we'll issue the
 566          *   EPSV ALL command first.
 567          *   EPSV is documented in RFC2428
 568          * - If EPSV fails, then we fall back to the older, yet ok, PASV
 569          * - If PASV fails as well, then we throw an exception and the calling
 570          *   method will have to try the EPRT or PORT command
 571          */
 572         if (issueCommand("EPSV ALL")) {
 573             // We can safely use EPSV commands
 574             issueCommandCheck("EPSV");
 575             serverAnswer = getResponseString();
 576 
 577             // The response string from a EPSV command will contain the port number
 578             // the format will be :
 579             //  229 Entering Extended PASSIVE Mode (|||58210|)
 580             //
 581             // So we'll use the regular expresions package to parse the output.
 582 
 583             if (epsvPat == null) {
 584                 epsvPat = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)");
 585             }
 586             Matcher m = epsvPat.matcher(serverAnswer);
 587             if (!m.find()) {
 588                 throw new sun.net.ftp.FtpProtocolException("EPSV failed : " + serverAnswer);
 589             }
 590             // Yay! Let's extract the port number
 591             String s = m.group(1);
 592             port = Integer.parseInt(s);
 593             InetAddress add = server.getInetAddress();
 594             if (add != null) {
 595                 dest = new InetSocketAddress(add, port);
 596             } else {
 597                 // This means we used an Unresolved address to connect in
 598                 // the first place. Most likely because the proxy is doing
 599                 // the name resolution for us, so let's keep using unresolved
 600                 // address.
 601                 dest = InetSocketAddress.createUnresolved(serverAddr.getHostName(), port);
 602             }
 603         } else {
 604             // EPSV ALL failed, so Let's try the regular PASV cmd
 605             issueCommandCheck("PASV");
 606             serverAnswer = getResponseString();
 607 
 608             // Let's parse the response String to get the IP & port to connect
 609             // to. The String should be in the following format :
 610             //
 611             // 227 Entering PASSIVE Mode (A1,A2,A3,A4,p1,p2)
 612             //
 613             // Note that the two parenthesis are optional
 614             //
 615             // The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2
 616             //
 617             // The regular expression is a bit more complex this time, because
 618             // the parenthesis are optionals and we have to use 3 groups.
 619 
 620             if (pasvPat == null) {
 621                 pasvPat = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?");
 622             }
 623             Matcher m = pasvPat.matcher(serverAnswer);
 624             if (!m.find()) {
 625                 throw new sun.net.ftp.FtpProtocolException("PASV failed : " + serverAnswer);
 626             }
 627             // Get port number out of group 2 & 3
 628             port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8);
 629             // IP address is simple
 630             String s = m.group(1).replace(',', '.');
 631             dest = new InetSocketAddress(s, port);
 632         }
 633         // Got everything, let's open the socket!
 634         Socket s;
 635         if (proxy != null) {
 636             if (proxy.type() == Proxy.Type.SOCKS) {
 637                 s = AccessController.doPrivileged(
 638                         new PrivilegedAction<Socket>() {
 639 
 640                             public Socket run() {
 641                                 return new Socket(proxy);
 642                             }
 643                         });
 644             } else {
 645                 s = new Socket(Proxy.NO_PROXY);
 646             }
 647         } else {
 648             s = new Socket();
 649         }
 650 
 651         InetAddress serverAddress = AccessController.doPrivileged(
 652                 new PrivilegedAction<InetAddress>() {
 653                     @Override
 654                     public InetAddress run() {
 655                         return server.getLocalAddress();
 656                     }
 657                 });
 658 
 659         // Bind the socket to the same address as the control channel. This
 660         // is needed in case of multi-homed systems.
 661         s.bind(new InetSocketAddress(serverAddress, 0));
 662         if (connectTimeout >= 0) {
 663             s.connect(dest, connectTimeout);
 664         } else {
 665             if (defaultConnectTimeout > 0) {
 666                 s.connect(dest, defaultConnectTimeout);
 667             } else {
 668                 s.connect(dest);
 669             }
 670         }
 671         if (readTimeout >= 0) {
 672             s.setSoTimeout(readTimeout);
 673         } else if (defaultSoTimeout > 0) {
 674             s.setSoTimeout(defaultSoTimeout);
 675         }
 676         if (useCrypto) {
 677             try {
 678                 s = sslFact.createSocket(s, dest.getHostName(), dest.getPort(), true);
 679             } catch (Exception e) {
 680                 throw new sun.net.ftp.FtpProtocolException("Can't open secure data channel: " + e);
 681             }
 682         }
 683         if (!issueCommand(cmd)) {
 684             s.close();
 685             if (getLastReplyCode() == FtpReplyCode.FILE_UNAVAILABLE) {
 686                 // Ensure backward compatibility
 687                 throw new FileNotFoundException(cmd);
 688             }
 689             throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());
 690         }
 691         return s;
 692     }
 693 
 694     /**
 695      * Opens a data connection with the server according to the set mode
 696      * (ACTIVE or PASSIVE) then send the command passed as an argument.
 697      *
 698      * @param cmd the <code>String</code> containing the command to execute
 699      * @return the connected <code>Socket</code>
 700      * @throws IOException if the connection or command failed
 701      */
 702     private Socket openDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
 703         Socket clientSocket;
 704 
 705         if (passiveMode) {
 706             try {
 707                 return openPassiveDataConnection(cmd);
 708             } catch (sun.net.ftp.FtpProtocolException e) {
 709                 // If Passive mode failed, fall back on PORT
 710                 // Otherwise throw exception
 711                 String errmsg = e.getMessage();
 712                 if (!errmsg.startsWith("PASV") && !errmsg.startsWith("EPSV")) {
 713                     throw e;
 714                 }
 715             }
 716         }
 717         ServerSocket portSocket;
 718         InetAddress myAddress;
 719         String portCmd;
 720 
 721         if (proxy != null && proxy.type() == Proxy.Type.SOCKS) {
 722             // We're behind a firewall and the passive mode fail,
 723             // since we can't accept a connection through SOCKS (yet)
 724             // throw an exception
 725             throw new sun.net.ftp.FtpProtocolException("Passive mode failed");
 726         }
 727         // Bind the ServerSocket to the same address as the control channel
 728         // This is needed for multi-homed systems
 729         portSocket = new ServerSocket(0, 1, server.getLocalAddress());
 730         try {
 731             myAddress = portSocket.getInetAddress();
 732             if (myAddress.isAnyLocalAddress()) {
 733                 myAddress = server.getLocalAddress();
 734             }
 735             // Let's try the new, IPv6 compatible EPRT command
 736             // See RFC2428 for specifics
 737             // Some FTP servers (like the one on Solaris) are bugged, they
 738             // will accept the EPRT command but then, the subsequent command
 739             // (e.g. RETR) will fail, so we have to check BOTH results (the
 740             // EPRT cmd then the actual command) to decide whether we should
 741             // fall back on the older PORT command.
 742             portCmd = "EPRT |" + ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" +
 743                     myAddress.getHostAddress() + "|" + portSocket.getLocalPort() + "|";
 744             if (!issueCommand(portCmd) || !issueCommand(cmd)) {
 745                 // The EPRT command failed, let's fall back to good old PORT
 746                 portCmd = "PORT ";
 747                 byte[] addr = myAddress.getAddress();
 748 
 749                 /* append host addr */
 750                 for (int i = 0; i < addr.length; i++) {
 751                     portCmd = portCmd + (addr[i] & 0xFF) + ",";
 752                 }
 753 
 754                 /* append port number */
 755                 portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff);
 756                 issueCommandCheck(portCmd);
 757                 issueCommandCheck(cmd);
 758             }
 759             // Either the EPRT or the PORT command was successful
 760             // Let's create the client socket
 761             if (connectTimeout >= 0) {
 762                 portSocket.setSoTimeout(connectTimeout);
 763             } else {
 764                 if (defaultConnectTimeout > 0) {
 765                     portSocket.setSoTimeout(defaultConnectTimeout);
 766                 }
 767             }
 768             clientSocket = portSocket.accept();
 769             if (readTimeout >= 0) {
 770                 clientSocket.setSoTimeout(readTimeout);
 771             } else {
 772                 if (defaultSoTimeout > 0) {
 773                     clientSocket.setSoTimeout(defaultSoTimeout);
 774                 }
 775             }
 776         } finally {
 777             portSocket.close();
 778         }
 779         if (useCrypto) {
 780             try {
 781                 clientSocket = sslFact.createSocket(clientSocket, serverAddr.getHostName(), serverAddr.getPort(), true);
 782             } catch (Exception ex) {
 783                 throw new IOException(ex.getLocalizedMessage());
 784             }
 785         }
 786         return clientSocket;
 787     }
 788 
 789     private InputStream createInputStream(InputStream in) {
 790         if (type == TransferType.ASCII) {
 791             return new sun.net.TelnetInputStream(in, false);
 792         }
 793         return in;
 794     }
 795 
 796     private OutputStream createOutputStream(OutputStream out) {
 797         if (type == TransferType.ASCII) {
 798             return new sun.net.TelnetOutputStream(out, false);
 799         }
 800         return out;
 801     }
 802 
 803     /**
 804      * Creates an instance of FtpClient. The client is not connected to any
 805      * server yet.
 806      *
 807      */
 808     protected FtpClient() {
 809     }
 810 
 811     /**
 812      * Creates an instance of FtpClient. The client is not connected to any
 813      * server yet.
 814      *
 815      */
 816     public static sun.net.ftp.FtpClient create() {
 817         return new FtpClient();
 818     }
 819 
 820     /**
 821      * Set the transfer mode to <I>passive</I>. In that mode, data connections
 822      * are established by having the client connect to the server.
 823      * This is the recommended default mode as it will work best through
 824      * firewalls and NATs.
 825      *
 826      * @return This FtpClient
 827      * @see #setActiveMode()
 828      */
 829     public sun.net.ftp.FtpClient enablePassiveMode(boolean passive) {
 830 
 831         // Only passive mode used in JDK. See Bug 8010784.
 832         // passiveMode = passive;
 833         return this;
 834     }
 835 
 836     /**
 837      * Gets the current transfer mode.
 838      *
 839      * @return the current <code>FtpTransferMode</code>
 840      */
 841     public boolean isPassiveModeEnabled() {
 842         return passiveMode;
 843     }
 844 
 845     /**
 846      * Sets the timeout value to use when connecting to the server,
 847      *
 848      * @param timeout the timeout value, in milliseconds, to use for the connect
 849      *        operation. A value of zero or less, means use the default timeout.
 850      *
 851      * @return This FtpClient
 852      */
 853     public sun.net.ftp.FtpClient setConnectTimeout(int timeout) {
 854         connectTimeout = timeout;
 855         return this;
 856     }
 857 
 858     /**
 859      * Returns the current connection timeout value.
 860      *
 861      * @return the value, in milliseconds, of the current connect timeout.
 862      * @see #setConnectTimeout(int)
 863      */
 864     public int getConnectTimeout() {
 865         return connectTimeout;
 866     }
 867 
 868     /**
 869      * Sets the timeout value to use when reading from the server,
 870      *
 871      * @param timeout the timeout value, in milliseconds, to use for the read
 872      *        operation. A value of zero or less, means use the default timeout.
 873      * @return This FtpClient
 874      */
 875     public sun.net.ftp.FtpClient setReadTimeout(int timeout) {
 876         readTimeout = timeout;
 877         return this;
 878     }
 879 
 880     /**
 881      * Returns the current read timeout value.
 882      *
 883      * @return the value, in milliseconds, of the current read timeout.
 884      * @see #setReadTimeout(int)
 885      */
 886     public int getReadTimeout() {
 887         return readTimeout;
 888     }
 889 
 890     public sun.net.ftp.FtpClient setProxy(Proxy p) {
 891         proxy = p;
 892         return this;
 893     }
 894 
 895     /**
 896      * Get the proxy of this FtpClient
 897      *
 898      * @return the <code>Proxy</code>, this client is using, or <code>null</code>
 899      *         if none is used.
 900      * @see #setProxy(Proxy)
 901      */
 902     public Proxy getProxy() {
 903         return proxy;
 904     }
 905 
 906     /**
 907      * Connects to the specified destination.
 908      *
 909      * @param dest the <code>InetSocketAddress</code> to connect to.
 910      * @throws IOException if the connection fails.
 911      */
 912     private void tryConnect(InetSocketAddress dest, int timeout) throws IOException {
 913         if (isConnected()) {
 914             disconnect();
 915         }
 916         server = doConnect(dest, timeout);
 917         try {
 918             out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
 919                     true, encoding);
 920         } catch (UnsupportedEncodingException e) {
 921             throw new InternalError(encoding + "encoding not found", e);
 922         }
 923         in = new BufferedInputStream(server.getInputStream());
 924     }
 925 
 926     private Socket doConnect(InetSocketAddress dest, int timeout) throws IOException {
 927         Socket s;
 928         if (proxy != null) {
 929             if (proxy.type() == Proxy.Type.SOCKS) {
 930                 s = AccessController.doPrivileged(
 931                         new PrivilegedAction<Socket>() {
 932 
 933                             public Socket run() {
 934                                 return new Socket(proxy);
 935                             }
 936                         });
 937             } else {
 938                 s = new Socket(Proxy.NO_PROXY);
 939             }
 940         } else {
 941             s = new Socket();
 942         }
 943         // Instance specific timeouts do have priority, that means
 944         // connectTimeout & readTimeout (-1 means not set)
 945         // Then global default timeouts
 946         // Then no timeout.
 947         if (timeout >= 0) {
 948             s.connect(dest, timeout);
 949         } else {
 950             if (connectTimeout >= 0) {
 951                 s.connect(dest, connectTimeout);
 952             } else {
 953                 if (defaultConnectTimeout > 0) {
 954                     s.connect(dest, defaultConnectTimeout);
 955                 } else {
 956                     s.connect(dest);
 957                 }
 958             }
 959         }
 960         if (readTimeout >= 0) {
 961             s.setSoTimeout(readTimeout);
 962         } else if (defaultSoTimeout > 0) {
 963             s.setSoTimeout(defaultSoTimeout);
 964         }
 965         return s;
 966     }
 967 
 968     private void disconnect() throws IOException {
 969         if (isConnected()) {
 970             server.close();
 971         }
 972         server = null;
 973         in = null;
 974         out = null;
 975         lastTransSize = -1;
 976         lastFileName = null;
 977         restartOffset = 0;
 978         welcomeMsg = null;
 979         lastReplyCode = null;
 980         serverResponse.setSize(0);
 981     }
 982 
 983     /**
 984      * Tests whether this client is connected or not to a server.
 985      *
 986      * @return <code>true</code> if the client is connected.
 987      */
 988     public boolean isConnected() {
 989         return server != null;
 990     }
 991 
 992     public SocketAddress getServerAddress() {
 993         return server == null ? null : server.getRemoteSocketAddress();
 994     }
 995 
 996     public sun.net.ftp.FtpClient connect(SocketAddress dest) throws sun.net.ftp.FtpProtocolException, IOException {
 997         return connect(dest, -1);
 998     }
 999 
1000     /**
1001      * Connects the FtpClient to the specified destination.
1002      *
1003      * @param dest the address of the destination server
1004      * @throws IOException if connection failed.
1005      */
1006     public sun.net.ftp.FtpClient connect(SocketAddress dest, int timeout) throws sun.net.ftp.FtpProtocolException, IOException {
1007         if (!(dest instanceof InetSocketAddress)) {
1008             throw new IllegalArgumentException("Wrong address type");
1009         }
1010         serverAddr = (InetSocketAddress) dest;
1011         tryConnect(serverAddr, timeout);
1012         if (!readReply()) {
1013             throw new sun.net.ftp.FtpProtocolException("Welcome message: " +
1014                     getResponseString(), lastReplyCode);
1015         }
1016         welcomeMsg = getResponseString().substring(4);
1017         return this;
1018     }
1019 
1020     private void tryLogin(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {
1021         issueCommandCheck("USER " + user);
1022 
1023         /*
1024          * Checks for "331 User name okay, need password." answer
1025          */
1026         if (lastReplyCode == FtpReplyCode.NEED_PASSWORD) {
1027             if ((password != null) && (password.length > 0)) {
1028                 issueCommandCheck("PASS " + String.valueOf(password));
1029             }
1030         }
1031     }
1032 
1033     /**
1034      * Attempts to log on the server with the specified user name and password.
1035      *
1036      * @param user The user name
1037      * @param password The password for that user
1038      * @return <code>true</code> if the login was successful.
1039      * @throws IOException if an error occurred during the transmission
1040      */
1041     public sun.net.ftp.FtpClient login(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {
1042         if (!isConnected()) {
1043             throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
1044         }
1045         if (user == null || user.length() == 0) {
1046             throw new IllegalArgumentException("User name can't be null or empty");
1047         }
1048         tryLogin(user, password);
1049 
1050         // keep the welcome message around so we can
1051         // put it in the resulting HTML page.
1052         String l;
1053         StringBuffer sb = new StringBuffer();
1054         for (int i = 0; i < serverResponse.size(); i++) {
1055             l = serverResponse.elementAt(i);
1056             if (l != null) {
1057                 if (l.length() >= 4 && l.startsWith("230")) {
1058                     // get rid of the "230-" prefix
1059                     l = l.substring(4);
1060                 }
1061                 sb.append(l);
1062             }
1063         }
1064         welcomeMsg = sb.toString();
1065         loggedIn = true;
1066         return this;
1067     }
1068 
1069     /**
1070      * Attempts to log on the server with the specified user name, password and
1071      * account name.
1072      *
1073      * @param user The user name
1074      * @param password The password for that user.
1075      * @param account The account name for that user.
1076      * @return <code>true</code> if the login was successful.
1077      * @throws IOException if an error occurs during the transmission.
1078      */
1079     public sun.net.ftp.FtpClient login(String user, char[] password, String account) throws sun.net.ftp.FtpProtocolException, IOException {
1080 
1081         if (!isConnected()) {
1082             throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
1083         }
1084         if (user == null || user.length() == 0) {
1085             throw new IllegalArgumentException("User name can't be null or empty");
1086         }
1087         tryLogin(user, password);
1088 
1089         /*
1090          * Checks for "332 Need account for login." answer
1091          */
1092         if (lastReplyCode == FtpReplyCode.NEED_ACCOUNT) {
1093             issueCommandCheck("ACCT " + account);
1094         }
1095 
1096         // keep the welcome message around so we can
1097         // put it in the resulting HTML page.
1098         StringBuffer sb = new StringBuffer();
1099         if (serverResponse != null) {
1100             for (String l : serverResponse) {
1101                 if (l != null) {
1102                     if (l.length() >= 4 && l.startsWith("230")) {
1103                         // get rid of the "230-" prefix
1104                         l = l.substring(4);
1105                     }
1106                     sb.append(l);
1107                 }
1108             }
1109         }
1110         welcomeMsg = sb.toString();
1111         loggedIn = true;
1112         return this;
1113     }
1114 
1115     /**
1116      * Logs out the current user. This is in effect terminates the current
1117      * session and the connection to the server will be closed.
1118      *
1119      */
1120     public void close() throws IOException {
1121         if (isConnected()) {
1122             issueCommand("QUIT");
1123             loggedIn = false;
1124         }
1125         disconnect();
1126     }
1127 
1128     /**
1129      * Checks whether the client is logged in to the server or not.
1130      *
1131      * @return <code>true</code> if the client has already completed a login.
1132      */
1133     public boolean isLoggedIn() {
1134         return loggedIn;
1135     }
1136 
1137     /**
1138      * Changes to a specific directory on a remote FTP server
1139      *
1140      * @param remoteDirectory path of the directory to CD to.
1141      * @return <code>true</code> if the operation was successful.
1142      * @exception <code>FtpProtocolException</code>
1143      */
1144     public sun.net.ftp.FtpClient changeDirectory(String remoteDirectory) throws sun.net.ftp.FtpProtocolException, IOException {
1145         if (remoteDirectory == null || "".equals(remoteDirectory)) {
1146             throw new IllegalArgumentException("directory can't be null or empty");
1147         }
1148 
1149         issueCommandCheck("CWD " + remoteDirectory);
1150         return this;
1151     }
1152 
1153     /**
1154      * Changes to the parent directory, sending the CDUP command to the server.
1155      *
1156      * @return <code>true</code> if the command was successful.
1157      * @throws IOException
1158      */
1159     public sun.net.ftp.FtpClient changeToParentDirectory() throws sun.net.ftp.FtpProtocolException, IOException {
1160         issueCommandCheck("CDUP");
1161         return this;
1162     }
1163 
1164     /**
1165      * Returns the server current working directory, or <code>null</code> if
1166      * the PWD command failed.
1167      *
1168      * @return a <code>String</code> containing the current working directory,
1169      *         or <code>null</code>
1170      * @throws IOException
1171      */
1172     public String getWorkingDirectory() throws sun.net.ftp.FtpProtocolException, IOException {
1173         issueCommandCheck("PWD");
1174         /*
1175          * answer will be of the following format :
1176          *
1177          * 257 "/" is current directory.
1178          */
1179         String answ = getResponseString();
1180         if (!answ.startsWith("257")) {
1181             return null;
1182         }
1183         return answ.substring(5, answ.lastIndexOf('"'));
1184     }
1185 
1186     /**
1187      * Sets the restart offset to the specified value.  That value will be
1188      * sent through a <code>REST</code> command to server before a file
1189      * transfer and has the effect of resuming a file transfer from the
1190      * specified point. After a transfer the restart offset is set back to
1191      * zero.
1192      *
1193      * @param offset the offset in the remote file at which to start the next
1194      *        transfer. This must be a value greater than or equal to zero.
1195      * @throws IllegalArgumentException if the offset is negative.
1196      */
1197     public sun.net.ftp.FtpClient setRestartOffset(long offset) {
1198         if (offset < 0) {
1199             throw new IllegalArgumentException("offset can't be negative");
1200         }
1201         restartOffset = offset;
1202         return this;
1203     }
1204 
1205     /**
1206      * Retrieves a file from the ftp server and writes it to the specified
1207      * <code>OutputStream</code>.
1208      * If the restart offset was set, then a <code>REST</code> command will be
1209      * sent before the RETR in order to restart the tranfer from the specified
1210      * offset.
1211      * The <code>OutputStream</code> is not closed by this method at the end
1212      * of the transfer.
1213      *
1214      * @param name a <code>String<code> containing the name of the file to
1215      *        retreive from the server.
1216      * @param local the <code>OutputStream</code> the file should be written to.
1217      * @throws IOException if the transfer fails.
1218      */
1219     public sun.net.ftp.FtpClient getFile(String name, OutputStream local) throws sun.net.ftp.FtpProtocolException, IOException {
1220         int mtu = 1500;
1221         if (restartOffset > 0) {
1222             Socket s;
1223             try {
1224                 s = openDataConnection("REST " + restartOffset);
1225             } finally {
1226                 restartOffset = 0;
1227             }
1228             issueCommandCheck("RETR " + name);
1229             getTransferSize();
1230             InputStream remote = createInputStream(s.getInputStream());
1231             byte[] buf = new byte[mtu * 10];
1232             int l;
1233             while ((l = remote.read(buf)) >= 0) {
1234                 if (l > 0) {
1235                     local.write(buf, 0, l);
1236                 }
1237             }
1238             remote.close();
1239         } else {
1240             Socket s = openDataConnection("RETR " + name);
1241             getTransferSize();
1242             InputStream remote = createInputStream(s.getInputStream());
1243             byte[] buf = new byte[mtu * 10];
1244             int l;
1245             while ((l = remote.read(buf)) >= 0) {
1246                 if (l > 0) {
1247                     local.write(buf, 0, l);
1248                 }
1249             }
1250             remote.close();
1251         }
1252         return completePending();
1253     }
1254 
1255     /**
1256      * Retrieves a file from the ftp server, using the RETR command, and
1257      * returns the InputStream from* the established data connection.
1258      * {@link #completePending()} <b>has</b> to be called once the application
1259      * is done reading from the returned stream.
1260      *
1261      * @param name the name of the remote file
1262      * @return the {@link java.io.InputStream} from the data connection, or
1263      *         <code>null</code> if the command was unsuccessful.
1264      * @throws IOException if an error occurred during the transmission.
1265      */
1266     public InputStream getFileStream(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1267         Socket s;
1268         if (restartOffset > 0) {
1269             try {
1270                 s = openDataConnection("REST " + restartOffset);
1271             } finally {
1272                 restartOffset = 0;
1273             }
1274             if (s == null) {
1275                 return null;
1276             }
1277             issueCommandCheck("RETR " + name);
1278             getTransferSize();
1279             return createInputStream(s.getInputStream());
1280         }
1281 
1282         s = openDataConnection("RETR " + name);
1283         if (s == null) {
1284             return null;
1285         }
1286         getTransferSize();
1287         return createInputStream(s.getInputStream());
1288     }
1289 
1290     /**
1291      * Transfers a file from the client to the server (aka a <I>put</I>)
1292      * by sending the STOR or STOU command, depending on the
1293      * <code>unique</code> argument, and returns the <code>OutputStream</code>
1294      * from the established data connection.
1295      * {@link #completePending()} <b>has</b> to be called once the application
1296      * is finished writing to the stream.
1297      *
1298      * A new file is created at the server site if the file specified does
1299      * not already exist.
1300      *
1301      * If <code>unique</code> is set to <code>true</code>, the resultant file
1302      * is to be created under a name unique to that directory, meaning
1303      * it will not overwrite an existing file, instead the server will
1304      * generate a new, unique, file name.
1305      * The name of the remote file can be retrieved, after completion of the
1306      * transfer, by calling {@link #getLastFileName()}.
1307      *
1308      * @param name the name of the remote file to write.
1309      * @param unique <code>true</code> if the remote files should be unique,
1310      *        in which case the STOU command will be used.
1311      * @return the {@link java.io.OutputStream} from the data connection or
1312      *         <code>null</code> if the command was unsuccessful.
1313      * @throws IOException if an error occurred during the transmission.
1314      */
1315     public OutputStream putFileStream(String name, boolean unique)
1316         throws sun.net.ftp.FtpProtocolException, IOException
1317     {
1318         String cmd = unique ? "STOU " : "STOR ";
1319         Socket s = openDataConnection(cmd + name);
1320         if (s == null) {
1321             return null;
1322         }
1323         boolean bm = (type == TransferType.BINARY);
1324         return new sun.net.TelnetOutputStream(s.getOutputStream(), bm);
1325     }
1326 
1327     /**
1328      * Transfers a file from the client to the server (aka a <I>put</I>)
1329      * by sending the STOR command. The content of the <code>InputStream</code>
1330      * passed in argument is written into the remote file, overwriting any
1331      * existing data.
1332      *
1333      * A new file is created at the server site if the file specified does
1334      * not already exist.
1335      *
1336      * @param name the name of the remote file to write.
1337      * @param local the <code>InputStream</code> that points to the data to
1338      *        transfer.
1339      * @param unique <code>true</code> if the remote file should be unique
1340      *        (i.e. not already existing), <code>false</code> otherwise.
1341      * @return <code>true</code> if the transfer was successful.
1342      * @throws IOException if an error occurred during the transmission.
1343      * @see #getLastFileName()
1344      */
1345     public sun.net.ftp.FtpClient putFile(String name, InputStream local, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException {
1346         String cmd = unique ? "STOU " : "STOR ";
1347         int mtu = 1500;
1348         if (type == TransferType.BINARY) {
1349             Socket s = openDataConnection(cmd + name);
1350             OutputStream remote = createOutputStream(s.getOutputStream());
1351             byte[] buf = new byte[mtu * 10];
1352             int l;
1353             while ((l = local.read(buf)) >= 0) {
1354                 if (l > 0) {
1355                     remote.write(buf, 0, l);
1356                 }
1357             }
1358             remote.close();
1359         }
1360         return completePending();
1361     }
1362 
1363     /**
1364      * Sends the APPE command to the server in order to transfer a data stream
1365      * passed in argument and append it to the content of the specified remote
1366      * file.
1367      *
1368      * @param name A <code>String</code> containing the name of the remote file
1369      *        to append to.
1370      * @param local The <code>InputStream</code> providing access to the data
1371      *        to be appended.
1372      * @return <code>true</code> if the transfer was successful.
1373      * @throws IOException if an error occurred during the transmission.
1374      */
1375     public sun.net.ftp.FtpClient appendFile(String name, InputStream local) throws sun.net.ftp.FtpProtocolException, IOException {
1376         int mtu = 1500;
1377         Socket s = openDataConnection("APPE " + name);
1378         OutputStream remote = createOutputStream(s.getOutputStream());
1379         byte[] buf = new byte[mtu * 10];
1380         int l;
1381         while ((l = local.read(buf)) >= 0) {
1382             if (l > 0) {
1383                 remote.write(buf, 0, l);
1384             }
1385         }
1386         remote.close();
1387         return completePending();
1388     }
1389 
1390     /**
1391      * Renames a file on the server.
1392      *
1393      * @param from the name of the file being renamed
1394      * @param to the new name for the file
1395      * @throws IOException if the command fails
1396      */
1397     public sun.net.ftp.FtpClient rename(String from, String to) throws sun.net.ftp.FtpProtocolException, IOException {
1398         issueCommandCheck("RNFR " + from);
1399         issueCommandCheck("RNTO " + to);
1400         return this;
1401     }
1402 
1403     /**
1404      * Deletes a file on the server.
1405      *
1406      * @param name a <code>String</code> containing the name of the file
1407      *        to delete.
1408      * @return <code>true</code> if the command was successful
1409      * @throws IOException if an error occurred during the exchange
1410      */
1411     public sun.net.ftp.FtpClient deleteFile(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1412         issueCommandCheck("DELE " + name);
1413         return this;
1414     }
1415 
1416     /**
1417      * Creates a new directory on the server.
1418      *
1419      * @param name a <code>String</code> containing the name of the directory
1420      *        to create.
1421      * @return <code>true</code> if the operation was successful.
1422      * @throws IOException if an error occurred during the exchange
1423      */
1424     public sun.net.ftp.FtpClient makeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1425         issueCommandCheck("MKD " + name);
1426         return this;
1427     }
1428 
1429     /**
1430      * Removes a directory on the server.
1431      *
1432      * @param name a <code>String</code> containing the name of the directory
1433      *        to remove.
1434      *
1435      * @return <code>true</code> if the operation was successful.
1436      * @throws IOException if an error occurred during the exchange.
1437      */
1438     public sun.net.ftp.FtpClient removeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1439         issueCommandCheck("RMD " + name);
1440         return this;
1441     }
1442 
1443     /**
1444      * Sends a No-operation command. It's useful for testing the connection
1445      * status or as a <I>keep alive</I> mechanism.
1446      *
1447      * @throws FtpProtocolException if the command fails
1448      */
1449     public sun.net.ftp.FtpClient noop() throws sun.net.ftp.FtpProtocolException, IOException {
1450         issueCommandCheck("NOOP");
1451         return this;
1452     }
1453 
1454     /**
1455      * Sends the STAT command to the server.
1456      * This can be used while a data connection is open to get a status
1457      * on the current transfer, in that case the parameter should be
1458      * <code>null</code>.
1459      * If used between file transfers, it may have a pathname as argument
1460      * in which case it will work as the LIST command except no data
1461      * connection will be created.
1462      *
1463      * @param name an optional <code>String</code> containing the pathname
1464      *        the STAT command should apply to.
1465      * @return the response from the server or <code>null</code> if the
1466      *         command failed.
1467      * @throws IOException if an error occurred during the transmission.
1468      */
1469     public String getStatus(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1470         issueCommandCheck((name == null ? "STAT" : "STAT " + name));
1471         /*
1472          * A typical response will be:
1473          *  213-status of t32.gif:
1474          * -rw-r--r--   1 jcc      staff     247445 Feb 17  1998 t32.gif
1475          * 213 End of Status
1476          *
1477          * or
1478          *
1479          * 211-jsn FTP server status:
1480          *     Version wu-2.6.2+Sun
1481          *     Connected to localhost (::1)
1482          *     Logged in as jccollet
1483          *     TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream
1484          *      No data connection
1485          *     0 data bytes received in 0 files
1486          *     0 data bytes transmitted in 0 files
1487          *     0 data bytes total in 0 files
1488          *     53 traffic bytes received in 0 transfers
1489          *     485 traffic bytes transmitted in 0 transfers
1490          *     587 traffic bytes total in 0 transfers
1491          * 211 End of status
1492          *
1493          * So we need to remove the 1st and last line
1494          */
1495         Vector<String> resp = getResponseStrings();
1496         StringBuffer sb = new StringBuffer();
1497         for (int i = 1; i < resp.size() - 1; i++) {
1498             sb.append(resp.get(i));
1499         }
1500         return sb.toString();
1501     }
1502 
1503     /**
1504      * Sends the FEAT command to the server and returns the list of supported
1505      * features in the form of strings.
1506      *
1507      * The features are the supported commands, like AUTH TLS, PROT or PASV.
1508      * See the RFCs for a complete list.
1509      *
1510      * Note that not all FTP servers support that command, in which case
1511      * the method will return <code>null</code>
1512      *
1513      * @return a <code>List</code> of <code>Strings</code> describing the
1514      *         supported additional features, or <code>null</code>
1515      *         if the command is not supported.
1516      * @throws IOException if an error occurs during the transmission.
1517      */
1518     public List<String> getFeatures() throws sun.net.ftp.FtpProtocolException, IOException {
1519         /*
1520          * The FEAT command, when implemented will return something like:
1521          *
1522          * 211-Features:
1523          *   AUTH TLS
1524          *   PBSZ
1525          *   PROT
1526          *   EPSV
1527          *   EPRT
1528          *   PASV
1529          *   REST STREAM
1530          *  211 END
1531          */
1532         ArrayList<String> features = new ArrayList<String>();
1533         issueCommandCheck("FEAT");
1534         Vector<String> resp = getResponseStrings();
1535         // Note that we start at index 1 to skip the 1st line (211-...)
1536         // and we stop before the last line.
1537         for (int i = 1; i < resp.size() - 1; i++) {
1538             String s = resp.get(i);
1539             // Get rid of leading space and trailing newline
1540             features.add(s.substring(1, s.length() - 1));
1541         }
1542         return features;
1543     }
1544 
1545     /**
1546      * sends the ABOR command to the server.
1547      * It tells the server to stop the previous command or transfer.
1548      *
1549      * @return <code>true</code> if the command was successful.
1550      * @throws IOException if an error occurred during the transmission.
1551      */
1552     public sun.net.ftp.FtpClient abort() throws sun.net.ftp.FtpProtocolException, IOException {
1553         issueCommandCheck("ABOR");
1554         // TODO: Must check the ReplyCode:
1555         /*
1556          * From the RFC:
1557          * There are two cases for the server upon receipt of this
1558          * command: (1) the FTP service command was already completed,
1559          * or (2) the FTP service command is still in progress.
1560          * In the first case, the server closes the data connection
1561          * (if it is open) and responds with a 226 reply, indicating
1562          * that the abort command was successfully processed.
1563          * In the second case, the server aborts the FTP service in
1564          * progress and closes the data connection, returning a 426
1565          * reply to indicate that the service request terminated
1566          * abnormally.  The server then sends a 226 reply,
1567          * indicating that the abort command was successfully
1568          * processed.
1569          */
1570 
1571 
1572         return this;
1573     }
1574 
1575     /**
1576      * Some methods do not wait until completion before returning, so this
1577      * method can be called to wait until completion. This is typically the case
1578      * with commands that trigger a transfer like {@link #getFileStream(String)}.
1579      * So this method should be called before accessing information related to
1580      * such a command.
1581      * <p>This method will actually block reading on the command channel for a
1582      * notification from the server that the command is finished. Such a
1583      * notification often carries extra information concerning the completion
1584      * of the pending action (e.g. number of bytes transfered).</p>
1585      * <p>Note that this will return true immediately if no command or action
1586      * is pending</p>
1587      * <p>It should be also noted that most methods issuing commands to the ftp
1588      * server will call this method if a previous command is pending.
1589      * <p>Example of use:
1590      * <pre>
1591      * InputStream in = cl.getFileStream("file");
1592      * ...
1593      * cl.completePending();
1594      * long size = cl.getLastTransferSize();
1595      * </pre>
1596      * On the other hand, it's not necessary in a case like:
1597      * <pre>
1598      * InputStream in = cl.getFileStream("file");
1599      * // read content
1600      * ...
1601      * cl.logout();
1602      * </pre>
1603      * <p>Since {@link #logout()} will call completePending() if necessary.</p>
1604      * @return <code>true</code> if the completion was successful or if no
1605      *         action was pending.
1606      * @throws IOException
1607      */
1608     public sun.net.ftp.FtpClient completePending() throws sun.net.ftp.FtpProtocolException, IOException {
1609         while (replyPending) {
1610             replyPending = false;
1611             if (!readReply()) {
1612                 throw new sun.net.ftp.FtpProtocolException(getLastResponseString(), lastReplyCode);
1613             }
1614         }
1615         return this;
1616     }
1617 
1618     /**
1619      * Reinitializes the USER parameters on the FTP server
1620      *
1621      * @throws FtpProtocolException if the command fails
1622      */
1623     public sun.net.ftp.FtpClient reInit() throws sun.net.ftp.FtpProtocolException, IOException {
1624         issueCommandCheck("REIN");
1625         loggedIn = false;
1626         if (useCrypto) {
1627             if (server instanceof SSLSocket) {
1628                 javax.net.ssl.SSLSession session = ((SSLSocket) server).getSession();
1629                 session.invalidate();
1630                 // Restore previous socket and streams
1631                 server = oldSocket;
1632                 oldSocket = null;
1633                 try {
1634                     out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
1635                             true, encoding);
1636                 } catch (UnsupportedEncodingException e) {
1637                     throw new InternalError(encoding + "encoding not found", e);
1638                 }
1639                 in = new BufferedInputStream(server.getInputStream());
1640             }
1641         }
1642         useCrypto = false;
1643         return this;
1644     }
1645 
1646     /**
1647      * Changes the transfer type (binary, ascii, ebcdic) and issue the
1648      * proper command (e.g. TYPE A) to the server.
1649      *
1650      * @param type the <code>FtpTransferType</code> to use.
1651      * @return This FtpClient
1652      * @throws IOException if an error occurs during transmission.
1653      */
1654     public sun.net.ftp.FtpClient setType(TransferType type) throws sun.net.ftp.FtpProtocolException, IOException {
1655         String cmd = "NOOP";
1656 
1657         this.type = type;
1658         if (type == TransferType.ASCII) {
1659             cmd = "TYPE A";
1660         }
1661         if (type == TransferType.BINARY) {
1662             cmd = "TYPE I";
1663         }
1664         if (type == TransferType.EBCDIC) {
1665             cmd = "TYPE E";
1666         }
1667         issueCommandCheck(cmd);
1668         return this;
1669     }
1670 
1671     /**
1672      * Issues a LIST command to the server to get the current directory
1673      * listing, and returns the InputStream from the data connection.
1674      * {@link #completePending()} <b>has</b> to be called once the application
1675      * is finished writing to the stream.
1676      *
1677      * @param path the pathname of the directory to list, or <code>null</code>
1678      *        for the current working directory.
1679      * @return the <code>InputStream</code> from the resulting data connection
1680      * @throws IOException if an error occurs during the transmission.
1681      * @see #changeDirectory(String)
1682      * @see #listFiles(String)
1683      */
1684     public InputStream list(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1685         Socket s;
1686         s = openDataConnection(path == null ? "LIST" : "LIST " + path);
1687         if (s != null) {
1688             return createInputStream(s.getInputStream());
1689         }
1690         return null;
1691     }
1692 
1693     /**
1694      * Issues a NLST path command to server to get the specified directory
1695      * content. It differs from {@link #list(String)} method by the fact that
1696      * it will only list the file names which would make the parsing of the
1697      * somewhat easier.
1698      *
1699      * {@link #completePending()} <b>has</b> to be called once the application
1700      * is finished writing to the stream.
1701      *
1702      * @param path a <code>String</code> containing the pathname of the
1703      *        directory to list or <code>null</code> for the current working
1704      *        directory.
1705      * @return the <code>InputStream</code> from the resulting data connection
1706      * @throws IOException if an error occurs during the transmission.
1707      */
1708     public InputStream nameList(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1709         Socket s;
1710         s = openDataConnection("NLST " + path);
1711         if (s != null) {
1712             return createInputStream(s.getInputStream());
1713         }
1714         return null;
1715     }
1716 
1717     /**
1718      * Issues the SIZE [path] command to the server to get the size of a
1719      * specific file on the server.
1720      * Note that this command may not be supported by the server. In which
1721      * case -1 will be returned.
1722      *
1723      * @param path a <code>String</code> containing the pathname of the
1724      *        file.
1725      * @return a <code>long</code> containing the size of the file or -1 if
1726      *         the server returned an error, which can be checked with
1727      *         {@link #getLastReplyCode()}.
1728      * @throws IOException if an error occurs during the transmission.
1729      */
1730     public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1731         if (path == null || path.length() == 0) {
1732             throw new IllegalArgumentException("path can't be null or empty");
1733         }
1734         issueCommandCheck("SIZE " + path);
1735         if (lastReplyCode == FtpReplyCode.FILE_STATUS) {
1736             String s = getResponseString();
1737             s = s.substring(4, s.length() - 1);
1738             return Long.parseLong(s);
1739         }
1740         return -1;
1741     }
1742     private static String[] MDTMformats = {
1743         "yyyyMMddHHmmss.SSS",
1744         "yyyyMMddHHmmss"
1745     };
1746     private static SimpleDateFormat[] dateFormats = new SimpleDateFormat[MDTMformats.length];
1747 
1748     static {
1749         for (int i = 0; i < MDTMformats.length; i++) {
1750             dateFormats[i] = new SimpleDateFormat(MDTMformats[i]);
1751             dateFormats[i].setTimeZone(TimeZone.getTimeZone("GMT"));
1752         }
1753     }
1754 
1755     /**
1756      * Issues the MDTM [path] command to the server to get the modification
1757      * time of a specific file on the server.
1758      * Note that this command may not be supported by the server, in which
1759      * case <code>null</code> will be returned.
1760      *
1761      * @param path a <code>String</code> containing the pathname of the file.
1762      * @return a <code>Date</code> representing the last modification time
1763      *         or <code>null</code> if the server returned an error, which
1764      *         can be checked with {@link #getLastReplyCode()}.
1765      * @throws IOException if an error occurs during the transmission.
1766      */
1767     public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1768         issueCommandCheck("MDTM " + path);
1769         if (lastReplyCode == FtpReplyCode.FILE_STATUS) {
1770             String s = getResponseString().substring(4);
1771             Date d = null;
1772             for (SimpleDateFormat dateFormat : dateFormats) {
1773                 try {
1774                     d = dateFormat.parse(s);
1775                 } catch (ParseException ex) {
1776                 }
1777                 if (d != null) {
1778                     return d;
1779                 }
1780             }
1781         }
1782         return null;
1783     }
1784 
1785     /**
1786      * Sets the parser used to handle the directory output to the specified
1787      * one. By default the parser is set to one that can handle most FTP
1788      * servers output (Unix base mostly). However it may be necessary for
1789      * and application to provide its own parser due to some uncommon
1790      * output format.
1791      *
1792      * @param p The <code>FtpDirParser</code> to use.
1793      * @see #listFiles(String)
1794      */
1795     public sun.net.ftp.FtpClient setDirParser(FtpDirParser p) {
1796         parser = p;
1797         return this;
1798     }
1799 
1800     private class FtpFileIterator implements Iterator<FtpDirEntry>, Closeable {
1801 
1802         private BufferedReader in = null;
1803         private FtpDirEntry nextFile = null;
1804         private FtpDirParser fparser = null;
1805         private boolean eof = false;
1806 
1807         public FtpFileIterator(FtpDirParser p, BufferedReader in) {
1808             this.in = in;
1809             this.fparser = p;
1810             readNext();
1811         }
1812 
1813         private void readNext() {
1814             nextFile = null;
1815             if (eof) {
1816                 return;
1817             }
1818             String line = null;
1819             try {
1820                 do {
1821                     line = in.readLine();
1822                     if (line != null) {
1823                         nextFile = fparser.parseLine(line);
1824                         if (nextFile != null) {
1825                             return;
1826                         }
1827                     }
1828                 } while (line != null);
1829                 in.close();
1830             } catch (IOException iOException) {
1831             }
1832             eof = true;
1833         }
1834 
1835         public boolean hasNext() {
1836             return nextFile != null;
1837         }
1838 
1839         public FtpDirEntry next() {
1840             FtpDirEntry ret = nextFile;
1841             readNext();
1842             return ret;
1843         }
1844 
1845         public void remove() {
1846             throw new UnsupportedOperationException("Not supported yet.");
1847         }
1848 
1849         public void close() throws IOException {
1850             if (in != null && !eof) {
1851                 in.close();
1852             }
1853             eof = true;
1854             nextFile = null;
1855         }
1856     }
1857 
1858     /**
1859      * Issues a MLSD command to the server to get the specified directory
1860      * listing and applies the current parser to create an Iterator of
1861      * {@link java.net.ftp.FtpDirEntry}. Note that the Iterator returned is also a
1862      * {@link java.io.Closeable}.
1863      * If the server doesn't support the MLSD command, the LIST command is used
1864      * instead.
1865      *
1866      * {@link #completePending()} <b>has</b> to be called once the application
1867      * is finished iterating through the files.
1868      *
1869      * @param path the pathname of the directory to list or <code>null</code>
1870      *        for the current working directoty.
1871      * @return a <code>Iterator</code> of files or <code>null</code> if the
1872      *         command failed.
1873      * @throws IOException if an error occurred during the transmission
1874      * @see #setDirParser(FtpDirParser)
1875      * @see #changeDirectory(String)
1876      */
1877     public Iterator<FtpDirEntry> listFiles(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1878         Socket s = null;
1879         BufferedReader sin = null;
1880         try {
1881             s = openDataConnection(path == null ? "MLSD" : "MLSD " + path);
1882         } catch (sun.net.ftp.FtpProtocolException FtpException) {
1883             // The server doesn't understand new MLSD command, ignore and fall
1884             // back to LIST
1885         }
1886 
1887         if (s != null) {
1888             sin = new BufferedReader(new InputStreamReader(s.getInputStream()));
1889             return new FtpFileIterator(mlsxParser, sin);
1890         } else {
1891             s = openDataConnection(path == null ? "LIST" : "LIST " + path);
1892             if (s != null) {
1893                 sin = new BufferedReader(new InputStreamReader(s.getInputStream()));
1894                 return new FtpFileIterator(parser, sin);
1895             }
1896         }
1897         return null;
1898     }
1899 
1900     private boolean sendSecurityData(byte[] buf) throws IOException {
1901         String s = Base64.getEncoder().encodeToString(buf);
1902         return issueCommand("ADAT " + s);
1903     }
1904 
1905     private byte[] getSecurityData() {
1906         String s = getLastResponseString();
1907         if (s.substring(4, 9).equalsIgnoreCase("ADAT=")) {
1908             // Need to get rid of the leading '315 ADAT='
1909             // and the trailing newline
1910             return Base64.getDecoder().decode(s.substring(9, s.length() - 1));
1911         }
1912         return null;
1913     }
1914 
1915     /**
1916      * Attempts to use Kerberos GSSAPI as an authentication mechanism with the
1917      * ftp server. This will issue an <code>AUTH GSSAPI</code> command, and if
1918      * it is accepted by the server, will followup with <code>ADAT</code>
1919      * command to exchange the various tokens until authentification is
1920      * successful. This conforms to Appendix I of RFC 2228.
1921      *
1922      * @return <code>true</code> if authentication was successful.
1923      * @throws IOException if an error occurs during the transmission.
1924      */
1925     public sun.net.ftp.FtpClient useKerberos() throws sun.net.ftp.FtpProtocolException, IOException {
1926         /*
1927          * Comment out for the moment since it's not in use and would create
1928          * needless cross-package links.
1929          *
1930         issueCommandCheck("AUTH GSSAPI");
1931         if (lastReplyCode != FtpReplyCode.NEED_ADAT)
1932         throw new sun.net.ftp.FtpProtocolException("Unexpected reply from server");
1933         try {
1934         GSSManager manager = GSSManager.getInstance();
1935         GSSName name = manager.createName("SERVICE:ftp@"+
1936         serverAddr.getHostName(), null);
1937         GSSContext context = manager.createContext(name, null, null,
1938         GSSContext.DEFAULT_LIFETIME);
1939         context.requestMutualAuth(true);
1940         context.requestReplayDet(true);
1941         context.requestSequenceDet(true);
1942         context.requestCredDeleg(true);
1943         byte []inToken = new byte[0];
1944         while (!context.isEstablished()) {
1945         byte[] outToken
1946         = context.initSecContext(inToken, 0, inToken.length);
1947         // send the output token if generated
1948         if (outToken != null) {
1949         if (sendSecurityData(outToken)) {
1950         inToken = getSecurityData();
1951         }
1952         }
1953         }
1954         loggedIn = true;
1955         } catch (GSSException e) {
1956 
1957         }
1958          */
1959         return this;
1960     }
1961 
1962     /**
1963      * Returns the Welcome string the server sent during initial connection.
1964      *
1965      * @return a <code>String</code> containing the message the server
1966      *         returned during connection or <code>null</code>.
1967      */
1968     public String getWelcomeMsg() {
1969         return welcomeMsg;
1970     }
1971 
1972     /**
1973      * Returns the last reply code sent by the server.
1974      *
1975      * @return the lastReplyCode
1976      */
1977     public FtpReplyCode getLastReplyCode() {
1978         return lastReplyCode;
1979     }
1980 
1981     /**
1982      * Returns the last response string sent by the server.
1983      *
1984      * @return the message string, which can be quite long, last returned
1985      *         by the server.
1986      */
1987     public String getLastResponseString() {
1988         StringBuffer sb = new StringBuffer();
1989         if (serverResponse != null) {
1990             for (String l : serverResponse) {
1991                 if (l != null) {
1992                     sb.append(l);
1993                 }
1994             }
1995         }
1996         return sb.toString();
1997     }
1998 
1999     /**
2000      * Returns, when available, the size of the latest started transfer.
2001      * This is retreived by parsing the response string received as an initial
2002      * response to a RETR or similar request.
2003      *
2004      * @return the size of the latest transfer or -1 if either there was no
2005      *         transfer or the information was unavailable.
2006      */
2007     public long getLastTransferSize() {
2008         return lastTransSize;
2009     }
2010 
2011     /**
2012      * Returns, when available, the remote name of the last transfered file.
2013      * This is mainly useful for "put" operation when the unique flag was
2014      * set since it allows to recover the unique file name created on the
2015      * server which may be different from the one submitted with the command.
2016      *
2017      * @return the name the latest transfered file remote name, or
2018      *         <code>null</code> if that information is unavailable.
2019      */
2020     public String getLastFileName() {
2021         return lastFileName;
2022     }
2023 
2024     /**
2025      * Attempts to switch to a secure, encrypted connection. This is done by
2026      * sending the "AUTH TLS" command.
2027      * <p>See <a href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</a></p>
2028      * If successful this will establish a secure command channel with the
2029      * server, it will also make it so that all other transfers (e.g. a RETR
2030      * command) will be done over an encrypted channel as well unless a
2031      * {@link #reInit()} command or a {@link #endSecureSession()} command is issued.
2032      *
2033      * @return <code>true</code> if the operation was successful.
2034      * @throws IOException if an error occurred during the transmission.
2035      * @see #endSecureSession()
2036      */
2037     public sun.net.ftp.FtpClient startSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {
2038         if (!isConnected()) {
2039             throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
2040         }
2041         if (sslFact == null) {
2042             try {
2043                 sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault();
2044             } catch (Exception e) {
2045                 throw new IOException(e.getLocalizedMessage());
2046             }
2047         }
2048         issueCommandCheck("AUTH TLS");
2049         Socket s = null;
2050         try {
2051             s = sslFact.createSocket(server, serverAddr.getHostName(), serverAddr.getPort(), true);
2052         } catch (javax.net.ssl.SSLException ssle) {
2053             try {
2054                 disconnect();
2055             } catch (Exception e) {
2056             }
2057             throw ssle;
2058         }
2059         // Remember underlying socket so we can restore it later
2060         oldSocket = server;
2061         server = s;
2062         try {
2063             out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
2064                     true, encoding);
2065         } catch (UnsupportedEncodingException e) {
2066             throw new InternalError(encoding + "encoding not found", e);
2067         }
2068         in = new BufferedInputStream(server.getInputStream());
2069 
2070         issueCommandCheck("PBSZ 0");
2071         issueCommandCheck("PROT P");
2072         useCrypto = true;
2073         return this;
2074     }
2075 
2076     /**
2077      * Sends a <code>CCC</code> command followed by a <code>PROT C</code>
2078      * command to the server terminating an encrypted session and reverting
2079      * back to a non crypted transmission.
2080      *
2081      * @return <code>true</code> if the operation was successful.
2082      * @throws IOException if an error occurred during transmission.
2083      * @see #startSecureSession()
2084      */
2085     public sun.net.ftp.FtpClient endSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {
2086         if (!useCrypto) {
2087             return this;
2088         }
2089 
2090         issueCommandCheck("CCC");
2091         issueCommandCheck("PROT C");
2092         useCrypto = false;
2093         // Restore previous socket and streams
2094         server = oldSocket;
2095         oldSocket = null;
2096         try {
2097             out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
2098                     true, encoding);
2099         } catch (UnsupportedEncodingException e) {
2100             throw new InternalError(encoding + "encoding not found", e);
2101         }
2102         in = new BufferedInputStream(server.getInputStream());
2103 
2104         return this;
2105     }
2106 
2107     /**
2108      * Sends the "Allocate" (ALLO) command to the server telling it to
2109      * pre-allocate the specified number of bytes for the next transfer.
2110      *
2111      * @param size The number of bytes to allocate.
2112      * @return <code>true</code> if the operation was successful.
2113      * @throws IOException if an error occurred during the transmission.
2114      */
2115     public sun.net.ftp.FtpClient allocate(long size) throws sun.net.ftp.FtpProtocolException, IOException {
2116         issueCommandCheck("ALLO " + size);
2117         return this;
2118     }
2119 
2120     /**
2121      * Sends the "Structure Mount" (SMNT) command to the server. This let the
2122      * user mount a different file system data structure without altering his
2123      * login or accounting information.
2124      *
2125      * @param struct a <code>String</code> containing the name of the
2126      *        structure to mount.
2127      * @return <code>true</code> if the operation was successful.
2128      * @throws IOException if an error occurred during the transmission.
2129      */
2130     public sun.net.ftp.FtpClient structureMount(String struct) throws sun.net.ftp.FtpProtocolException, IOException {
2131         issueCommandCheck("SMNT " + struct);
2132         return this;
2133     }
2134 
2135     /**
2136      * Sends a SYST (System) command to the server and returns the String
2137      * sent back by the server describing the operating system at the
2138      * server.
2139      *
2140      * @return a <code>String</code> describing the OS, or <code>null</code>
2141      *         if the operation was not successful.
2142      * @throws IOException if an error occurred during the transmission.
2143      */
2144     public String getSystem() throws sun.net.ftp.FtpProtocolException, IOException {
2145         issueCommandCheck("SYST");
2146         /*
2147          * 215 UNIX Type: L8 Version: SUNOS
2148          */
2149         String resp = getResponseString();
2150         // Get rid of the leading code and blank
2151         return resp.substring(4);
2152     }
2153 
2154     /**
2155      * Sends the HELP command to the server, with an optional command, like
2156      * SITE, and returns the text sent back by the server.
2157      *
2158      * @param cmd the command for which the help is requested or
2159      *        <code>null</code> for the general help
2160      * @return a <code>String</code> containing the text sent back by the
2161      *         server, or <code>null</code> if the command failed.
2162      * @throws IOException if an error occurred during transmission
2163      */
2164     public String getHelp(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
2165         issueCommandCheck("HELP " + cmd);
2166         /**
2167          *
2168          * HELP
2169          * 214-The following commands are implemented.
2170          *   USER    EPRT    STRU    ALLO    DELE    SYST    RMD     MDTM    ADAT
2171          *   PASS    EPSV    MODE    REST    CWD     STAT    PWD     PROT
2172          *   QUIT    LPRT    RETR    RNFR    LIST    HELP    CDUP    PBSZ
2173          *   PORT    LPSV    STOR    RNTO    NLST    NOOP    STOU    AUTH
2174          *   PASV    TYPE    APPE    ABOR    SITE    MKD     SIZE    CCC
2175          * 214 Direct comments to ftp-bugs@jsn.
2176          *
2177          * HELP SITE
2178          * 214-The following SITE commands are implemented.
2179          *   UMASK           HELP            GROUPS
2180          *   IDLE            ALIAS           CHECKMETHOD
2181          *   CHMOD           CDPATH          CHECKSUM
2182          * 214 Direct comments to ftp-bugs@jsn.
2183          */
2184         Vector<String> resp = getResponseStrings();
2185         if (resp.size() == 1) {
2186             // Single line response
2187             return resp.get(0).substring(4);
2188         }
2189         // on multiple lines answers, like the ones above, remove 1st and last
2190         // line, concat the the others.
2191         StringBuffer sb = new StringBuffer();
2192         for (int i = 1; i < resp.size() - 1; i++) {
2193             sb.append(resp.get(i).substring(3));
2194         }
2195         return sb.toString();
2196     }
2197 
2198     /**
2199      * Sends the SITE command to the server. This is used by the server
2200      * to provide services specific to his system that are essential
2201      * to file transfer.
2202      *
2203      * @param cmd the command to be sent.
2204      * @return <code>true</code> if the command was successful.
2205      * @throws IOException if an error occurred during transmission
2206      */
2207     public sun.net.ftp.FtpClient siteCmd(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
2208         issueCommandCheck("SITE " + cmd);
2209         return this;
2210     }
2211 }