1 /*
2  * Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package sun.net.ftp.impl;
26 
27 import java.net.*;
28 import java.io.*;
29 import java.security.AccessController;
30 import java.security.PrivilegedAction;
31 import java.security.PrivilegedExceptionAction;
32 import java.text.DateFormat;
33 import java.text.ParseException;
34 import java.text.SimpleDateFormat;
35 import java.util.ArrayList;
36 import java.util.Calendar;
37 import java.util.Date;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.TimeZone;
41 import java.util.Vector;
42 import java.util.regex.Matcher;
43 import java.util.regex.Pattern;
44 import javax.net.ssl.SSLSocket;
45 import javax.net.ssl.SSLSocketFactory;
46 import sun.misc.BASE64Decoder;
47 import sun.misc.BASE64Encoder;
48 import sun.net.ftp.*;
49 import sun.util.logging.PlatformLogger;
50 
51 
52 public 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 
AccessController.doPrivileged( new PrivilegedAction<Object>() { public Object run() { vals[0] = Integer.getInteger("sun.net.client.defaultReadTimeout", 0).intValue(); vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 0).intValue(); encs[0] = System.getProperty("file.encoding", "ISO8859_1"); return null; } })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      */
isASCIISuperset(String encoding)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          */
DefaultParser()205         private DefaultParser() {
206         }
207 
parseLine(String line)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 
parseLine(String line)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 
getTransferSize()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      */
getTransferName()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      */
readServerResponse()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. */
sendServer(String cmd)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. */
getResponseString()482     private String getResponseString() {
483         return serverResponse.elementAt(0);
484     }
485 
486     /** Returns all server response strings. */
getResponseStrings()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      */
readReply()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      */
issueCommand(String cmd)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      */
issueCommandCheck(String cmd)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      */
openPassiveDataConnection(String cmd)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      */
openDataConnection(String cmd)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 
createInputStream(InputStream in)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 
createOutputStream(OutputStream out)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      */
FtpClient()818     protected FtpClient() {
819     }
820 
821     /**
822      * Creates an instance of FtpClient. The client is not connected to any
823      * server yet.
824      *
825      */
create()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      */
enablePassiveMode(boolean passive)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      */
isPassiveModeEnabled()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      */
setConnectTimeout(int timeout)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      */
getConnectTimeout()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      */
setReadTimeout(int timeout)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      */
getReadTimeout()895     public int getReadTimeout() {
896         return readTimeout;
897     }
898 
setProxy(Proxy p)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      */
getProxy()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      */
tryConnect(InetSocketAddress dest, int timeout)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 
doConnect(InetSocketAddress dest, int timeout)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 
disconnect()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      */
isConnected()997     public boolean isConnected() {
998         return server != null;
999     }
1000 
getServerAddress()1001     public SocketAddress getServerAddress() {
1002         return server == null ? null : server.getRemoteSocketAddress();
1003     }
1004 
connect(SocketAddress dest)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      */
connect(SocketAddress dest, int timeout)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 
tryLogin(String user, char[] password)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      */
login(String user, char[] password)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      */
login(String user, char[] password, String account)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      */
close()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      */
isLoggedIn()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      */
changeDirectory(String remoteDirectory)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      */
changeToParentDirectory()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      */
getWorkingDirectory()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      */
setRestartOffset(long offset)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      */
getFile(String name, OutputStream local)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      */
getFileStream(String name)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      */
putFileStream(String name, boolean unique)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      */
putFile(String name, InputStream local, boolean unique)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      */
appendFile(String name, InputStream local)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      */
rename(String from, String to)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      */
deleteFile(String name)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      */
makeDirectory(String name)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      */
removeDirectory(String name)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      */
noop()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      */
getStatus(String name)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      */
getFeatures()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      */
abort()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      */
completePending()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      */
reInit()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      */
setType(TransferType type)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      */
list(String path)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      */
nameList(String path)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      */
getSize(String path)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      */
getLastModified(String path)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      */
setDirParser(FtpDirParser p)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 
FtpFileIterator(FtpDirParser p, BufferedReader in)1819         public FtpFileIterator(FtpDirParser p, BufferedReader in) {
1820             this.in = in;
1821             this.fparser = p;
1822             readNext();
1823         }
1824 
readNext()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 
hasNext()1847         public boolean hasNext() {
1848             return nextFile != null;
1849         }
1850 
next()1851         public FtpDirEntry next() {
1852             FtpDirEntry ret = nextFile;
1853             readNext();
1854             return ret;
1855         }
1856 
remove()1857         public void remove() {
1858             throw new UnsupportedOperationException("Not supported yet.");
1859         }
1860 
close()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      */
listFiles(String path)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 
sendSecurityData(byte[] buf)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 
getSecurityData()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      */
useKerberos()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      */
getWelcomeMsg()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      */
getLastReplyCode()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      */
getLastResponseString()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      */
getLastTransferSize()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      */
getLastFileName()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      */
startSecureSession()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      */
endSecureSession()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      */
allocate(long size)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      */
structureMount(String struct)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      */
getSystem()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      */
getHelp(String cmd)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      */
siteCmd(String cmd)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