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