You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ant.apache.org by bo...@apache.org on 2008/09/10 17:41:50 UTC

svn commit: r693870 - in /ant/core/trunk/src: main/org/apache/tools/ant/DirectoryScanner.java main/org/apache/tools/ant/types/AbstractFileSet.java main/org/apache/tools/ant/util/CollectionUtils.java tests/antunit/core/dirscanner-symlinks-test.xml

Author: bodewig
Date: Wed Sep 10 08:41:49 2008
New Revision: 693870

URL: http://svn.apache.org/viewvc?rev=693870&view=rev
Log:
don't run into infinite lopps caused by symbolic links.  PR 45499.

Modified:
    ant/core/trunk/src/main/org/apache/tools/ant/DirectoryScanner.java
    ant/core/trunk/src/main/org/apache/tools/ant/types/AbstractFileSet.java
    ant/core/trunk/src/main/org/apache/tools/ant/util/CollectionUtils.java
    ant/core/trunk/src/tests/antunit/core/dirscanner-symlinks-test.xml

Modified: ant/core/trunk/src/main/org/apache/tools/ant/DirectoryScanner.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/DirectoryScanner.java?rev=693870&r1=693869&r2=693870&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/ant/DirectoryScanner.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/ant/DirectoryScanner.java Wed Sep 10 08:41:49 2008
@@ -27,6 +27,7 @@
 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.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 @@
         "**/.DS_Store"
     };
 
+    public static final int MAX_LEVELS_OF_SYMLINKS = 1;
+
     /** Helper. */
     private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
 
@@ -376,6 +380,14 @@
     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.
      */
     public DirectoryScanner() {
@@ -644,6 +656,16 @@
     }
 
     /**
+     * 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 <code>File.separatorChar</code>, so the separator used
      * need not match <code>File.separatorChar</code>.
@@ -1086,11 +1108,11 @@
                                          + 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 @@
                 }
             }
             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 @@
                     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 @@
     }
 
     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 @@
         return (PathPattern[]) al.toArray(new PathPattern[al.size()]);
     }
 
+    /**
+     * Would following the given directory cause a loop of symbolic
+     * links deeper than allowed?
+     *
+     * <p>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.</p>
+     *
+     * @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);
+        }
+    }
+
 }

Modified: ant/core/trunk/src/main/org/apache/tools/ant/types/AbstractFileSet.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/types/AbstractFileSet.java?rev=693870&r1=693869&r2=693870&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/ant/types/AbstractFileSet.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/ant/types/AbstractFileSet.java Wed Sep 10 08:41:49 2008
@@ -68,6 +68,7 @@
     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 @@
         this.caseSensitive = fileset.caseSensitive;
         this.followSymlinks = fileset.followSymlinks;
         this.errorOnMissingDir = fileset.errorOnMissingDir;
+        this.maxLevelsOfSymlinks = fileset.maxLevelsOfSymlinks;
         setProject(fileset.getProject());
     }
 
@@ -397,6 +399,16 @@
     }
 
     /**
+     * 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.
      *
      * @param errorOnMissingDir true if missing directories cause errors,
@@ -444,6 +456,7 @@
                 setupDirectoryScanner(ds, p);
                 ds.setFollowSymlinks(followSymlinks);
                 ds.setErrorOnMissingDir(errorOnMissingDir);
+                ds.setMaxLevelsOfSymlinks(maxLevelsOfSymlinks);
                 directoryScanner = (p == getProject()) ? ds : directoryScanner;
             }
         }

Modified: ant/core/trunk/src/main/org/apache/tools/ant/util/CollectionUtils.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/util/CollectionUtils.java?rev=693870&r1=693869&r2=693870&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/ant/util/CollectionUtils.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/ant/util/CollectionUtils.java Wed Sep 10 08:41:49 2008
@@ -222,4 +222,24 @@
 
     }
 
+    /**
+     * 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;
+    }
+            
 }

Modified: ant/core/trunk/src/tests/antunit/core/dirscanner-symlinks-test.xml
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/tests/antunit/core/dirscanner-symlinks-test.xml?rev=693870&r1=693869&r2=693870&view=diff
==============================================================================
--- ant/core/trunk/src/tests/antunit/core/dirscanner-symlinks-test.xml (original)
+++ ant/core/trunk/src/tests/antunit/core/dirscanner-symlinks-test.xml Wed Sep 10 08:41:49 2008
@@ -75,14 +75,26 @@
     <au:assertFileDoesntExist file="${output}/file.txt"/>
   </target>
 
-  <target name="INFINITEtestLinkToParentFollow"
+  <target name="testLinkToParentFollow"
           depends="checkOs, setUp, -link-to-parent"
           if="unix">
     <copy todir="${output}">
-      <fileset dir="${base}" followsymlinks="true"/>
+      <fileset dir="${base}" followsymlinks="true" maxLevelsOfSymlinks="1"/>
+    </copy>
+    <symlink action="delete" link="${base}/A"/>
+    <au:assertFileExists file="${output}/A/B/file.txt"/>
+    <au:assertFileDoesntExist file="${output}/A/base/A/B/file.txt"/>
+  </target>
+
+  <target name="testLinkToParentFollowMax2"
+          depends="checkOs, setUp, -link-to-parent"
+          if="unix">
+    <copy todir="${output}">
+      <fileset dir="${base}" followsymlinks="true" maxLevelsOfSymlinks="2"/>
     </copy>
     <symlink action="delete" link="${base}/A"/>
     <au:assertFileExists file="${output}/A/B/file.txt"/>
+    <au:assertFileExists file="${output}/A/base/A/B/file.txt"/>
   </target>
 
   <target name="testLinkToParentFollowWithInclude"
@@ -120,7 +132,7 @@
     <au:assertFileDoesntExist file="${output}/A/B/file.txt"/>
   </target>
 
-  <target name="INFINITE testSillyLoopFollow"
+  <target name="testSillyLoopFollow"
           depends="checkOs, setUp, -silly-loop"
           if="unix">
     <copy todir="${output}">
@@ -140,6 +152,28 @@
     <au:assertFileDoesntExist file="${output}"/>
   </target>
 
+  <target name="testRepeatedName"
+          depends="setUp">
+    <mkdir dir="${base}/A/A/A/A"/>
+    <touch file="${base}/A/A/A/A/file.txt"/>
+    <copy todir="${output}">
+      <fileset dir="${base}" followsymlinks="true" maxLevelsOfSymlinks="1"/>
+    </copy>
+    <au:assertFileExists file="${output}/A/A/A/A/file.txt"/>
+  </target>
+
+  <target name="testRepeatedNameWithLinkButNoLoop"
+          depends="checkOs, setUp"
+          if="unix">
+    <mkdir dir="${base}/A/A/A/B"/>
+    <touch file="${base}/A/A/A/B/file.txt"/>
+    <symlink link="${base}/A/A/A/A" resource="${base}/A/A/A/B"/>
+    <copy todir="${output}">
+      <fileset dir="${base}" followsymlinks="true" maxLevelsOfSymlinks="1"/>
+    </copy>
+    <au:assertFileExists file="${output}/A/A/A/A/file.txt"/>
+  </target>
+
   <target name="-sibling" if="unix">
     <mkdir dir="${base}/A"/>
     <touch file="${base}/A/file.txt"/>