FtpClient.java revision 11099:678faa7d1a6a
1/*
2 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
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 (StringIndexOutOfBoundsException 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        if (!isConnected()) {
521            throw new IllegalStateException("Not connected");
522        }
523        if (replyPending) {
524            try {
525                completePending();
526            } catch (sun.net.ftp.FtpProtocolException e) {
527                // ignore...
528            }
529        }
530        sendServer(cmd + "\r\n");
531        return readReply();
532    }
533
534    /**
535     * Send a command to the FTP server and check for success.
536     *
537     * @param cmd String containing the command
538     *
539     * @throws FtpProtocolException if an error occurred
540     */
541    private void issueCommandCheck(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
542        if (!issueCommand(cmd)) {
543            throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());
544        }
545    }
546    private static Pattern epsvPat = null;
547    private static Pattern pasvPat = null;
548
549    /**
550     * Opens a "PASSIVE" connection with the server and returns the connected
551     * <code>Socket</code>.
552     *
553     * @return the connected <code>Socket</code>
554     * @throws IOException if the connection was unsuccessful.
555     */
556    private Socket openPassiveDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
557        String serverAnswer;
558        int port;
559        InetSocketAddress dest = null;
560
561        /**
562         * Here is the idea:
563         *
564         * - First we want to try the new (and IPv6 compatible) EPSV command
565         *   But since we want to be nice with NAT software, we'll issue the
566         *   EPSV ALL command first.
567         *   EPSV is documented in RFC2428
568         * - If EPSV fails, then we fall back to the older, yet ok, PASV
569         * - If PASV fails as well, then we throw an exception and the calling
570         *   method will have to try the EPRT or PORT command
571         */
572        if (issueCommand("EPSV ALL")) {
573            // We can safely use EPSV commands
574            issueCommandCheck("EPSV");
575            serverAnswer = getResponseString();
576
577            // The response string from a EPSV command will contain the port number
578            // the format will be :
579            //  229 Entering Extended PASSIVE Mode (|||58210|)
580            //
581            // So we'll use the regular expresions package to parse the output.
582
583            if (epsvPat == null) {
584                epsvPat = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)");
585            }
586            Matcher m = epsvPat.matcher(serverAnswer);
587            if (!m.find()) {
588                throw new sun.net.ftp.FtpProtocolException("EPSV failed : " + serverAnswer);
589            }
590            // Yay! Let's extract the port number
591            String s = m.group(1);
592            port = Integer.parseInt(s);
593            InetAddress add = server.getInetAddress();
594            if (add != null) {
595                dest = new InetSocketAddress(add, port);
596            } else {
597                // This means we used an Unresolved address to connect in
598                // the first place. Most likely because the proxy is doing
599                // the name resolution for us, so let's keep using unresolved
600                // address.
601                dest = InetSocketAddress.createUnresolved(serverAddr.getHostName(), port);
602            }
603        } else {
604            // EPSV ALL failed, so Let's try the regular PASV cmd
605            issueCommandCheck("PASV");
606            serverAnswer = getResponseString();
607
608            // Let's parse the response String to get the IP & port to connect
609            // to. The String should be in the following format :
610            //
611            // 227 Entering PASSIVE Mode (A1,A2,A3,A4,p1,p2)
612            //
613            // Note that the two parenthesis are optional
614            //
615            // The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2
616            //
617            // The regular expression is a bit more complex this time, because
618            // the parenthesis are optionals and we have to use 3 groups.
619
620            if (pasvPat == null) {
621                pasvPat = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?");
622            }
623            Matcher m = pasvPat.matcher(serverAnswer);
624            if (!m.find()) {
625                throw new sun.net.ftp.FtpProtocolException("PASV failed : " + serverAnswer);
626            }
627            // Get port number out of group 2 & 3
628            port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8);
629            // IP address is simple
630            String s = m.group(1).replace(',', '.');
631            dest = new InetSocketAddress(s, port);
632        }
633        // Got everything, let's open the socket!
634        Socket s;
635        if (proxy != null) {
636            if (proxy.type() == Proxy.Type.SOCKS) {
637                s = AccessController.doPrivileged(
638                        new PrivilegedAction<Socket>() {
639
640                            public Socket run() {
641                                return new Socket(proxy);
642                            }
643                        });
644            } else {
645                s = new Socket(Proxy.NO_PROXY);
646            }
647        } else {
648            s = new Socket();
649        }
650
651        InetAddress serverAddress = AccessController.doPrivileged(
652                new PrivilegedAction<InetAddress>() {
653                    @Override
654                    public InetAddress run() {
655                        return server.getLocalAddress();
656                    }
657                });
658
659        // Bind the socket to the same address as the control channel. This
660        // is needed in case of multi-homed systems.
661        s.bind(new InetSocketAddress(serverAddress, 0));
662        if (connectTimeout >= 0) {
663            s.connect(dest, connectTimeout);
664        } else {
665            if (defaultConnectTimeout > 0) {
666                s.connect(dest, defaultConnectTimeout);
667            } else {
668                s.connect(dest);
669            }
670        }
671        if (readTimeout >= 0) {
672            s.setSoTimeout(readTimeout);
673        } else if (defaultSoTimeout > 0) {
674            s.setSoTimeout(defaultSoTimeout);
675        }
676        if (useCrypto) {
677            try {
678                s = sslFact.createSocket(s, dest.getHostName(), dest.getPort(), true);
679            } catch (Exception e) {
680                throw new sun.net.ftp.FtpProtocolException("Can't open secure data channel: " + e);
681            }
682        }
683        if (!issueCommand(cmd)) {
684            s.close();
685            if (getLastReplyCode() == FtpReplyCode.FILE_UNAVAILABLE) {
686                // Ensure backward compatibility
687                throw new FileNotFoundException(cmd);
688            }
689            throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode());
690        }
691        return s;
692    }
693
694    /**
695     * Opens a data connection with the server according to the set mode
696     * (ACTIVE or PASSIVE) then send the command passed as an argument.
697     *
698     * @param cmd the <code>String</code> containing the command to execute
699     * @return the connected <code>Socket</code>
700     * @throws IOException if the connection or command failed
701     */
702    private Socket openDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
703        Socket clientSocket;
704
705        if (passiveMode) {
706            try {
707                return openPassiveDataConnection(cmd);
708            } catch (sun.net.ftp.FtpProtocolException e) {
709                // If Passive mode failed, fall back on PORT
710                // Otherwise throw exception
711                String errmsg = e.getMessage();
712                if (!errmsg.startsWith("PASV") && !errmsg.startsWith("EPSV")) {
713                    throw e;
714                }
715            }
716        }
717        ServerSocket portSocket;
718        InetAddress myAddress;
719        String portCmd;
720
721        if (proxy != null && proxy.type() == Proxy.Type.SOCKS) {
722            // We're behind a firewall and the passive mode fail,
723            // since we can't accept a connection through SOCKS (yet)
724            // throw an exception
725            throw new sun.net.ftp.FtpProtocolException("Passive mode failed");
726        }
727        // Bind the ServerSocket to the same address as the control channel
728        // This is needed for multi-homed systems
729        portSocket = new ServerSocket(0, 1, server.getLocalAddress());
730        try {
731            myAddress = portSocket.getInetAddress();
732            if (myAddress.isAnyLocalAddress()) {
733                myAddress = server.getLocalAddress();
734            }
735            // Let's try the new, IPv6 compatible EPRT command
736            // See RFC2428 for specifics
737            // Some FTP servers (like the one on Solaris) are bugged, they
738            // will accept the EPRT command but then, the subsequent command
739            // (e.g. RETR) will fail, so we have to check BOTH results (the
740            // EPRT cmd then the actual command) to decide whether we should
741            // fall back on the older PORT command.
742            portCmd = "EPRT |" + ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" +
743                    myAddress.getHostAddress() + "|" + portSocket.getLocalPort() + "|";
744            if (!issueCommand(portCmd) || !issueCommand(cmd)) {
745                // The EPRT command failed, let's fall back to good old PORT
746                portCmd = "PORT ";
747                byte[] addr = myAddress.getAddress();
748
749                /* append host addr */
750                for (int i = 0; i < addr.length; i++) {
751                    portCmd = portCmd + (addr[i] & 0xFF) + ",";
752                }
753
754                /* append port number */
755                portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff);
756                issueCommandCheck(portCmd);
757                issueCommandCheck(cmd);
758            }
759            // Either the EPRT or the PORT command was successful
760            // Let's create the client socket
761            if (connectTimeout >= 0) {
762                portSocket.setSoTimeout(connectTimeout);
763            } else {
764                if (defaultConnectTimeout > 0) {
765                    portSocket.setSoTimeout(defaultConnectTimeout);
766                }
767            }
768            clientSocket = portSocket.accept();
769            if (readTimeout >= 0) {
770                clientSocket.setSoTimeout(readTimeout);
771            } else {
772                if (defaultSoTimeout > 0) {
773                    clientSocket.setSoTimeout(defaultSoTimeout);
774                }
775            }
776        } finally {
777            portSocket.close();
778        }
779        if (useCrypto) {
780            try {
781                clientSocket = sslFact.createSocket(clientSocket, serverAddr.getHostName(), serverAddr.getPort(), true);
782            } catch (Exception ex) {
783                throw new IOException(ex.getLocalizedMessage());
784            }
785        }
786        return clientSocket;
787    }
788
789    private InputStream createInputStream(InputStream in) {
790        if (type == TransferType.ASCII) {
791            return new sun.net.TelnetInputStream(in, false);
792        }
793        return in;
794    }
795
796    private OutputStream createOutputStream(OutputStream out) {
797        if (type == TransferType.ASCII) {
798            return new sun.net.TelnetOutputStream(out, false);
799        }
800        return out;
801    }
802
803    /**
804     * Creates an instance of FtpClient. The client is not connected to any
805     * server yet.
806     *
807     */
808    protected FtpClient() {
809    }
810
811    /**
812     * Creates an instance of FtpClient. The client is not connected to any
813     * server yet.
814     *
815     */
816    public static sun.net.ftp.FtpClient create() {
817        return new FtpClient();
818    }
819
820    /**
821     * Set the transfer mode to <I>passive</I>. In that mode, data connections
822     * are established by having the client connect to the server.
823     * This is the recommended default mode as it will work best through
824     * firewalls and NATs.
825     *
826     * @return This FtpClient
827     * @see #setActiveMode()
828     */
829    public sun.net.ftp.FtpClient enablePassiveMode(boolean passive) {
830
831        // Only passive mode used in JDK. See Bug 8010784.
832        // passiveMode = passive;
833        return this;
834    }
835
836    /**
837     * Gets the current transfer mode.
838     *
839     * @return the current <code>FtpTransferMode</code>
840     */
841    public boolean isPassiveModeEnabled() {
842        return passiveMode;
843    }
844
845    /**
846     * Sets the timeout value to use when connecting to the server,
847     *
848     * @param timeout the timeout value, in milliseconds, to use for the connect
849     *        operation. A value of zero or less, means use the default timeout.
850     *
851     * @return This FtpClient
852     */
853    public sun.net.ftp.FtpClient setConnectTimeout(int timeout) {
854        connectTimeout = timeout;
855        return this;
856    }
857
858    /**
859     * Returns the current connection timeout value.
860     *
861     * @return the value, in milliseconds, of the current connect timeout.
862     * @see #setConnectTimeout(int)
863     */
864    public int getConnectTimeout() {
865        return connectTimeout;
866    }
867
868    /**
869     * Sets the timeout value to use when reading from the server,
870     *
871     * @param timeout the timeout value, in milliseconds, to use for the read
872     *        operation. A value of zero or less, means use the default timeout.
873     * @return This FtpClient
874     */
875    public sun.net.ftp.FtpClient setReadTimeout(int timeout) {
876        readTimeout = timeout;
877        return this;
878    }
879
880    /**
881     * Returns the current read timeout value.
882     *
883     * @return the value, in milliseconds, of the current read timeout.
884     * @see #setReadTimeout(int)
885     */
886    public int getReadTimeout() {
887        return readTimeout;
888    }
889
890    public sun.net.ftp.FtpClient setProxy(Proxy p) {
891        proxy = p;
892        return this;
893    }
894
895    /**
896     * Get the proxy of this FtpClient
897     *
898     * @return the <code>Proxy</code>, this client is using, or <code>null</code>
899     *         if none is used.
900     * @see #setProxy(Proxy)
901     */
902    public Proxy getProxy() {
903        return proxy;
904    }
905
906    /**
907     * Connects to the specified destination.
908     *
909     * @param dest the <code>InetSocketAddress</code> to connect to.
910     * @throws IOException if the connection fails.
911     */
912    private void tryConnect(InetSocketAddress dest, int timeout) throws IOException {
913        if (isConnected()) {
914            disconnect();
915        }
916        server = doConnect(dest, timeout);
917        try {
918            out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
919                    true, encoding);
920        } catch (UnsupportedEncodingException e) {
921            throw new InternalError(encoding + "encoding not found", e);
922        }
923        in = new BufferedInputStream(server.getInputStream());
924    }
925
926    private Socket doConnect(InetSocketAddress dest, int timeout) throws IOException {
927        Socket s;
928        if (proxy != null) {
929            if (proxy.type() == Proxy.Type.SOCKS) {
930                s = AccessController.doPrivileged(
931                        new PrivilegedAction<Socket>() {
932
933                            public Socket run() {
934                                return new Socket(proxy);
935                            }
936                        });
937            } else {
938                s = new Socket(Proxy.NO_PROXY);
939            }
940        } else {
941            s = new Socket();
942        }
943        // Instance specific timeouts do have priority, that means
944        // connectTimeout & readTimeout (-1 means not set)
945        // Then global default timeouts
946        // Then no timeout.
947        if (timeout >= 0) {
948            s.connect(dest, timeout);
949        } else {
950            if (connectTimeout >= 0) {
951                s.connect(dest, connectTimeout);
952            } else {
953                if (defaultConnectTimeout > 0) {
954                    s.connect(dest, defaultConnectTimeout);
955                } else {
956                    s.connect(dest);
957                }
958            }
959        }
960        if (readTimeout >= 0) {
961            s.setSoTimeout(readTimeout);
962        } else if (defaultSoTimeout > 0) {
963            s.setSoTimeout(defaultSoTimeout);
964        }
965        return s;
966    }
967
968    private void disconnect() throws IOException {
969        if (isConnected()) {
970            server.close();
971        }
972        server = null;
973        in = null;
974        out = null;
975        lastTransSize = -1;
976        lastFileName = null;
977        restartOffset = 0;
978        welcomeMsg = null;
979        lastReplyCode = null;
980        serverResponse.setSize(0);
981    }
982
983    /**
984     * Tests whether this client is connected or not to a server.
985     *
986     * @return <code>true</code> if the client is connected.
987     */
988    public boolean isConnected() {
989        return server != null;
990    }
991
992    public SocketAddress getServerAddress() {
993        return server == null ? null : server.getRemoteSocketAddress();
994    }
995
996    public sun.net.ftp.FtpClient connect(SocketAddress dest) throws sun.net.ftp.FtpProtocolException, IOException {
997        return connect(dest, -1);
998    }
999
1000    /**
1001     * Connects the FtpClient to the specified destination.
1002     *
1003     * @param dest the address of the destination server
1004     * @throws IOException if connection failed.
1005     */
1006    public sun.net.ftp.FtpClient connect(SocketAddress dest, int timeout) throws sun.net.ftp.FtpProtocolException, IOException {
1007        if (!(dest instanceof InetSocketAddress)) {
1008            throw new IllegalArgumentException("Wrong address type");
1009        }
1010        serverAddr = (InetSocketAddress) dest;
1011        tryConnect(serverAddr, timeout);
1012        if (!readReply()) {
1013            throw new sun.net.ftp.FtpProtocolException("Welcome message: " +
1014                    getResponseString(), lastReplyCode);
1015        }
1016        welcomeMsg = getResponseString().substring(4);
1017        return this;
1018    }
1019
1020    private void tryLogin(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {
1021        issueCommandCheck("USER " + user);
1022
1023        /*
1024         * Checks for "331 User name okay, need password." answer
1025         */
1026        if (lastReplyCode == FtpReplyCode.NEED_PASSWORD) {
1027            if ((password != null) && (password.length > 0)) {
1028                issueCommandCheck("PASS " + String.valueOf(password));
1029            }
1030        }
1031    }
1032
1033    /**
1034     * Attempts to log on the server with the specified user name and password.
1035     *
1036     * @param user The user name
1037     * @param password The password for that user
1038     * @return <code>true</code> if the login was successful.
1039     * @throws IOException if an error occurred during the transmission
1040     */
1041    public sun.net.ftp.FtpClient login(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException {
1042        if (!isConnected()) {
1043            throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
1044        }
1045        if (user == null || user.length() == 0) {
1046            throw new IllegalArgumentException("User name can't be null or empty");
1047        }
1048        tryLogin(user, password);
1049
1050        // keep the welcome message around so we can
1051        // put it in the resulting HTML page.
1052        String l;
1053        StringBuilder sb = new StringBuilder();
1054        for (int i = 0; i < serverResponse.size(); i++) {
1055            l = serverResponse.elementAt(i);
1056            if (l != null) {
1057                if (l.length() >= 4 && l.startsWith("230")) {
1058                    // get rid of the "230-" prefix
1059                    l = l.substring(4);
1060                }
1061                sb.append(l);
1062            }
1063        }
1064        welcomeMsg = sb.toString();
1065        loggedIn = true;
1066        return this;
1067    }
1068
1069    /**
1070     * Attempts to log on the server with the specified user name, password and
1071     * account name.
1072     *
1073     * @param user The user name
1074     * @param password The password for that user.
1075     * @param account The account name for that user.
1076     * @return <code>true</code> if the login was successful.
1077     * @throws IOException if an error occurs during the transmission.
1078     */
1079    public sun.net.ftp.FtpClient login(String user, char[] password, String account) throws sun.net.ftp.FtpProtocolException, IOException {
1080
1081        if (!isConnected()) {
1082            throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
1083        }
1084        if (user == null || user.length() == 0) {
1085            throw new IllegalArgumentException("User name can't be null or empty");
1086        }
1087        tryLogin(user, password);
1088
1089        /*
1090         * Checks for "332 Need account for login." answer
1091         */
1092        if (lastReplyCode == FtpReplyCode.NEED_ACCOUNT) {
1093            issueCommandCheck("ACCT " + account);
1094        }
1095
1096        // keep the welcome message around so we can
1097        // put it in the resulting HTML page.
1098        StringBuilder sb = new StringBuilder();
1099        if (serverResponse != null) {
1100            for (String l : serverResponse) {
1101                if (l != null) {
1102                    if (l.length() >= 4 && l.startsWith("230")) {
1103                        // get rid of the "230-" prefix
1104                        l = l.substring(4);
1105                    }
1106                    sb.append(l);
1107                }
1108            }
1109        }
1110        welcomeMsg = sb.toString();
1111        loggedIn = true;
1112        return this;
1113    }
1114
1115    /**
1116     * Logs out the current user. This is in effect terminates the current
1117     * session and the connection to the server will be closed.
1118     *
1119     */
1120    public void close() throws IOException {
1121        if (isConnected()) {
1122            issueCommand("QUIT");
1123            loggedIn = false;
1124        }
1125        disconnect();
1126    }
1127
1128    /**
1129     * Checks whether the client is logged in to the server or not.
1130     *
1131     * @return <code>true</code> if the client has already completed a login.
1132     */
1133    public boolean isLoggedIn() {
1134        return loggedIn;
1135    }
1136
1137    /**
1138     * Changes to a specific directory on a remote FTP server
1139     *
1140     * @param remoteDirectory path of the directory to CD to.
1141     * @return <code>true</code> if the operation was successful.
1142     * @exception <code>FtpProtocolException</code>
1143     */
1144    public sun.net.ftp.FtpClient changeDirectory(String remoteDirectory) throws sun.net.ftp.FtpProtocolException, IOException {
1145        if (remoteDirectory == null || "".equals(remoteDirectory)) {
1146            throw new IllegalArgumentException("directory can't be null or empty");
1147        }
1148
1149        issueCommandCheck("CWD " + remoteDirectory);
1150        return this;
1151    }
1152
1153    /**
1154     * Changes to the parent directory, sending the CDUP command to the server.
1155     *
1156     * @return <code>true</code> if the command was successful.
1157     * @throws IOException
1158     */
1159    public sun.net.ftp.FtpClient changeToParentDirectory() throws sun.net.ftp.FtpProtocolException, IOException {
1160        issueCommandCheck("CDUP");
1161        return this;
1162    }
1163
1164    /**
1165     * Returns the server current working directory, or <code>null</code> if
1166     * the PWD command failed.
1167     *
1168     * @return a <code>String</code> containing the current working directory,
1169     *         or <code>null</code>
1170     * @throws IOException
1171     */
1172    public String getWorkingDirectory() throws sun.net.ftp.FtpProtocolException, IOException {
1173        issueCommandCheck("PWD");
1174        /*
1175         * answer will be of the following format :
1176         *
1177         * 257 "/" is current directory.
1178         */
1179        String answ = getResponseString();
1180        if (!answ.startsWith("257")) {
1181            return null;
1182        }
1183        return answ.substring(5, answ.lastIndexOf('"'));
1184    }
1185
1186    /**
1187     * Sets the restart offset to the specified value.  That value will be
1188     * sent through a <code>REST</code> command to server before a file
1189     * transfer and has the effect of resuming a file transfer from the
1190     * specified point. After a transfer the restart offset is set back to
1191     * zero.
1192     *
1193     * @param offset the offset in the remote file at which to start the next
1194     *        transfer. This must be a value greater than or equal to zero.
1195     * @throws IllegalArgumentException if the offset is negative.
1196     */
1197    public sun.net.ftp.FtpClient setRestartOffset(long offset) {
1198        if (offset < 0) {
1199            throw new IllegalArgumentException("offset can't be negative");
1200        }
1201        restartOffset = offset;
1202        return this;
1203    }
1204
1205    /**
1206     * Retrieves a file from the ftp server and writes it to the specified
1207     * <code>OutputStream</code>.
1208     * If the restart offset was set, then a <code>REST</code> command will be
1209     * sent before the RETR in order to restart the tranfer from the specified
1210     * offset.
1211     * The <code>OutputStream</code> is not closed by this method at the end
1212     * of the transfer.
1213     *
1214     * @param name a <code>String<code> containing the name of the file to
1215     *        retreive from the server.
1216     * @param local the <code>OutputStream</code> the file should be written to.
1217     * @throws IOException if the transfer fails.
1218     */
1219    public sun.net.ftp.FtpClient getFile(String name, OutputStream local) throws sun.net.ftp.FtpProtocolException, IOException {
1220        int mtu = 1500;
1221        if (restartOffset > 0) {
1222            Socket s;
1223            try {
1224                s = openDataConnection("REST " + restartOffset);
1225            } finally {
1226                restartOffset = 0;
1227            }
1228            issueCommandCheck("RETR " + name);
1229            getTransferSize();
1230            InputStream remote = createInputStream(s.getInputStream());
1231            byte[] buf = new byte[mtu * 10];
1232            int l;
1233            while ((l = remote.read(buf)) >= 0) {
1234                if (l > 0) {
1235                    local.write(buf, 0, l);
1236                }
1237            }
1238            remote.close();
1239        } else {
1240            Socket s = openDataConnection("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        }
1252        return completePending();
1253    }
1254
1255    /**
1256     * Retrieves a file from the ftp server, using the RETR command, and
1257     * returns the InputStream from* the established data connection.
1258     * {@link #completePending()} <b>has</b> to be called once the application
1259     * is done reading from the returned stream.
1260     *
1261     * @param name the name of the remote file
1262     * @return the {@link java.io.InputStream} from the data connection, or
1263     *         <code>null</code> if the command was unsuccessful.
1264     * @throws IOException if an error occurred during the transmission.
1265     */
1266    public InputStream getFileStream(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1267        Socket s;
1268        if (restartOffset > 0) {
1269            try {
1270                s = openDataConnection("REST " + restartOffset);
1271            } finally {
1272                restartOffset = 0;
1273            }
1274            if (s == null) {
1275                return null;
1276            }
1277            issueCommandCheck("RETR " + name);
1278            getTransferSize();
1279            return createInputStream(s.getInputStream());
1280        }
1281
1282        s = openDataConnection("RETR " + name);
1283        if (s == null) {
1284            return null;
1285        }
1286        getTransferSize();
1287        return createInputStream(s.getInputStream());
1288    }
1289
1290    /**
1291     * Transfers a file from the client to the server (aka a <I>put</I>)
1292     * by sending the STOR or STOU command, depending on the
1293     * <code>unique</code> argument, and returns the <code>OutputStream</code>
1294     * from the established data connection.
1295     * {@link #completePending()} <b>has</b> to be called once the application
1296     * is finished writing to the stream.
1297     *
1298     * A new file is created at the server site if the file specified does
1299     * not already exist.
1300     *
1301     * If <code>unique</code> is set to <code>true</code>, the resultant file
1302     * is to be created under a name unique to that directory, meaning
1303     * it will not overwrite an existing file, instead the server will
1304     * generate a new, unique, file name.
1305     * The name of the remote file can be retrieved, after completion of the
1306     * transfer, by calling {@link #getLastFileName()}.
1307     *
1308     * @param name the name of the remote file to write.
1309     * @param unique <code>true</code> if the remote files should be unique,
1310     *        in which case the STOU command will be used.
1311     * @return the {@link java.io.OutputStream} from the data connection or
1312     *         <code>null</code> if the command was unsuccessful.
1313     * @throws IOException if an error occurred during the transmission.
1314     */
1315    public OutputStream putFileStream(String name, boolean unique)
1316        throws sun.net.ftp.FtpProtocolException, IOException
1317    {
1318        String cmd = unique ? "STOU " : "STOR ";
1319        Socket s = openDataConnection(cmd + name);
1320        if (s == null) {
1321            return null;
1322        }
1323        boolean bm = (type == TransferType.BINARY);
1324        return new sun.net.TelnetOutputStream(s.getOutputStream(), bm);
1325    }
1326
1327    /**
1328     * Transfers a file from the client to the server (aka a <I>put</I>)
1329     * by sending the STOR command. The content of the <code>InputStream</code>
1330     * passed in argument is written into the remote file, overwriting any
1331     * existing data.
1332     *
1333     * A new file is created at the server site if the file specified does
1334     * not already exist.
1335     *
1336     * @param name the name of the remote file to write.
1337     * @param local the <code>InputStream</code> that points to the data to
1338     *        transfer.
1339     * @param unique <code>true</code> if the remote file should be unique
1340     *        (i.e. not already existing), <code>false</code> otherwise.
1341     * @return <code>true</code> if the transfer was successful.
1342     * @throws IOException if an error occurred during the transmission.
1343     * @see #getLastFileName()
1344     */
1345    public sun.net.ftp.FtpClient putFile(String name, InputStream local, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException {
1346        String cmd = unique ? "STOU " : "STOR ";
1347        int mtu = 1500;
1348        if (type == TransferType.BINARY) {
1349            Socket s = openDataConnection(cmd + name);
1350            OutputStream remote = createOutputStream(s.getOutputStream());
1351            byte[] buf = new byte[mtu * 10];
1352            int l;
1353            while ((l = local.read(buf)) >= 0) {
1354                if (l > 0) {
1355                    remote.write(buf, 0, l);
1356                }
1357            }
1358            remote.close();
1359        }
1360        return completePending();
1361    }
1362
1363    /**
1364     * Sends the APPE command to the server in order to transfer a data stream
1365     * passed in argument and append it to the content of the specified remote
1366     * file.
1367     *
1368     * @param name A <code>String</code> containing the name of the remote file
1369     *        to append to.
1370     * @param local The <code>InputStream</code> providing access to the data
1371     *        to be appended.
1372     * @return <code>true</code> if the transfer was successful.
1373     * @throws IOException if an error occurred during the transmission.
1374     */
1375    public sun.net.ftp.FtpClient appendFile(String name, InputStream local) throws sun.net.ftp.FtpProtocolException, IOException {
1376        int mtu = 1500;
1377        Socket s = openDataConnection("APPE " + name);
1378        OutputStream remote = createOutputStream(s.getOutputStream());
1379        byte[] buf = new byte[mtu * 10];
1380        int l;
1381        while ((l = local.read(buf)) >= 0) {
1382            if (l > 0) {
1383                remote.write(buf, 0, l);
1384            }
1385        }
1386        remote.close();
1387        return completePending();
1388    }
1389
1390    /**
1391     * Renames a file on the server.
1392     *
1393     * @param from the name of the file being renamed
1394     * @param to the new name for the file
1395     * @throws IOException if the command fails
1396     */
1397    public sun.net.ftp.FtpClient rename(String from, String to) throws sun.net.ftp.FtpProtocolException, IOException {
1398        issueCommandCheck("RNFR " + from);
1399        issueCommandCheck("RNTO " + to);
1400        return this;
1401    }
1402
1403    /**
1404     * Deletes a file on the server.
1405     *
1406     * @param name a <code>String</code> containing the name of the file
1407     *        to delete.
1408     * @return <code>true</code> if the command was successful
1409     * @throws IOException if an error occurred during the exchange
1410     */
1411    public sun.net.ftp.FtpClient deleteFile(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1412        issueCommandCheck("DELE " + name);
1413        return this;
1414    }
1415
1416    /**
1417     * Creates a new directory on the server.
1418     *
1419     * @param name a <code>String</code> containing the name of the directory
1420     *        to create.
1421     * @return <code>true</code> if the operation was successful.
1422     * @throws IOException if an error occurred during the exchange
1423     */
1424    public sun.net.ftp.FtpClient makeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1425        issueCommandCheck("MKD " + name);
1426        return this;
1427    }
1428
1429    /**
1430     * Removes a directory on the server.
1431     *
1432     * @param name a <code>String</code> containing the name of the directory
1433     *        to remove.
1434     *
1435     * @return <code>true</code> if the operation was successful.
1436     * @throws IOException if an error occurred during the exchange.
1437     */
1438    public sun.net.ftp.FtpClient removeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1439        issueCommandCheck("RMD " + name);
1440        return this;
1441    }
1442
1443    /**
1444     * Sends a No-operation command. It's useful for testing the connection
1445     * status or as a <I>keep alive</I> mechanism.
1446     *
1447     * @throws FtpProtocolException if the command fails
1448     */
1449    public sun.net.ftp.FtpClient noop() throws sun.net.ftp.FtpProtocolException, IOException {
1450        issueCommandCheck("NOOP");
1451        return this;
1452    }
1453
1454    /**
1455     * Sends the STAT command to the server.
1456     * This can be used while a data connection is open to get a status
1457     * on the current transfer, in that case the parameter should be
1458     * <code>null</code>.
1459     * If used between file transfers, it may have a pathname as argument
1460     * in which case it will work as the LIST command except no data
1461     * connection will be created.
1462     *
1463     * @param name an optional <code>String</code> containing the pathname
1464     *        the STAT command should apply to.
1465     * @return the response from the server or <code>null</code> if the
1466     *         command failed.
1467     * @throws IOException if an error occurred during the transmission.
1468     */
1469    public String getStatus(String name) throws sun.net.ftp.FtpProtocolException, IOException {
1470        issueCommandCheck((name == null ? "STAT" : "STAT " + name));
1471        /*
1472         * A typical response will be:
1473         *  213-status of t32.gif:
1474         * -rw-r--r--   1 jcc      staff     247445 Feb 17  1998 t32.gif
1475         * 213 End of Status
1476         *
1477         * or
1478         *
1479         * 211-jsn FTP server status:
1480         *     Version wu-2.6.2+Sun
1481         *     Connected to localhost (::1)
1482         *     Logged in as jccollet
1483         *     TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream
1484         *      No data connection
1485         *     0 data bytes received in 0 files
1486         *     0 data bytes transmitted in 0 files
1487         *     0 data bytes total in 0 files
1488         *     53 traffic bytes received in 0 transfers
1489         *     485 traffic bytes transmitted in 0 transfers
1490         *     587 traffic bytes total in 0 transfers
1491         * 211 End of status
1492         *
1493         * So we need to remove the 1st and last line
1494         */
1495        Vector<String> resp = getResponseStrings();
1496        StringBuilder sb = new StringBuilder();
1497        for (int i = 1; i < resp.size() - 1; i++) {
1498            sb.append(resp.get(i));
1499        }
1500        return sb.toString();
1501    }
1502
1503    /**
1504     * Sends the FEAT command to the server and returns the list of supported
1505     * features in the form of strings.
1506     *
1507     * The features are the supported commands, like AUTH TLS, PROT or PASV.
1508     * See the RFCs for a complete list.
1509     *
1510     * Note that not all FTP servers support that command, in which case
1511     * the method will return <code>null</code>
1512     *
1513     * @return a <code>List</code> of <code>Strings</code> describing the
1514     *         supported additional features, or <code>null</code>
1515     *         if the command is not supported.
1516     * @throws IOException if an error occurs during the transmission.
1517     */
1518    public List<String> getFeatures() throws sun.net.ftp.FtpProtocolException, IOException {
1519        /*
1520         * The FEAT command, when implemented will return something like:
1521         *
1522         * 211-Features:
1523         *   AUTH TLS
1524         *   PBSZ
1525         *   PROT
1526         *   EPSV
1527         *   EPRT
1528         *   PASV
1529         *   REST STREAM
1530         *  211 END
1531         */
1532        ArrayList<String> features = new ArrayList<String>();
1533        issueCommandCheck("FEAT");
1534        Vector<String> resp = getResponseStrings();
1535        // Note that we start at index 1 to skip the 1st line (211-...)
1536        // and we stop before the last line.
1537        for (int i = 1; i < resp.size() - 1; i++) {
1538            String s = resp.get(i);
1539            // Get rid of leading space and trailing newline
1540            features.add(s.substring(1, s.length() - 1));
1541        }
1542        return features;
1543    }
1544
1545    /**
1546     * sends the ABOR command to the server.
1547     * It tells the server to stop the previous command or transfer.
1548     *
1549     * @return <code>true</code> if the command was successful.
1550     * @throws IOException if an error occurred during the transmission.
1551     */
1552    public sun.net.ftp.FtpClient abort() throws sun.net.ftp.FtpProtocolException, IOException {
1553        issueCommandCheck("ABOR");
1554        // TODO: Must check the ReplyCode:
1555        /*
1556         * From the RFC:
1557         * There are two cases for the server upon receipt of this
1558         * command: (1) the FTP service command was already completed,
1559         * or (2) the FTP service command is still in progress.
1560         * In the first case, the server closes the data connection
1561         * (if it is open) and responds with a 226 reply, indicating
1562         * that the abort command was successfully processed.
1563         * In the second case, the server aborts the FTP service in
1564         * progress and closes the data connection, returning a 426
1565         * reply to indicate that the service request terminated
1566         * abnormally.  The server then sends a 226 reply,
1567         * indicating that the abort command was successfully
1568         * processed.
1569         */
1570
1571
1572        return this;
1573    }
1574
1575    /**
1576     * Some methods do not wait until completion before returning, so this
1577     * method can be called to wait until completion. This is typically the case
1578     * with commands that trigger a transfer like {@link #getFileStream(String)}.
1579     * So this method should be called before accessing information related to
1580     * such a command.
1581     * <p>This method will actually block reading on the command channel for a
1582     * notification from the server that the command is finished. Such a
1583     * notification often carries extra information concerning the completion
1584     * of the pending action (e.g. number of bytes transfered).</p>
1585     * <p>Note that this will return true immediately if no command or action
1586     * is pending</p>
1587     * <p>It should be also noted that most methods issuing commands to the ftp
1588     * server will call this method if a previous command is pending.
1589     * <p>Example of use:
1590     * <pre>
1591     * InputStream in = cl.getFileStream("file");
1592     * ...
1593     * cl.completePending();
1594     * long size = cl.getLastTransferSize();
1595     * </pre>
1596     * On the other hand, it's not necessary in a case like:
1597     * <pre>
1598     * InputStream in = cl.getFileStream("file");
1599     * // read content
1600     * ...
1601     * cl.logout();
1602     * </pre>
1603     * <p>Since {@link #logout()} will call completePending() if necessary.</p>
1604     * @return <code>true</code> if the completion was successful or if no
1605     *         action was pending.
1606     * @throws IOException
1607     */
1608    public sun.net.ftp.FtpClient completePending() throws sun.net.ftp.FtpProtocolException, IOException {
1609        while (replyPending) {
1610            replyPending = false;
1611            if (!readReply()) {
1612                throw new sun.net.ftp.FtpProtocolException(getLastResponseString(), lastReplyCode);
1613            }
1614        }
1615        return this;
1616    }
1617
1618    /**
1619     * Reinitializes the USER parameters on the FTP server
1620     *
1621     * @throws FtpProtocolException if the command fails
1622     */
1623    public sun.net.ftp.FtpClient reInit() throws sun.net.ftp.FtpProtocolException, IOException {
1624        issueCommandCheck("REIN");
1625        loggedIn = false;
1626        if (useCrypto) {
1627            if (server instanceof SSLSocket) {
1628                javax.net.ssl.SSLSession session = ((SSLSocket) server).getSession();
1629                session.invalidate();
1630                // Restore previous socket and streams
1631                server = oldSocket;
1632                oldSocket = null;
1633                try {
1634                    out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
1635                            true, encoding);
1636                } catch (UnsupportedEncodingException e) {
1637                    throw new InternalError(encoding + "encoding not found", e);
1638                }
1639                in = new BufferedInputStream(server.getInputStream());
1640            }
1641        }
1642        useCrypto = false;
1643        return this;
1644    }
1645
1646    /**
1647     * Changes the transfer type (binary, ascii, ebcdic) and issue the
1648     * proper command (e.g. TYPE A) to the server.
1649     *
1650     * @param type the <code>FtpTransferType</code> to use.
1651     * @return This FtpClient
1652     * @throws IOException if an error occurs during transmission.
1653     */
1654    public sun.net.ftp.FtpClient setType(TransferType type) throws sun.net.ftp.FtpProtocolException, IOException {
1655        String cmd = "NOOP";
1656
1657        this.type = type;
1658        if (type == TransferType.ASCII) {
1659            cmd = "TYPE A";
1660        }
1661        if (type == TransferType.BINARY) {
1662            cmd = "TYPE I";
1663        }
1664        if (type == TransferType.EBCDIC) {
1665            cmd = "TYPE E";
1666        }
1667        issueCommandCheck(cmd);
1668        return this;
1669    }
1670
1671    /**
1672     * Issues a LIST command to the server to get the current directory
1673     * listing, and returns the InputStream from the data connection.
1674     * {@link #completePending()} <b>has</b> to be called once the application
1675     * is finished writing to the stream.
1676     *
1677     * @param path the pathname of the directory to list, or <code>null</code>
1678     *        for the current working directory.
1679     * @return the <code>InputStream</code> from the resulting data connection
1680     * @throws IOException if an error occurs during the transmission.
1681     * @see #changeDirectory(String)
1682     * @see #listFiles(String)
1683     */
1684    public InputStream list(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1685        Socket s;
1686        s = openDataConnection(path == null ? "LIST" : "LIST " + path);
1687        if (s != null) {
1688            return createInputStream(s.getInputStream());
1689        }
1690        return null;
1691    }
1692
1693    /**
1694     * Issues a NLST path command to server to get the specified directory
1695     * content. It differs from {@link #list(String)} method by the fact that
1696     * it will only list the file names which would make the parsing of the
1697     * somewhat easier.
1698     *
1699     * {@link #completePending()} <b>has</b> to be called once the application
1700     * is finished writing to the stream.
1701     *
1702     * @param path a <code>String</code> containing the pathname of the
1703     *        directory to list or <code>null</code> for the current working
1704     *        directory.
1705     * @return the <code>InputStream</code> from the resulting data connection
1706     * @throws IOException if an error occurs during the transmission.
1707     */
1708    public InputStream nameList(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1709        Socket s;
1710        s = openDataConnection("NLST " + path);
1711        if (s != null) {
1712            return createInputStream(s.getInputStream());
1713        }
1714        return null;
1715    }
1716
1717    /**
1718     * Issues the SIZE [path] command to the server to get the size of a
1719     * specific file on the server.
1720     * Note that this command may not be supported by the server. In which
1721     * case -1 will be returned.
1722     *
1723     * @param path a <code>String</code> containing the pathname of the
1724     *        file.
1725     * @return a <code>long</code> containing the size of the file or -1 if
1726     *         the server returned an error, which can be checked with
1727     *         {@link #getLastReplyCode()}.
1728     * @throws IOException if an error occurs during the transmission.
1729     */
1730    public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1731        if (path == null || path.length() == 0) {
1732            throw new IllegalArgumentException("path can't be null or empty");
1733        }
1734        issueCommandCheck("SIZE " + path);
1735        if (lastReplyCode == FtpReplyCode.FILE_STATUS) {
1736            String s = getResponseString();
1737            s = s.substring(4, s.length() - 1);
1738            return Long.parseLong(s);
1739        }
1740        return -1;
1741    }
1742    private static String[] MDTMformats = {
1743        "yyyyMMddHHmmss.SSS",
1744        "yyyyMMddHHmmss"
1745    };
1746    private static SimpleDateFormat[] dateFormats = new SimpleDateFormat[MDTMformats.length];
1747
1748    static {
1749        for (int i = 0; i < MDTMformats.length; i++) {
1750            dateFormats[i] = new SimpleDateFormat(MDTMformats[i]);
1751            dateFormats[i].setTimeZone(TimeZone.getTimeZone("GMT"));
1752        }
1753    }
1754
1755    /**
1756     * Issues the MDTM [path] command to the server to get the modification
1757     * time of a specific file on the server.
1758     * Note that this command may not be supported by the server, in which
1759     * case <code>null</code> will be returned.
1760     *
1761     * @param path a <code>String</code> containing the pathname of the file.
1762     * @return a <code>Date</code> representing the last modification time
1763     *         or <code>null</code> if the server returned an error, which
1764     *         can be checked with {@link #getLastReplyCode()}.
1765     * @throws IOException if an error occurs during the transmission.
1766     */
1767    public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1768        issueCommandCheck("MDTM " + path);
1769        if (lastReplyCode == FtpReplyCode.FILE_STATUS) {
1770            String s = getResponseString().substring(4);
1771            Date d = null;
1772            for (SimpleDateFormat dateFormat : dateFormats) {
1773                try {
1774                    d = dateFormat.parse(s);
1775                } catch (ParseException ex) {
1776                }
1777                if (d != null) {
1778                    return d;
1779                }
1780            }
1781        }
1782        return null;
1783    }
1784
1785    /**
1786     * Sets the parser used to handle the directory output to the specified
1787     * one. By default the parser is set to one that can handle most FTP
1788     * servers output (Unix base mostly). However it may be necessary for
1789     * and application to provide its own parser due to some uncommon
1790     * output format.
1791     *
1792     * @param p The <code>FtpDirParser</code> to use.
1793     * @see #listFiles(String)
1794     */
1795    public sun.net.ftp.FtpClient setDirParser(FtpDirParser p) {
1796        parser = p;
1797        return this;
1798    }
1799
1800    private class FtpFileIterator implements Iterator<FtpDirEntry>, Closeable {
1801
1802        private BufferedReader in = null;
1803        private FtpDirEntry nextFile = null;
1804        private FtpDirParser fparser = null;
1805        private boolean eof = false;
1806
1807        public FtpFileIterator(FtpDirParser p, BufferedReader in) {
1808            this.in = in;
1809            this.fparser = p;
1810            readNext();
1811        }
1812
1813        private void readNext() {
1814            nextFile = null;
1815            if (eof) {
1816                return;
1817            }
1818            String line = null;
1819            try {
1820                do {
1821                    line = in.readLine();
1822                    if (line != null) {
1823                        nextFile = fparser.parseLine(line);
1824                        if (nextFile != null) {
1825                            return;
1826                        }
1827                    }
1828                } while (line != null);
1829                in.close();
1830            } catch (IOException iOException) {
1831            }
1832            eof = true;
1833        }
1834
1835        public boolean hasNext() {
1836            return nextFile != null;
1837        }
1838
1839        public FtpDirEntry next() {
1840            FtpDirEntry ret = nextFile;
1841            readNext();
1842            return ret;
1843        }
1844
1845        public void remove() {
1846            throw new UnsupportedOperationException("Not supported yet.");
1847        }
1848
1849        public void close() throws IOException {
1850            if (in != null && !eof) {
1851                in.close();
1852            }
1853            eof = true;
1854            nextFile = null;
1855        }
1856    }
1857
1858    /**
1859     * Issues a MLSD command to the server to get the specified directory
1860     * listing and applies the current parser to create an Iterator of
1861     * {@link java.net.ftp.FtpDirEntry}. Note that the Iterator returned is also a
1862     * {@link java.io.Closeable}.
1863     * If the server doesn't support the MLSD command, the LIST command is used
1864     * instead.
1865     *
1866     * {@link #completePending()} <b>has</b> to be called once the application
1867     * is finished iterating through the files.
1868     *
1869     * @param path the pathname of the directory to list or <code>null</code>
1870     *        for the current working directoty.
1871     * @return a <code>Iterator</code> of files or <code>null</code> if the
1872     *         command failed.
1873     * @throws IOException if an error occurred during the transmission
1874     * @see #setDirParser(FtpDirParser)
1875     * @see #changeDirectory(String)
1876     */
1877    public Iterator<FtpDirEntry> listFiles(String path) throws sun.net.ftp.FtpProtocolException, IOException {
1878        Socket s = null;
1879        BufferedReader sin = null;
1880        try {
1881            s = openDataConnection(path == null ? "MLSD" : "MLSD " + path);
1882        } catch (sun.net.ftp.FtpProtocolException FtpException) {
1883            // The server doesn't understand new MLSD command, ignore and fall
1884            // back to LIST
1885        }
1886
1887        if (s != null) {
1888            sin = new BufferedReader(new InputStreamReader(s.getInputStream()));
1889            return new FtpFileIterator(mlsxParser, sin);
1890        } else {
1891            s = openDataConnection(path == null ? "LIST" : "LIST " + path);
1892            if (s != null) {
1893                sin = new BufferedReader(new InputStreamReader(s.getInputStream()));
1894                return new FtpFileIterator(parser, sin);
1895            }
1896        }
1897        return null;
1898    }
1899
1900    private boolean sendSecurityData(byte[] buf) throws IOException {
1901        String s = Base64.getMimeEncoder().encodeToString(buf);
1902        return issueCommand("ADAT " + s);
1903    }
1904
1905    private byte[] getSecurityData() {
1906        String s = getLastResponseString();
1907        if (s.substring(4, 9).equalsIgnoreCase("ADAT=")) {
1908            // Need to get rid of the leading '315 ADAT='
1909            // and the trailing newline
1910            return Base64.getMimeDecoder().decode(s.substring(9, s.length() - 1));
1911        }
1912        return null;
1913    }
1914
1915    /**
1916     * Attempts to use Kerberos GSSAPI as an authentication mechanism with the
1917     * ftp server. This will issue an <code>AUTH GSSAPI</code> command, and if
1918     * it is accepted by the server, will followup with <code>ADAT</code>
1919     * command to exchange the various tokens until authentification is
1920     * successful. This conforms to Appendix I of RFC 2228.
1921     *
1922     * @return <code>true</code> if authentication was successful.
1923     * @throws IOException if an error occurs during the transmission.
1924     */
1925    public sun.net.ftp.FtpClient useKerberos() throws sun.net.ftp.FtpProtocolException, IOException {
1926        /*
1927         * Comment out for the moment since it's not in use and would create
1928         * needless cross-package links.
1929         *
1930        issueCommandCheck("AUTH GSSAPI");
1931        if (lastReplyCode != FtpReplyCode.NEED_ADAT)
1932        throw new sun.net.ftp.FtpProtocolException("Unexpected reply from server");
1933        try {
1934        GSSManager manager = GSSManager.getInstance();
1935        GSSName name = manager.createName("SERVICE:ftp@"+
1936        serverAddr.getHostName(), null);
1937        GSSContext context = manager.createContext(name, null, null,
1938        GSSContext.DEFAULT_LIFETIME);
1939        context.requestMutualAuth(true);
1940        context.requestReplayDet(true);
1941        context.requestSequenceDet(true);
1942        context.requestCredDeleg(true);
1943        byte []inToken = new byte[0];
1944        while (!context.isEstablished()) {
1945        byte[] outToken
1946        = context.initSecContext(inToken, 0, inToken.length);
1947        // send the output token if generated
1948        if (outToken != null) {
1949        if (sendSecurityData(outToken)) {
1950        inToken = getSecurityData();
1951        }
1952        }
1953        }
1954        loggedIn = true;
1955        } catch (GSSException e) {
1956
1957        }
1958         */
1959        return this;
1960    }
1961
1962    /**
1963     * Returns the Welcome string the server sent during initial connection.
1964     *
1965     * @return a <code>String</code> containing the message the server
1966     *         returned during connection or <code>null</code>.
1967     */
1968    public String getWelcomeMsg() {
1969        return welcomeMsg;
1970    }
1971
1972    /**
1973     * Returns the last reply code sent by the server.
1974     *
1975     * @return the lastReplyCode
1976     */
1977    public FtpReplyCode getLastReplyCode() {
1978        return lastReplyCode;
1979    }
1980
1981    /**
1982     * Returns the last response string sent by the server.
1983     *
1984     * @return the message string, which can be quite long, last returned
1985     *         by the server.
1986     */
1987    public String getLastResponseString() {
1988        StringBuilder sb = new StringBuilder();
1989        if (serverResponse != null) {
1990            for (String l : serverResponse) {
1991                if (l != null) {
1992                    sb.append(l);
1993                }
1994            }
1995        }
1996        return sb.toString();
1997    }
1998
1999    /**
2000     * Returns, when available, the size of the latest started transfer.
2001     * This is retreived by parsing the response string received as an initial
2002     * response to a RETR or similar request.
2003     *
2004     * @return the size of the latest transfer or -1 if either there was no
2005     *         transfer or the information was unavailable.
2006     */
2007    public long getLastTransferSize() {
2008        return lastTransSize;
2009    }
2010
2011    /**
2012     * Returns, when available, the remote name of the last transfered file.
2013     * This is mainly useful for "put" operation when the unique flag was
2014     * set since it allows to recover the unique file name created on the
2015     * server which may be different from the one submitted with the command.
2016     *
2017     * @return the name the latest transfered file remote name, or
2018     *         <code>null</code> if that information is unavailable.
2019     */
2020    public String getLastFileName() {
2021        return lastFileName;
2022    }
2023
2024    /**
2025     * Attempts to switch to a secure, encrypted connection. This is done by
2026     * sending the "AUTH TLS" command.
2027     * <p>See <a href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</a></p>
2028     * If successful this will establish a secure command channel with the
2029     * server, it will also make it so that all other transfers (e.g. a RETR
2030     * command) will be done over an encrypted channel as well unless a
2031     * {@link #reInit()} command or a {@link #endSecureSession()} command is issued.
2032     *
2033     * @return <code>true</code> if the operation was successful.
2034     * @throws IOException if an error occurred during the transmission.
2035     * @see #endSecureSession()
2036     */
2037    public sun.net.ftp.FtpClient startSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {
2038        if (!isConnected()) {
2039            throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE);
2040        }
2041        if (sslFact == null) {
2042            try {
2043                sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault();
2044            } catch (Exception e) {
2045                throw new IOException(e.getLocalizedMessage());
2046            }
2047        }
2048        issueCommandCheck("AUTH TLS");
2049        Socket s = null;
2050        try {
2051            s = sslFact.createSocket(server, serverAddr.getHostName(), serverAddr.getPort(), true);
2052        } catch (javax.net.ssl.SSLException ssle) {
2053            try {
2054                disconnect();
2055            } catch (Exception e) {
2056            }
2057            throw ssle;
2058        }
2059        // Remember underlying socket so we can restore it later
2060        oldSocket = server;
2061        server = s;
2062        try {
2063            out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
2064                    true, encoding);
2065        } catch (UnsupportedEncodingException e) {
2066            throw new InternalError(encoding + "encoding not found", e);
2067        }
2068        in = new BufferedInputStream(server.getInputStream());
2069
2070        issueCommandCheck("PBSZ 0");
2071        issueCommandCheck("PROT P");
2072        useCrypto = true;
2073        return this;
2074    }
2075
2076    /**
2077     * Sends a <code>CCC</code> command followed by a <code>PROT C</code>
2078     * command to the server terminating an encrypted session and reverting
2079     * back to a non crypted transmission.
2080     *
2081     * @return <code>true</code> if the operation was successful.
2082     * @throws IOException if an error occurred during transmission.
2083     * @see #startSecureSession()
2084     */
2085    public sun.net.ftp.FtpClient endSecureSession() throws sun.net.ftp.FtpProtocolException, IOException {
2086        if (!useCrypto) {
2087            return this;
2088        }
2089
2090        issueCommandCheck("CCC");
2091        issueCommandCheck("PROT C");
2092        useCrypto = false;
2093        // Restore previous socket and streams
2094        server = oldSocket;
2095        oldSocket = null;
2096        try {
2097            out = new PrintStream(new BufferedOutputStream(server.getOutputStream()),
2098                    true, encoding);
2099        } catch (UnsupportedEncodingException e) {
2100            throw new InternalError(encoding + "encoding not found", e);
2101        }
2102        in = new BufferedInputStream(server.getInputStream());
2103
2104        return this;
2105    }
2106
2107    /**
2108     * Sends the "Allocate" (ALLO) command to the server telling it to
2109     * pre-allocate the specified number of bytes for the next transfer.
2110     *
2111     * @param size The number of bytes to allocate.
2112     * @return <code>true</code> if the operation was successful.
2113     * @throws IOException if an error occurred during the transmission.
2114     */
2115    public sun.net.ftp.FtpClient allocate(long size) throws sun.net.ftp.FtpProtocolException, IOException {
2116        issueCommandCheck("ALLO " + size);
2117        return this;
2118    }
2119
2120    /**
2121     * Sends the "Structure Mount" (SMNT) command to the server. This let the
2122     * user mount a different file system data structure without altering his
2123     * login or accounting information.
2124     *
2125     * @param struct a <code>String</code> containing the name of the
2126     *        structure to mount.
2127     * @return <code>true</code> if the operation was successful.
2128     * @throws IOException if an error occurred during the transmission.
2129     */
2130    public sun.net.ftp.FtpClient structureMount(String struct) throws sun.net.ftp.FtpProtocolException, IOException {
2131        issueCommandCheck("SMNT " + struct);
2132        return this;
2133    }
2134
2135    /**
2136     * Sends a SYST (System) command to the server and returns the String
2137     * sent back by the server describing the operating system at the
2138     * server.
2139     *
2140     * @return a <code>String</code> describing the OS, or <code>null</code>
2141     *         if the operation was not successful.
2142     * @throws IOException if an error occurred during the transmission.
2143     */
2144    public String getSystem() throws sun.net.ftp.FtpProtocolException, IOException {
2145        issueCommandCheck("SYST");
2146        /*
2147         * 215 UNIX Type: L8 Version: SUNOS
2148         */
2149        String resp = getResponseString();
2150        // Get rid of the leading code and blank
2151        return resp.substring(4);
2152    }
2153
2154    /**
2155     * Sends the HELP command to the server, with an optional command, like
2156     * SITE, and returns the text sent back by the server.
2157     *
2158     * @param cmd the command for which the help is requested or
2159     *        <code>null</code> for the general help
2160     * @return a <code>String</code> containing the text sent back by the
2161     *         server, or <code>null</code> if the command failed.
2162     * @throws IOException if an error occurred during transmission
2163     */
2164    public String getHelp(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
2165        issueCommandCheck("HELP " + cmd);
2166        /**
2167         *
2168         * HELP
2169         * 214-The following commands are implemented.
2170         *   USER    EPRT    STRU    ALLO    DELE    SYST    RMD     MDTM    ADAT
2171         *   PASS    EPSV    MODE    REST    CWD     STAT    PWD     PROT
2172         *   QUIT    LPRT    RETR    RNFR    LIST    HELP    CDUP    PBSZ
2173         *   PORT    LPSV    STOR    RNTO    NLST    NOOP    STOU    AUTH
2174         *   PASV    TYPE    APPE    ABOR    SITE    MKD     SIZE    CCC
2175         * 214 Direct comments to ftp-bugs@jsn.
2176         *
2177         * HELP SITE
2178         * 214-The following SITE commands are implemented.
2179         *   UMASK           HELP            GROUPS
2180         *   IDLE            ALIAS           CHECKMETHOD
2181         *   CHMOD           CDPATH          CHECKSUM
2182         * 214 Direct comments to ftp-bugs@jsn.
2183         */
2184        Vector<String> resp = getResponseStrings();
2185        if (resp.size() == 1) {
2186            // Single line response
2187            return resp.get(0).substring(4);
2188        }
2189        // on multiple lines answers, like the ones above, remove 1st and last
2190        // line, concat the others.
2191        StringBuilder sb = new StringBuilder();
2192        for (int i = 1; i < resp.size() - 1; i++) {
2193            sb.append(resp.get(i).substring(3));
2194        }
2195        return sb.toString();
2196    }
2197
2198    /**
2199     * Sends the SITE command to the server. This is used by the server
2200     * to provide services specific to his system that are essential
2201     * to file transfer.
2202     *
2203     * @param cmd the command to be sent.
2204     * @return <code>true</code> if the command was successful.
2205     * @throws IOException if an error occurred during transmission
2206     */
2207    public sun.net.ftp.FtpClient siteCmd(String cmd) throws sun.net.ftp.FtpProtocolException, IOException {
2208        issueCommandCheck("SITE " + cmd);
2209        return this;
2210    }
2211}
2212