Is there a way in Java to determine if a path is valid without attempting to create a file?
Asked Answered
F

7

92

I need to determine if a user-supplied string is a valid file path (i.e., if createNewFile() will succeed or throw an Exception) but I don't want to bloat the file system with useless files, created just for validation purposes.

Is there a way to determine if the string I have is a valid file path without attempting to create the file?

I know the definition of "valid file path" varies depending on the OS, but I was wondering if there was any quick way of accepting C:/foo or /foo and rejecting banana.

A possible approach may be attempting to create the file and eventually deleting it if the creation succeeded, but I hope there is a more elegant way of achieving the same result.

Footling answered 22/1, 2009 at 11:39 Comment(1)
related checker documentation from klocwork : SV.PATH : contains usefull guidelinesFriar
M
42

This would check for the existance of the directory as well.

File file = new File("c:\\cygwin\\cygwin.bat");
if (!file.isDirectory())
   file = file.getParentFile();
if (file.exists()){
    ...
}

It seems like file.canWrite() does not give you a clear indication if you have permissions to write to the directory.

Morphogenesis answered 22/1, 2009 at 11:41 Comment(6)
Uhm...i don't want to check if the file exists, i want to check if the file can be created on the current file systemFootling
The problem there is that the ability to create the file could change after you've checked to see if it could be createdExistent
krosenvold's code is doing the following: Does the filename passed in exist already as a directory, and if not, does the directory it would be in exist? This makes sense since if the directory is there you can create the file (permissions allowing) Note that new File("foo") doesn't create a file.Paediatrics
put this in the constructor of File "c:[][]cygwin\\cygwin.bat"...then put print statements in both ifs - "dir found" in 1st if and "file found" in second if. output is dir found which is obviously not the same as checking if a path name is valid/legal.Credence
Remark: You don't actually create a file on the file system with new File("some/path"). So you can use these for validating purposes. To create files on the file system to have to createNewFile etc.Jeth
I don't understand Java has no native way to test for a path validness (please do not consider Java 7 Path class, it is "recent" and seems unsuccessful for that matter under Linux). Is there any particular reason to this? Heck, it's not like pathing changed that much those past decades.Osbourn
D
54

Path class introduced in Java 7 adds new alternatives, like the following:

/**
 * <pre>
 * Checks if a string is a valid path.
 * Null safe.
 *  
 * Calling examples:
 *    isValidPath("c:/test");      //returns true
 *    isValidPath("c:/te:t");      //returns false
 *    isValidPath("c:/te?t");      //returns false
 *    isValidPath("c/te*t");       //returns false
 *    isValidPath("good.txt");     //returns true
 *    isValidPath("not|good.txt"); //returns false
 *    isValidPath("not:good.txt"); //returns false
 * </pre>
 */
public static boolean isValidPath(String path) {
    try {
        Paths.get(path);
    } catch (InvalidPathException | NullPointerException ex) {
        return false;
    }
    return true;
}

Edit:
Note Ferrybig's comment : "The only disallowed character in a file name on Linux is the NUL character, this does work under Linux."

