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