View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.examples.ftp;
19  
20  import java.io.FileInputStream;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.net.InetAddress;
26  import java.net.UnknownHostException;
27  import java.nio.charset.Charset;
28  import java.time.Duration;
29  import java.util.Arrays;
30  
31  import org.apache.commons.net.examples.PrintCommandListeners;
32  import org.apache.commons.net.ftp.FTP;
33  import org.apache.commons.net.ftp.FTPClient;
34  import org.apache.commons.net.ftp.FTPClientConfig;
35  import org.apache.commons.net.ftp.FTPConnectionClosedException;
36  import org.apache.commons.net.ftp.FTPFile;
37  import org.apache.commons.net.ftp.FTPHTTPClient;
38  import org.apache.commons.net.ftp.FTPReply;
39  import org.apache.commons.net.ftp.FTPSClient;
40  import org.apache.commons.net.io.CopyStreamEvent;
41  import org.apache.commons.net.io.CopyStreamListener;
42  import org.apache.commons.net.util.TrustManagerUtils;
43  
44  /**
45   * This is an example program demonstrating how to use the FTPClient class. This program connects to an FTP server and retrieves the specified file. If the -s
46   * flag is used, it stores the local file at the FTP server. Just so you can see what's happening, all reply strings are printed. If the -b flag is used, a
47   * binary transfer is assumed (default is ASCII). See below for further options.
48   */
49  public final class FTPClientExample {
50  
51      /**
52       * Command line usage.
53       */
54      // @formatter:off
55      public static final String USAGE = "Expected Parameters: [options] <hostname> <user> <password> [<remote file> [<local file>]]\n"
56              + "\nDefault behavior is to download a file and use ASCII transfer mode.\n"
57              + "\t-a - use local active mode (default is local passive)\n"
58              + "\t-A - anonymous login (omit user and password parameters)\n"
59              + "\t-b - use binary transfer mode\n"
60              + "\t-c cmd - issue arbitrary command (remote is used as a parameter if provided) \n"
61              + "\t-d - list directory details using MLSD (remote is used as the path if provided)\n"
62              + "\t-e - use EPSV with IPv4 (default false)\n"
63              + "\t-E - encoding to use for control channel\n"
64              + "\t-f - issue FEAT command (remote and local files are ignored)\n"
65              + "\t-h - list hidden files (applies to -l and -n only)\n"
66              + "\t-i - issue SIZE command for a file\n"
67              + "\t-k secs - use keep-alive timer (setControlKeepAliveTimeout)\n"
68              + "\t-l - list files using LIST (remote is used as the path if provided)\n"
69              + "\t     Files are listed twice: first in raw mode, then as the formatted parsed data.\n"
70              + "\t     N.B. if the wrong server-type is used, output may be lost. Use -U or -S as necessary.\n"
71              + "\t-L - use lenient future dates (server dates may be up to 1 day into future)\n"
72              + "\t-m - list file details using MDTM (remote is used as the path if provided)\n"
73              + "\t-n - list file names using NLST (remote is used as the path if provided)\n"
74              + "\t--OPTS \"opts-cmd command-name command-options\" - Sends the OPTS\n"
75              + "\t-p true|false|protocol[,true|false] - use FTPSClient with the specified protocol and/or isImplicit setting\n"
76              + "\t-s - store file on server (upload)\n"
77              + "\t-S - systemType set server system type (e.g. Unix VMS WINDOWS)\n"
78              + "\t-t - list file details using MLST (remote is used as the path if provided)\n"
79              + "\t-U - save unparseable responses\n"
80              + "\t-w msec - wait time for keep-alive reply (setControlKeepAliveReplyTimeout)\n"
81              + "\t-T  all|valid|none - use one of the built-in TrustManager implementations (none = JVM default)\n"
82              + "\t-y format - set default date format string\n"
83              + "\t-Y format - set recent date format string\n"
84              + "\t-Z timezone - set the server time zone for parsing LIST responses\n"
85              + "\t-z timezone - set the time zone for displaying MDTM, LIST, MLSD, MLST responses\n"
86              + "\t-PrH server[:port] - HTTP Proxy host and optional port[80] \n"
87              + "\t-PrU user - HTTP Proxy server user\n"
88              + "\t-PrP password - HTTP Proxy server password\n"
89              + "\t-# - add hash display during transfers\n";
90      // @formatter:on
91  
92      private static void configure(final FTPClient ftp, final FTPClientConfig config, final String defaultDateFormat, final String recentDateFormat,
93              final boolean saveUnparseable) {
94          config.setUnparseableEntries(saveUnparseable);
95          if (defaultDateFormat != null) {
96              config.setDefaultDateFormatStr(defaultDateFormat);
97          }
98          if (recentDateFormat != null) {
99              config.setRecentDateFormatStr(recentDateFormat);
100         }
101         ftp.configure(config);
102     }
103 
104     private static CopyStreamListener createListener() {
105         return new CopyStreamListener() {
106             private long megsTotal;
107 
108             @Override
109             public void bytesTransferred(final CopyStreamEvent event) {
110                 bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize());
111             }
112 
113             @Override
114             public void bytesTransferred(final long totalBytesTransferred, final int bytesTransferred, final long streamSize) {
115                 final long megs = totalBytesTransferred / 1000000;
116                 for (long l = megsTotal; l < megs; l++) {
117                     System.err.print("#");
118                 }
119                 megsTotal = megs;
120             }
121         };
122     }
123 
124     public static void main(final String[] args) throws UnknownHostException {
125         boolean storeFile = false;
126         boolean binaryTransfer = false;
127         boolean error = false;
128         boolean listFiles = false;
129         boolean listNames = false;
130         boolean hidden = false;
131         boolean localActive = false;
132         boolean useEpsvWithIPv4 = false;
133         boolean feat = false;
134         boolean printHash = false;
135         boolean mlst = false;
136         boolean mlsd = false;
137         boolean mdtm = false;
138         boolean saveUnparseable = false;
139         boolean size = false;
140         boolean lenient = false;
141         long keepAliveTimeoutSeconds = -1;
142         int controlKeepAliveReplyTimeoutMillis = -1;
143         int minParams = 5; // listings require 3 params
144         String protocol = null; // SSL protocol
145         String doCommand = null;
146         String trustManager = null;
147         String proxyHost = null;
148         int proxyPort = 80;
149         String proxyUser = null;
150         String proxyPassword = null;
151         String user = null;
152         String password = null;
153         Charset encoding = null;
154         String serverTimeZoneId = null;
155         String displayTimeZoneId = null;
156         String serverType = null;
157         String defaultDateFormat = null;
158         String recentDateFormat = null;
159         String opts = null;
160 
161         int base = 0;
162         for (base = 0; base < args.length; base++) {
163             if (args[base].equals("-s")) {
164                 storeFile = true;
165             } else if (args[base].equals("-a")) {
166                 localActive = true;
167             } else if (args[base].equals("-A")) {
168                 user = "anonymous";
169                 password = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName();
170             } else if (args[base].equals("-b")) {
171                 binaryTransfer = true;
172             } else if (args[base].equals("-c")) {
173                 doCommand = args[++base];
174                 minParams = 3;
175             } else if (args[base].equals("-d")) {
176                 mlsd = true;
177                 minParams = 3;
178             } else if (args[base].equals("-e")) {
179                 useEpsvWithIPv4 = true;
180             } else if (args[base].equals("-E")) {
181                 encoding = Charset.forName(args[++base]);
182             } else if (args[base].equals("-f")) {
183                 feat = true;
184                 minParams = 3;
185             } else if (args[base].equals("-h")) {
186                 hidden = true;
187             } else if (args[base].equals("-i")) {
188                 size = true;
189                 minParams = 3;
190             } else if (args[base].equals("-k")) {
191                 keepAliveTimeoutSeconds = Long.parseLong(args[++base]);
192             } else if (args[base].equals("-l")) {
193                 listFiles = true;
194                 minParams = 3;
195             } else if (args[base].equals("-m")) {
196                 mdtm = true;
197                 minParams = 3;
198             } else if (args[base].equals("-L")) {
199                 lenient = true;
200             } else if (args[base].equals("--OPTS")) {
201                 opts = args[++base];
202             } else if (args[base].equals("-n")) {
203                 listNames = true;
204                 minParams = 3;
205             } else if (args[base].equals("-p")) {
206                 protocol = args[++base];
207             } else if (args[base].equals("-S")) {
208                 serverType = args[++base];
209             } else if (args[base].equals("-t")) {
210                 mlst = true;
211                 minParams = 3;
212             } else if (args[base].equals("-U")) {
213                 saveUnparseable = true;
214             } else if (args[base].equals("-w")) {
215                 controlKeepAliveReplyTimeoutMillis = Integer.parseInt(args[++base]);
216             } else if (args[base].equals("-T")) {
217                 trustManager = args[++base];
218             } else if (args[base].equals("-y")) {
219                 defaultDateFormat = args[++base];
220             } else if (args[base].equals("-Y")) {
221                 recentDateFormat = args[++base];
222             } else if (args[base].equals("-Z")) {
223                 serverTimeZoneId = args[++base];
224             } else if (args[base].equals("-z")) {
225                 displayTimeZoneId = args[++base];
226             } else if (args[base].equals("-PrH")) {
227                 proxyHost = args[++base];
228                 final String[] parts = proxyHost.split(":");
229                 if (parts.length == 2) {
230                     proxyHost = parts[0];
231                     proxyPort = Integer.parseInt(parts[1]);
232                 }
233             } else if (args[base].equals("-PrU")) {
234                 proxyUser = args[++base];
235             } else if (args[base].equals("-PrP")) {
236                 proxyPassword = args[++base];
237             } else if (args[base].equals("-#")) {
238                 printHash = true;
239             } else {
240                 break;
241             }
242         }
243 
244         final int remain = args.length - base;
245         if (user != null) {
246             minParams -= 2;
247         }
248         if (remain < minParams) {
249             // server, user, pass, remote, local [protocol]
250             if (args.length > 0) {
251                 System.err.println("Actual Parameters: " + Arrays.toString(args));
252             }
253             System.err.println(USAGE);
254             System.exit(1);
255         }
256 
257         String server = args[base++];
258         int port = 0;
259         final String[] parts = server.split(":");
260         if (parts.length == 2) {
261             server = parts[0];
262             port = Integer.parseInt(parts[1]);
263         }
264         if (user == null) {
265             user = args[base++];
266             password = args[base++];
267         }
268 
269         String remote = null;
270         if (args.length - base > 0) {
271             remote = args[base++];
272         }
273 
274         String local = null;
275         if (args.length - base > 0) {
276             local = args[base++];
277         }
278 
279         final FTPClient ftp;
280         if (protocol == null) {
281             if (proxyHost != null) {
282                 System.out.println("Using HTTP proxy server: " + proxyHost);
283                 ftp = new FTPHTTPClient(proxyHost, proxyPort, proxyUser, proxyPassword);
284             } else {
285                 ftp = new FTPClient();
286             }
287         } else {
288             final FTPSClient ftps;
289             if (protocol.equals("true")) {
290                 ftps = new FTPSClient(true);
291             } else if (protocol.equals("false")) {
292                 ftps = new FTPSClient(false);
293             } else {
294                 final String[] prot = protocol.split(",");
295                 if (prot.length == 1) { // Just protocol
296                     ftps = new FTPSClient(protocol);
297                 } else { // protocol,true|false
298                     ftps = new FTPSClient(prot[0], Boolean.parseBoolean(prot[1]));
299                 }
300             }
301             ftp = ftps;
302             if (trustManager != null) {
303                 switch (trustManager) {
304                 case "all":
305                     ftps.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager());
306                     break;
307                 case "valid":
308                     ftps.setTrustManager(TrustManagerUtils.getValidateServerCertificateTrustManager());
309                     break;
310                 case "none":
311                     ftps.setTrustManager(null);
312                     break;
313                 default:
314                     break;
315                 }
316             }
317         }
318 
319         if (printHash) {
320             ftp.setCopyStreamListener(createListener());
321         }
322         if (keepAliveTimeoutSeconds >= 0) {
323             ftp.setControlKeepAliveTimeout(Duration.ofSeconds(keepAliveTimeoutSeconds));
324         }
325         if (controlKeepAliveReplyTimeoutMillis >= 0) {
326             ftp.setControlKeepAliveReplyTimeout(Duration.ofMillis(controlKeepAliveReplyTimeoutMillis));
327         }
328         if (encoding != null) {
329             ftp.setControlEncoding(encoding);
330         }
331         ftp.setListHiddenFiles(hidden);
332 
333         // suppress login details
334         ftp.addProtocolCommandListener(PrintCommandListeners.sysOutPrintCommandListener());
335 
336         final FTPClientConfig config;
337         if (serverType != null) {
338             config = new FTPClientConfig(serverType);
339         } else {
340             config = new FTPClientConfig();
341         }
342         configure(ftp, config, defaultDateFormat, recentDateFormat, saveUnparseable);
343 
344         try {
345             final int reply;
346             if (port > 0) {
347                 ftp.connect(server, port);
348             } else {
349                 ftp.connect(server);
350             }
351             System.out.println("Connected to " + server + " on " + (port > 0 ? port : ftp.getDefaultPort()));
352 
353             // After connection attempt, you should check the reply code to verify
354             // success.
355             reply = ftp.getReplyCode();
356 
357             if (!FTPReply.isPositiveCompletion(reply)) {
358                 ftp.disconnect();
359                 System.err.println("FTP server refused connection.");
360                 System.exit(1);
361             }
362         } catch (final IOException e) {
363             if (ftp.isConnected()) {
364                 try {
365                     ftp.disconnect();
366                 } catch (final IOException f) {
367                     // do nothing
368                 }
369             }
370             System.err.println("Could not connect to server.");
371             e.printStackTrace();
372             System.exit(1);
373         }
374 
375         __main: try {
376             if (!ftp.login(user, password)) {
377                 ftp.logout();
378                 error = true;
379                 break __main;
380             }
381 
382             System.out.println("Remote system is " + ftp.getSystemType());
383             if (!ftp.getSystemType().contains(config.getServerSystemKey())) {
384                 configure(ftp, new FTPClientConfig(ftp.getSystemType()), defaultDateFormat, recentDateFormat, saveUnparseable);
385             }
386             if (opts != null) {
387                 ftp.opts(opts);
388             }
389 
390             if (binaryTransfer) {
391                 ftp.setFileType(FTP.BINARY_FILE_TYPE);
392             } else {
393                 // in theory this should not be necessary as servers should default to ASCII,
394                 // but they don't all do so - see NET-500
395                 ftp.setFileType(FTP.ASCII_FILE_TYPE);
396             }
397 
398             // Use passive mode as default because most of us are
399             // behind firewalls these days.
400             if (localActive) {
401                 ftp.enterLocalActiveMode();
402             } else {
403                 ftp.enterLocalPassiveMode();
404             }
405 
406             ftp.setUseEPSVwithIPv4(useEpsvWithIPv4);
407 
408             if (storeFile) {
409                 try (InputStream input = new FileInputStream(local)) {
410                     ftp.storeFile(remote, input);
411                 }
412 
413                 if (keepAliveTimeoutSeconds > 0) {
414                     showCslStats(ftp);
415                 }
416             } else if (listFiles || mlsd || mdtm || mlst || listNames || size) {
417                 // Allow multiple list types for single invocation
418                 if (mlsd) {
419                     for (final FTPFile f : ftp.mlistDir(remote)) {
420                         System.out.println(f.getRawListing());
421                         System.out.println(f.toFormattedString(displayTimeZoneId));
422                     }
423                 }
424                 if (mdtm) {
425                     final FTPFile f = ftp.mdtmFile(remote);
426                     if (f != null) {
427                         System.out.println(f.getRawListing());
428                         System.out.println(f.toFormattedString(displayTimeZoneId));
429                     } else {
430                         System.out.println("File not found");
431                     }
432                 }
433                 if (mlst) {
434                     final FTPFile f = ftp.mlistFile(remote);
435                     if (f != null) {
436                         System.out.println(f.toFormattedString(displayTimeZoneId));
437                     }
438                 }
439                 if (listNames) {
440                     for (final String s : ftp.listNames(remote)) {
441                         System.out.println(s);
442                     }
443                 }
444                 if (size) {
445                     System.out.println("Size=" + ftp.getSize(remote));
446                 }
447                 // Do this last because it changes the client
448                 if (listFiles) {
449                     if (lenient || serverTimeZoneId != null) {
450                         config.setLenientFutureDates(lenient);
451                         if (serverTimeZoneId != null) {
452                             config.setServerTimeZoneId(serverTimeZoneId);
453                         }
454                         ftp.configure(config);
455                     }
456 
457                     for (final FTPFile f : ftp.listFiles(remote)) {
458                         System.out.println(f.getRawListing());
459                         System.out.println(f.toFormattedString(displayTimeZoneId));
460                     }
461                 }
462             } else if (feat) {
463                 // boolean feature check
464                 if (remote != null) { // See if the command is present
465                     if (ftp.hasFeature(remote)) {
466                         System.out.println("Has feature: " + remote);
467                     } else if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
468                         System.out.println("FEAT " + remote + " was not detected");
469                     } else {
470                         System.out.println("Command failed: " + ftp.getReplyString());
471                     }
472 
473                     // Strings feature check
474                     final String[] features = ftp.featureValues(remote);
475                     if (features != null) {
476                         for (final String f : features) {
477                             System.out.println("FEAT " + remote + "=" + f + ".");
478                         }
479                     } else if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
480                         System.out.println("FEAT " + remote + " is not present");
481                     } else {
482                         System.out.println("Command failed: " + ftp.getReplyString());
483                     }
484                 } else if (ftp.features()) {
485 //                        Command listener has already printed the output
486                 } else {
487                     System.out.println("Failed: " + ftp.getReplyString());
488                 }
489             } else if (doCommand != null) {
490                 if (ftp.doCommand(doCommand, remote)) {
491 //                  Command listener has already printed the output
492 //                    for(String s : ftp.getReplyStrings()) {
493 //                        System.out.println(s);
494 //                    }
495                 } else {
496                     System.out.println("Failed: " + ftp.getReplyString());
497                 }
498             } else {
499                 try (OutputStream output = new FileOutputStream(local)) {
500                     ftp.retrieveFile(remote, output);
501                 }
502 
503                 if (keepAliveTimeoutSeconds > 0) {
504                     showCslStats(ftp);
505                 }
506             }
507 
508             ftp.noop(); // check that control connection is working OK
509 
510             ftp.logout();
511         } catch (final FTPConnectionClosedException e) {
512             error = true;
513             System.err.println("Server closed connection.");
514             e.printStackTrace();
515         } catch (final IOException e) {
516             error = true;
517             e.printStackTrace();
518         } finally {
519             if (ftp.isConnected()) {
520                 try {
521                     ftp.disconnect();
522                 } catch (final IOException f) {
523                     // do nothing
524                 }
525             }
526         }
527 
528         System.exit(error ? 1 : 0);
529     } // end main
530 
531     private static void showCslStats(final FTPClient ftp) {
532         @SuppressWarnings("deprecation") // debug code
533         final int[] stats = ftp.getCslDebug();
534         System.out.println("CslDebug=" + Arrays.toString(stats));
535 
536     }
537 }