Skip to content

Commit 7bfef47

Browse files
author
Niall Pemberton
committed
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
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/io/trunk@684715 13f79535-47bb-0310-9956-ffa450edef68
1 parent 7949b2e commit 7bfef47

2 files changed

Lines changed: 284 additions & 1 deletion

File tree

src/java/org/apache/commons/io/FileUtils.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,10 @@ public static void deleteDirectory(File directory) throws IOException {
979979
return;
980980
}
981981

982-
cleanDirectory(directory);
982+
if (!isSymlink(directory)) {
983+
cleanDirectory(directory);
984+
}
985+
983986
if (!directory.delete()) {
984987
String message =
985988
"Unable to delete directory " + directory + ".";
@@ -1921,4 +1924,31 @@ public static void moveToDirectory(File src, File destDir, boolean createDestDir
19211924
}
19221925
}
19231926

1927+
/**
1928+
* Determines whether the specified file is a link rather than an actual file.
1929+
* Will not return true if there is a symlink anywhere in the path, only if the specific file is.
1930+
*
1931+
* @param file the file to check
1932+
* @return true iff the file is a symlink
1933+
* @throws IOException if an IO error occurs while checking the file
1934+
* @since 2.0
1935+
*/
1936+
public static boolean isSymlink(File file) throws IOException {
1937+
if (file == null) {
1938+
throw new NullPointerException("File must not be null");
1939+
}
1940+
File fileInCanonicalDir = null;
1941+
if (file.getParent() == null) {
1942+
fileInCanonicalDir = file;
1943+
} else {
1944+
File canonicalDir = file.getParentFile().getCanonicalFile();
1945+
fileInCanonicalDir = new File(canonicalDir, file.getName());
1946+
}
1947+
1948+
if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) {
1949+
return false;
1950+
} else {
1951+
return true;
1952+
}
1953+
}
19241954
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
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+
package org.apache.commons.io;
18+
19+
import org.apache.commons.io.testtools.FileBasedTestCase;
20+
21+
import java.io.File;
22+
import java.util.List;
23+
import java.util.ArrayList;
24+
25+
import junit.textui.TestRunner;
26+
import junit.framework.Test;
27+
import junit.framework.TestSuite;
28+
29+
/**
30+
* Test cases for FileUtils.cleanDirectory() method that involve symlinks.
31+
* & FileUtils.isSymlink(File file)
32+
*/
33+
public class FileUtilsCleanSymlinksTestCase extends FileBasedTestCase {
34+
35+
final File top = getTestDirectory();
36+
37+
public static void main(String[] args) {
38+
TestRunner.run(suite());
39+
}
40+
41+
public static Test suite() {
42+
return new TestSuite(FileUtilsCleanSymlinksTestCase.class);
43+
}
44+
45+
public FileUtilsCleanSymlinksTestCase(String name) {
46+
super(name);
47+
}
48+
49+
protected void setUp() throws Exception {
50+
top.mkdirs();
51+
}
52+
53+
protected void tearDown() throws Exception {
54+
FileUtils.deleteDirectory(top);
55+
}
56+
57+
public void testCleanDirWithSymlinkFile() throws Exception {
58+
if (System.getProperty("os.name").startsWith("Win")) {
59+
// cant create symlinks in windows.
60+
return;
61+
}
62+
63+
final File realOuter = new File(top, "realouter");
64+
assertTrue(realOuter.mkdirs());
65+
66+
final File realInner = new File(realOuter, "realinner");
67+
assertTrue(realInner.mkdirs());
68+
69+
final File realFile = new File(realInner, "file1");
70+
FileUtils.touch(realFile);
71+
assertEquals(1, realInner.list().length);
72+
73+
final File randomFile = new File(top, "randomfile");
74+
FileUtils.touch(randomFile);
75+
76+
final File symlinkFile = new File(realInner, "fakeinner");
77+
setupSymlink(randomFile, symlinkFile);
78+
79+
assertEquals(2, realInner.list().length);
80+
81+
// assert contents of the real directory were removed including the symlink
82+
FileUtils.cleanDirectory(realOuter);
83+
assertEquals(0, realOuter.list().length);
84+
85+
// ensure that the contents of the symlink were NOT removed.
86+
assertTrue(randomFile.exists());
87+
assertFalse(symlinkFile.exists());
88+
}
89+
90+
91+
public void testCleanDirWithASymlinkDir() throws Exception {
92+
if (System.getProperty("os.name").startsWith("Win")) {
93+
// cant create symlinks in windows.
94+
return;
95+
}
96+
97+
final File realOuter = new File(top, "realouter");
98+
assertTrue(realOuter.mkdirs());
99+
100+
final File realInner = new File(realOuter, "realinner");
101+
assertTrue(realInner.mkdirs());
102+
103+
FileUtils.touch(new File(realInner, "file1"));
104+
assertEquals(1, realInner.list().length);
105+
106+
final File randomDirectory = new File(top, "randomDir");
107+
assertTrue(randomDirectory.mkdirs());
108+
109+
FileUtils.touch(new File(randomDirectory, "randomfile"));
110+
assertEquals(1, randomDirectory.list().length);
111+
112+
final File symlinkDirectory = new File(realOuter, "fakeinner");
113+
setupSymlink(randomDirectory, symlinkDirectory);
114+
115+
assertEquals(1, symlinkDirectory.list().length);
116+
117+
// assert contents of the real directory were removed including the symlink
118+
FileUtils.cleanDirectory(realOuter);
119+
assertEquals(0, realOuter.list().length);
120+
121+
// ensure that the contents of the symlink were NOT removed.
122+
assertEquals("Contents of sym link should not have been removed", 1, randomDirectory.list().length);
123+
}
124+
125+
public void testCleanDirWithParentSymlinks() throws Exception {
126+
if (System.getProperty("os.name").startsWith("Win")) {
127+
// cant create symlinks in windows.
128+
return;
129+
}
130+
131+
final File realParent = new File(top, "realparent");
132+
assertTrue(realParent.mkdirs());
133+
134+
final File realInner = new File(realParent, "realinner");
135+
assertTrue(realInner.mkdirs());
136+
137+
FileUtils.touch(new File(realInner, "file1"));
138+
assertEquals(1, realInner.list().length);
139+
140+
final File randomDirectory = new File(top, "randomDir");
141+
assertTrue(randomDirectory.mkdirs());
142+
143+
FileUtils.touch(new File(randomDirectory, "randomfile"));
144+
assertEquals(1, randomDirectory.list().length);
145+
146+
final File symlinkDirectory = new File(realParent, "fakeinner");
147+
setupSymlink(randomDirectory, symlinkDirectory);
148+
149+
assertEquals(1, symlinkDirectory.list().length);
150+
151+
final File symlinkParentDirectory = new File(top, "fakeouter");
152+
setupSymlink(realParent, symlinkParentDirectory);
153+
154+
// assert contents of the real directory were removed including the symlink
155+
FileUtils.cleanDirectory(symlinkParentDirectory);// should clean the contents of this but not recurse into other links
156+
assertEquals(0, symlinkParentDirectory.list().length);
157+
assertEquals(0, realParent.list().length);
158+
159+
// ensure that the contents of the symlink were NOT removed.
160+
assertEquals("Contents of sym link should not have been removed", 1, randomDirectory.list().length);
161+
}
162+
163+
public void testStillClearsIfGivenDirectoryIsASymlink() throws Exception {
164+
if (System.getProperty("os.name").startsWith("Win")) {
165+
// cant create symlinks in windows.
166+
return;
167+
}
168+
169+
final File randomDirectory = new File(top, "randomDir");
170+
assertTrue(randomDirectory.mkdirs());
171+
172+
FileUtils.touch(new File(randomDirectory, "randomfile"));
173+
assertEquals(1, randomDirectory.list().length);
174+
175+
final File symlinkDirectory = new File(top, "fakeDir");
176+
setupSymlink(randomDirectory, symlinkDirectory);
177+
178+
FileUtils.cleanDirectory(symlinkDirectory);
179+
assertEquals(0, symlinkDirectory.list().length);
180+
assertEquals(0, randomDirectory.list().length);
181+
}
182+
183+
184+
public void testIdentifiesSymlinkDir() throws Exception {
185+
if (System.getProperty("os.name").startsWith("Win")) {
186+
// cant create symlinks in windows.
187+
return;
188+
}
189+
190+
final File randomDirectory = new File(top, "randomDir");
191+
assertTrue(randomDirectory.mkdirs());
192+
193+
final File symlinkDirectory = new File(top, "fakeDir");
194+
setupSymlink(randomDirectory, symlinkDirectory);
195+
196+
assertTrue(FileUtils.isSymlink(symlinkDirectory));
197+
assertFalse(FileUtils.isSymlink(randomDirectory));
198+
}
199+
200+
public void testIdentifiesSymlinkFile() throws Exception {
201+
if (System.getProperty("os.name").startsWith("Win")) {
202+
// cant create symlinks in windows.
203+
return;
204+
}
205+
206+
final File randomFile = new File(top, "randomfile");
207+
FileUtils.touch(randomFile);
208+
209+
final File symlinkFile = new File(top, "fakeinner");
210+
setupSymlink(randomFile, symlinkFile);
211+
212+
assertTrue(FileUtils.isSymlink(symlinkFile));
213+
assertFalse(FileUtils.isSymlink(randomFile));
214+
}
215+
216+
public void testCorrectlyIdentifySymlinkWithParentSymLink() throws Exception {
217+
if (System.getProperty("os.name").startsWith("Win")) {
218+
// cant create symlinks in windows.
219+
return;
220+
}
221+
222+
final File realParent = new File(top, "realparent");
223+
assertTrue(realParent.mkdirs());
224+
225+
final File symlinkParentDirectory = new File(top, "fakeparent");
226+
setupSymlink(realParent, symlinkParentDirectory);
227+
228+
final File realChild = new File(symlinkParentDirectory, "realChild");
229+
assertTrue(realChild.mkdirs());
230+
231+
final File symlinkChild = new File(symlinkParentDirectory, "fakeChild");
232+
setupSymlink(realChild, symlinkChild);
233+
234+
assertTrue(FileUtils.isSymlink(symlinkChild));
235+
assertFalse(FileUtils.isSymlink(realChild));
236+
}
237+
238+
private void setupSymlink(File res, File link) throws Exception {
239+
// create symlink
240+
List args = new ArrayList();
241+
args.add("ln");
242+
args.add("-s");
243+
244+
args.add(res.getAbsolutePath());
245+
args.add(link.getAbsolutePath());
246+
247+
Process proc;
248+
249+
proc = Runtime.getRuntime().exec((String[]) args.toArray(new String[args.size()]));
250+
proc.waitFor();
251+
}
252+
253+
}

0 commit comments

Comments
 (0)