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