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