58

In my Java application I am renaming files to a file name provided in a String parameter. There is a method

boolean OKtoRename(String oldName, String newName)

which basically checks whether the newName isn't already taken by some other file, as I wouldn't want to bury existing ones.

It now occurred to me that perhaps the newName String will not denote a valid file name. So I thought to add this check to the method:

if (new File(newName).isFile()) { 
    return false; 
}

Which obviously isn't the right way to do it, since in most cases the newFile does not yet exist and therefore although it is OKtoRename, the function returns false.

I was wondering, is there a method (I know there isn't for the java.io.File objects) like canExist()? Or would I have to resort to regex to make sure the newFile String does not contain invalid characters (e.g. ?, *, ", :)? I wonder if there is perhaps a function hidden somewhere in the JDK that would tell me if a string could possibly denote a valid file name.

OscarRyz
  • 196,001
  • 113
  • 385
  • 569
Peter Perháč
  • 20,434
  • 21
  • 120
  • 152
  • 1
    please, no more exists() answers, this is not helpful. I am trying to check whether a file COULD possibly exist. – Peter Perháč May 21 '09 at 17:15
  • 1
    @MasterPeter: Try rewording your question. It initally look like you want to know if the file exists, when what you actually need is to know if the file NAME is valid. I've change the title, but didn't touch the question wording. – OscarRyz May 21 '09 at 17:18
  • Seems relevant: http://eng-przemelek.blogspot.com/2009/07/how-to-create-valid-file-name.html – greenoldman Nov 30 '11 at 19:20
  • 2
    Why is this marked as being a duplicate? The link to the duplicate indicates checking for a directory. The question here is about checking for a file. – Johann Feb 07 '16 at 17:04

8 Answers8

66

I assembled a list of illegal filename characters (considering UNIX, Mac OS X and Windows systems) based on some online research a couple of months ago. If the new filename contains any of these, there's a risk that it might not be valid on all platforms.

private static final char[] ILLEGAL_CHARACTERS = { '/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':' };

EDIT: I would like to stress, that this is not a complete solution: as a commenter pointed out, even though it passes this test your file name could still be a Windows specific keyword like COM, PRN, etc. However, if your file name contains any of these characters, it will certainly cause trouble in a cross-platform environment.

Zsolt Török
  • 10,289
  • 2
  • 26
  • 26
  • 13
    But don't forget keywords, like trying to create a file called COM, COM1, COM2, PRN, etc. (at least on Windows platforms) – Instantsoup May 26 '09 at 17:52
  • Point taken, haven't even thought about that... – Zsolt Török May 27 '09 at 10:26
  • 1
    while i realize this isnt a complete solution, this worked great for me (where only a part of the file name is dynamic and so keywords are not a risk). thank you for compiling this list. – radai Aug 30 '10 at 11:46
  • Added a warning to indicate that this is only a partial solution. – Zsolt Török Sep 01 '10 at 06:12
  • 2
    @ZsoltTörök thanks for the list. This article may be helpful for those seeking official documentation for Windows: http://support.microsoft.com/kb/177506 – Nate Sep 24 '12 at 20:59
  • You should also reject ".", "..", and "" as file names. – Carsten Jun 03 '14 at 00:39
  • The previous link to the Windows documentation is not valid any more. Here is another one: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file – S. Doe May 31 '21 at 07:19
24

Here system specific way is suggested.

public static boolean isFilenameValid(String file) {
  File f = new File(file);
  try {
    f.getCanonicalPath();
    return true;
  } catch (IOException e) {
    return false;
  }
}
Maxim Mazin
  • 3,816
  • 1
  • 21
  • 15
  • Oh my bad, I didn't see your answer... –  Jan 20 '12 at 10:30
  • 2
    nice solution but unfortunately it doesn't work on Android :-( i.e. f.getCanonicalPath() doesn't throw exception for incorrect file names – Iwo Banas Sep 04 '12 at 22:01
  • 7
    Isn't it risky to rely on a function that is [not made for the job](http://docs.oracle.com/javase/7/docs/api/java/io/File.html#getCanonicalFile())? – Nielsvh Jan 09 '14 at 22:16
  • 7
    Nice; but note that (even on Windows) this does not catch the case where `file` is a (valid) directory name, e.g., ends on a slash. If you want to rule that out as well, replace lines 4+5 with `return f.getCanonicalFile().getName().equals(file)`. – Arend Jul 15 '14 at 19:53
  • What @Arend suggested plus check for f.isFile() in the first place, otherwise a valid directory might pass. – Terran Apr 16 '18 at 12:42
  • didn't work on Windows 10 for me – cupiqi09 Jan 16 '21 at 21:00
  • (didn't raise any Exception) – cupiqi09 Jan 16 '21 at 21:11
24

Use createNewFile(), which will atomically create the file only if it doesn't yet exist.

If the file is created, the name is valid and it is not clobbering an existing file. You can then open the files and efficiently copy data from one to the other with FileChannel.transferXXX operations.

An important thing to keep in mind that, in general, the check and the creation should be atomic. If you first check whether an operation is safe, then perform the operation as a separate step, conditions may have changed in the meantime, making the operation unsafe.

Additional food for thought is available at this related post: "Move/Copy operations in Java."


Update:

Since this answer, the NIO.2 APIs have been introduced, which add more interaction with the file system.

Suppose you have an interactive program, and want to validate after each keystroke whether the file is potentially valid. For example, you might want to enable a "Save" button only when the entry is valid rather than popping up an error dialog after pressing "Save". Creating and ensuring the deletion of a lot of unnecessary files that my suggestion above would require seems like a mess.

With NIO.2, you can't create a Path instance containing characters that are illegal for the file system. An InvalidPathException is raised as soon as you try to create the Path.

However, there isn't an API to validate illegal names comprised of valid characters, like "PRN" on Windows. As a workaround, experimentation showed that using an illegal file name would raise a distinct exception when trying to access attributes (using Files.getLastModifiedTime(), for example).

If you specify a legal name for a file that does exist, you get no exception.

If you specify a legal name for a file that does not exist, it raises NoSuchFileException.

If you specify an illegal name, FileSystemException is raised.

However, this seems very kludgey and might not be reliable on other operating systems.

Community
  • 1
  • 1
erickson
  • 265,237
  • 58
  • 395
  • 493
  • 2
    You'll want to delete the file you just created, though, because the function OKtoRename isn't supposed to actually change the file system, just answer if the file name would work. – Matt Poush May 21 '09 at 17:18
  • 1
    @Matt: Ooor, you just create the file and if createNewFile return false, it means the file name was not valid ( ... or could not be created :-S ) ¬¬ – OscarRyz May 21 '09 at 17:20
  • exactly, I think I'll go with this solution but I don't like the idea of creating an empty file just to delete it immediately afterwards... – Peter Perháč May 21 '09 at 17:21
  • You really shouldn't delete the file if there's any possibility that another process or thread could be creating a file of the same name. (And if there isn't, why are you bothering to check in the first place?) – erickson May 21 '09 at 17:24
  • 1
    Sorry for the focus on concurrency; I originally interpreted that as the primary question. However, actually creating the file is the only way to see if the name is valid. You can check out my question http://stackoverflow.com/questions/122400/what-are-reserved-filenames-for-various-platforms about reserved names on different platforms if you are interested in trying to go that route. – erickson May 21 '09 at 17:27
  • @Oscar I agree, it makes me feel wasteful to touch and then delete a file. The problem with not deleting it is you're adding side effects to a boolean function. The best bet would be to move createNewFile out of the OKto function, and if the createNewFile returns false, just bail out with 'return' (or throw an exception, etc). – Matt Poush May 21 '09 at 17:48
  • 1
    @Matt Poush, @MasterPeter: I don't think erickson was suggesting to create a file then delete it. It seems like the proper approach is to try to rename the file in one step and not test for "ok" first. If that step fails it wasn't ok. If it succeeds it was ok. – Mr. Shiny and New 安宇 May 21 '09 at 18:36
  • That's right. Let me re-emphasize: **"in general,** *the check and the creation should be atomic. If you first check whether an operation is safe, then perform the operation as a separate step, conditions may have changed in the meantime, making the operation unsafe."* If this doesn't seem to fit a particular situation, you should be able to articulate a clear defense for making an exception and opening the door to concurrency problems. – erickson Jul 08 '10 at 16:44
  • Now consider the scenario that you have a DocumentListener checking whether the text entered in a JTextField is usable as filename. You will only want to perform the file operation (whatever it may be) once the user has OKed the name, and definitely not attempt to do it at every single text edit. I guess this is no longer @erickson's "general" case; still, it would be good to have a reliable, side effect-free way to test the entered name. – Arend Jul 15 '14 at 19:50
  • @Arend Do you mean that you'd want to validate after each keystroke whether the file is potentially valid? For example, so that you can enable a "Save" button only when the entry is valid rather than popping up an error dialog after pressing "Save"? Since this answer, the NIO.2 APIs have been introduced, which add more interaction with the file system. It might be possible to do this now. I will research a bit more. – erickson Jul 15 '14 at 19:55
  • @erickson Yes, that's precisely what I have in mind. – Arend Jul 17 '14 at 00:37
  • @Arend I experimented a bit on Windows. I didn't find an API designed for this in NIO2. I did find that, on Windows at least, using an invalid file name like `"PRN"` would raise a *different* exception when trying to access attributes (using `Files.getLastModifiedTime()`, for example). Used on a missing file, it raises `NoSuchFileException`, while an illegal name raises `FileSystemException`. However, this seems very kludgey and might not be reliable on other operating systems. For illegal characters, however, an `InvalidPathException` is raised as soon as you try to create the `Path` object. – erickson Jul 22 '14 at 05:42
  • 1
    @erickson thanks for looking into this. Your proposal to use paths seems similar to the f.getCanonicalPath() solution proposed in a different answer, which I have adopted since and does what I want - but there's a comment to that answer saying it doesn't work on Android. I will try out the nio functionality instead, it's probably the way of the future. – Arend Jul 23 '14 at 06:42
  • 1
    Re-read the answer again after few years and found your edit for nio 2. Superb answer. Thanks again. – Peter Perháč Mar 11 '15 at 21:17
9

If developing for Eclipse, check out org.eclipse.core.internal.resources.OS

public abstract class OS {
   private static final String INSTALLED_PLATFORM;

   public static final char[] INVALID_RESOURCE_CHARACTERS;
   private static final String[] INVALID_RESOURCE_BASENAMES;
   private static final String[] INVALID_RESOURCE_FULLNAMES;

   static {
      //find out the OS being used
      //setup the invalid names
      INSTALLED_PLATFORM = Platform.getOS();
      if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) {
         //valid names and characters taken from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp
         INVALID_RESOURCE_CHARACTERS = new char[] {'\\', '/', ':', '*', '?', '"', '<', '>', '|'};
         INVALID_RESOURCE_BASENAMES = new String[] {"aux", "com1", "com2", "com3", "com4", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ 
               "com5", "com6", "com7", "com8", "com9", "con", "lpt1", "lpt2", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
               "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "nul", "prn"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$
         Arrays.sort(INVALID_RESOURCE_BASENAMES);
         //CLOCK$ may be used if an extension is provided
         INVALID_RESOURCE_FULLNAMES = new String[] {"clock$"}; //$NON-NLS-1$
      } else {
         //only front slash and null char are invalid on UNIXes
         //taken from http://www.faqs.org/faqs/unix-faq/faq/part2/section-2.html
         INVALID_RESOURCE_CHARACTERS = new char[] {'/', '\0',};
         INVALID_RESOURCE_BASENAMES = null;
         INVALID_RESOURCE_FULLNAMES = null;
      }
   }

   /**
    * Returns true if the given name is a valid resource name on this operating system,
    * and false otherwise.
    */
   public static boolean isNameValid(String name) {
      //. and .. have special meaning on all platforms
      if (name.equals(".") || name.equals("..")) //$NON-NLS-1$ //$NON-NLS-2$
         return false;
      if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) {
         //empty names are not valid
         final int length = name.length();
         if (length == 0)
            return false;
         final char lastChar = name.charAt(length-1);
         // filenames ending in dot are not valid
         if (lastChar == '.')
            return false;
         // file names ending with whitespace are truncated (bug 118997)
         if (Character.isWhitespace(lastChar))
            return false;
         int dot = name.indexOf('.');
         //on windows, filename suffixes are not relevant to name validity
         String basename = dot == -1 ? name : name.substring(0, dot);
         if (Arrays.binarySearch(INVALID_RESOURCE_BASENAMES, basename.toLowerCase()) >= 0)
            return false;
         return Arrays.binarySearch(INVALID_RESOURCE_FULLNAMES, name.toLowerCase()) < 0;
      }
      return true;
   }
}
Nick J. R. T.
  • 370
  • 3
  • 8
4

Just something i found, in java 7 and later, there is a class called Paths that has a method called get that takes one or more Strings and throws

InvalidPathException - if the path string cannot be converted to a Path

BrainStorm.exe
  • 1,565
  • 3
  • 23
  • 40
3

This is how I implemented this:

public boolean isValidFileName(final String aFileName) {
    final File aFile = new File(aFileName);
    boolean isValid = true;
    try {
        if (aFile.createNewFile()) {
            aFile.delete();
        }
    } catch (IOException e) {
        isValid = false;
    }
    return isValid;
}
Mosty Mostacho
  • 42,742
  • 16
  • 96
  • 123
  • 5
    This leads to a directory traversal security bug. It is not a secure way of coding if the file name originates from third party. – Vishnu Prasad Kallummel Jul 02 '13 at 11:56
  • @VishnuPrasadKallummel A directory traversal consists in exploiting insufficient security validation / sanitization of user-supplied input file names. You should have a different layer in your application to perform the sanitization. Can you please explain why you consider user input validation should be mixed in a method that validates if a given path is valid? – Mosty Mostacho Jul 02 '13 at 15:52
  • 1
    This method as such will be a directory traversal issue. But if you have an additional layer where you do check before this method is called then it looks ok. It was a spontaneous comment as I was searching for the same without actually creating a file and then deleting it. – Vishnu Prasad Kallummel Jul 03 '13 at 10:11
  • @MostyMostacho what if the file already exists in your system? You would flag the filename as invalid since createNewFile() would fail... – Anis LOUNIS aka AnixPasBesoin May 29 '22 at 23:01
2

To me it appears to be an OS dependent problem. You may simply want to check for some invalid character in the file name. Windows does this when you try to rename the file, it pops a message saying that a file cannot contain any of the following characters: \ / : * ? < > | I am not sure if your question is "is there a library doing the job for me?" in that case I don't know any.

svachon
  • 7,666
  • 1
  • 18
  • 16
0

Using

String validName = URLEncoder.encode( fileName , "UTF-8");

File newFile = new File( validName );

Does the work.

I have just found today. I'm not sure if it works 100% of the time, but so far, I have been able to create valid file names.

OscarRyz
  • 196,001
  • 113
  • 385
  • 569
  • 5
    Sorry, but if there were stars in the fileName, your code would not help. System.out.println(URLEncoder.encode( "*hello world*", "UTF-8")); prints out *hello+world* And that would not be a valid file name. – Peter Perháč May 26 '09 at 19:06
  • Great!.. You are correct, actually I was about to ask this. :) – OscarRyz May 26 '09 at 19:18