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