Demodena answered 17/2, 2016 at 9:31 Comment(18)
Beautiful! Nice and clean solution without the need to include Apache libraries.North
Thank you @ Tim Visée. I am glad you find it a good solution (0:Demodena
note that this always returns true on linux. In my own testing, even Paths.get("file\u0000") doesnt throw InvalidPathException.Sperm
@ Groostav thanks for the important note. I can't test it on Linux. If the issue you raise will be confirmed I will add a note to it. If you have a solution please post it.Demodena
@Demodena I tried the solution with Paths.get(path) on Ubuntu and it returned true even though the file didn't exists. Looks like the same issue as for @Groostav.Prevot
@Demodena Any idea if the bug is still present under Linux?Osbourn
Sorry I have no idea.Demodena
Either java.nio.file.Path nor java java.nio.file.Paths seem to have a static method "isValidPath". I am using JDK 8Edana
@for3st isValidPath is a custom method I wrote. It uses java's Paths class. Is that the reason for the down vote ?Demodena
@Demodena oh I see, my bad, I tought it is supposed to be a JDK method. Unfortunatly I can't remove the downvote any more :(Edana
That's ok. As long as I know that the solution is not flawed it's fine.Demodena
also it returns true when you write Paths.get("myFile.txt/dir/") on Linux. edit: I saw "not working on linux" comment, sorry.Subaquatic
"Does not work properly under Linux - always returns true"Demodena
@Subaquatic - Not working on Linux aside, what's wrong with it returning true for that? It's perfectly valid to have a directory name with periods in it, if that's what you think is wrong with it...Cathe
Nice solution. The only thing I don't like is that an exception is thrown if a path is invalid, which isn't ideal for checking many paths (e.g. from a user created file) very quickly (as exceptions can be somewhat slow).Telmatelo
@Kröw I see your point. I guess in such use case responsiveness has to be checked.Demodena
The only disallowed character in a file name on Linux is the NUL character, this does work under LinuxFalkirk
@PatrickFavre the Files api offers some validation mechanics like Files.exists(Path, LinkOption) or Files.isDirectory(Path, LinkOption)Folks
M
42

This would check for the existance of the directory as well.

File file = new File("c:\\cygwin\\cygwin.bat");
if (!file.isDirectory())
   file = file.getParentFile();
if (file.exists()){
    ...
}

It seems like file.canWrite() does not give you a clear indication if you have permissions to write to the directory.

Morphogenesis answered 22/1, 2009 at 11:41 Comment(6)
Uhm...i don't want to check if the file exists, i want to check if the file can be created on the current file systemFootling
The problem there is that the ability to create the file could change after you've checked to see if it could be createdExistent
krosenvold's code is doing the following: Does the filename passed in exist already as a directory, and if not, does the directory it would be in exist? This makes sense since if the directory is there you can create the file (permissions allowing) Note that new File("foo") doesn't create a file.Paediatrics
put this in the constructor of File "c:[][]cygwin\\cygwin.bat"...then put print statements in both ifs - "dir found" in 1st if and "file found" in second if. output is dir found which is obviously not the same as checking if a path name is valid/legal.Credence
Remark: You don't actually create a file on the file system with new File("some/path"). So you can use these for validating purposes. To create files on the file system to have to createNewFile etc.Jeth
I don't understand Java has no native way to test for a path validness (please do not consider Java 7 Path class, it is "recent" and seems unsuccessful for that matter under Linux). Is there any particular reason to this? Heck, it's not like pathing changed that much those past decades.Osbourn
S
17

File.getCanonicalPath() is quite useful for this purpose. IO exceptions are thrown for certain types of invalid filenames (e.g. CON, PRN, *?* in Windows) when resolving against the OS or file system. However, this only serves as a preliminary check; you will still need to handle other failures when actually creating the file (e.g. insufficient permissions, lack of drive space, security restrictions).

Swank answered 22/1, 2009 at 13:25 Comment(1)
AFAICT, there's nothing to prevent creating a windows 10 file named CON programatically, although some shell environments might complain. Maybe this refers only to how CON (and other strings) are interpreted by CMD.exe (and possibly others).Wolcott
M
15

A number of things can go wrong when you try and create a file:

  • Your lack the requisite permissions;
  • There is not enough space on the device;
  • The device experiences an error;
  • Some policy of custom security prohibits you from creating a file of a particular type;
  • etc.

More to the point, those can change between when you try and query to see if you can and when you actually can. In a multithreaded environment this is one of the primary causes of race conditions and can be a real vulnerability of some programs.

Basically you just have to try and create it and see if it works. And that's the correct way to do it. It's why things like ConcurrentHashMap has a putIfAbsent() so the check and insert is an atomic operation and doesn't suffer from race conditions. Exactly the same principle is in play here.

If this is just part of some diagnostic or install process, just do it and see if it works. Again there's no guarantee that it'll work later however.

Basically your program has to be robust enough to die gracefully if it can't write a relevant file.

Milklivered answered 22/1, 2009 at 11:53 Comment(1)
DON"T DIE (in some cases), allow the user to pick another volume or media. I have an IDE that dies when it can't write its project file. Well the volume is offline - allow me to select another spot and continue.Outstrip
C
1
boolean canWrite(File file) {
  if (file.exists()) {
    return file.canWrite();
  }
  else {
    try {
      file.createNewFile();
      file.delete();
      return true;
    }
    catch (Exception e) {
      return false;
    }
  }
}
Chew answered 14/7, 2011 at 18:7 Comment(1)
This doesn't fully work since File.canWrite() is not reliable on Windows. See this post and Peter Tseng's first comment for a workaround.Chew
A
1

Here's something you can do that works across operating systems

Using regex match to check for existing known invalid characters.

if (newName.matches(".*[/\n\r\t\0\f`?*\\<>|\":].*")) {
    System.out.println("Invalid!");
} else {
    System.out.println("Valid!");
}

Pros

  • This works across operating systems
  • You can customize it whatever way you want by editing that regex.

Cons

  • This might not be a complete list and need more research to fill in more invalid patterns or characters.
Alkyd answered 15/10, 2018 at 18:59 Comment(0)
P
0

Just do it (and clean up after yourself)

A possible approach may be attempting to create the file and eventually deleting it if the creation succeeded, but I hope there is a more elegant way of achieving the same result.

Maybe that's the most robust way.

Below is canCreateOrIsWritable that determines whether your program is able to create a file and its parent directories at a given path, or, if there's already a file there, write to it.

It does so by actually creating the necessary parent directories as well as an empty file at the path. Afterwards, it deletes them (if there existed a file at the path, it's left alone).

Here's how you might use it:

var myFile = new File("/home/me/maybe/write/here.log")

if (canCreateOrIsWritable(myFile)) {
    // We're good. Create the file or append to it
    createParents(myFile);
    appendOrCreate(myFile, "new content");
} else {
    // Let's pick another destination. Maybe the OS's temporary directory:
    var tempDir = System.getProperty("java.io.tmpdir");
    var alternative = Paths.get(tempDir, "second_choice.log");
    appendOrCreate(alternative, "new content in temporary directory");
}

The essential method with a few helper methods:

static boolean canCreateOrIsWritable(File file) {
    boolean canCreateOrIsWritable;

    // The non-existent ancestor directories of the file.
    // The file's parent directory is first
    List<File> parentDirsToCreate = getParentDirsToCreate(file);

    // Create the parent directories that don't exist, starting with the one
    // highest up in the file system hierarchy (closest to root, farthest
    // away from the file)
    reverse(parentDirsToCreate).forEach(File::mkdir);

    try {
        boolean wasCreated = file.createNewFile();
        if (wasCreated) {
            canCreateOrIsWritable = true;
            // Remove the file and its parent dirs that didn't exist before
            file.delete();
            parentDirsToCreate.forEach(File::delete);
        } else {
            // There was already a file at the path → Let's see if we can
            // write to it
            canCreateOrIsWritable = java.nio.file.Files.isWritable(file.toPath());
        }
    } catch (IOException e) {
        // File creation failed
        canCreateOrIsWritable = false;
    }
    return canCreateOrIsWritable;
}

static List<File> getParentDirsToCreate(File file) {
    var parentsToCreate = new ArrayList<File>();
    File parent = file.getParentFile();
    while (parent != null && !parent.exists()) {
        parentsToCreate.add(parent);

        parent = parent.getParentFile();
    }
    return parentsToCreate;
}

static <T> List<T> reverse(List<T> input) {
    var reversed = new ArrayList<T>();
    for (int i = input.size() - 1; i >= 0; i--) {
        reversed.add(input.get(i));
    }
    return reversed;
}

static void createParents(File file) {
    File parent = file.getParentFile();
    if (parent != null) {
        parent.mkdirs();
    }
}

Keep in mind that between calling canCreateOrIsWritable and creating the actual file, the contents and permissions of your file system might have changed.

Pensioner answered 1/5, 2019 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.