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