Author: niallp Date: Mon Aug 11 04:21:38 2008 New Revision: 684715 URL: http://svn.apache.org/viewvc?rev=684715&view=rev Log: Fix IO-168 Symbolic links (symlinks) followed when deleting directory - reported by Apostolos Lerios, thanks to Attila Szegedi for the solution and Brydie McCoy for the patch
Added: commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java (with props) Modified: commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java Modified: commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java?rev=684715&r1=684714&r2=684715&view=diff ============================================================================== --- commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java (original) +++ commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java Mon Aug 11 04:21:38 2008 @@ -979,7 +979,10 @@ return; } - cleanDirectory(directory); + if (!isSymlink(directory)) { + cleanDirectory(directory); + } + if (!directory.delete()) { String message = "Unable to delete directory " + directory + "."; @@ -1921,4 +1924,31 @@ } } + /** + * Determines whether the specified file is a link rather than an actual file. + * Will not return true if there is a symlink anywhere in the path, only if the specific file is. + * + * @param file the file to check + * @return true iff the file is a symlink + * @throws IOException if an IO error occurs while checking the file + * @since 2.0 + */ + public static boolean isSymlink(File file) throws IOException { + if (file == null) { + throw new NullPointerException("File must not be null"); + } + File fileInCanonicalDir = null; + if (file.getParent() == null) { + fileInCanonicalDir = file; + } else { + File canonicalDir = file.getParentFile().getCanonicalFile(); + fileInCanonicalDir = new File(canonicalDir, file.getName()); + } + + if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) { + return false; + } else { + return true; + } + } } Added: commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java?rev=684715&view=auto ============================================================================== --- commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java (added) +++ commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java Mon Aug 11 04:21:38 2008 @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.io; + +import org.apache.commons.io.testtools.FileBasedTestCase; + +import java.io.File; +import java.util.List; +import java.util.ArrayList; + +import junit.textui.TestRunner; +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Test cases for FileUtils.cleanDirectory() method that involve symlinks. + * & FileUtils.isSymlink(File file) + */ +public class FileUtilsCleanSymlinksTestCase extends FileBasedTestCase { + + final File top = getTestDirectory(); + + public static void main(String[] args) { + TestRunner.run(suite()); + } + + public static Test suite() { + return new TestSuite(FileUtilsCleanSymlinksTestCase.class); + } + + public FileUtilsCleanSymlinksTestCase(String name) { + super(name); + } + + protected void setUp() throws Exception { + top.mkdirs(); + } + + protected void tearDown() throws Exception { + FileUtils.deleteDirectory(top); + } + + public void testCleanDirWithSymlinkFile() throws Exception { + if (System.getProperty("os.name").startsWith("Win")) { + // cant create symlinks in windows. + return; + } + + final File realOuter = new File(top, "realouter"); + assertTrue(realOuter.mkdirs()); + + final File realInner = new File(realOuter, "realinner"); + assertTrue(realInner.mkdirs()); + + final File realFile = new File(realInner, "file1"); + FileUtils.touch(realFile); + assertEquals(1, realInner.list().length); + + final File randomFile = new File(top, "randomfile"); + FileUtils.touch(randomFile); + + final File symlinkFile = new File(realInner, "fakeinner"); + setupSymlink(randomFile, symlinkFile); + + assertEquals(2, realInner.list().length); + + // assert contents of the real directory were removed including the symlink + FileUtils.cleanDirectory(realOuter); + assertEquals(0, realOuter.list().length); + + // ensure that the contents of the symlink were NOT removed. + assertTrue(randomFile.exists()); + assertFalse(symlinkFile.exists()); + } + + + public void testCleanDirWithASymlinkDir() throws Exception { + if (System.getProperty("os.name").startsWith("Win")) { + // cant create symlinks in windows. + return; + } + + final File realOuter = new File(top, "realouter"); + assertTrue(realOuter.mkdirs()); + + final File realInner = new File(realOuter, "realinner"); + assertTrue(realInner.mkdirs()); + + FileUtils.touch(new File(realInner, "file1")); + assertEquals(1, realInner.list().length); + + final File randomDirectory = new File(top, "randomDir"); + assertTrue(randomDirectory.mkdirs()); + + FileUtils.touch(new File(randomDirectory, "randomfile")); + assertEquals(1, randomDirectory.list().length); + + final File symlinkDirectory = new File(realOuter, "fakeinner"); + setupSymlink(randomDirectory, symlinkDirectory); + + assertEquals(1, symlinkDirectory.list().length); + + // assert contents of the real directory were removed including the symlink + FileUtils.cleanDirectory(realOuter); + assertEquals(0, realOuter.list().length); + + // ensure that the contents of the symlink were NOT removed. + assertEquals("Contents of sym link should not have been removed", 1, randomDirectory.list().length); + } + + public void testCleanDirWithParentSymlinks() throws Exception { + if (System.getProperty("os.name").startsWith("Win")) { + // cant create symlinks in windows. + return; + } + + final File realParent = new File(top, "realparent"); + assertTrue(realParent.mkdirs()); + + final File realInner = new File(realParent, "realinner"); + assertTrue(realInner.mkdirs()); + + FileUtils.touch(new File(realInner, "file1")); + assertEquals(1, realInner.list().length); + + final File randomDirectory = new File(top, "randomDir"); + assertTrue(randomDirectory.mkdirs()); + + FileUtils.touch(new File(randomDirectory, "randomfile")); + assertEquals(1, randomDirectory.list().length); + + final File symlinkDirectory = new File(realParent, "fakeinner"); + setupSymlink(randomDirectory, symlinkDirectory); + + assertEquals(1, symlinkDirectory.list().length); + + final File symlinkParentDirectory = new File(top, "fakeouter"); + setupSymlink(realParent, symlinkParentDirectory); + + // assert contents of the real directory were removed including the symlink + FileUtils.cleanDirectory(symlinkParentDirectory);// should clean the contents of this but not recurse into other links + assertEquals(0, symlinkParentDirectory.list().length); + assertEquals(0, realParent.list().length); + + // ensure that the contents of the symlink were NOT removed. + assertEquals("Contents of sym link should not have been removed", 1, randomDirectory.list().length); + } + + public void testStillClearsIfGivenDirectoryIsASymlink() throws Exception { + if (System.getProperty("os.name").startsWith("Win")) { + // cant create symlinks in windows. + return; + } + + final File randomDirectory = new File(top, "randomDir"); + assertTrue(randomDirectory.mkdirs()); + + FileUtils.touch(new File(randomDirectory, "randomfile")); + assertEquals(1, randomDirectory.list().length); + + final File symlinkDirectory = new File(top, "fakeDir"); + setupSymlink(randomDirectory, symlinkDirectory); + + FileUtils.cleanDirectory(symlinkDirectory); + assertEquals(0, symlinkDirectory.list().length); + assertEquals(0, randomDirectory.list().length); + } + + + public void testIdentifiesSymlinkDir() throws Exception { + if (System.getProperty("os.name").startsWith("Win")) { + // cant create symlinks in windows. + return; + } + + final File randomDirectory = new File(top, "randomDir"); + assertTrue(randomDirectory.mkdirs()); + + final File symlinkDirectory = new File(top, "fakeDir"); + setupSymlink(randomDirectory, symlinkDirectory); + + assertTrue(FileUtils.isSymlink(symlinkDirectory)); + assertFalse(FileUtils.isSymlink(randomDirectory)); + } + + public void testIdentifiesSymlinkFile() throws Exception { + if (System.getProperty("os.name").startsWith("Win")) { + // cant create symlinks in windows. + return; + } + + final File randomFile = new File(top, "randomfile"); + FileUtils.touch(randomFile); + + final File symlinkFile = new File(top, "fakeinner"); + setupSymlink(randomFile, symlinkFile); + + assertTrue(FileUtils.isSymlink(symlinkFile)); + assertFalse(FileUtils.isSymlink(randomFile)); + } + + public void testCorrectlyIdentifySymlinkWithParentSymLink() throws Exception { + if (System.getProperty("os.name").startsWith("Win")) { + // cant create symlinks in windows. + return; + } + + final File realParent = new File(top, "realparent"); + assertTrue(realParent.mkdirs()); + + final File symlinkParentDirectory = new File(top, "fakeparent"); + setupSymlink(realParent, symlinkParentDirectory); + + final File realChild = new File(symlinkParentDirectory, "realChild"); + assertTrue(realChild.mkdirs()); + + final File symlinkChild = new File(symlinkParentDirectory, "fakeChild"); + setupSymlink(realChild, symlinkChild); + + assertTrue(FileUtils.isSymlink(symlinkChild)); + assertFalse(FileUtils.isSymlink(realChild)); + } + + private void setupSymlink(File res, File link) throws Exception { + // create symlink + List args = new ArrayList(); + args.add("ln"); + args.add("-s"); + + args.add(res.getAbsolutePath()); + args.add(link.getAbsolutePath()); + + Process proc; + + proc = Runtime.getRuntime().exec((String[]) args.toArray(new String[args.size()])); + proc.waitFor(); + } + +} Propchange: commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: commons/proper/io/trunk/src/test/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL