diff --git a/src/main/org/apache/tools/ant/DirectoryScanner.java b/src/main/org/apache/tools/ant/DirectoryScanner.java
index 3074161c9..dd89e49e3 100644
--- a/src/main/org/apache/tools/ant/DirectoryScanner.java
+++ b/src/main/org/apache/tools/ant/DirectoryScanner.java
@@ -27,6 +27,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
+import java.util.Stack;
import java.util.Vector;
import org.apache.tools.ant.taskdefs.condition.Os;
@@ -37,6 +38,7 @@ import org.apache.tools.ant.types.selectors.FileSelector;
import org.apache.tools.ant.types.selectors.PathPattern;
import org.apache.tools.ant.types.selectors.SelectorScanner;
import org.apache.tools.ant.types.selectors.SelectorUtils;
+import org.apache.tools.ant.util.CollectionUtils;
import org.apache.tools.ant.util.FileUtils;
/**
@@ -168,6 +170,8 @@ public class DirectoryScanner
"**/.DS_Store"
};
+ public static final int MAX_LEVELS_OF_SYMLINKS = 1;
+
/** Helper. */
private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
@@ -375,6 +379,14 @@ public class DirectoryScanner
*/
private IllegalStateException illegal = null;
+ /**
+ * The maximum number of times a symbolic link may be followed
+ * during a scan.
+ *
+ * @since Ant 1.8.0
+ */
+ private int maxLevelsOfSymlinks = MAX_LEVELS_OF_SYMLINKS;
+
/**
* Sole constructor.
*/
@@ -643,6 +655,16 @@ public class DirectoryScanner
this.followSymlinks = followSymlinks;
}
+ /**
+ * The maximum number of times a symbolic link may be followed
+ * during a scan.
+ *
+ * @since Ant 1.8.0
+ */
+ public void setMaxLevelsOfSymlinks(int max) {
+ maxLevelsOfSymlinks = max;
+ }
+
/**
* Set the list of include patterns to use. All '/' and '\' characters
* are replaced by File.separatorChar
, so the separator used
@@ -1086,11 +1108,11 @@ public class DirectoryScanner
+ dir.getAbsolutePath() + "'");
}
}
- scandir(dir, vpath, fast, newfiles);
+ scandir(dir, vpath, fast, newfiles, new Stack());
}
private void scandir(File dir, String vpath, boolean fast,
- String[] newfiles) {
+ String[] newfiles, Stack directoryNamesFollowed) {
// avoid double scanning of directories, can only happen in fast mode
if (fast && hasBeenScanned(vpath)) {
return;
@@ -1116,7 +1138,10 @@ public class DirectoryScanner
}
}
newfiles = (String[]) (noLinks.toArray(new String[noLinks.size()]));
+ } else {
+ directoryNamesFollowed.push(dir.getName());
}
+
for (int i = 0; i < newfiles.length; i++) {
String name = vpath + newfiles[i];
File file = new File(dir, newfiles[i]);
@@ -1129,20 +1154,39 @@ public class DirectoryScanner
filesNotIncluded.addElement(name);
}
} else { // dir
+
+ if (followSymlinks
+ && causesIllegalSymlinkLoop(newfiles[i], dir,
+ directoryNamesFollowed)) {
+ // will be caught and redirected to Ant's logging system
+ System.err.println("skipping symbolic link "
+ + file.getAbsolutePath()
+ + " -- too many levels of symbolic"
+ + " links.");
+ continue;
+ }
+
if (isIncluded(name)) {
- accountForIncludedDir(name, file, fast, children);
+ accountForIncludedDir(name, file, fast, children,
+ directoryNamesFollowed);
} else {
everythingIncluded = false;
dirsNotIncluded.addElement(name);
if (fast && couldHoldIncluded(name)) {
- scandir(file, name + File.separator, fast, children);
+ scandir(file, name + File.separator, fast, children,
+ directoryNamesFollowed);
}
}
if (!fast) {
- scandir(file, name + File.separator, fast, children);
+ scandir(file, name + File.separator, fast, children,
+ directoryNamesFollowed);
}
}
}
+
+ if (followSymlinks) {
+ directoryNamesFollowed.pop();
+ }
}
/**
@@ -1170,10 +1214,12 @@ public class DirectoryScanner
}
private void accountForIncludedDir(String name, File file, boolean fast,
- String[] children) {
+ String[] children,
+ Stack directoryNamesFollowed) {
processIncluded(name, file, dirsIncluded, dirsExcluded, dirsDeselected);
if (fast && couldHoldIncluded(name) && !contentsExcluded(name)) {
- scandir(file, name + File.separator, fast, children);
+ scandir(file, name + File.separator, fast, children,
+ directoryNamesFollowed);
}
}
@@ -1744,4 +1790,50 @@ public class DirectoryScanner
return (PathPattern[]) al.toArray(new PathPattern[al.size()]);
}
+ /**
+ * Would following the given directory cause a loop of symbolic
+ * links deeper than allowed?
+ *
+ *
Can only happen if the given directory has been seen at + * least more often than allowed during the current scan and it is + * a symbolic link and enough other occurences of the same name + * higher up are symbolic links that point to the same place.
+ * + * @since Ant 1.8.0 + */ + private boolean causesIllegalSymlinkLoop(String dirName, File parent, + Stack directoryNamesFollowed) { + try { + if (CollectionUtils.frequency(directoryNamesFollowed, dirName) + >= maxLevelsOfSymlinks + && FILE_UTILS.isSymbolicLink(parent, dirName)) { + + Stack s = (Stack) directoryNamesFollowed.clone(); + ArrayList files = new ArrayList(); + File f = FILE_UTILS.resolveFile(parent, dirName); + String target = f.getCanonicalPath(); + files.add(target); + + String relPath = ""; + while (!s.empty()) { + relPath += "../"; + String dir = (String) s.pop(); + if (dirName.equals(dir)) { + f = FILE_UTILS.resolveFile(parent, relPath + dir); + files.add(f.getCanonicalPath()); + if (CollectionUtils.frequency(files, target) + > maxLevelsOfSymlinks) { + return true; + } + } + } + + } + return false; + } catch (IOException ex) { + throw new BuildException("Caught error while checking for" + + " symbolic links", ex); + } + } + } diff --git a/src/main/org/apache/tools/ant/types/AbstractFileSet.java b/src/main/org/apache/tools/ant/types/AbstractFileSet.java index 4e1dee9da..e1a743d68 100644 --- a/src/main/org/apache/tools/ant/types/AbstractFileSet.java +++ b/src/main/org/apache/tools/ant/types/AbstractFileSet.java @@ -68,6 +68,7 @@ public abstract class AbstractFileSet extends DataType private boolean caseSensitive = true; private boolean followSymlinks = true; private boolean errorOnMissingDir = true; + private int maxLevelsOfSymlinks = DirectoryScanner.MAX_LEVELS_OF_SYMLINKS; /* cached DirectoryScanner instance for our own Project only */ private DirectoryScanner directoryScanner = null; @@ -93,6 +94,7 @@ public abstract class AbstractFileSet extends DataType this.caseSensitive = fileset.caseSensitive; this.followSymlinks = fileset.followSymlinks; this.errorOnMissingDir = fileset.errorOnMissingDir; + this.maxLevelsOfSymlinks = fileset.maxLevelsOfSymlinks; setProject(fileset.getProject()); } @@ -396,6 +398,16 @@ public abstract class AbstractFileSet extends DataType ? getRef(getProject()).isFollowSymlinks() : followSymlinks; } + /** + * The maximum number of times a symbolic link may be followed + * during a scan. + * + * @since Ant 1.8.0 + */ + public void setMaxLevelsOfSymlinks(int max) { + maxLevelsOfSymlinks = max; + } + /** * Sets whether an error is thrown if a directory does not exist. * @@ -444,6 +456,7 @@ public abstract class AbstractFileSet extends DataType setupDirectoryScanner(ds, p); ds.setFollowSymlinks(followSymlinks); ds.setErrorOnMissingDir(errorOnMissingDir); + ds.setMaxLevelsOfSymlinks(maxLevelsOfSymlinks); directoryScanner = (p == getProject()) ? ds : directoryScanner; } } diff --git a/src/main/org/apache/tools/ant/util/CollectionUtils.java b/src/main/org/apache/tools/ant/util/CollectionUtils.java index 375548357..bb5be8ca6 100644 --- a/src/main/org/apache/tools/ant/util/CollectionUtils.java +++ b/src/main/org/apache/tools/ant/util/CollectionUtils.java @@ -222,4 +222,24 @@ public class CollectionUtils { } + /** + * Counts how often the given Object occurs in the given + * collection using equals() for comparison. + * + * @since Ant 1.8.0 + */ + public static int frequency(Collection c, Object o) { + // same as Collections.frequency introduced with JDK 1.5 + int freq = 0; + if (c != null) { + for (Iterator i = c.iterator(); i.hasNext(); ) { + Object test = i.next(); + if (o == null ? test == null : o.equals(test)) { + freq++; + } + } + } + return freq; + } + } diff --git a/src/tests/antunit/core/dirscanner-symlinks-test.xml b/src/tests/antunit/core/dirscanner-symlinks-test.xml index 551338ebe..0645946af 100644 --- a/src/tests/antunit/core/dirscanner-symlinks-test.xml +++ b/src/tests/antunit/core/dirscanner-symlinks-test.xml @@ -75,14 +75,26 @@