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