4

Assume I have the following java.io.File and the corresponding java.nio.file.Path objects:

final String windir = "WINNT";
final String comspec = "cmd.exe";
final File absoluteFile = new File(format("C:\\./foo/../bar/../%s/./././././SYSTEM32/../system/../System32/%s", windir, comspec)).getAbsoluteFile();
final Path absolutePath = absoluteFile.toPath();

Now, I want to determine the canonical path, i. e. remove any . or .. path entries, so that the resulting path is C:\WINNT\System32\cmd.exe.

java.io.File.getCanonicalPath() is fine, except that it follows symbolic links on Unices which I would like to avoid.

java.nio.file.Path.toRealPath(NOFOLLOW_LINKS), on the other hand, returns the canonical path without following symbolic links, but it throws a java.nio.file.NoSuchFileException.

How can I determine the canonical path of a file

  • in a safe way (so that no exceptions are thrown in case the file doesn't exist),
  • in a platform-independent way, and
  • without following symbolic links?

The only solution I have found so far is falling back to the old java.io API:

@NonNull Path toCanonicalPath(final @NonNull Path path) throws IOException {
    try {
        /*
         * Fails for nonexistent files.
         */
        return path.toRealPath(NOFOLLOW_LINKS);
    } catch (final NoSuchFileException ignored) {
        /*
         * This one is fine except it always follows symbolic links on Unices.
         */
        return path.toFile().getCanonicalFile().toPath();
    } catch (final FileSystemException ignored) {
        /*
         * Thrown when there's a file/directory conflict, e. g.
         * for a non-existent file "foo/bar", "foo" already
         * exists and is a symlink, not a directory. In this
         * case, we can't use the File#getCanonicalFile() call.
         */
        return path.toAbsolutePath();
    }
}

Is there any less ugly approach?

Bass
  • 4,977
  • 2
  • 36
  • 82
  • I don't understand how you can get a canonical path *without* following symbolic links. [It's *defined* as "resolving symbolic links"](https://docs.oracle.com/javase/7/docs/api/java/io/File.html#getCanonicalPath()). (OK, it's also defined as "unique", which may not be true on Unix and Unix-like systems...) – Andrew Henle Aug 29 '17 at 12:24
  • @AndrewHenle "Canonical" in terms of removing any `.` or `..` path entries, i. e. I want the **shortest absolute** path (yet without symlink resolution). – Bass Aug 29 '17 at 13:49

1 Answers1

2

path.toAbsolutePath().normalize() actually did the trick.

Consider we have a /var/spool/mail symlink pointing to /var/mail:

final Path path = Paths.get("/var/./spool/../spool//mail/./");
System.out.println(path.toAbsolutePath().normalize());
System.out.println(path.toRealPath(NOFOLLOW_LINKS));

In the above example, in both cases the canonical path is printed with symlinks left unresolved:

/var/spool/mail
/var/spool/mail
Bass
  • 4,977
  • 2
  • 36
  • 82