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