Skip to content

Commit 289a56c

Browse files
author
Stephen Colebourne
committed
Rewrite catPath, renaming to concat
git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/io/trunk@140645 13f79535-47bb-0310-9956-ffa450edef68
1 parent 3323d0a commit 289a56c

2 files changed

Lines changed: 102 additions & 59 deletions

File tree

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

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,28 @@
3131
* <li>the base name - file</li>
3232
* <li>the extension - txt</li>
3333
* </ul>
34-
* The class only supports Unix and Windows style names.
34+
* The class only supports Unix and Windows style names. Prefixes are matched as follows:
35+
* <pre>
36+
* Windows style:
37+
* a\b\c.txt --> "" --> relative
38+
* \a\b\c.txt --> "\" --> drive relative
39+
* C:\a\b\c.txt --> "C:\" --> absolute
40+
* \\server\a\b\c.txt --> "\\server\" --> UNC
41+
*
42+
* Unix style:
43+
* a/b/c.txt --> "" --> relative
44+
* /a/b/c.txt --> "/" --> absolute
45+
* ~/a/b/c.txt --> "~/" --> current user relative
46+
* ~user/a/b/c.txt --> "~user/" --> named user relative
47+
* </pre>
48+
*
3549
* </p>
3650
* <h3>Origin of code</h3>
3751
* <ul>
3852
* <li>Commons Utils</li>
3953
* <li>Alexandria's FileUtils</li>
4054
* <li>Avalon Excalibur's IO</li>
55+
* <li>Tomcat</li>
4156
* </ul>
4257
*
4358
* @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
@@ -50,7 +65,7 @@
5065
* @author Martin Cooper
5166
* @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
5267
* @author Stephen Colebourne
53-
* @version $Id: FilenameUtils.java,v 1.29 2004/11/27 01:22:05 scolebourne Exp $
68+
* @version $Id: FilenameUtils.java,v 1.30 2004/11/27 17:00:51 scolebourne Exp $
5469
* @since Commons IO 1.1
5570
*/
5671
public class FilenameUtils {
@@ -90,14 +105,15 @@ public class FilenameUtils {
90105
/**
91106
* Instances should NOT be constructed in standard programming.
92107
*/
93-
public FilenameUtils() { }
108+
public FilenameUtils() {
109+
}
94110

95111
//-----------------------------------------------------------------------
96112
/**
97113
* Checks if the character is a separator.
98114
*
99115
* @param ch the character to check
100-
* @return true if it is a separators
116+
* @return true if it is a separator character
101117
*/
102118
private static boolean isSeparator(char ch) {
103119
return (ch == UNIX_SEPARATOR) || (ch == WINDOWS_SEPARATOR);
@@ -135,9 +151,10 @@ private static boolean isSeparator(char ch) {
135151
* ~/foo/../bar --> ~/bar
136152
* ~/../bar --> null
137153
* </pre>
154+
* (Note the file separator returned will be correct for windows/unix)
138155
*
139156
* @param filename the filename to normalize, null returns null
140-
* @return the normalized String, or null if too many ..'s.
157+
* @return the normalized String, or null if invalid
141158
*/
142159
public static String normalize(String filename) {
143160
if (filename == null) {
@@ -215,49 +232,53 @@ public static String normalize(String filename) {
215232
}
216233

217234
/**
218-
* Will concatenate 2 paths. Paths with <code>..</code> will be
219-
* properly handled. The path separator between the 2 paths is the
220-
* system default path separator.
221-
*
222-
* <p>Eg. on UNIX,<br />
223-
* <code>/a/b/c</code> + <code>d</code> = <code>/a/b/d</code><br />
224-
* <code>/a/b/c</code> + <code>../d</code> = <code>/a/d</code><br />
225-
* </p>
226-
*
227-
* <p>Eg. on Microsoft Windows,<br />
228-
* <code>C:\a\b\c</code> + <code>d</code> = <code>C:\a\b\d</code><br />
229-
* <code>C:\a\b\c</code> + <code>..\d</code> = <code>C:\a\d</code><br />
230-
* <code>/a/b/c</code> + <code>d</code> = <code>/a/b\d</code><br />
231-
* </p>
232-
*
233-
* Thieved from Tomcat sources...
235+
* Concatenates two paths using normal command line style rules.
236+
* <p>
237+
* The first argument is the base path, the second is the path to concatenate.
238+
* The returned path is always normalized via {@link #normalize(String)},
239+
* thus <code>..</code> is handled.
240+
* <p>
241+
* If <code>pathToAdd</code> is absolute (has a prefix), then it will
242+
* be normalized and returned.
243+
* Otherwise, the paths will be joined, normalized and returned.
244+
* <pre>
245+
* /foo/ + bar --> /foo/bar
246+
* /foo/a + bar --> /foo/a/bar
247+
* /foo/c.txt + bar --> /foo/c.txt/bar
248+
* /foo/ + ../bar --> /bar
249+
* /foo/ + ../../bar --> null
250+
* /foo/ + /bar --> /bar
251+
* /foo/.. + /bar --> /bar
252+
* </pre>
253+
* Note that the first parameter must be a path. If it ends with a name, then
254+
* the name will be built into the concatenated path. If this might be a problem,
255+
* use {@link #getFullPath(String)} on the base path argument.
234256
*
235-
* @param lookupPath the base path to attach to
236-
* @param path path the second path to attach to the first
237-
* @return The concatenated paths, or null if error occurs
257+
* @param basePath the base path to attach to, always treated as a path
258+
* @param pathToAdd path the second path to attach to the first
259+
* @return the concatenated path, or null if invalid
238260
*/
239-
// TODO UNIX/Windows only. Is this a problem?
240-
public static String catPath(String lookupPath, String path) {
241-
// Cut off the last slash and everything beyond
242-
int index = indexOfLastSeparator(lookupPath);
243-
String lookup = lookupPath.substring(0, index);
244-
String pth = path;
245-
246-
// Deal with .. by chopping dirs off the lookup path
247-
while (pth.startsWith("../") || pth.startsWith("..\\")) {
248-
if (lookup.length() > 0) {
249-
index = indexOfLastSeparator(lookup);
250-
lookup = lookup.substring(0, index);
251-
} else {
252-
// More ..'s than dirs, return null
253-
return null;
254-
}
255-
256-
pth = pth.substring(3);
261+
public static String concat(String basePath, String pathToAdd) {
262+
int prefix = getPrefixLength(pathToAdd);
263+
if (prefix < 0) {
264+
return null;
265+
}
266+
if (prefix > 0) {
267+
return normalize(pathToAdd);
268+
}
269+
if (basePath == null) {
270+
return null;
271+
}
272+
int len = basePath.length();
273+
if (len == 0) {
274+
return normalize(pathToAdd);
275+
}
276+
char ch = basePath.charAt(len - 1);
277+
if (isSeparator(basePath.charAt(len - 1))) {
278+
return normalize(basePath + pathToAdd);
279+
} else {
280+
return normalize(basePath + '/' + pathToAdd);
257281
}
258-
259-
return new StringBuffer(lookup).
260-
append(File.separator).append(pth).toString();
261282
}
262283

263284
//-----------------------------------------------------------------------
@@ -338,8 +359,11 @@ public static int getPrefixLength(String filename) {
338359
return 0;
339360
}
340361
char ch0 = filename.charAt(0);
362+
if (ch0 == ':') {
363+
return -1;
364+
}
341365
if (len == 1) {
342-
if (ch0 == '~' || ch0 == ':') {
366+
if (ch0 == '~') {
343367
return -1;
344368
}
345369
return (isSeparator(ch0) ? 1 : 0);

src/test/org/apache/commons/io/FilenameUtilsTestCase.java

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* @author Peter Donald
3434
* @author Matthew Hawthorne
3535
* @author Martin Cooper
36-
* @version $Id: FilenameUtilsTestCase.java,v 1.21 2004/11/27 01:22:05 scolebourne Exp $
36+
* @version $Id: FilenameUtilsTestCase.java,v 1.22 2004/11/27 17:00:51 scolebourne Exp $
3737
* @see FilenameUtils
3838
*/
3939
public class FilenameUtilsTestCase extends FileBasedTestCase {
@@ -81,18 +81,6 @@ protected void tearDown() throws Exception {
8181
FileUtils.deleteDirectory(getTestDirectory());
8282
}
8383

84-
//-----------------------------------------------------------------------
85-
public void testCatPath() {
86-
// TODO StringIndexOutOfBoundsException thrown if file doesn't contain slash.
87-
// Is this acceptable?
88-
//assertEquals("", FilenameUtils.catPath("a", "b"));
89-
90-
assertEquals("/a" + File.separator + "c", FilenameUtils.catPath("/a/b", "c"));
91-
assertEquals("/a" + File.separator + "d", FilenameUtils.catPath("/a/b/c", "../d"));
92-
assertEquals("C:\\a" + File.separator + "c", FilenameUtils.catPath("C:\\a\\b", "c"));
93-
assertEquals("C:\\a" + File.separator + "d", FilenameUtils.catPath("C:\\a\\b\\c", "../d"));
94-
}
95-
9684
//-----------------------------------------------------------------------
9785
public void testNormalize() throws Exception {
9886
assertEquals(null, FilenameUtils.normalize(null));
@@ -215,6 +203,37 @@ public void testNormalize() throws Exception {
215203
assertEquals(SEP + SEP + "server" + SEP + "", FilenameUtils.normalize("//server/"));
216204
}
217205

206+
//-----------------------------------------------------------------------
207+
public void testConcat() {
208+
assertEquals(null, FilenameUtils.concat("", null));
209+
assertEquals(null, FilenameUtils.concat(null, null));
210+
assertEquals(null, FilenameUtils.concat(null, ""));
211+
assertEquals(null, FilenameUtils.concat(null, "a"));
212+
assertEquals(SEP + "a", FilenameUtils.concat(null, "/a"));
213+
214+
assertEquals(null, FilenameUtils.concat("", ":")); // invalid prefix
215+
assertEquals(null, FilenameUtils.concat(":", "")); // invalid prefix
216+
217+
assertEquals("f", FilenameUtils.concat("", "f/"));
218+
assertEquals("f", FilenameUtils.concat("", "f"));
219+
assertEquals("a" + SEP + "f", FilenameUtils.concat("a/", "f/"));
220+
assertEquals("a" + SEP + "f", FilenameUtils.concat("a", "f"));
221+
assertEquals("a" + SEP + "b" + SEP + "f", FilenameUtils.concat("a/b/", "f/"));
222+
assertEquals("a" + SEP + "b" + SEP + "f", FilenameUtils.concat("a/b", "f"));
223+
224+
assertEquals("a" + SEP + "f", FilenameUtils.concat("a/b/", "../f/"));
225+
assertEquals("a" + SEP + "f", FilenameUtils.concat("a/b", "../f"));
226+
assertEquals("a" + SEP + "c" + SEP + "g", FilenameUtils.concat("a/b/../c/", "f/../g/"));
227+
assertEquals("a" + SEP + "c" + SEP + "g", FilenameUtils.concat("a/b/../c", "f/../g"));
228+
229+
assertEquals("a" + SEP + "c.txt" + SEP + "f", FilenameUtils.concat("a/c.txt", "f"));
230+
231+
assertEquals(SEP + "f", FilenameUtils.concat("", "/f/"));
232+
assertEquals(SEP + "f", FilenameUtils.concat("", "/f"));
233+
assertEquals(SEP + "f", FilenameUtils.concat("a/", "/f/"));
234+
assertEquals(SEP + "f", FilenameUtils.concat("a", "/f"));
235+
}
236+
218237
//-----------------------------------------------------------------------
219238
public void testSeparatorsToUnix() {
220239
assertEquals(null, FilenameUtils.separatorsToUnix(null));

0 commit comments

Comments
 (0)