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 * http://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.tools.ant.taskdefs.optional.net;
19
20 import java.io.BufferedInputStream;
21 import java.io.BufferedOutputStream;
22 import java.io.BufferedWriter;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.FileWriter;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.text.SimpleDateFormat;
31 import java.util.Collection;
32 import java.util.Date;
33 import java.util.Enumeration;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Hashtable;
37 import java.util.Iterator;
38 import java.util.Locale;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.StringTokenizer;
42 import java.util.Vector;
43
44 import org.apache.commons.net.ftp.FTPClient;
45 import org.apache.commons.net.ftp.FTPClientConfig;
46 import org.apache.commons.net.ftp.FTPFile;
47 import org.apache.commons.net.ftp.FTPReply;
48 import org.apache.tools.ant.BuildException;
49 import org.apache.tools.ant.DirectoryScanner;
50 import org.apache.tools.ant.Project;
51 import org.apache.tools.ant.Task;
52 import org.apache.tools.ant.taskdefs.Delete;
53 import org.apache.tools.ant.types.EnumeratedAttribute;
54 import org.apache.tools.ant.types.FileSet;
55 import org.apache.tools.ant.types.selectors.SelectorUtils;
56 import org.apache.tools.ant.util.FileUtils;
57 import org.apache.tools.ant.util.RetryHandler;
58 import org.apache.tools.ant.util.Retryable;
59 import org.apache.tools.ant.util.VectorSet;
60
61 /**
62 * Basic FTP client. Performs the following actions:
63 * <ul>
64 * <li> <strong>send</strong> - send files to a remote server. This is the
65 * default action.</li>
66 * <li> <strong>get</strong> - retrieve files from a remote server.</li>
67 * <li> <strong>del</strong> - delete files from a remote server.</li>
68 * <li> <strong>list</strong> - create a file listing.</li>
69 * <li> <strong>chmod</strong> - change unix file permissions.</li>
70 * <li> <strong>rmdir</strong> - remove directories, if empty, from a
71 * remote server.</li>
72 * </ul>
73 * <strong>Note:</strong> Some FTP servers - notably the Solaris server - seem
74 * to hold data ports open after a "retr" operation, allowing them to timeout
75 * instead of shutting them down cleanly. This happens in active or passive
76 * mode, and the ports will remain open even after ending the FTP session. FTP
77 * "send" operations seem to close ports immediately. This behavior may cause
78 * problems on some systems when downloading large sets of files.
79 *
80 * @since Ant 1.3
81 */
82 public class FTP extends Task implements FTPTaskConfig {
83 protected static final int SEND_FILES = 0;
84 protected static final int GET_FILES = 1;
85 protected static final int DEL_FILES = 2;
86 protected static final int LIST_FILES = 3;
87 protected static final int MK_DIR = 4;
88 protected static final int CHMOD = 5;
89 protected static final int RM_DIR = 6;
90 protected static final int SITE_CMD = 7;
91 /** return code of ftp - not implemented in commons-net version 1.0 */
92 private static final int CODE_521 = 521;
93
94 /** adjust uptodate calculations where server timestamps are HH:mm and client's
95 * are HH:mm:ss */
96 private static final long GRANULARITY_MINUTE = 60000L;
97
98 /** Date formatter used in logging, note not thread safe! */
99 private static final SimpleDateFormat TIMESTAMP_LOGGING_SDF =
100 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
101
102 /** Default port for FTP */
103 public static final int DEFAULT_FTP_PORT = 21;
104
105 private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
106
107 private String remotedir;
108 private String server;
109 private String userid;
110 private String password;
111 private String account;
112 private File listing;
113 private boolean binary = true;
114 private boolean passive = false;
115 private boolean verbose = false;
116 private boolean newerOnly = false;
117 private long timeDiffMillis = 0;
118 private long granularityMillis = 0L;
119 private boolean timeDiffAuto = false;
120 private int action = SEND_FILES;
121 private Vector filesets = new Vector();
122 private Set dirCache = new HashSet();
123 private int transferred = 0;
124 private String remoteFileSep = "/";
125 private int port = DEFAULT_FTP_PORT;
126 private boolean skipFailedTransfers = false;
127 private int skipped = 0;
128 private boolean ignoreNoncriticalErrors = false;
129 private boolean preserveLastModified = false;
130 private String chmod = null;
131 private String umask = null;
132 private FTPSystemType systemTypeKey = FTPSystemType.getDefault();
133 private String defaultDateFormatConfig = null;
134 private String recentDateFormatConfig = null;
135 private LanguageCode serverLanguageCodeConfig = LanguageCode.getDefault();
136 private String serverTimeZoneConfig = null;
137 private String shortMonthNamesConfig = null;
138 private Granularity timestampGranularity = Granularity.getDefault();
139 private boolean isConfigurationSet = false;
140 private int retriesAllowed = 0;
141 private String siteCommand = null;
142 private String initialSiteCommand = null;
143 private boolean enableRemoteVerification = true;
144
145 protected static final String[] ACTION_STRS = {
146 "sending",
147 "getting",
148 "deleting",
149 "listing",
150 "making directory",
151 "chmod",
152 "removing",
153 "site"
154 };
155
156 protected static final String[] COMPLETED_ACTION_STRS = {
157 "sent",
158 "retrieved",
159 "deleted",
160 "listed",
161 "created directory",
162 "mode changed",
163 "removed",
164 "site command executed"
165 };
166
167 protected static final String[] ACTION_TARGET_STRS = {
168 "files",
169 "files",
170 "files",
171 "files",
172 "directory",
173 "files",
174 "directories",
175 "site command"
176 };
177
178 /**
179 * internal class providing a File-like interface to some of the information
180 * available from the FTP server
181 *
182 */
183 protected static class FTPFileProxy extends File {
184
185 private final FTPFile file;
186 private final String[] parts;
187 private final String name;
188
189 /**
190 * creates a proxy to a FTP file
191 * @param file
192 */
193 public FTPFileProxy(FTPFile file) {
194 super(file.getName());
195 name = file.getName();
196 this.file = file;
197 parts = FileUtils.getPathStack(name);
198 }
199
200 /**
201 * creates a proxy to a FTP directory
202 * @param completePath the remote directory.
203 */
204 public FTPFileProxy(String completePath) {
205 super(completePath);
206 file = null;
207 name = completePath;
208 parts = FileUtils.getPathStack(completePath);
209 }
210
211
212 /* (non-Javadoc)
213 * @see java.io.File#exists()
214 */
215 public boolean exists() {
216 return true;
217 }
218
219
220 /* (non-Javadoc)
221 * @see java.io.File#getAbsolutePath()
222 */
223 public String getAbsolutePath() {
224 return name;
225 }
226
227
228 /* (non-Javadoc)
229 * @see java.io.File#getName()
230 */
231 public String getName() {
232 return parts.length > 0 ? parts[parts.length - 1] : name;
233 }
234
235
236 /* (non-Javadoc)
237 * @see java.io.File#getParent()
238 */
239 public String getParent() {
240 String result = "";
241 for(int i = 0; i < parts.length - 1; i++){
242 result += File.separatorChar + parts[i];
243 }
244 return result;
245 }
246
247
248 /* (non-Javadoc)
249 * @see java.io.File#getPath()
250 */
251 public String getPath() {
252 return name;
253 }
254
255
256 /**
257 * FTP files are stored as absolute paths
258 * @return true
259 */
260 public boolean isAbsolute() {
261 return true;
262 }
263
264
265 /* (non-Javadoc)
266 * @see java.io.File#isDirectory()
267 */
268 public boolean isDirectory() {
269 return file == null;
270 }
271
272
273 /* (non-Javadoc)
274 * @see java.io.File#isFile()
275 */
276 public boolean isFile() {
277 return file != null;
278 }
279
280
281 /**
282 * FTP files cannot be hidden
283 *
284 * @return false
285 */
286 public boolean isHidden() {
287 return false;
288 }
289
290
291 /* (non-Javadoc)
292 * @see java.io.File#lastModified()
293 */
294 public long lastModified() {
295 if (file != null) {
296 return file.getTimestamp().getTimeInMillis();
297 }
298 return 0;
299 }
300
301
302 /* (non-Javadoc)
303 * @see java.io.File#length()
304 */
305 public long length() {
306 if (file != null) {
307 return file.getSize();
308 }
309 return 0;
310 }
311 }
312
313 /**
314 * internal class allowing to read the contents of a remote file system
315 * using the FTP protocol
316 * used in particular for ftp get operations
317 * differences with DirectoryScanner
318 * "" (the root of the fileset) is never included in the included directories
319 * followSymlinks defaults to false
320 */
321 protected class FTPDirectoryScanner extends DirectoryScanner {
322 // CheckStyle:VisibilityModifier OFF - bc
323 protected FTPClient ftp = null;
324 // CheckStyle:VisibilityModifier ON
325
326 private String rootPath = null;
327
328 /**
329 * since ant 1.6
330 * this flag should be set to true on UNIX and can save scanning time
331 */
332 private boolean remoteSystemCaseSensitive = false;
333 private boolean remoteSensitivityChecked = false;
334
335 /**
336 * constructor
337 * @param ftp ftpclient object
338 */
339 public FTPDirectoryScanner(FTPClient ftp) {
340 super();
341 this.ftp = ftp;
342 this.setFollowSymlinks(false);
343 }
344
345
346 /**
347 * scans the remote directory,
348 * storing internally the included files, directories, ...
349 */
350 public void scan() {
351 if (includes == null) {
352 // No includes supplied, so set it to 'matches all'
353 includes = new String[1];
354 includes[0] = "**";
355 }
356 if (excludes == null) {
357 excludes = new String[0];
358 }
359
360 filesIncluded = new VectorSet();
361 filesNotIncluded = new Vector();
362 filesExcluded = new VectorSet();
363 dirsIncluded = new VectorSet();
364 dirsNotIncluded = new Vector();
365 dirsExcluded = new VectorSet();
366
367 try {
368 String cwd = ftp.printWorkingDirectory();
369 // always start from the current ftp working dir
370 forceRemoteSensitivityCheck();
371
372 checkIncludePatterns();
373 clearCaches();
374 ftp.changeWorkingDirectory(cwd);
375 } catch (IOException e) {
376 throw new BuildException("Unable to scan FTP server: ", e);
377 }
378 }
379
380
381 /**
382 * this routine is actually checking all the include patterns in
383 * order to avoid scanning everything under base dir
384 * @since ant1.6
385 */
386 private void checkIncludePatterns() {
387
388 Hashtable newroots = new Hashtable();
389 // put in the newroots vector the include patterns without
390 // wildcard tokens
391 for (int icounter = 0; icounter < includes.length; icounter++) {
392 String newpattern =
393 SelectorUtils.rtrimWildcardTokens(includes[icounter]);
394 newroots.put(newpattern, includes[icounter]);
395 }
396 if (remotedir == null) {
397 try {
398 remotedir = ftp.printWorkingDirectory();
399 } catch (IOException e) {
400 throw new BuildException("could not read current ftp directory",
401 getLocation());
402 }
403 }
404 AntFTPFile baseFTPFile = new AntFTPRootFile(ftp, remotedir);
405 rootPath = baseFTPFile.getAbsolutePath();
406 // construct it
407 if (newroots.containsKey("")) {
408 // we are going to scan everything anyway
409 scandir(rootPath, "", true);
410 } else {
411 // only scan directories that can include matched files or
412 // directories
413 Enumeration enum2 = newroots.keys();
414
415 while (enum2.hasMoreElements()) {
416 String currentelement = (String) enum2.nextElement();
417 String originalpattern = (String) newroots.get(currentelement);
418 AntFTPFile myfile = new AntFTPFile(baseFTPFile, currentelement);
419 boolean isOK = true;
420 boolean traversesSymlinks = false;
421 String path = null;
422
423 if (myfile.exists()) {
424 forceRemoteSensitivityCheck();
425 if (remoteSensitivityChecked
426 && remoteSystemCaseSensitive && isFollowSymlinks()) {
427 // cool case,
428 //we do not need to scan all the subdirs in the relative path
429 path = myfile.getFastRelativePath();
430 } else {
431 // may be on a case insensitive file system. We want
432 // the results to show what's really on the disk, so
433 // we need to double check.
434 try {
435 path = myfile.getRelativePath();
436 traversesSymlinks = myfile.isTraverseSymlinks();
437 } catch (IOException be) {
438 throw new BuildException(be, getLocation());
439 } catch (BuildException be) {
440 isOK = false;
441 }
442 }
443 } else {
444 isOK = false;
445 }
446 if (isOK) {
447 currentelement = path.replace(remoteFileSep.charAt(0), File.separatorChar);
448 if (!isFollowSymlinks()
449 && traversesSymlinks) {
450 continue;
451 }
452
453 if (myfile.isDirectory()) {
454 if (isIncluded(currentelement)
455 && currentelement.length() > 0) {
456 accountForIncludedDir(currentelement, myfile, true);
457 } else {
458 if (currentelement.length() > 0) {
459 if (currentelement.charAt(currentelement
460 .length() - 1)
461 != File.separatorChar) {
462 currentelement =
463 currentelement + File.separatorChar;
464 }
465 }
466 scandir(myfile.getAbsolutePath(), currentelement, true);
467 }
468 } else {
469 if (isCaseSensitive
470 && originalpattern.equals(currentelement)) {
471 accountForIncludedFile(currentelement);
472 } else if (!isCaseSensitive
473 && originalpattern
474 .equalsIgnoreCase(currentelement)) {
475 accountForIncludedFile(currentelement);
476 }
477 }
478 }
479 }
480 }
481 }
482 /**
483 * scans a particular directory. populates the scannedDirs cache.
484 *
485 * @param dir directory to scan
486 * @param vpath relative path to the base directory of the remote fileset
487 * always ended with a File.separator
488 * @param fast seems to be always true in practice
489 */
490 protected void scandir(String dir, String vpath, boolean fast) {
491 // avoid double scanning of directories, can only happen in fast mode
492 if (fast && hasBeenScanned(vpath)) {
493 return;
494 }
495 try {
496 if (!ftp.changeWorkingDirectory(dir)) {
497 return;
498 }
499 String completePath = null;
500 if (!vpath.equals("")) {
501 completePath = rootPath + remoteFileSep
502 + vpath.replace(File.separatorChar, remoteFileSep.charAt(0));
503 } else {
504 completePath = rootPath;
505 }
506 FTPFile[] newfiles = listFiles(completePath, false);
507
508 if (newfiles == null) {
509 ftp.changeToParentDirectory();
510 return;
511 }
512 for (int i = 0; i < newfiles.length; i++) {
513 FTPFile file = newfiles[i];
514 if (file != null
515 && !file.getName().equals(".")
516 && !file.getName().equals("..")) {
517 String name = vpath + file.getName();
518 scannedDirs.put(name, new FTPFileProxy(file));
519 if (isFunctioningAsDirectory(ftp, dir, file)) {
520 boolean slowScanAllowed = true;
521 if (!isFollowSymlinks() && file.isSymbolicLink()) {
522 dirsExcluded.addElement(name);
523 slowScanAllowed = false;
524 } else if (isIncluded(name)) {
525 accountForIncludedDir(name,
526 new AntFTPFile(ftp, file, completePath) , fast);
527 } else {
528 dirsNotIncluded.addElement(name);
529 if (fast && couldHoldIncluded(name)) {
530 scandir(file.getName(),
531 name + File.separator, fast);
532 }
533 }
534 if (!fast && slowScanAllowed) {
535 scandir(file.getName(),
536 name + File.separator, fast);
537 }
538 } else {
539 if (!isFollowSymlinks() && file.isSymbolicLink()) {
540 filesExcluded.addElement(name);
541 } else if (isFunctioningAsFile(ftp, dir, file)) {
542 accountForIncludedFile(name);
543 }
544 }
545 }
546 }
547 ftp.changeToParentDirectory();
548 } catch (IOException e) {
549 throw new BuildException("Error while communicating with FTP "
550 + "server: ", e);
551 }
552 }
553 /**
554 * process included file
555 * @param name path of the file relative to the directory of the fileset
556 */
557 private void accountForIncludedFile(String name) {
558 if (!filesIncluded.contains(name)
559 && !filesExcluded.contains(name)) {
560
561 if (isIncluded(name)) {
562 if (!isExcluded(name)
563 && isSelected(name, (File) scannedDirs.get(name))) {
564 filesIncluded.addElement(name);
565 } else {
566 filesExcluded.addElement(name);
567 }
568 } else {
569 filesNotIncluded.addElement(name);
570 }
571 }
572 }
573
574 /**
575 *
576 * @param name path of the directory relative to the directory of
577 * the fileset
578 * @param file directory as file
579 * @param fast
580 */
581 private void accountForIncludedDir(String name, AntFTPFile file, boolean fast) {
582 if (!dirsIncluded.contains(name)
583 && !dirsExcluded.contains(name)) {
584
585 if (!isExcluded(name)) {
586 if (fast) {
587 if (file.isSymbolicLink()) {
588 try {
589 file.getClient().changeWorkingDirectory(file.curpwd);
590 } catch (IOException ioe) {
591 throw new BuildException("could not change directory to curpwd");
592 }
593 scandir(file.getLink(),
594 name + File.separator, fast);
595 } else {
596 try {
597 file.getClient().changeWorkingDirectory(file.curpwd);
598 } catch (IOException ioe) {
599 throw new BuildException("could not change directory to curpwd");
600 }
601 scandir(file.getName(),
602 name + File.separator, fast);
603 }
604 }
605 dirsIncluded.addElement(name);
606 } else {
607 dirsExcluded.addElement(name);
608 if (fast && couldHoldIncluded(name)) {
609 try {
610 file.getClient().changeWorkingDirectory(file.curpwd);
611 } catch (IOException ioe) {
612 throw new BuildException("could not change directory to curpwd");
613 }
614 scandir(file.getName(),
615 name + File.separator, fast);
616 }
617 }
618 }
619 }
620 /**
621 * temporary table to speed up the various scanning methods below
622 *
623 * @since Ant 1.6
624 */
625 private Map fileListMap = new HashMap();
626 /**
627 * List of all scanned directories.
628 *
629 * @since Ant 1.6
630 */
631
632 private Map scannedDirs = new HashMap();
633
634 /**
635 * Has the directory with the given path relative to the base
636 * directory already been scanned?
637 *
638 * @since Ant 1.6
639 */
640 private boolean hasBeenScanned(String vpath) {
641 return scannedDirs.containsKey(vpath);
642 }
643
644 /**
645 * Clear internal caches.
646 *
647 * @since Ant 1.6
648 */
649 private void clearCaches() {
650 fileListMap.clear();
651 scannedDirs.clear();
652 }
653 /**
654 * list the files present in one directory.
655 * @param directory full path on the remote side
656 * @param changedir if true change to directory directory before listing
657 * @return array of FTPFile
658 */
659 public FTPFile[] listFiles(String directory, boolean changedir) {
660 //getProject().log("listing files in directory " + directory, Project.MSG_DEBUG);
661 String currentPath = directory;
662 if (changedir) {
663 try {
664 boolean result = ftp.changeWorkingDirectory(directory);
665 if (!result) {
666 return null;
667 }
668 currentPath = ftp.printWorkingDirectory();
669 } catch (IOException ioe) {
670 throw new BuildException(ioe, getLocation());
671 }
672 }
673 if (fileListMap.containsKey(currentPath)) {
674 getProject().log("filelist map used in listing files", Project.MSG_DEBUG);
675 return ((FTPFile[]) fileListMap.get(currentPath));
676 }
677 FTPFile[] result = null;
678 try {
679 result = ftp.listFiles();
680 } catch (IOException ioe) {
681 throw new BuildException(ioe, getLocation());
682 }
683 fileListMap.put(currentPath, result);
684 if (!remoteSensitivityChecked) {
685 checkRemoteSensitivity(result, directory);
686 }
687 return result;
688 }
689
690 private void forceRemoteSensitivityCheck() {
691 if (!remoteSensitivityChecked) {
692 try {
693 checkRemoteSensitivity(ftp.listFiles(), ftp.printWorkingDirectory());
694 } catch (IOException ioe) {
695 throw new BuildException(ioe, getLocation());
696 }
697 }
698 }
699 /**
700 * cd into one directory and
701 * list the files present in one directory.
702 * @param directory full path on the remote side
703 * @return array of FTPFile
704 */
705 public FTPFile[] listFiles(String directory) {
706 return listFiles(directory, true);
707 }
708 private void checkRemoteSensitivity(FTPFile[] array, String directory) {
709 if (array == null) {
710 return;
711 }
712 boolean candidateFound = false;
713 String target = null;
714 for (int icounter = 0; icounter < array.length; icounter++) {
715 if (array[icounter] != null && array[icounter].isDirectory()) {
716 if (!array[icounter].getName().equals(".")
717 && !array[icounter].getName().equals("..")) {
718 candidateFound = true;
719 target = fiddleName(array[icounter].getName());
720 getProject().log("will try to cd to "
721 + target + " where a directory called " + array[icounter].getName()
722 + " exists", Project.MSG_DEBUG);
723 for (int pcounter = 0; pcounter < array.length; pcounter++) {
724 if (array[pcounter] != null
725 && pcounter != icounter
726 && target.equals(array[pcounter].getName())) {
727 candidateFound = false;
728 }
729 }
730 if (candidateFound) {
731 break;
732 }
733 }
734 }
735 }
736 if (candidateFound) {
737 try {
738 getProject().log("testing case sensitivity, attempting to cd to "
739 + target, Project.MSG_DEBUG);
740 remoteSystemCaseSensitive = !ftp.changeWorkingDirectory(target);
741 } catch (IOException ioe) {
742 remoteSystemCaseSensitive = true;
743 } finally {
744 try {
745 ftp.changeWorkingDirectory(directory);
746 } catch (IOException ioe) {
747 throw new BuildException(ioe, getLocation());
748 }
749 }
750 getProject().log("remote system is case sensitive : " + remoteSystemCaseSensitive,
751 Project.MSG_VERBOSE);
752 remoteSensitivityChecked = true;
753 }
754 }
755 private String fiddleName(String origin) {
756 StringBuffer result = new StringBuffer();
757 for (int icounter = 0; icounter < origin.length(); icounter++) {
758 if (Character.isLowerCase(origin.charAt(icounter))) {
759 result.append(Character.toUpperCase(origin.charAt(icounter)));
760 } else if (Character.isUpperCase(origin.charAt(icounter))) {
761 result.append(Character.toLowerCase(origin.charAt(icounter)));
762 } else {
763 result.append(origin.charAt(icounter));
764 }
765 }
766 return result.toString();
767 }
768 /**
769 * an AntFTPFile is a representation of a remote file
770 * @since Ant 1.6
771 */
772 protected class AntFTPFile {
773 /**
774 * ftp client
775 */
776 private FTPClient client;
777 /**
778 * parent directory of the file
779 */
780 private String curpwd;
781 /**
782 * the file itself
783 */
784 private FTPFile ftpFile;
785 /**
786 *
787 */
788 private AntFTPFile parent = null;
789 private boolean relativePathCalculated = false;
790 private boolean traversesSymlinks = false;
791 private String relativePath = "";
792 /**
793 * constructor
794 * @param client ftp client variable
795 * @param ftpFile the file
796 * @param curpwd absolute remote path where the file is found
797 */
798 public AntFTPFile(FTPClient client, FTPFile ftpFile, String curpwd) {
799 this.client = client;
800 this.ftpFile = ftpFile;
801 this.curpwd = curpwd;
802 }
803 /**
804 * other constructor
805 * @param parent the parent file
806 * @param path a relative path to the parent file
807 */
808 public AntFTPFile(AntFTPFile parent, String path) {
809 this.parent = parent;
810 this.client = parent.client;
811 Vector pathElements = SelectorUtils.tokenizePath(path);
812 try {
813 boolean result = this.client.changeWorkingDirectory(parent.getAbsolutePath());
814 //this should not happen, except if parent has been deleted by another process
815 if (!result) {
816 return;
817 }
818 this.curpwd = parent.getAbsolutePath();
819 } catch (IOException ioe) {
820 throw new BuildException("could not change working dir to "
821 + parent.curpwd);
822 }
823 for (int fcount = 0; fcount < pathElements.size() - 1; fcount++) {
824 String currentPathElement = (String) pathElements.elementAt(fcount);
825 try {
826 boolean result = this.client.changeWorkingDirectory(currentPathElement);
827 if (!result && !isCaseSensitive()
828 && (remoteSystemCaseSensitive || !remoteSensitivityChecked)) {
829 currentPathElement = findPathElementCaseUnsensitive(this.curpwd,
830 currentPathElement);
831 if (currentPathElement == null) {
832 return;
833 }
834 } else if (!result) {
835 return;
836 }
837 this.curpwd = this.curpwd + remoteFileSep
838 + currentPathElement;
839 } catch (IOException ioe) {
840 throw new BuildException("could not change working dir to "
841 + (String) pathElements.elementAt(fcount)
842 + " from " + this.curpwd);
843 }
844
845 }
846 String lastpathelement = (String) pathElements.elementAt(pathElements.size() - 1);
847 FTPFile [] theFiles = listFiles(this.curpwd);
848 this.ftpFile = getFile(theFiles, lastpathelement);
849 }
850 /**
851 * find a file in a directory in case unsensitive way
852 * @param parentPath where we are
853 * @param soughtPathElement what is being sought
854 * @return the first file found or null if not found
855 */
856 private String findPathElementCaseUnsensitive(String parentPath,
857 String soughtPathElement) {
858 // we are already in the right path, so the second parameter
859 // is false
860 FTPFile[] theFiles = listFiles(parentPath, false);
861 if (theFiles == null) {
862 return null;
863 }
864 for (int icounter = 0; icounter < theFiles.length; icounter++) {
865 if (theFiles[icounter] != null
866 && theFiles[icounter].getName().equalsIgnoreCase(soughtPathElement)) {
867 return theFiles[icounter].getName();
868 }
869 }
870 return null;
871 }
872 /**
873 * find out if the file exists
874 * @return true if the file exists
875 */
876 public boolean exists() {
877 return (ftpFile != null);
878 }
879 /**
880 * if the file is a symbolic link, find out to what it is pointing
881 * @return the target of the symbolic link
882 */
883 public String getLink() {
884 return ftpFile.getLink();
885 }
886 /**
887 * get the name of the file
888 * @return the name of the file
889 */
890 public String getName() {
891 return ftpFile.getName();
892 }
893 /**
894 * find out the absolute path of the file
895 * @return absolute path as string
896 */
897 public String getAbsolutePath() {
898 return curpwd + remoteFileSep + ftpFile.getName();
899 }
900 /**
901 * find out the relative path assuming that the path used to construct
902 * this AntFTPFile was spelled properly with regards to case.
903 * This is OK on a case sensitive system such as UNIX
904 * @return relative path
905 */
906 public String getFastRelativePath() {
907 String absPath = getAbsolutePath();
908 if (absPath.indexOf(rootPath + remoteFileSep) == 0) {
909 return absPath.substring(rootPath.length() + remoteFileSep.length());
910 }
911 return null;
912 }
913 /**
914 * find out the relative path to the rootPath of the enclosing scanner.
915 * this relative path is spelled exactly like on disk,
916 * for instance if the AntFTPFile has been instantiated as ALPHA,
917 * but the file is really called alpha, this method will return alpha.
918 * If a symbolic link is encountered, it is followed, but the name of the link
919 * rather than the name of the target is returned.
920 * (ie does not behave like File.getCanonicalPath())
921 * @return relative path, separated by remoteFileSep
922 * @throws IOException if a change directory fails, ...
923 * @throws BuildException if one of the components of the relative path cannot
924 * be found.
925 */
926 public String getRelativePath() throws IOException, BuildException {
927 if (!relativePathCalculated) {
928 if (parent != null) {
929 traversesSymlinks = parent.isTraverseSymlinks();
930 relativePath = getRelativePath(parent.getAbsolutePath(),
931 parent.getRelativePath());
932 } else {
933 relativePath = getRelativePath(rootPath, "");
934 relativePathCalculated = true;
935 }
936 }
937 return relativePath;
938 }
939 /**
940 * get thge relative path of this file
941 * @param currentPath base path
942 * @param currentRelativePath relative path of the base path with regards to remote dir
943 * @return relative path
944 */
945 private String getRelativePath(String currentPath, String currentRelativePath) {
946 Vector pathElements = SelectorUtils.tokenizePath(getAbsolutePath(), remoteFileSep);
947 Vector pathElements2 = SelectorUtils.tokenizePath(currentPath, remoteFileSep);
948 String relPath = currentRelativePath;
949 for (int pcount = pathElements2.size(); pcount < pathElements.size(); pcount++) {
950 String currentElement = (String) pathElements.elementAt(pcount);
951 FTPFile[] theFiles = listFiles(currentPath);
952 FTPFile theFile = null;
953 if (theFiles != null) {
954 theFile = getFile(theFiles, currentElement);
955 }
956 if (!relPath.equals("")) {
957 relPath = relPath + remoteFileSep;
958 }
959 if (theFile == null) {
960 // hit a hidden file assume not a symlink
961 relPath = relPath + currentElement;
962 currentPath = currentPath + remoteFileSep + currentElement;
963 log("Hidden file " + relPath
964 + " assumed to not be a symlink.",
965 Project.MSG_VERBOSE);
966 } else {
967 traversesSymlinks = traversesSymlinks || theFile.isSymbolicLink();
968 relPath = relPath + theFile.getName();
969 currentPath = currentPath + remoteFileSep + theFile.getName();
970 }
971 }
972 return relPath;
973 }
974 /**
975 * find a file matching a string in an array of FTPFile.
976 * This method will find "alpha" when requested for "ALPHA"
977 * if and only if the caseSensitive attribute is set to false.
978 * When caseSensitive is set to true, only the exact match is returned.
979 * @param theFiles array of files
980 * @param lastpathelement the file name being sought
981 * @return null if the file cannot be found, otherwise return the matching file.
982 */
983 public FTPFile getFile(FTPFile[] theFiles, String lastpathelement) {
984 if (theFiles == null) {
985 return null;
986 }
987 for (int fcount = 0; fcount < theFiles.length; fcount++) {
988 if (theFiles[fcount] != null) {
989 if (theFiles[fcount].getName().equals(lastpathelement)) {
990 return theFiles[fcount];
991 } else if (!isCaseSensitive()
992 && theFiles[fcount].getName().equalsIgnoreCase(
993 lastpathelement)) {
994 return theFiles[fcount];
995 }
996 }
997 }
998 return null;
999 }
1000 /**
1001 * tell if a file is a directory.
1002 * note that it will return false for symbolic links pointing to directories.
1003 * @return <code>true</code> for directories
1004 */
1005 public boolean isDirectory() {
1006 return ftpFile.isDirectory();
1007 }
1008 /**
1009 * tell if a file is a symbolic link
1010 * @return <code>true</code> for symbolic links
1011 */
1012 public boolean isSymbolicLink() {
1013 return ftpFile.isSymbolicLink();
1014 }
1015 /**
1016 * return the attached FTP client object.
1017 * Warning : this instance is really shared with the enclosing class.
1018 * @return FTP client
1019 */
1020 protected FTPClient getClient() {
1021 return client;
1022 }
1023
1024 /**
1025 * sets the current path of an AntFTPFile
1026 * @param curpwd the current path one wants to set
1027 */
1028 protected void setCurpwd(String curpwd) {
1029 this.curpwd = curpwd;
1030 }
1031 /**
1032 * returns the path of the directory containing the AntFTPFile.
1033 * of the full path of the file itself in case of AntFTPRootFile
1034 * @return parent directory of the AntFTPFile
1035 */
1036 public String getCurpwd() {
1037 return curpwd;
1038 }
1039 /**
1040 * find out if a symbolic link is encountered in the relative path of this file
1041 * from rootPath.
1042 * @return <code>true</code> if a symbolic link is encountered in the relative path.
1043 * @throws IOException if one of the change directory or directory listing operations
1044 * fails
1045 * @throws BuildException if a path component in the relative path cannot be found.
1046 */
1047 public boolean isTraverseSymlinks() throws IOException, BuildException {
1048 if (!relativePathCalculated) {
1049 // getRelativePath also finds about symlinks
1050 getRelativePath();
1051 }
1052 return traversesSymlinks;
1053 }
1054
1055 /**
1056 * Get a string rep of this object.
1057 * @return a string containing the pwd and the file.
1058 */
1059 public String toString() {
1060 return "AntFtpFile: " + curpwd + "%" + ftpFile;
1061 }
1062 }
1063 /**
1064 * special class to represent the remote directory itself
1065 * @since Ant 1.6
1066 */
1067 protected class AntFTPRootFile extends AntFTPFile {
1068 private String remotedir;
1069 /**
1070 * constructor
1071 * @param aclient FTP client
1072 * @param remotedir remote directory
1073 */
1074 public AntFTPRootFile(FTPClient aclient, String remotedir) {
1075 super(aclient, null, remotedir);
1076 this.remotedir = remotedir;
1077 try {
1078 this.getClient().changeWorkingDirectory(this.remotedir);
1079 this.setCurpwd(this.getClient().printWorkingDirectory());
1080 } catch (IOException ioe) {
1081 throw new BuildException(ioe, getLocation());
1082 }
1083 }
1084 /**
1085 * find the absolute path
1086 * @return absolute path
1087 */
1088 public String getAbsolutePath() {
1089 return this.getCurpwd();
1090 }
1091 /**
1092 * find out the relative path to root
1093 * @return empty string
1094 * @throws BuildException actually never
1095 * @throws IOException actually never
1096 */
1097 public String getRelativePath() throws BuildException, IOException {
1098 return "";
1099 }
1100 }
1101 }
1102 /**
1103 * check FTPFiles to check whether they function as directories too
1104 * the FTPFile API seem to make directory and symbolic links incompatible
1105 * we want to find out if we can cd to a symbolic link
1106 * @param dir the parent directory of the file to test
1107 * @param file the file to test
1108 * @return true if it is possible to cd to this directory
1109 * @since ant 1.6
1110 */
1111 private boolean isFunctioningAsDirectory(FTPClient ftp, String dir, FTPFile file) {
1112 boolean result = false;
1113 String currentWorkingDir = null;
1114 if (file.isDirectory()) {
1115 return true;
1116 } else if (file.isFile()) {
1117 return false;
1118 }
1119 try {
1120 currentWorkingDir = ftp.printWorkingDirectory();
1121 } catch (IOException ioe) {
1122 getProject().log("could not find current working directory " + dir
1123 + " while checking a symlink",
1124 Project.MSG_DEBUG);
1125 }
1126 if (currentWorkingDir != null) {
1127 try {
1128 result = ftp.changeWorkingDirectory(file.getLink());
1129 } catch (IOException ioe) {
1130 getProject().log("could not cd to " + file.getLink() + " while checking a symlink",
1131 Project.MSG_DEBUG);
1132 }
1133 if (result) {
1134 boolean comeback = false;
1135 try {
1136 comeback = ftp.changeWorkingDirectory(currentWorkingDir);
1137 } catch (IOException ioe) {
1138 getProject().log("could not cd back to " + dir + " while checking a symlink",
1139 Project.MSG_ERR);
1140 } finally {
1141 if (!comeback) {
1142 throw new BuildException("could not cd back to " + dir
1143 + " while checking a symlink");
1144 }
1145 }
1146 }
1147 }
1148 return result;
1149 }
1150 /**
1151 * check FTPFiles to check whether they function as directories too
1152 * the FTPFile API seem to make directory and symbolic links incompatible
1153 * we want to find out if we can cd to a symbolic link
1154 * @param dir the parent directory of the file to test
1155 * @param file the file to test
1156 * @return true if it is possible to cd to this directory
1157 * @since ant 1.6
1158 */
1159 private boolean isFunctioningAsFile(FTPClient ftp, String dir, FTPFile file) {
1160 if (file.isDirectory()) {
1161 return false;
1162 } else if (file.isFile()) {
1163 return true;
1164 }
1165 return !isFunctioningAsDirectory(ftp, dir, file);
1166 }
1167 /**
1168 * Sets the remote directory where files will be placed. This may be a
1169 * relative or absolute path, and must be in the path syntax expected by
1170 * the remote server. No correction of path syntax will be performed.
1171 *
1172 * @param dir the remote directory name.
1173 */
1174 public void setRemotedir(String dir) {
1175 this.remotedir = dir;
1176 }
1177
1178
1179 /**
1180 * Sets the FTP server to send files to.
1181 *
1182 * @param server the remote server name.
1183 */
1184 public void setServer(String server) {
1185 this.server = server;
1186 }
1187
1188
1189 /**
1190 * Sets the FTP port used by the remote server.
1191 *
1192 * @param port the port on which the remote server is listening.
1193 */
1194 public void setPort(int port) {
1195 this.port = port;
1196 }
1197
1198
1199 /**
1200 * Sets the login user id to use on the specified server.
1201 *
1202 * @param userid remote system userid.
1203 */
1204 public void setUserid(String userid) {
1205 this.userid = userid;
1206 }
1207
1208
1209 /**
1210 * Sets the login password for the given user id.
1211 *
1212 * @param password the password on the remote system.
1213 */
1214 public void setPassword(String password) {
1215 this.password = password;
1216 }
1217
1218 /**
1219 * Sets the login account to use on the specified server.
1220 *
1221 * @param pAccount the account name on remote system
1222 * @since Ant 1.7
1223 */
1224 public void setAccount(String pAccount) {
1225 this.account = pAccount;
1226 }
1227
1228
1229 /**
1230 * If true, uses binary mode, otherwise text mode (default is binary).
1231 *
1232 * @param binary if true use binary mode in transfers.
1233 */
1234 public void setBinary(boolean binary) {
1235 this.binary = binary;
1236 }
1237
1238
1239 /**
1240 * Specifies whether to use passive mode. Set to true if you are behind a
1241 * firewall and cannot connect without it. Passive mode is disabled by
1242 * default.
1243 *
1244 * @param passive true is passive mode should be used.
1245 */
1246 public void setPassive(boolean passive) {
1247 this.passive = passive;
1248 }
1249
1250
1251 /**
1252 * Set to true to receive notification about each file as it is
1253 * transferred.
1254 *
1255 * @param verbose true if verbose notifications are required.
1256 */
1257 public void setVerbose(boolean verbose) {
1258 this.verbose = verbose;
1259 }
1260
1261
1262 /**
1263 * A synonym for <tt>depends</tt>. Set to true to transmit only new
1264 * or changed files.
1265 *
1266 * See the related attributes timediffmillis and timediffauto.
1267 *
1268 * @param newer if true only transfer newer files.
1269 */
1270 public void setNewer(boolean newer) {
1271 this.newerOnly = newer;
1272 }
1273
1274 /**
1275 * number of milliseconds to add to the time on the remote machine
1276 * to get the time on the local machine.
1277 *
1278 * use in conjunction with <code>newer</code>
1279 *
1280 * @param timeDiffMillis number of milliseconds
1281 *
1282 * @since ant 1.6
1283 */
1284 public void setTimeDiffMillis(long timeDiffMillis) {
1285 this.timeDiffMillis = timeDiffMillis;
1286 }
1287
1288 /**
1289 * "true" to find out automatically the time difference
1290 * between local and remote machine.
1291 *
1292 * This requires right to create
1293 * and delete a temporary file in the remote directory.
1294 *
1295 * @param timeDiffAuto true = find automatically the time diff
1296 *
1297 * @since ant 1.6
1298 */
1299 public void setTimeDiffAuto(boolean timeDiffAuto) {
1300 this.timeDiffAuto = timeDiffAuto;
1301 }
1302
1303 /**
1304 * Set to true to preserve modification times for "gotten" files.
1305 *
1306 * @param preserveLastModified if true preserver modification times.
1307 */
1308 public void setPreserveLastModified(boolean preserveLastModified) {
1309 this.preserveLastModified = preserveLastModified;
1310 }
1311
1312
1313 /**
1314 * Set to true to transmit only files that are new or changed from their
1315 * remote counterparts. The default is to transmit all files.
1316 *
1317 * @param depends if true only transfer newer files.
1318 */
1319 public void setDepends(boolean depends) {
1320 this.newerOnly = depends;
1321 }
1322
1323
1324 /**
1325 * Sets the remote file separator character. This normally defaults to the
1326 * Unix standard forward slash, but can be manually overridden using this
1327 * call if the remote server requires some other separator. Only the first
1328 * character of the string is used.
1329 *
1330 * @param separator the file separator on the remote system.
1331 */
1332 public void setSeparator(String separator) {
1333 remoteFileSep = separator;
1334 }
1335
1336
1337 /**
1338 * Sets the file permission mode (Unix only) for files sent to the
1339 * server.
1340 *
1341 * @param theMode unix style file mode for the files sent to the remote
1342 * system.
1343 */
1344 public void setChmod(String theMode) {
1345 this.chmod = theMode;
1346 }
1347
1348
1349 /**
1350 * Sets the default mask for file creation on a unix server.
1351 *
1352 * @param theUmask unix style umask for files created on the remote server.
1353 */
1354 public void setUmask(String theUmask) {
1355 this.umask = theUmask;
1356 }
1357
1358
1359 /**
1360 * A set of files to upload or download
1361 *
1362 * @param set the set of files to be added to the list of files to be
1363 * transferred.
1364 */
1365 public void addFileset(FileSet set) {
1366 filesets.addElement(set);
1367 }
1368
1369
1370 /**
1371 * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
1372 * "mkdir", "chmod", "list", and "site".
1373 *
1374 * @deprecated since 1.5.x.
1375 * setAction(String) is deprecated and is replaced with
1376 * setAction(FTP.Action) to make Ant's Introspection mechanism do the
1377 * work and also to encapsulate operations on the type in its own
1378 * class.
1379 * @ant.attribute ignore="true"
1380 *
1381 * @param action the FTP action to be performed.
1382 *
1383 * @throws BuildException if the action is not a valid action.
1384 */
1385 public void setAction(String action) throws BuildException {
1386 log("DEPRECATED - The setAction(String) method has been deprecated."
1387 + " Use setAction(FTP.Action) instead.");
1388
1389 Action a = new Action();
1390
1391 a.setValue(action);
1392 this.action = a.getAction();
1393 }
1394
1395
1396 /**
1397 * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
1398 * "mkdir", "chmod", "list", and "site".
1399 *
1400 * @param action the FTP action to be performed.
1401 *
1402 * @throws BuildException if the action is not a valid action.
1403 */
1404 public void setAction(Action action) throws BuildException {
1405 this.action = action.getAction();
1406 }
1407
1408
1409 /**
1410 * The output file for the "list" action. This attribute is ignored for
1411 * any other actions.
1412 *
1413 * @param listing file in which to store the listing.
1414 */
1415 public void setListing(File listing) {
1416 this.listing = listing;
1417 }
1418
1419
1420 /**
1421 * If true, enables unsuccessful file put, delete and get
1422 * operations to be skipped with a warning and the remainder
1423 * of the files still transferred.
1424 *
1425 * @param skipFailedTransfers true if failures in transfers are ignored.
1426 */
1427 public void setSkipFailedTransfers(boolean skipFailedTransfers) {
1428 this.skipFailedTransfers = skipFailedTransfers;
1429 }
1430
1431
1432 /**
1433 * set the flag to skip errors on directory creation.
1434 * (and maybe later other server specific errors)
1435 *
1436 * @param ignoreNoncriticalErrors true if non-critical errors should not
1437 * cause a failure.
1438 */
1439 public void setIgnoreNoncriticalErrors(boolean ignoreNoncriticalErrors) {
1440 this.ignoreNoncriticalErrors = ignoreNoncriticalErrors;
1441 }
1442
1443 private void configurationHasBeenSet() {
1444 this.isConfigurationSet = true;
1445 }
1446
1447 /**
1448 * Sets the systemTypeKey attribute.
1449 * Method for setting <code>FTPClientConfig</code> remote system key.
1450 *
1451 * @param systemKey the key to be set - BUT if blank
1452 * the default value of null (which signifies "autodetect") will be kept.
1453 * @see org.apache.commons.net.ftp.FTPClientConfig
1454 */
1455 public void setSystemTypeKey(FTPSystemType systemKey) {
1456 if (systemKey != null && !systemKey.getValue().equals("")) {
1457 this.systemTypeKey = systemKey;
1458 configurationHasBeenSet();
1459 }
1460 }
1461
1462 /**
1463 * Sets the defaultDateFormatConfig attribute.
1464 * @param defaultDateFormat configuration to be set, unless it is
1465 * null or empty string, in which case ignored.
1466 * @see org.apache.commons.net.ftp.FTPClientConfig
1467 */
1468 public void setDefaultDateFormatConfig(String defaultDateFormat) {
1469 if (defaultDateFormat != null && !defaultDateFormat.equals("")) {
1470 this.defaultDateFormatConfig = defaultDateFormat;
1471 configurationHasBeenSet();
1472 }
1473 }
1474
1475 /**
1476 * Sets the recentDateFormatConfig attribute.
1477 * @param recentDateFormat configuration to be set, unless it is
1478 * null or empty string, in which case ignored.
1479 * @see org.apache.commons.net.ftp.FTPClientConfig
1480 */
1481 public void setRecentDateFormatConfig(String recentDateFormat) {
1482 if (recentDateFormat != null && !recentDateFormat.equals("")) {
1483 this.recentDateFormatConfig = recentDateFormat;
1484 configurationHasBeenSet();
1485 }
1486 }
1487
1488 /**
1489 * Sets the serverLanguageCode attribute.
1490 * @param serverLanguageCode configuration to be set, unless it is
1491 * null or empty string, in which case ignored.
1492 * @see org.apache.commons.net.ftp.FTPClientConfig
1493 */
1494 public void setServerLanguageCodeConfig(LanguageCode serverLanguageCode) {
1495 if (serverLanguageCode != null && !"".equals(serverLanguageCode.getValue())) {
1496 this.serverLanguageCodeConfig = serverLanguageCode;
1497 configurationHasBeenSet();
1498 }
1499 }
1500
1501 /**
1502 * Sets the serverTimeZoneConfig attribute.
1503 * @param serverTimeZoneId configuration to be set, unless it is
1504 * null or empty string, in which case ignored.
1505 * @see org.apache.commons.net.ftp.FTPClientConfig
1506 */
1507 public void setServerTimeZoneConfig(String serverTimeZoneId) {
1508 if (serverTimeZoneId != null && !serverTimeZoneId.equals("")) {
1509 this.serverTimeZoneConfig = serverTimeZoneId;
1510 configurationHasBeenSet();
1511 }
1512 }
1513
1514 /**
1515 * Sets the shortMonthNamesConfig attribute
1516 *
1517 * @param shortMonthNames configuration to be set, unless it is
1518 * null or empty string, in which case ignored.
1519 * @see org.apache.commons.net.ftp.FTPClientConfig
1520 */
1521 public void setShortMonthNamesConfig(String shortMonthNames) {
1522 if (shortMonthNames != null && !shortMonthNames.equals("")) {
1523 this.shortMonthNamesConfig = shortMonthNames;
1524 configurationHasBeenSet();
1525 }
1526 }
1527
1528
1529
1530 /**
1531 * Defines how many times to retry executing FTP command before giving up.
1532 * Default is 0 - try once and if failure then give up.
1533 *
1534 * @param retriesAllowed number of retries to allow. -1 means
1535 * keep trying forever. "forever" may also be specified as a
1536 * synonym for -1.
1537 */
1538 public void setRetriesAllowed(String retriesAllowed) {
1539 if ("FOREVER".equalsIgnoreCase(retriesAllowed)) {
1540 this.retriesAllowed = Retryable.RETRY_FOREVER;
1541 } else {
1542 try {
1543 int retries = Integer.parseInt(retriesAllowed);
1544 if (retries < Retryable.RETRY_FOREVER) {
1545 throw new BuildException(
1546 "Invalid value for retriesAllowed attribute: "
1547 + retriesAllowed);
1548
1549 }
1550 this.retriesAllowed = retries;
1551 } catch (NumberFormatException px) {
1552 throw new BuildException(
1553 "Invalid value for retriesAllowed attribute: "
1554 + retriesAllowed);
1555
1556 }
1557
1558 }
1559 }
1560 /**
1561 * @return Returns the systemTypeKey.
1562 */
1563 public String getSystemTypeKey() {
1564 return systemTypeKey.getValue();
1565 }
1566 /**
1567 * @return Returns the defaultDateFormatConfig.
1568 */
1569 public String getDefaultDateFormatConfig() {
1570 return defaultDateFormatConfig;
1571 }
1572 /**
1573 * @return Returns the recentDateFormatConfig.
1574 */
1575 public String getRecentDateFormatConfig() {
1576 return recentDateFormatConfig;
1577 }
1578 /**
1579 * @return Returns the serverLanguageCodeConfig.
1580 */
1581 public String getServerLanguageCodeConfig() {
1582 return serverLanguageCodeConfig.getValue();
1583 }
1584 /**
1585 * @return Returns the serverTimeZoneConfig.
1586 */
1587 public String getServerTimeZoneConfig() {
1588 return serverTimeZoneConfig;
1589 }
1590 /**
1591 * @return Returns the shortMonthNamesConfig.
1592 */
1593 public String getShortMonthNamesConfig() {
1594 return shortMonthNamesConfig;
1595 }
1596 /**
1597 * @return Returns the timestampGranularity.
1598 */
1599 Granularity getTimestampGranularity() {
1600 return timestampGranularity;
1601 }
1602 /**
1603 * Sets the timestampGranularity attribute
1604 * @param timestampGranularity The timestampGranularity to set.
1605 */
1606 public void setTimestampGranularity(Granularity timestampGranularity) {
1607 if (null == timestampGranularity || "".equals(timestampGranularity.getValue())) {
1608 return;
1609 }
1610 this.timestampGranularity = timestampGranularity;
1611 }
1612 /**
1613 * Sets the siteCommand attribute. This attribute
1614 * names the command that will be executed if the action
1615 * is "site".
1616 * @param siteCommand The siteCommand to set.
1617 */
1618 public void setSiteCommand(String siteCommand) {
1619 this.siteCommand = siteCommand;
1620 }
1621 /**
1622 * Sets the initialSiteCommand attribute. This attribute
1623 * names a site command that will be executed immediately
1624 * after connection.
1625 * @param initialCommand The initialSiteCommand to set.
1626 */
1627 public void setInitialSiteCommand(String initialCommand) {
1628 this.initialSiteCommand = initialCommand;
1629 }
1630
1631 /**
1632 * Whether to verify that data and control connections are
1633 * connected to the same remote host.
1634 *
1635 * @since Ant 1.8.0
1636 */
1637 public void setEnableRemoteVerification(boolean b) {
1638 enableRemoteVerification = b;
1639 }
1640
1641 /**
1642 * Checks to see that all required parameters are set.
1643 *
1644 * @throws BuildException if the configuration is not valid.
1645 */
1646 protected void checkAttributes() throws BuildException {
1647 if (server == null) {
1648 throw new BuildException("server attribute must be set!");
1649 }
1650 if (userid == null) {
1651 throw new BuildException("userid attribute must be set!");
1652 }
1653 if (password == null) {
1654 throw new BuildException("password attribute must be set!");
1655 }
1656
1657 if ((action == LIST_FILES) && (listing == null)) {
1658 throw new BuildException("listing attribute must be set for list "
1659 + "action!");
1660 }
1661
1662 if (action == MK_DIR && remotedir == null) {
1663 throw new BuildException("remotedir attribute must be set for "
1664 + "mkdir action!");
1665 }
1666
1667 if (action == CHMOD && chmod == null) {
1668 throw new BuildException("chmod attribute must be set for chmod "
1669 + "action!");
1670 }
1671 if (action == SITE_CMD && siteCommand == null) {
1672 throw new BuildException("sitecommand attribute must be set for site "
1673 + "action!");
1674 }
1675
1676
1677 if (this.isConfigurationSet) {
1678 try {
1679 Class.forName("org.apache.commons.net.ftp.FTPClientConfig");
1680 } catch (ClassNotFoundException e) {
1681 throw new BuildException(
1682 "commons-net.jar >= 1.4.0 is required for at least one"
1683 + " of the attributes specified.");
1684 }
1685 }
1686 }
1687
1688 /**
1689 * Executable a retryable object.
1690 * @param h the retry hander.
1691 * @param r the object that should be retried until it succeeds
1692 * or the number of retrys is reached.
1693 * @param descr a description of the command that is being run.
1694 * @throws IOException if there is a problem.
1695 */
1696 protected void executeRetryable(RetryHandler h, Retryable r, String descr)
1697 throws IOException {
1698 h.execute(r, descr);
1699 }
1700
1701
1702 /**
1703 * For each file in the fileset, do the appropriate action: send, get,
1704 * delete, or list.
1705 *
1706 * @param ftp the FTPClient instance used to perform FTP actions
1707 * @param fs the fileset on which the actions are performed.
1708 *
1709 * @return the number of files to be transferred.
1710 *
1711 * @throws IOException if there is a problem reading a file
1712 * @throws BuildException if there is a problem in the configuration.
1713 */
1714 protected int transferFiles(final FTPClient ftp, FileSet fs)
1715 throws IOException, BuildException {
1716 DirectoryScanner ds;
1717 if (action == SEND_FILES) {
1718 ds = fs.getDirectoryScanner(getProject());
1719 } else {
1720 ds = new FTPDirectoryScanner(ftp);
1721 fs.setupDirectoryScanner(ds, getProject());
1722 ds.setFollowSymlinks(fs.isFollowSymlinks());
1723 ds.scan();
1724 }
1725
1726 String[] dsfiles = null;
1727 if (action == RM_DIR) {
1728 dsfiles = ds.getIncludedDirectories();
1729 } else {
1730 dsfiles = ds.getIncludedFiles();
1731 }
1732 String dir = null;
1733
1734 if ((ds.getBasedir() == null)
1735 && ((action == SEND_FILES) || (action == GET_FILES))) {
1736 throw new BuildException("the dir attribute must be set for send "
1737 + "and get actions");
1738 } else {
1739 if ((action == SEND_FILES) || (action == GET_FILES)) {
1740 dir = ds.getBasedir().getAbsolutePath();
1741 }
1742 }
1743
1744 // If we are doing a listing, we need the output stream created now.
1745 BufferedWriter bw = null;
1746
1747 try {
1748 if (action == LIST_FILES) {
1749 File pd = listing.getParentFile();
1750
1751 if (!pd.exists()) {
1752 pd.mkdirs();
1753 }
1754 bw = new BufferedWriter(new FileWriter(listing));
1755 }
1756 RetryHandler h = new RetryHandler(this.retriesAllowed, this);
1757 if (action == RM_DIR) {
1758 // to remove directories, start by the end of the list
1759 // the trunk does not let itself be removed before the leaves
1760 for (int i = dsfiles.length - 1; i >= 0; i--) {
1761 final String dsfile = dsfiles[i];
1762 executeRetryable(h, new Retryable() {
1763 public void execute() throws IOException {
1764 rmDir(ftp, dsfile);
1765 }
1766 }, dsfile);
1767 }
1768 } else {
1769 final BufferedWriter fbw = bw;
1770 final String fdir = dir;
1771 if (this.newerOnly) {
1772 this.granularityMillis =
1773 this.timestampGranularity.getMilliseconds(action);
1774 }
1775 for (int i = 0; i < dsfiles.length; i++) {
1776 final String dsfile = dsfiles[i];
1777 executeRetryable(h, new Retryable() {
1778 public void execute() throws IOException {
1779 switch (action) {
1780 case SEND_FILES:
1781 sendFile(ftp, fdir, dsfile);
1782 break;
1783 case GET_FILES:
1784 getFile(ftp, fdir, dsfile);
1785 break;
1786 case DEL_FILES:
1787 delFile(ftp, dsfile);
1788 break;
1789 case LIST_FILES:
1790 listFile(ftp, fbw, dsfile);
1791 break;
1792 case CHMOD:
1793 doSiteCommand(ftp, "chmod " + chmod
1794 + " " + resolveFile(dsfile));
1795 transferred++;
1796 break;
1797 default:
1798 throw new BuildException("unknown ftp action " + action);
1799 }
1800 }
1801 }, dsfile);
1802 }
1803 }
1804 } finally {
1805 FileUtils.close(bw);
1806 }
1807
1808 return dsfiles.length;
1809 }
1810
1811
1812 /**
1813 * Sends all files specified by the configured filesets to the remote
1814 * server.
1815 *
1816 * @param ftp the FTPClient instance used to perform FTP actions
1817 *
1818 * @throws IOException if there is a problem reading a file
1819 * @throws BuildException if there is a problem in the configuration.
1820 */
1821 protected void transferFiles(FTPClient ftp)
1822 throws IOException, BuildException {
1823 transferred = 0;
1824 skipped = 0;
1825
1826 if (filesets.size() == 0) {
1827 throw new BuildException("at least one fileset must be specified.");
1828 } else {
1829 // get files from filesets
1830 for (int i = 0; i < filesets.size(); i++) {
1831 FileSet fs = (FileSet) filesets.elementAt(i);
1832
1833 if (fs != null) {
1834 transferFiles(ftp, fs);
1835 }
1836 }
1837 }
1838
1839 log(transferred + " " + ACTION_TARGET_STRS[action] + " "
1840 + COMPLETED_ACTION_STRS[action]);
1841 if (skipped != 0) {
1842 log(skipped + " " + ACTION_TARGET_STRS[action]
1843 + " were not successfully " + COMPLETED_ACTION_STRS[action]);
1844 }
1845 }
1846
1847
1848 /**
1849 * Correct a file path to correspond to the remote host requirements. This
1850 * implementation currently assumes that the remote end can handle
1851 * Unix-style paths with forward-slash separators. This can be overridden
1852 * with the <code>separator</code> task parameter. No attempt is made to
1853 * determine what syntax is appropriate for the remote host.
1854 *
1855 * @param file the remote file name to be resolved
1856 *
1857 * @return the filename as it will appear on the server.
1858 */
1859 protected String resolveFile(String file) {
1860 return file.replace(System.getProperty("file.separator").charAt(0),
1861 remoteFileSep.charAt(0));
1862 }
1863
1864
1865 /**
1866 * Creates all parent directories specified in a complete relative
1867 * pathname. Attempts to create existing directories will not cause
1868 * errors.
1869 *
1870 * @param ftp the FTP client instance to use to execute FTP actions on
1871 * the remote server.
1872 * @param filename the name of the file whose parents should be created.
1873 * @throws IOException under non documented circumstances
1874 * @throws BuildException if it is impossible to cd to a remote directory
1875 *
1876 */
1877 protected void createParents(FTPClient ftp, String filename)
1878 throws IOException, BuildException {
1879
1880 File dir = new File(filename);
1881 if (dirCache.contains(dir)) {
1882 return;
1883 }
1884
1885 Vector parents = new Vector();
1886 String dirname;
1887
1888 while ((dirname = dir.getParent()) != null) {
1889 File checkDir = new File(dirname);
1890 if (dirCache.contains(checkDir)) {
1891 break;
1892 }
1893 dir = checkDir;
1894 parents.addElement(dir);
1895 }
1896
1897 // find first non cached dir
1898 int i = parents.size() - 1;
1899
1900 if (i >= 0) {
1901 String cwd = ftp.printWorkingDirectory();
1902 String parent = dir.getParent();
1903 if (parent != null) {
1904 if (!ftp.changeWorkingDirectory(resolveFile(parent))) {
1905 throw new BuildException("could not change to "
1906 + "directory: " + ftp.getReplyString());
1907 }
1908 }
1909
1910 while (i >= 0) {
1911 dir = (File) parents.elementAt(i--);
1912 // check if dir exists by trying to change into it.
1913 if (!ftp.changeWorkingDirectory(dir.getName())) {
1914 // could not change to it - try to create it
1915 log("creating remote directory "
1916 + resolveFile(dir.getPath()), Project.MSG_VERBOSE);
1917 if (!ftp.makeDirectory(dir.getName())) {
1918 handleMkDirFailure(ftp);
1919 }
1920 if (!ftp.changeWorkingDirectory(dir.getName())) {
1921 throw new BuildException("could not change to "
1922 + "directory: " + ftp.getReplyString());
1923 }
1924 }
1925 dirCache.add(dir);
1926 }
1927 ftp.changeWorkingDirectory(cwd);
1928 }
1929 }
1930 /**
1931 * auto find the time difference between local and remote
1932 * @param ftp handle to ftp client
1933 * @return number of millis to add to remote time to make it comparable to local time
1934 * @since ant 1.6
1935 */
1936 private long getTimeDiff(FTPClient ftp) {
1937 long returnValue = 0;
1938 File tempFile = findFileName(ftp);
1939 try {
1940 // create a local temporary file
1941 FILE_UTILS.createNewFile(tempFile);
1942 long localTimeStamp = tempFile.lastModified();
1943 BufferedInputStream instream = new BufferedInputStream(new FileInputStream(tempFile));
1944 ftp.storeFile(tempFile.getName(), instream);
1945 instream.close();
1946 boolean success = FTPReply.isPositiveCompletion(ftp.getReplyCode());
1947 if (success) {
1948 FTPFile [] ftpFiles = ftp.listFiles(tempFile.getName());
1949 if (ftpFiles.length == 1) {
1950 long remoteTimeStamp = ftpFiles[0].getTimestamp().getTime().getTime();
1951 returnValue = localTimeStamp - remoteTimeStamp;
1952 }
1953 ftp.deleteFile(ftpFiles[0].getName());
1954 }
1955 // delegate the deletion of the local temp file to the delete task
1956 // because of race conditions occuring on Windows
1957 Delete mydelete = new Delete();
1958 mydelete.bindToOwner(this);
1959 mydelete.setFile(tempFile.getCanonicalFile());
1960 mydelete.execute();
1961 } catch (Exception e) {
1962 throw new BuildException(e, getLocation());
1963 }
1964 return returnValue;
1965 }
1966 /**
1967 * find a suitable name for local and remote temporary file
1968 */
1969 private File findFileName(FTPClient ftp) {
1970 FTPFile [] theFiles = null;
1971 final int maxIterations = 1000;
1972 for (int counter = 1; counter < maxIterations; counter++) {
1973 File localFile = FILE_UTILS.createTempFile(
1974 "ant" + Integer.toString(counter), ".tmp",
1975 null, false, false);
1976 String fileName = localFile.getName();
1977 boolean found = false;
1978 try {
1979 if (theFiles == null) {
1980 theFiles = ftp.listFiles();
1981 }
1982 for (int counter2 = 0; counter2 < theFiles.length; counter2++) {
1983 if (theFiles[counter2] != null
1984 && theFiles[counter2].getName().equals(fileName)) {
1985 found = true;
1986 break;
1987 }
1988 }
1989 } catch (IOException ioe) {
1990 throw new BuildException(ioe, getLocation());
1991 }
1992 if (!found) {
1993 localFile.deleteOnExit();
1994 return localFile;
1995 }
1996 }
1997 return null;
1998 }
1999
2000 /**
2001 * Checks to see if the remote file is current as compared with the local
2002 * file. Returns true if the target file is up to date.
2003 * @param ftp ftpclient
2004 * @param localFile local file
2005 * @param remoteFile remote file
2006 * @return true if the target file is up to date
2007 * @throws IOException in unknown circumstances
2008 * @throws BuildException if the date of the remote files cannot be found and the action is
2009 * GET_FILES
2010 */
2011 protected boolean isUpToDate(FTPClient ftp, File localFile,
2012 String remoteFile)
2013 throws IOException, BuildException {
2014 log("checking date for " + remoteFile, Project.MSG_VERBOSE);
2015
2016 FTPFile[] files = ftp.listFiles(remoteFile);
2017
2018 // For Microsoft's Ftp-Service an Array with length 0 is
2019 // returned if configured to return listings in "MS-DOS"-Format
2020 if (files == null || files.length == 0) {
2021 // If we are sending files, then assume out of date.
2022 // If we are getting files, then throw an error
2023
2024 if (action == SEND_FILES) {
2025 log("Could not date test remote file: " + remoteFile
2026 + "assuming out of date.", Project.MSG_VERBOSE);
2027 return false;
2028 } else {
2029 throw new BuildException("could not date test remote file: "
2030 + ftp.getReplyString());
2031 }
2032 }
2033
2034 long remoteTimestamp = files[0].getTimestamp().getTime().getTime();
2035 long localTimestamp = localFile.lastModified();
2036 long adjustedRemoteTimestamp =
2037 remoteTimestamp + this.timeDiffMillis + this.granularityMillis;
2038
2039 StringBuffer msg;
2040 synchronized(TIMESTAMP_LOGGING_SDF) {
2041 msg = new StringBuffer(" [")
2042 .append(TIMESTAMP_LOGGING_SDF.format(new Date(localTimestamp)))
2043 .append("] local");
2044 }
2045 log(msg.toString(), Project.MSG_VERBOSE);
2046
2047 synchronized(TIMESTAMP_LOGGING_SDF) {
2048 msg = new StringBuffer(" [")
2049 .append(TIMESTAMP_LOGGING_SDF.format(new Date(adjustedRemoteTimestamp)))
2050 .append("] remote");
2051 }
2052 if (remoteTimestamp != adjustedRemoteTimestamp) {
2053 synchronized(TIMESTAMP_LOGGING_SDF) {
2054 msg.append(" - (raw: ")
2055 .append(TIMESTAMP_LOGGING_SDF.format(new Date(remoteTimestamp)))
2056 .append(")");
2057 }
2058 }
2059 log(msg.toString(), Project.MSG_VERBOSE);
2060
2061
2062
2063 if (this.action == SEND_FILES) {
2064 return adjustedRemoteTimestamp >= localTimestamp;
2065 } else {
2066 return localTimestamp >= adjustedRemoteTimestamp;
2067 }
2068 }
2069
2070
2071 /**
2072 * Sends a site command to the ftp server
2073 * @param ftp ftp client
2074 * @param theCMD command to execute
2075 * @throws IOException in unknown circumstances
2076 * @throws BuildException in unknown circumstances
2077 */
2078 protected void doSiteCommand(FTPClient ftp, String theCMD)
2079 throws IOException, BuildException {
2080 boolean rc;
2081 String[] myReply = null;
2082
2083 log("Doing Site Command: " + theCMD, Project.MSG_VERBOSE);
2084
2085 rc = ftp.sendSiteCommand(theCMD);
2086
2087 if (!rc) {
2088 log("Failed to issue Site Command: " + theCMD, Project.MSG_WARN);
2089 } else {
2090
2091 myReply = ftp.getReplyStrings();
2092
2093 for (int x = 0; x < myReply.length; x++) {
2094 if (myReply[x].indexOf("200") == -1) {
2095 log(myReply[x], Project.MSG_WARN);
2096 }
2097 }
2098 }
2099 }
2100
2101
2102 /**
2103 * Sends a single file to the remote host. <code>filename</code> may
2104 * contain a relative path specification. When this is the case, <code>sendFile</code>
2105 * will attempt to create any necessary parent directories before sending
2106 * the file. The file will then be sent using the entire relative path
2107 * spec - no attempt is made to change directories. It is anticipated that
2108 * this may eventually cause problems with some FTP servers, but it
2109 * simplifies the coding.
2110 * @param ftp ftp client
2111 * @param dir base directory of the file to be sent (local)
2112 * @param filename relative path of the file to be send
2113 * locally relative to dir
2114 * remotely relative to the remotedir attribute
2115 * @throws IOException in unknown circumstances
2116 * @throws BuildException in unknown circumstances
2117 */
2118 protected void sendFile(FTPClient ftp, String dir, String filename)
2119 throws IOException, BuildException {
2120 InputStream instream = null;
2121
2122 try {
2123 // XXX - why not simply new File(dir, filename)?
2124 File file = getProject().resolveFile(new File(dir, filename).getPath());
2125
2126 if (newerOnly && isUpToDate(ftp, file, resolveFile(filename))) {
2127 return;
2128 }
2129
2130 if (verbose) {
2131 log("transferring " + file.getAbsolutePath());
2132 }
2133
2134 instream = new BufferedInputStream(new FileInputStream(file));
2135
2136 createParents(ftp, filename);
2137
2138 ftp.storeFile(resolveFile(filename), instream);
2139
2140 boolean success = FTPReply.isPositiveCompletion(ftp.getReplyCode());
2141
2142 if (!success) {
2143 String s = "could not put file: " + ftp.getReplyString();
2144
2145 if (skipFailedTransfers) {
2146 log(s, Project.MSG_WARN);
2147 skipped++;
2148 } else {
2149 throw new BuildException(s);
2150 }
2151
2152 } else {
2153 // see if we should issue a chmod command
2154 if (chmod != null) {
2155 doSiteCommand(ftp, "chmod " + chmod + " " + resolveFile(filename));
2156 }
2157 log("File " + file.getAbsolutePath() + " copied to " + server,
2158 Project.MSG_VERBOSE);
2159 transferred++;
2160 }
2161 } finally {
2162 FileUtils.close(instream);
2163 }
2164 }
2165
2166
2167 /**
2168 * Delete a file from the remote host.
2169 * @param ftp ftp client
2170 * @param filename file to delete
2171 * @throws IOException in unknown circumstances
2172 * @throws BuildException if skipFailedTransfers is set to false
2173 * and the deletion could not be done
2174 */
2175 protected void delFile(FTPClient ftp, String filename)
2176 throws IOException, BuildException {
2177 if (verbose) {
2178 log("deleting " + filename);
2179 }
2180
2181 if (!ftp.deleteFile(resolveFile(filename))) {
2182 String s = "could not delete file: " + ftp.getReplyString();
2183
2184 if (skipFailedTransfers) {
2185 log(s, Project.MSG_WARN);
2186 skipped++;
2187 } else {
2188 throw new BuildException(s);
2189 }
2190 } else {
2191 log("File " + filename + " deleted from " + server,
2192 Project.MSG_VERBOSE);
2193 transferred++;
2194 }
2195 }
2196
2197 /**
2198 * Delete a directory, if empty, from the remote host.
2199 * @param ftp ftp client
2200 * @param dirname directory to delete
2201 * @throws IOException in unknown circumstances
2202 * @throws BuildException if skipFailedTransfers is set to false
2203 * and the deletion could not be done
2204 */
2205 protected void rmDir(FTPClient ftp, String dirname)
2206 throws IOException, BuildException {
2207 if (verbose) {
2208 log("removing " + dirname);
2209 }
2210
2211 if (!ftp.removeDirectory(resolveFile(dirname))) {
2212 String s = "could not remove directory: " + ftp.getReplyString();
2213
2214 if (skipFailedTransfers) {
2215 log(s, Project.MSG_WARN);
2216 skipped++;
2217 } else {
2218 throw new BuildException(s);
2219 }
2220 } else {
2221 log("Directory " + dirname + " removed from " + server,
2222 Project.MSG_VERBOSE);
2223 transferred++;
2224 }
2225 }
2226
2227
2228 /**
2229 * Retrieve a single file from the remote host. <code>filename</code> may
2230 * contain a relative path specification. <p>
2231 *
2232 * The file will then be retreived using the entire relative path spec -
2233 * no attempt is made to change directories. It is anticipated that this
2234 * may eventually cause problems with some FTP servers, but it simplifies
2235 * the coding.</p>
2236 * @param ftp the ftp client
2237 * @param dir local base directory to which the file should go back
2238 * @param filename relative path of the file based upon the ftp remote directory
2239 * and/or the local base directory (dir)
2240 * @throws IOException in unknown circumstances
2241 * @throws BuildException if skipFailedTransfers is false
2242 * and the file cannot be retrieved.
2243 */
2244 protected void getFile(FTPClient ftp, String dir, String filename)
2245 throws IOException, BuildException {
2246 OutputStream outstream = null;
2247 try {
2248 File file = getProject().resolveFile(new File(dir, filename).getPath());
2249
2250 if (newerOnly && isUpToDate(ftp, file, resolveFile(filename))) {
2251 return;
2252 }
2253
2254 if (verbose) {
2255 log("transferring " + filename + " to "
2256 + file.getAbsolutePath());
2257 }
2258
2259 File pdir = file.getParentFile();
2260
2261 if (!pdir.exists()) {
2262 pdir.mkdirs();
2263 }
2264 outstream = new BufferedOutputStream(new FileOutputStream(file));
2265 ftp.retrieveFile(resolveFile(filename), outstream);
2266
2267 if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2268 String s = "could not get file: " + ftp.getReplyString();
2269
2270 if (skipFailedTransfers) {
2271 log(s, Project.MSG_WARN);
2272 skipped++;
2273 } else {
2274 throw new BuildException(s);
2275 }
2276
2277 } else {
2278 log("File " + file.getAbsolutePath() + " copied from "
2279 + server, Project.MSG_VERBOSE);
2280 transferred++;
2281 if (preserveLastModified) {
2282 outstream.close();
2283 outstream = null;
2284 FTPFile[] remote = ftp.listFiles(resolveFile(filename));
2285 if (remote.length > 0) {
2286 FILE_UTILS.setFileLastModified(file,
2287 remote[0].getTimestamp()
2288 .getTime().getTime());
2289 }
2290 }
2291 }
2292 } finally {
2293 FileUtils.close(outstream);
2294 }
2295 }
2296
2297
2298 /**
2299 * List information about a single file from the remote host. <code>filename</code>
2300 * may contain a relative path specification. <p>
2301 *
2302 * The file listing will then be retrieved using the entire relative path
2303 * spec - no attempt is made to change directories. It is anticipated that
2304 * this may eventually cause problems with some FTP servers, but it
2305 * simplifies the coding.</p>
2306 * @param ftp ftp client
2307 * @param bw buffered writer
2308 * @param filename the directory one wants to list
2309 * @throws IOException in unknown circumstances
2310 * @throws BuildException in unknown circumstances
2311 */
2312 protected void listFile(FTPClient ftp, BufferedWriter bw, String filename)
2313 throws IOException, BuildException {
2314 if (verbose) {
2315 log("listing " + filename);
2316 }
2317 FTPFile[] ftpfiles = ftp.listFiles(resolveFile(filename));
2318
2319 if (ftpfiles != null && ftpfiles.length > 0) {
2320 bw.write(ftpfiles[0].toString());
2321 bw.newLine();
2322 transferred++;
2323 }
2324 }
2325
2326
2327 /**
2328 * Create the specified directory on the remote host.
2329 *
2330 * @param ftp The FTP client connection
2331 * @param dir The directory to create (format must be correct for host
2332 * type)
2333 * @throws IOException in unknown circumstances
2334 * @throws BuildException if ignoreNoncriticalErrors has not been set to true
2335 * and a directory could not be created, for instance because it was
2336 * already existing. Precisely, the codes 521, 550 and 553 will trigger
2337 * a BuildException
2338 */
2339 protected void makeRemoteDir(FTPClient ftp, String dir)
2340 throws IOException, BuildException {
2341 String workingDirectory = ftp.printWorkingDirectory();
2342 if (verbose) {
2343 if (dir.indexOf("/") == 0 || workingDirectory == null) {
2344 log("Creating directory: " + dir + " in /");
2345 } else {
2346 log("Creating directory: " + dir + " in " + workingDirectory);
2347 }
2348 }
2349 if (dir.indexOf("/") == 0) {
2350 ftp.changeWorkingDirectory("/");
2351 }
2352 String subdir = "";
2353 StringTokenizer st = new StringTokenizer(dir, "/");
2354 while (st.hasMoreTokens()) {
2355 subdir = st.nextToken();
2356 log("Checking " + subdir, Project.MSG_DEBUG);
2357 if (!ftp.changeWorkingDirectory(subdir)) {
2358 if (!ftp.makeDirectory(subdir)) {
2359 // codes 521, 550 and 553 can be produced by FTP Servers
2360 // to indicate that an attempt to create a directory has
2361 // failed because the directory already exists.
2362 int rc = ftp.getReplyCode();
2363 if (!(ignoreNoncriticalErrors
2364 && (rc == FTPReply.CODE_550 || rc == FTPReply.CODE_553
2365 || rc == CODE_521))) {
2366 throw new BuildException("could not create directory: "
2367 + ftp.getReplyString());
2368 }
2369 if (verbose) {
2370 log("Directory already exists");
2371 }
2372 } else {
2373 if (verbose) {
2374 log("Directory created OK");
2375 }
2376 ftp.changeWorkingDirectory(subdir);
2377 }
2378 }
2379 }
2380 if (workingDirectory != null) {
2381 ftp.changeWorkingDirectory(workingDirectory);
2382 }
2383 }
2384
2385 /**
2386 * look at the response for a failed mkdir action, decide whether
2387 * it matters or not. If it does, we throw an exception
2388 * @param ftp current ftp connection
2389 * @throws BuildException if this is an error to signal
2390 */
2391 private void handleMkDirFailure(FTPClient ftp)
2392 throws BuildException {
2393 int rc = ftp.getReplyCode();
2394 if (!(ignoreNoncriticalErrors
2395 && (rc == FTPReply.CODE_550 || rc == FTPReply.CODE_553 || rc == CODE_521))) {
2396 throw new BuildException("could not create directory: "
2397 + ftp.getReplyString());
2398 }
2399 }
2400
2401 /**
2402 * Runs the task.
2403 *
2404 * @throws BuildException if the task fails or is not configured
2405 * correctly.
2406 */
2407 public void execute() throws BuildException {
2408 checkAttributes();
2409
2410 FTPClient ftp = null;
2411
2412 try {
2413 log("Opening FTP connection to " + server, Project.MSG_VERBOSE);
2414
2415 ftp = new FTPClient();
2416 if (this.isConfigurationSet) {
2417 ftp = FTPConfigurator.configure(ftp, this);
2418 }
2419
2420 ftp.setRemoteVerificationEnabled(enableRemoteVerification);
2421 ftp.connect(server, port);
2422 if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2423 throw new BuildException("FTP connection failed: "
2424 + ftp.getReplyString());
2425 }
2426
2427 log("connected", Project.MSG_VERBOSE);
2428 log("logging in to FTP server", Project.MSG_VERBOSE);
2429
2430 if ((this.account != null && !ftp.login(userid, password, account))
2431 || (this.account == null && !ftp.login(userid, password))) {
2432 throw new BuildException("Could not login to FTP server");
2433 }
2434
2435 log("login succeeded", Project.MSG_VERBOSE);
2436
2437 if (binary) {
2438 ftp.setFileType(org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);
2439 if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2440 throw new BuildException("could not set transfer type: "
2441 + ftp.getReplyString());
2442 }
2443 } else {
2444 ftp.setFileType(org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE);
2445 if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2446 throw new BuildException("could not set transfer type: "
2447 + ftp.getReplyString());
2448 }
2449 }
2450
2451 if (passive) {
2452 log("entering passive mode", Project.MSG_VERBOSE);
2453 ftp.enterLocalPassiveMode();
2454 if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2455 throw new BuildException("could not enter into passive "
2456 + "mode: " + ftp.getReplyString());
2457 }
2458 }
2459
2460 // If an initial command was configured then send it.
2461 // Some FTP servers offer different modes of operation,
2462 // E.G. switching between a UNIX file system mode and
2463 // a legacy file system.
2464 if (this.initialSiteCommand != null) {
2465 RetryHandler h = new RetryHandler(this.retriesAllowed, this);
2466 final FTPClient lftp = ftp;
2467 executeRetryable(h, new Retryable() {
2468 public void execute() throws IOException {
2469 doSiteCommand(lftp, FTP.this.initialSiteCommand);
2470 }
2471 }, "initial site command: " + this.initialSiteCommand);
2472 }
2473
2474
2475 // For a unix ftp server you can set the default mask for all files
2476 // created.
2477
2478 if (umask != null) {
2479 RetryHandler h = new RetryHandler(this.retriesAllowed, this);
2480 final FTPClient lftp = ftp;
2481 executeRetryable(h, new Retryable() {
2482 public void execute() throws IOException {
2483 doSiteCommand(lftp, "umask " + umask);
2484 }
2485 }, "umask " + umask);
2486 }
2487
2488 // If the action is MK_DIR, then the specified remote
2489 // directory is the directory to create.
2490
2491 if (action == MK_DIR) {
2492 RetryHandler h = new RetryHandler(this.retriesAllowed, this);
2493 final FTPClient lftp = ftp;
2494 executeRetryable(h, new Retryable() {
2495 public void execute() throws IOException {
2496 makeRemoteDir(lftp, remotedir);
2497 }
2498 }, remotedir);
2499 } else if (action == SITE_CMD) {
2500 RetryHandler h = new RetryHandler(this.retriesAllowed, this);
2501 final FTPClient lftp = ftp;
2502 executeRetryable(h, new Retryable() {
2503 public void execute() throws IOException {
2504 doSiteCommand(lftp, FTP.this.siteCommand);
2505 }
2506 }, "Site Command: " + this.siteCommand);
2507 } else {
2508 if (remotedir != null) {
2509 log("changing the remote directory to " + remotedir,
2510 Project.MSG_VERBOSE);
2511 ftp.changeWorkingDirectory(remotedir);
2512 if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2513 throw new BuildException("could not change remote "
2514 + "directory: " + ftp.getReplyString());
2515 }
2516 }
2517 if (newerOnly && timeDiffAuto) {
2518 // in this case we want to find how much time span there is between local
2519 // and remote
2520 timeDiffMillis = getTimeDiff(ftp);
2521 }
2522 log(ACTION_STRS[action] + " " + ACTION_TARGET_STRS[action]);
2523 transferFiles(ftp);
2524 }
2525
2526 } catch (IOException ex) {
2527 throw new BuildException("error during FTP transfer: " + ex, ex);
2528 } finally {
2529 if (ftp != null && ftp.isConnected()) {
2530 try {
2531 log("disconnecting", Project.MSG_VERBOSE);
2532 ftp.logout();
2533 ftp.disconnect();
2534 } catch (IOException ex) {
2535 // ignore it
2536 }
2537 }
2538 }
2539 }
2540
2541
2542 /**
2543 * an action to perform, one of
2544 * "send", "put", "recv", "get", "del", "delete", "list", "mkdir", "chmod",
2545 * "rmdir"
2546 */
2547 public static class Action extends EnumeratedAttribute {
2548
2549 private static final String[] VALID_ACTIONS = {
2550 "send", "put", "recv", "get", "del", "delete", "list", "mkdir",
2551 "chmod", "rmdir", "site"
2552 };
2553
2554
2555 /**
2556 * Get the valid values
2557 *
2558 * @return an array of the valid FTP actions.
2559 */
2560 public String[] getValues() {
2561 return VALID_ACTIONS;
2562 }
2563
2564
2565 /**
2566 * Get the symbolic equivalent of the action value.
2567 *
2568 * @return the SYMBOL representing the given action.
2569 */
2570 public int getAction() {
2571 String actionL = getValue().toLowerCase(Locale.ENGLISH);
2572 if (actionL.equals("send") || actionL.equals("put")) {
2573 return SEND_FILES;
2574 } else if (actionL.equals("recv") || actionL.equals("get")) {
2575 return GET_FILES;
2576 } else if (actionL.equals("del") || actionL.equals("delete")) {
2577 return DEL_FILES;
2578 } else if (actionL.equals("list")) {
2579 return LIST_FILES;
2580 } else if (actionL.equals("chmod")) {
2581 return CHMOD;
2582 } else if (actionL.equals("mkdir")) {
2583 return MK_DIR;
2584 } else if (actionL.equals("rmdir")) {
2585 return RM_DIR;
2586 } else if (actionL.equals("site")) {
2587 return SITE_CMD;
2588 }
2589 return SEND_FILES;
2590 }
2591 }
2592 /**
2593 * represents one of the valid timestamp adjustment values
2594 * recognized by the <code>timestampGranularity</code> attribute.<p>
2595
2596 * A timestamp adjustment may be used in file transfers for checking
2597 * uptodateness. MINUTE means to add one minute to the server
2598 * timestamp. This is done because FTP servers typically list
2599 * timestamps HH:mm and client FileSystems typically use HH:mm:ss.
2600 *
2601 * The default is to use MINUTE for PUT actions and NONE for GET
2602 * actions, since GETs have the <code>preserveLastModified</code>
2603 * option, which takes care of the problem in most use cases where
2604 * this level of granularity is an issue.
2605 *
2606 */
2607 public static class Granularity extends EnumeratedAttribute {
2608
2609 private static final String[] VALID_GRANULARITIES = {
2610 "", "MINUTE", "NONE"
2611 };
2612
2613 /**
2614 * Get the valid values.
2615 * @return the list of valid Granularity values
2616 */
2617 public String[] getValues() {
2618 return VALID_GRANULARITIES;
2619 }
2620 /**
2621 * returns the number of milliseconds associated with
2622 * the attribute, which can vary in some cases depending
2623 * on the value of the action parameter.
2624 * @param action SEND_FILES or GET_FILES
2625 * @return the number of milliseconds associated with
2626 * the attribute, in the context of the supplied action
2627 */
2628 public long getMilliseconds(int action) {
2629 String granularityU = getValue().toUpperCase(Locale.ENGLISH);
2630 if ("".equals(granularityU)) {
2631 if (action == SEND_FILES) {
2632 return GRANULARITY_MINUTE;
2633 }
2634 } else if ("MINUTE".equals(granularityU)) {
2635 return GRANULARITY_MINUTE;
2636 }
2637 return 0L;
2638 }
2639 static final Granularity getDefault() {
2640 Granularity g = new Granularity();
2641 g.setValue("");
2642 return g;
2643 }
2644
2645 }
2646 /**
2647 * one of the valid system type keys recognized by the systemTypeKey
2648 * attribute.
2649 */
2650 public static class FTPSystemType extends EnumeratedAttribute {
2651
2652 private static final String[] VALID_SYSTEM_TYPES = {
2653 "", "UNIX", "VMS", "WINDOWS", "OS/2", "OS/400",
2654 "MVS"
2655 };
2656
2657
2658 /**
2659 * Get the valid values.
2660 * @return the list of valid system types.
2661 */
2662 public String[] getValues() {
2663 return VALID_SYSTEM_TYPES;
2664 }
2665
2666 static final FTPSystemType getDefault() {
2667 FTPSystemType ftpst = new FTPSystemType();
2668 ftpst.setValue("");
2669 return ftpst;
2670 }
2671 }
2672 /**
2673 * Enumerated class for languages.
2674 */
2675 public static class LanguageCode extends EnumeratedAttribute {
2676
2677
2678 private static final String[] VALID_LANGUAGE_CODES =
2679 getValidLanguageCodes();
2680
2681 private static String[] getValidLanguageCodes() {
2682 Collection c = FTPClientConfig.getSupportedLanguageCodes();
2683 String[] ret = new String[c.size() + 1];
2684 int i = 0;
2685 ret[i++] = "";
2686 for (Iterator it = c.iterator(); it.hasNext(); i++) {
2687 ret[i] = (String) it.next();
2688 }
2689 return ret;
2690 }
2691
2692
2693 /**
2694 * Return the value values.
2695 * @return the list of valid language types.
2696 */
2697 public String[] getValues() {
2698 return VALID_LANGUAGE_CODES;
2699 }
2700
2701 static final LanguageCode getDefault() {
2702 LanguageCode lc = new LanguageCode();
2703 lc.setValue("");
2704 return lc;
2705 }
2706 }
2707
2708 }