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