001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.net.examples.ftp;
019
020import java.io.FileInputStream;
021import java.io.FileOutputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.OutputStream;
025import java.io.PrintWriter;
026import java.net.InetAddress;
027import java.net.UnknownHostException;
028import java.time.Duration;
029import java.util.Arrays;
030
031import org.apache.commons.net.PrintCommandListener;
032import org.apache.commons.net.ftp.FTP;
033import org.apache.commons.net.ftp.FTPClient;
034import org.apache.commons.net.ftp.FTPClientConfig;
035import org.apache.commons.net.ftp.FTPConnectionClosedException;
036import org.apache.commons.net.ftp.FTPFile;
037import org.apache.commons.net.ftp.FTPHTTPClient;
038import org.apache.commons.net.ftp.FTPReply;
039import org.apache.commons.net.ftp.FTPSClient;
040import org.apache.commons.net.io.CopyStreamEvent;
041import org.apache.commons.net.io.CopyStreamListener;
042import org.apache.commons.net.util.TrustManagerUtils;
043
044/**
045 * 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
046 * 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
047 * binary transfer is assumed (default is ASCII). See below for further options.
048 */
049public final class FTPClientExample {
050
051    public static final String USAGE = "Expected Parameters: [options] <hostname> <username> <password> [<remote file> [<local file>]]\n"
052            + "\nDefault behavior is to download a file and use ASCII transfer mode.\n" + "\t-a - use local active mode (default is local passive)\n"
053            + "\t-A - anonymous login (omit username and password parameters)\n" + "\t-b - use binary transfer mode\n"
054            + "\t-c cmd - issue arbitrary command (remote is used as a parameter if provided) \n"
055            + "\t-d - list directory details using MLSD (remote is used as the pathname if provided)\n" + "\t-e - use EPSV with IPv4 (default false)\n"
056            + "\t-E - encoding to use for control channel\n" + "\t-f - issue FEAT command (remote and local files are ignored)\n"
057            + "\t-h - list hidden files (applies to -l and -n only)\n" + "\t-i - issue SIZE command for a file\n"
058            + "\t-k secs - use keep-alive timer (setControlKeepAliveTimeout)\n" + "\t-l - list files using LIST (remote is used as the pathname if provided)\n"
059            + "\t     Files are listed twice: first in raw mode, then as the formatted parsed data.\n"
060            + "\t     N.B. if the wrong server-type is used, output may be lost. Use -U or -S as necessary.\n"
061            + "\t-L - use lenient future dates (server dates may be up to 1 day into future)\n"
062            + "\t-m - list file details using MDTM (remote is used as the pathname if provided)\n"
063            + "\t-n - list file names using NLST (remote is used as the pathname if provided)\n"
064            + "\t-p true|false|protocol[,true|false] - use FTPSClient with the specified protocol and/or isImplicit setting\n"
065            + "\t-s - store file on server (upload)\n" + "\t-S - systemType set server system type (e.g. UNIX VMS WINDOWS)\n"
066            + "\t-t - list file details using MLST (remote is used as the pathname if provided)\n" + "\t-U - save unparseable responses\n"
067            + "\t-w msec - wait time for keep-alive reply (setControlKeepAliveReplyTimeout)\n"
068            + "\t-T  all|valid|none - use one of the built-in TrustManager implementations (none = JVM default)\n"
069            + "\t-y format - set default date format string\n" + "\t-Y format - set recent date format string\n"
070            + "\t-Z timezone - set the server time zone for parsing LIST responses\n"
071            + "\t-z timezone - set the time zone for displaying MDTM, LIST, MLSD, MLST responses\n"
072            + "\t-PrH server[:port] - HTTP Proxy host and optional port[80] \n" + "\t-PrU user - HTTP Proxy server username\n"
073            + "\t-PrP password - HTTP Proxy server password\n" + "\t-# - add hash display during transfers\n";
074
075    private static CopyStreamListener createListener() {
076        return new CopyStreamListener() {
077            private long megsTotal;
078
079            @Override
080            public void bytesTransferred(final CopyStreamEvent event) {
081                bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize());
082            }
083
084            @Override
085            public void bytesTransferred(final long totalBytesTransferred, final int bytesTransferred, final long streamSize) {
086                final long megs = totalBytesTransferred / 1000000;
087                for (long l = megsTotal; l < megs; l++) {
088                    System.err.print("#");
089                }
090                megsTotal = megs;
091            }
092        };
093    }
094
095    public static void main(final String[] args) throws UnknownHostException {
096        boolean storeFile = false, binaryTransfer = false, error = false, listFiles = false, listNames = false, hidden = false;
097        boolean localActive = false, useEpsvWithIPv4 = false, feat = false, printHash = false;
098        boolean mlst = false, mlsd = false, mdtm = false, saveUnparseable = false;
099        boolean size = false;
100        boolean lenient = false;
101        long keepAliveTimeoutSeconds = -1;
102        int controlKeepAliveReplyTimeoutMillis = -1;
103        int minParams = 5; // listings require 3 params
104        String protocol = null; // SSL protocol
105        String doCommand = null;
106        String trustmgr = null;
107        String proxyHost = null;
108        int proxyPort = 80;
109        String proxyUser = null;
110        String proxyPassword = null;
111        String username = null;
112        String password = null;
113        String encoding = null;
114        String serverTimeZoneId = null;
115        String displayTimeZoneId = null;
116        String serverType = null;
117        String defaultDateFormat = null;
118        String recentDateFormat = null;
119
120        int base = 0;
121        for (base = 0; base < args.length; base++) {
122            if (args[base].equals("-s")) {
123                storeFile = true;
124            } else if (args[base].equals("-a")) {
125                localActive = true;
126            } else if (args[base].equals("-A")) {
127                username = "anonymous";
128                password = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName();
129            } else if (args[base].equals("-b")) {
130                binaryTransfer = true;
131            } else if (args[base].equals("-c")) {
132                doCommand = args[++base];
133                minParams = 3;
134            } else if (args[base].equals("-d")) {
135                mlsd = true;
136                minParams = 3;
137            } else if (args[base].equals("-e")) {
138                useEpsvWithIPv4 = true;
139            } else if (args[base].equals("-E")) {
140                encoding = args[++base];
141            } else if (args[base].equals("-f")) {
142                feat = true;
143                minParams = 3;
144            } else if (args[base].equals("-h")) {
145                hidden = true;
146            } else if (args[base].equals("-i")) {
147                size = true;
148                minParams = 3;
149            } else if (args[base].equals("-k")) {
150                keepAliveTimeoutSeconds = Long.parseLong(args[++base]);
151            } else if (args[base].equals("-l")) {
152                listFiles = true;
153                minParams = 3;
154            } else if (args[base].equals("-m")) {
155                mdtm = true;
156                minParams = 3;
157            } else if (args[base].equals("-L")) {
158                lenient = true;
159            } else if (args[base].equals("-n")) {
160                listNames = true;
161                minParams = 3;
162            } else if (args[base].equals("-p")) {
163                protocol = args[++base];
164            } else if (args[base].equals("-S")) {
165                serverType = args[++base];
166            } else if (args[base].equals("-t")) {
167                mlst = true;
168                minParams = 3;
169            } else if (args[base].equals("-U")) {
170                saveUnparseable = true;
171            } else if (args[base].equals("-w")) {
172                controlKeepAliveReplyTimeoutMillis = Integer.parseInt(args[++base]);
173            } else if (args[base].equals("-T")) {
174                trustmgr = args[++base];
175            } else if (args[base].equals("-y")) {
176                defaultDateFormat = args[++base];
177            } else if (args[base].equals("-Y")) {
178                recentDateFormat = args[++base];
179            } else if (args[base].equals("-Z")) {
180                serverTimeZoneId = args[++base];
181            } else if (args[base].equals("-z")) {
182                displayTimeZoneId = args[++base];
183            } else if (args[base].equals("-PrH")) {
184                proxyHost = args[++base];
185                final String parts[] = proxyHost.split(":");
186                if (parts.length == 2) {
187                    proxyHost = parts[0];
188                    proxyPort = Integer.parseInt(parts[1]);
189                }
190            } else if (args[base].equals("-PrU")) {
191                proxyUser = args[++base];
192            } else if (args[base].equals("-PrP")) {
193                proxyPassword = args[++base];
194            } else if (args[base].equals("-#")) {
195                printHash = true;
196            } else {
197                break;
198            }
199        }
200
201        final int remain = args.length - base;
202        if (username != null) {
203            minParams -= 2;
204        }
205        if (remain < minParams) // server, user, pass, remote, local [protocol]
206        {
207            if (args.length > 0) {
208                System.err.println("Actual Parameters: " + Arrays.toString(args));
209            }
210            System.err.println(USAGE);
211            System.exit(1);
212        }
213
214        String server = args[base++];
215        int port = 0;
216        final String parts[] = server.split(":");
217        if (parts.length == 2) {
218            server = parts[0];
219            port = Integer.parseInt(parts[1]);
220        }
221        if (username == null) {
222            username = args[base++];
223            password = args[base++];
224        }
225
226        String remote = null;
227        if (args.length - base > 0) {
228            remote = args[base++];
229        }
230
231        String local = null;
232        if (args.length - base > 0) {
233            local = args[base++];
234        }
235
236        final FTPClient ftp;
237        if (protocol == null) {
238            if (proxyHost != null) {
239                System.out.println("Using HTTP proxy server: " + proxyHost);
240                ftp = new FTPHTTPClient(proxyHost, proxyPort, proxyUser, proxyPassword);
241            } else {
242                ftp = new FTPClient();
243            }
244        } else {
245            final FTPSClient ftps;
246            if (protocol.equals("true")) {
247                ftps = new FTPSClient(true);
248            } else if (protocol.equals("false")) {
249                ftps = new FTPSClient(false);
250            } else {
251                final String prot[] = protocol.split(",");
252                if (prot.length == 1) { // Just protocol
253                    ftps = new FTPSClient(protocol);
254                } else { // protocol,true|false
255                    ftps = new FTPSClient(prot[0], Boolean.parseBoolean(prot[1]));
256                }
257            }
258            ftp = ftps;
259            if ("all".equals(trustmgr)) {
260                ftps.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager());
261            } else if ("valid".equals(trustmgr)) {
262                ftps.setTrustManager(TrustManagerUtils.getValidateServerCertificateTrustManager());
263            } else if ("none".equals(trustmgr)) {
264                ftps.setTrustManager(null);
265            }
266        }
267
268        if (printHash) {
269            ftp.setCopyStreamListener(createListener());
270        }
271        if (keepAliveTimeoutSeconds >= 0) {
272            ftp.setControlKeepAliveTimeout(Duration.ofSeconds(keepAliveTimeoutSeconds));
273        }
274        if (controlKeepAliveReplyTimeoutMillis >= 0) {
275            ftp.setControlKeepAliveReplyTimeout(Duration.ofMillis(controlKeepAliveReplyTimeoutMillis));
276        }
277        if (encoding != null) {
278            ftp.setControlEncoding(encoding);
279        }
280        ftp.setListHiddenFiles(hidden);
281
282        // suppress login details
283        ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
284
285        final FTPClientConfig config;
286        if (serverType != null) {
287            config = new FTPClientConfig(serverType);
288        } else {
289            config = new FTPClientConfig();
290        }
291        config.setUnparseableEntries(saveUnparseable);
292        if (defaultDateFormat != null) {
293            config.setDefaultDateFormatStr(defaultDateFormat);
294        }
295        if (recentDateFormat != null) {
296            config.setRecentDateFormatStr(recentDateFormat);
297        }
298        ftp.configure(config);
299
300        try {
301            final int reply;
302            if (port > 0) {
303                ftp.connect(server, port);
304            } else {
305                ftp.connect(server);
306            }
307            System.out.println("Connected to " + server + " on " + (port > 0 ? port : ftp.getDefaultPort()));
308
309            // After connection attempt, you should check the reply code to verify
310            // success.
311            reply = ftp.getReplyCode();
312
313            if (!FTPReply.isPositiveCompletion(reply)) {
314                ftp.disconnect();
315                System.err.println("FTP server refused connection.");
316                System.exit(1);
317            }
318        } catch (final IOException e) {
319            if (ftp.isConnected()) {
320                try {
321                    ftp.disconnect();
322                } catch (final IOException f) {
323                    // do nothing
324                }
325            }
326            System.err.println("Could not connect to server.");
327            e.printStackTrace();
328            System.exit(1);
329        }
330
331        __main: try {
332            if (!ftp.login(username, password)) {
333                ftp.logout();
334                error = true;
335                break __main;
336            }
337
338            System.out.println("Remote system is " + ftp.getSystemType());
339
340            if (binaryTransfer) {
341                ftp.setFileType(FTP.BINARY_FILE_TYPE);
342            } else {
343                // in theory this should not be necessary as servers should default to ASCII
344                // but they don't all do so - see NET-500
345                ftp.setFileType(FTP.ASCII_FILE_TYPE);
346            }
347
348            // Use passive mode as default because most of us are
349            // behind firewalls these days.
350            if (localActive) {
351                ftp.enterLocalActiveMode();
352            } else {
353                ftp.enterLocalPassiveMode();
354            }
355
356            ftp.setUseEPSVwithIPv4(useEpsvWithIPv4);
357
358            if (storeFile) {
359                try (final InputStream input = new FileInputStream(local)) {
360                    ftp.storeFile(remote, input);
361                }
362
363                if (keepAliveTimeoutSeconds > 0) {
364                    showCslStats(ftp);
365                }
366            }
367            // Allow multiple list types for single invocation
368            else if (listFiles || mlsd || mdtm || mlst || listNames || size) {
369                if (mlsd) {
370                    for (final FTPFile f : ftp.mlistDir(remote)) {
371                        System.out.println(f.getRawListing());
372                        System.out.println(f.toFormattedString(displayTimeZoneId));
373                    }
374                }
375                if (mdtm) {
376                    final FTPFile f = ftp.mdtmFile(remote);
377                    if (f != null) {
378                        System.out.println(f.getRawListing());
379                        System.out.println(f.toFormattedString(displayTimeZoneId));
380                    } else {
381                        System.out.println("File not found");
382                    }
383                }
384                if (mlst) {
385                    final FTPFile f = ftp.mlistFile(remote);
386                    if (f != null) {
387                        System.out.println(f.toFormattedString(displayTimeZoneId));
388                    }
389                }
390                if (listNames) {
391                    for (final String s : ftp.listNames(remote)) {
392                        System.out.println(s);
393                    }
394                }
395                if (size) {
396                    System.out.println("Size=" + ftp.getSize(remote));
397                }
398                // Do this last because it changes the client
399                if (listFiles) {
400                    if (lenient || serverTimeZoneId != null) {
401                        config.setLenientFutureDates(lenient);
402                        if (serverTimeZoneId != null) {
403                            config.setServerTimeZoneId(serverTimeZoneId);
404                        }
405                        ftp.configure(config);
406                    }
407
408                    for (final FTPFile f : ftp.listFiles(remote)) {
409                        System.out.println(f.getRawListing());
410                        System.out.println(f.toFormattedString(displayTimeZoneId));
411                    }
412                }
413            } else if (feat) {
414                // boolean feature check
415                if (remote != null) { // See if the command is present
416                    if (ftp.hasFeature(remote)) {
417                        System.out.println("Has feature: " + remote);
418                    } else if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
419                        System.out.println("FEAT " + remote + " was not detected");
420                    } else {
421                        System.out.println("Command failed: " + ftp.getReplyString());
422                    }
423
424                    // Strings feature check
425                    final String[] features = ftp.featureValues(remote);
426                    if (features != null) {
427                        for (final String f : features) {
428                            System.out.println("FEAT " + remote + "=" + f + ".");
429                        }
430                    } else if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
431                        System.out.println("FEAT " + remote + " is not present");
432                    } else {
433                        System.out.println("Command failed: " + ftp.getReplyString());
434                    }
435                } else if (ftp.features()) {
436//                        Command listener has already printed the output
437                } else {
438                    System.out.println("Failed: " + ftp.getReplyString());
439                }
440            } else if (doCommand != null) {
441                if (ftp.doCommand(doCommand, remote)) {
442//                  Command listener has already printed the output
443//                    for(String s : ftp.getReplyStrings()) {
444//                        System.out.println(s);
445//                    }
446                } else {
447                    System.out.println("Failed: " + ftp.getReplyString());
448                }
449            } else {
450                try (final OutputStream output = new FileOutputStream(local)) {
451                    ftp.retrieveFile(remote, output);
452                }
453
454                if (keepAliveTimeoutSeconds > 0) {
455                    showCslStats(ftp);
456                }
457            }
458
459            ftp.noop(); // check that control connection is working OK
460
461            ftp.logout();
462        } catch (final FTPConnectionClosedException e) {
463            error = true;
464            System.err.println("Server closed connection.");
465            e.printStackTrace();
466        } catch (final IOException e) {
467            error = true;
468            e.printStackTrace();
469        } finally {
470            if (ftp.isConnected()) {
471                try {
472                    ftp.disconnect();
473                } catch (final IOException f) {
474                    // do nothing
475                }
476            }
477        }
478
479        System.exit(error ? 1 : 0);
480    } // end main
481
482    private static void showCslStats(final FTPClient ftp) {
483        @SuppressWarnings("deprecation") // debug code
484        final int[] stats = ftp.getCslDebug();
485        System.out.println("CslDebug=" + Arrays.toString(stats));
486
487    }
488}