I'm currently investigating in some pathTraversal related security mechanisms and came across a weird behavior of java.io.File.getCanonicalPath(). I thought CanonicalPath will always represent the true unique path of the abstract underlying File. However if the file name consists a of two dots followed by a space the CanonicalPath does not seem to represent the correct path anymore.
Here is the example:
File root = new File("c:/git/");
String relative = ".. /.. \\";
File concatFile = new File (root.getCanonicalPath(), relative);
System.out.println("ConcatFileAbsolute: '" + concatFile.getAbsolutePath() + "'");
System.out.println("ConcatFileCanonical: '" + concatFile.getCanonicalPath() + "'");
File canonFile = new File(concatFile.getCanonicalPath());
System.out.println("\ncanonFileCanonical: '" + canonFile.getCanonicalPath() + "'");
System.out.println("canonFileAbsolute: '" + canonFile.getAbsolutePath() + "'");
System.out.println("canonFileName: '" + canonFile.getName() + "'\n");
for (File file : canonFile.listFiles()) {
System.out.println("canon: '" + file.getCanonicalPath() + "' - absolute: '" + file.getAbsolutePath()+ "'");
}
Console Output:
ConcatFileAbsolute: 'C:\git\.. \.. '
ConcatFileCanonical: 'C:\git\.. \'
canonFileCanonical: 'C:\git\'
canonFileAbsolute: 'C:\git\.. '
canonFileName: '.. '
canon: 'C:\git\.. \$Recycle.Bin' - absolute: 'C:\git\.. \$Recycle.Bin'
canon: 'C:\git\.. \.m2' - absolute: 'C:\git\.. \.m2'
canon: 'C:\git\.. \boot' - absolute: 'C:\git\.. \boot'
...other content of C:/
As you can see although the canonicalPath of canonFile clearly indicates it's location in C:\git\, but .listFiles lists all files in C:.
This only happens if I use the new File(String, String) constructor. If I change the third line to File concatFile = new File (root.getCanonicalPath() + relative); then the output is as expected:
ConcatFileAbsolute: 'C:\git.. \.. '
ConcatFileCanonical: 'C:\git\'
- The following output then lists files under C:\git\
I don't know if this behavior is similar on unix-like systems.
Can somebody clarify this behavior? Is this intended?
Thanks upfront!
Congrats! You found a possible bug (or feature?) of WinAPI.
More specifically, your question can be reduced to one line of code:
Why does
new File("c:\\temp\\.. ").getCanonicalPath()
returnsc:\temp\
instead ofc:\
?Short answer: because of tailing space after
..
(yep, you've mentioned above it).Long answer: if we will look inside Java implementation of underlying class, we will found that for Windows it is
WinNTFileSystem
class, where native methodcanonicalize0("c:\\temp\\.. ")
returns this broken value. Why? To test it I've written simple VBS code to test Win API:It gives exactly same result: