From 25484253db5920a2b34079a486f15d051503ac1a Mon Sep 17 00:00:00 2001 From: Stefan Bodewig Date: Wed, 8 Aug 2001 16:13:08 +0000 Subject: [PATCH] Move resolveFile methods to FileUtils, add a normalize method there. Make sure Ant normalizes whatever it takes as its basedir - since we've dropped canonicalPath ant -f foo.xml and ant -f ./foo.xml would have given different results (the . needed to be stripped). git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@269522 13f79535-47bb-0310-9956-ffa450edef68 --- src/main/org/apache/tools/ant/Project.java | 62 +----- .../tools/ant/taskdefs/XSLTProcess.java | 8 +- .../org/apache/tools/ant/util/FileUtils.java | 179 +++++++++++++++++- .../org/apache/tools/ant/ProjectTest.java | 3 + .../apache/tools/ant/util/FileUtilsTest.java | 141 +++++++++++++- 5 files changed, 327 insertions(+), 66 deletions(-) diff --git a/src/main/org/apache/tools/ant/Project.java b/src/main/org/apache/tools/ant/Project.java index ef3422092..e6f759326 100644 --- a/src/main/org/apache/tools/ant/Project.java +++ b/src/main/org/apache/tools/ant/Project.java @@ -339,11 +339,12 @@ public class Project { } public void setBaseDir(File baseDir) throws BuildException { + baseDir = fileUtils.normalize(baseDir.getAbsolutePath()); if (!baseDir.exists()) throw new BuildException("Basedir " + baseDir.getAbsolutePath() + " does not exist"); if (!baseDir.isDirectory()) throw new BuildException("Basedir " + baseDir.getAbsolutePath() + " is not a directory"); - this.baseDir = new File(baseDir.getAbsolutePath()); + this.baseDir = baseDir; setProperty( "basedir", this.baseDir.getPath()); String msg = "Project base dir set to: " + this.baseDir; log(msg, MSG_VERBOSE); @@ -594,66 +595,15 @@ public class Project { * *

If fileName is a relative file name, resolve it relative to * rootDir.

