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