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