+ * + * @deprecated */ public File resolveFile(String fileName, File rootDir) { - fileName = fileName.replace('/', File.separatorChar).replace('\\', File.separatorChar); - - // deal with absolute files - if (fileName.startsWith(File.separator)) { - return new File(fileName); - } - - // Eliminate consecutive slashes after the drive spec - if (fileName.length() >= 2 && - Character.isLetter(fileName.charAt(0)) && - fileName.charAt(1) == ':') { - char[] ca = fileName.replace('/', '\\').toCharArray(); - char c; - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < ca.length; i++) { - if ((ca[i] != '\\') || - (ca[i] == '\\' && - i > 0 && - ca[i - 1] != '\\')) { - if (i == 0 && - Character.isLetter(ca[i]) && - i < ca.length - 1 && - ca[i + 1] == ':') { - c = Character.toUpperCase(ca[i]); - } else { - c = ca[i]; - } - - sb.append(c); - } - } - - return new File(sb.toString()); - } - - File file = new File(rootDir.getAbsolutePath()); - StringTokenizer tok = new StringTokenizer(fileName, File.separator, false); - while (tok.hasMoreTokens()) { - String part = tok.nextToken(); - if (part.equals("..")) { - String parentFile = file.getParent(); - if (parentFile == null) { - throw new BuildException("The file or path you specified (" + fileName + ") is invalid relative to " + rootDir.getAbsolutePath()); - } - file = new File(parentFile); - } else if (part.equals(".")) { - // Do nothing here - } else { - file = new File(file, part); - } - } - - return new File(file.getAbsolutePath()); + return fileUtils.resolveFile(rootDir, fileName); } public File resolveFile(String fileName) { - return resolveFile(fileName, baseDir); + return fileUtils.resolveFile(baseDir, fileName); } /** diff --git a/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java b/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java index d53817cb2..9496c32e0 100644 --- a/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java +++ b/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java @@ -63,6 +63,7 @@ import java.util.Vector; import org.apache.tools.ant.*; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; /** @@ -111,10 +112,13 @@ public class XSLTProcess extends MatchingTask { private boolean force = false; + private FileUtils fileUtils; + /** * Creates a new XSLTProcess Task. **/ public XSLTProcess() { + fileUtils = FileUtils.newFileUtils(); } //-- XSLTProcess /** @@ -137,9 +141,9 @@ public class XSLTProcess extends MatchingTask { liaison = getLiaison(); log("Using "+liaison.getClass().toString(), Project.MSG_VERBOSE); - File stylesheet = project.resolveFile(xslFile, project.getBaseDir()); + File stylesheet = project.resolveFile(xslFile); if (!stylesheet.exists()) { - stylesheet = project.resolveFile(xslFile, baseDir); + stylesheet = fileUtils.resolveFile(baseDir, xslFile); /* * shouldn't throw out deprecation warnings before we know, * the wrong version has been used. diff --git a/src/main/org/apache/tools/ant/util/FileUtils.java b/src/main/org/apache/tools/ant/util/FileUtils.java index 2b42e9e37..03d3b9691 100644 --- a/src/main/org/apache/tools/ant/util/FileUtils.java +++ b/src/main/org/apache/tools/ant/util/FileUtils.java @@ -56,22 +56,22 @@ package org.apache.tools.ant.util; import java.io.*; import java.lang.reflect.Method; +import java.util.StringTokenizer; +import java.util.Stack; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.FilterSet; /** - * Central representation of an Ant project. This class defines a - * Ant project with all of it's targets and tasks. It also provides - * the mechanism to kick off a build using a particular target name. - *

- * This class also encapsulates methods which allow Files to be refered - * to using abstract path names which are translated to native system - * file paths at runtime as well as defining various project properties. + * This class also encapsulates methods which allow Files to be + * refered to using abstract path names which are translated to native + * system file paths at runtime as well as copying files or setting + * there last modification time. * * @author duncan@x180.com * @author Conor MacNeill + * @author Stefan Bodewig */ public class FileUtils { @@ -292,5 +292,170 @@ public class FileUtils { } } + /** + * Interpret the filename as a file relative to the given file - + * unless the filename already represents an absolute filename. + * + * @param file the "reference" file for relative paths. This + * instance must be an absolute file and must not contain + * "./" or "../" sequences (same for \ instead + * of /). + * @param filename a file name + * + * @return an absolute file that doesn't contain "./" or + * "../" sequences and uses the correct separator for + * the current platform. + */ + public File resolveFile(File file, String filename) { + filename = filename.replace('/', File.separatorChar) + .replace('\\', File.separatorChar); + + // deal with absolute files + if (filename.startsWith(File.separator) || + + (filename.length() >= 2 && + Character.isLetter(filename.charAt(0)) && + filename.charAt(1) == ':') + + ) { + return normalize(filename); + } + + if (filename.length() >= 2 && + Character.isLetter(filename.charAt(0)) && + filename.charAt(1) == ':') { + return normalize(filename); + } + + File helpFile = new File(file.getAbsolutePath()); + StringTokenizer tok = new StringTokenizer(filename, File.separator); + while (tok.hasMoreTokens()) { + String part = tok.nextToken(); + if (part.equals("..")) { + String parentFile = helpFile.getParent(); + if (parentFile == null) { + String msg = "The file or path you specified (" + + filename + ") is invalid relative to " + + file.getPath(); + throw new BuildException(msg); + } + helpFile = new File(parentFile); + } else if (part.equals(".")) { + // Do nothing here + } else { + helpFile = new File(helpFile, part); + } + } + + return new File(helpFile.getAbsolutePath()); + } + + /** + * "normalize" the given absolute path. + * + *

This includes: + *

+ */ + public File normalize(String path) { + String orig = path; + + path = path.replace('/', File.separatorChar) + .replace('\\', File.separatorChar); + + // make sure we are dealing with an absolute path + if (!path.startsWith(File.separator) && + ! (path.length() >= 2 && + Character.isLetter(path.charAt(0)) && + path.charAt(1) == ':') + ) { + String msg = path + " is not an absolute path"; + throw new BuildException(msg); + } + + boolean dosWithDrive = false; + String root = null; + // Eliminate consecutive slashes after the drive spec + if (path.length() >= 2 && + Character.isLetter(path.charAt(0)) && + path.charAt(1) == ':') { + + dosWithDrive = true; + + char[] ca = path.replace('/', '\\').toCharArray(); + StringBuffer sb = new StringBuffer(); + sb.append(Character.toUpperCase(ca[0])).append(':'); + + for (int i = 2; i < ca.length; i++) { + if ((ca[i] != '\\') || + (ca[i] == '\\' && ca[i - 1] != '\\') + ) { + sb.append(ca[i]); + } + } + + path = sb.toString().replace('\\', File.separatorChar); + if (path.length() == 2) { + root = path; + path = ""; + } else { + root = path.substring(0, 3); + path = path.substring(3); + } + + } else { + if (path.length() == 1) { + root = File.separator; + path = ""; + } else if (path.charAt(1) == File.separatorChar) { + // UNC drive + root = File.separator+File.separator; + path = path.substring(2); + } else { + root = File.separator; + path = path.substring(1); + } + } + + Stack s = new Stack(); + s.push(root); + StringTokenizer tok = new StringTokenizer(path, File.separator); + while (tok.hasMoreTokens()) { + String thisToken = tok.nextToken(); + if (".".equals(thisToken)) { + continue; + } else if ("..".equals(thisToken)) { + if (s.size() < 2) { + throw new BuildException("Cannot resolve path "+orig); + } else { + s.pop(); + } + } else { // plain component + s.push(thisToken); + } + } + + StringBuffer sb = new StringBuffer(); + for (int i=0; i 1) { + // not before the filesystem root and not after it, since root + // already contains one + sb.append(File.separatorChar); + } + sb.append(s.elementAt(i)); + } + + + path = sb.toString(); + if (dosWithDrive) { + path = path.replace('/', '\\'); + } + return new File(path); + } } diff --git a/src/testcases/org/apache/tools/ant/ProjectTest.java b/src/testcases/org/apache/tools/ant/ProjectTest.java index f1807f0d9..83c64ce56 100644 --- a/src/testcases/org/apache/tools/ant/ProjectTest.java +++ b/src/testcases/org/apache/tools/ant/ProjectTest.java @@ -93,6 +93,9 @@ public class ProjectTest extends TestCase { assert("Path", p.createDataType("path") instanceof Path); } + /** + * This test has been a starting point for moving the code to FileUtils. + */ public void testResolveFile() { /* * Start with simple absolute file names. diff --git a/src/testcases/org/apache/tools/ant/util/FileUtilsTest.java b/src/testcases/org/apache/tools/ant/util/FileUtilsTest.java index af1daecb8..cdceb38e9 100644 --- a/src/testcases/org/apache/tools/ant/util/FileUtilsTest.java +++ b/src/testcases/org/apache/tools/ant/util/FileUtilsTest.java @@ -58,6 +58,8 @@ import java.io.*; import junit.framework.TestCase; +import org.apache.tools.ant.BuildException; + /** * Tests for org.apache.tools.ant.util.FileUtils. * @@ -65,12 +67,19 @@ import junit.framework.TestCase; */ public class FileUtilsTest extends TestCase { + private FileUtils fu; private File removeThis; + private String root; public FileUtilsTest(String name) { super(name); } + public void setUp() { + fu = FileUtils.newFileUtils(); + root = new File(File.separator).getAbsolutePath(); + } + public void tearDown() { if (removeThis != null && removeThis.exists()) { removeThis.delete(); @@ -78,7 +87,6 @@ public class FileUtilsTest extends TestCase { } public void testSetLastModified() throws IOException { - FileUtils fu = FileUtils.newFileUtils(); removeThis = new File("dummy"); FileOutputStream fos = new FileOutputStream(removeThis); fos.write(new byte[0]); @@ -125,4 +133,135 @@ public class FileUtilsTest extends TestCase { } } + public void testResolveFile() { + /* + * Start with simple absolute file names. + */ + assertEquals(File.separator, + fu.resolveFile(null, "/").getPath()); + assertEquals(File.separator, + fu.resolveFile(null, "\\").getPath()); + + /* + * throw in drive letters + */ + String driveSpec = "C:"; + assertEquals(driveSpec + "\\", + fu.resolveFile(null, driveSpec + "/").getPath()); + assertEquals(driveSpec + "\\", + fu.resolveFile(null, driveSpec + "\\").getPath()); + String driveSpecLower = "c:"; + assertEquals(driveSpec + "\\", + fu.resolveFile(null, driveSpecLower + "/").getPath()); + assertEquals(driveSpec + "\\", + fu.resolveFile(null, driveSpecLower + "\\").getPath()); + /* + * promised to eliminate consecutive slashes after drive letter. + */ + assertEquals(driveSpec + "\\", + fu.resolveFile(null, driveSpec + "/////").getPath()); + assertEquals(driveSpec + "\\", + fu.resolveFile(null, driveSpec + "\\\\\\\\\\\\").getPath()); + + /* + * Now test some relative file name magic. + */ + assertEquals(localize("/1/2/3/4"), + fu.resolveFile(new File(localize("/1/2/3")), "4").getPath()); + assertEquals(localize("/1/2/3/4"), + fu.resolveFile(new File(localize("/1/2/3")), "./4").getPath()); + assertEquals(localize("/1/2/3/4"), + fu.resolveFile(new File(localize("/1/2/3")), ".\\4").getPath()); + assertEquals(localize("/1/2/3/4"), + fu.resolveFile(new File(localize("/1/2/3")), "./.\\4").getPath()); + assertEquals(localize("/1/2/3/4"), + fu.resolveFile(new File(localize("/1/2/3")), "../3/4").getPath()); + assertEquals(localize("/1/2/3/4"), + fu.resolveFile(new File(localize("/1/2/3")), "..\\3\\4").getPath()); + assertEquals(localize("/1/2/3/4"), + fu.resolveFile(new File(localize("/1/2/3")), "../../5/.././2/./3/6/../4").getPath()); + assertEquals(localize("/1/2/3/4"), + fu.resolveFile(new File(localize("/1/2/3")), "..\\../5/..\\./2/./3/6\\../4").getPath()); + + try { + fu.resolveFile(new File(localize("/1")), "../../b"); + fail("successfully crawled beyond the filesystem root"); + } catch (BuildException e) { + // Expected Exception caught + } + + } + + public void testNormalize() { + /* + * Start with simple absolute file names. + */ + assertEquals(File.separator, + fu.normalize("/").getPath()); + assertEquals(File.separator, + fu.normalize("\\").getPath()); + + /* + * throw in drive letters + */ + String driveSpec = "C:"; + assertEquals(driveSpec + "\\", + fu.normalize(driveSpec + "/").getPath()); + assertEquals(driveSpec + "\\", + fu.normalize(driveSpec + "\\").getPath()); + String driveSpecLower = "c:"; + assertEquals(driveSpec + "\\", + fu.normalize(driveSpecLower + "/").getPath()); + assertEquals(driveSpec + "\\", + fu.normalize(driveSpecLower + "\\").getPath()); + /* + * promised to eliminate consecutive slashes after drive letter. + */ + assertEquals(driveSpec + "\\", + fu.normalize(driveSpec + "/////").getPath()); + assertEquals(driveSpec + "\\", + fu.normalize(driveSpec + "\\\\\\\\\\\\").getPath()); + + /* + * Now test some relative file name magic. + */ + assertEquals(localize("/1/2/3/4"), + fu.normalize(localize("/1/2/3/4")).getPath()); + assertEquals(localize("/1/2/3/4"), + fu.normalize(localize("/1/2/3/./4")).getPath()); + assertEquals(localize("/1/2/3/4"), + fu.normalize(localize("/1/2/3/.\\4")).getPath()); + assertEquals(localize("/1/2/3/4"), + fu.normalize(localize("/1/2/3/./.\\4")).getPath()); + assertEquals(localize("/1/2/3/4"), + fu.normalize(localize("/1/2/3/../3/4")).getPath()); + assertEquals(localize("/1/2/3/4"), + fu.normalize(localize("/1/2/3/..\\3\\4")).getPath()); + assertEquals(localize("/1/2/3/4"), + fu.normalize(localize("/1/2/3/../../5/.././2/./3/6/../4")).getPath()); + assertEquals(localize("/1/2/3/4"), + fu.normalize(localize("/1/2/3/..\\../5/..\\./2/./3/6\\../4")).getPath()); + + try { + fu.normalize("foo"); + fail("foo is not an absolute path"); + } catch (BuildException e) { + // Expected exception caught + } + + try { + fu.normalize(localize("/1/../../b")); + fail("successfully crawled beyond the filesystem root"); + } catch (BuildException e) { + // Expected exception caught + } + } + + /** + * adapt file separators to local conventions + */ + private String localize(String path) { + path = root + path.substring(1); + return path.replace('\\', File.separatorChar).replace('/', File.separatorChar); + } }