2

I am facing issue that my path string passes check for Path.GetInvalidPathChars() but fails when trying to create directory.

static void Main(string[] args)
{

    string str2 = @"C:\Temp\hjk&(*&ghj\config\";

    foreach (var character in System.IO.Path.GetInvalidPathChars())
    {
         if (str2.IndexOf(character) > -1)
         {

             Console.WriteLine("String contains invalid path character '{0}'", character);
             return;
         }
    }


    Directory.CreateDirectory(str2); //<-- Throws exception saying Invalid character.

    Console.WriteLine("Press any key..");
    Console.ReadKey();
}

Any idea what could be the issue?

Farukh
  • 2,173
  • 2
  • 23
  • 38
  • Paths cannot contain a `*` – Equalsk Nov 11 '16 at 17:26
  • Even if there is an invalid character, you will get the message and then your code will proceed to create the directory. That will cause exception. – Habib Nov 11 '16 at 17:27
  • Then why it's not part of the Path.GetInvalidPathChars()? Code meant to be as an example. Actual production code will show warning. – Farukh Nov 11 '16 at 17:27
  • 1
    Why are you using this construct in the first place? What is the actual purpose of this code? Are you trying to prevent an exception to occur when creating the directory? The only way you can try this, is by asking the filesystem to create the directory. If it can't, .NET will wrap that result in an exception... – CodeCaster Nov 11 '16 at 17:31

2 Answers2

13

This is one of those times where slight issues in the wording of the documentation can make all the difference on how we look at or use the API. In our case, that part of the API doesn't do us much good.


You haven't completely read the documentation on Path.GetInvalidPathChars():

The array returned from this method is not guaranteed to contain the complete set of characters that are invalid in file and directory names. The full set of invalid characters can vary by file system. For example, on Windows-based desktop platforms, invalid path characters might include ASCII/Unicode characters 1 through 31, as well as quote ("), less than (<), greater than (>), pipe (|), backspace (\b), null (\0) and tab (\t).

And don't think that Path.GetInvalidFileNameChars() will do you any better immediately (we'll prove how this is the better choice below):

The array returned from this method is not guaranteed to contain the complete set of characters that are invalid in file and directory names. The full set of invalid characters can vary by file system. For example, on Windows-based desktop platforms, invalid path characters might include ASCII/Unicode characters 1 through 31, as well as quote ("), less than (<), greater than (>), pipe (|), backspace (\b), null (\0) and tab (\t).


In this situation, it's best to try { Directory.CreateDirectory(str2); } catch (ArgumentException e) { /* Most likely the path was invalid */ } instead of manually validating the path*. This will work independent of file-system.


When I tried to create your directory on my Windows system:

Invalid Path Characters

Now if we go through all the characters in that array:

foreach (char c in Path.GetInvalidPathChars())
{
    Console.WriteLine($"0x{(int)c:X4} : {c}");
}

We get:

0x0022 : "
0x003C : <
0x003E : >
0x007C : |
0x0000 : 
0x0001 : 
0x0002 : 
0x0003 : 
0x0004 : 
0x0005 : 
0x0006 : 
0x0007 : 
0x0008 : 
0x0009 :   
0x000A : 

0x000B : 
0x000C : 
0x000D : 
0x000E : 
0x000F : 
0x0010 : 
0x0011 : 
0x0012 : 
0x0013 : 
0x0014 : 
0x0015 : 
0x0016 : 
0x0017 : 
0x0018 : 
0x0019 : 
0x001A : 
0x001B : 
0x001C : 
0x001D : 
0x001E : 
0x001F : 

As you can see, that list is incomplete.

However: if we do the same for GetInvalidFileNameChars()

foreach (char c in Path.GetInvalidFileNameChars())
{
    Console.WriteLine($"0x{(int)c:X4} : {c}");
}

We end up with a different list, which includes all of the above, as well as:

0x003A : :
0x002A : *
0x003F : ?
0x005C : \
0x002F : /

Which is exactly what our error-message indicates. In this situation, you may decide you want to use that instead. Just remember our warning above, Microsoft makes no guarantees as to the accuracy of either of these methods.

Of course, this isn't perfect, because using Path.GetInvalidFileNameChars() on a path will throw a false invalidation (\ is invalid in a filename, but it's perfectly valid in a path!), so you'll need to correct for that. You can do so by ignoring (at the very least) the following characters:

0x003A : :
0x005C : \

You may also want to ignore the following character (as sometimes people use the web/*nix style paths):

0x002F : /

The last thing to do here is demonstrate a slightly easier way of writing this code. (I'm a regular on Code Review so it's second nature.)

We can do this whole thing in one expresion:

System.IO.Path.GetInvalidFileNameChars().Except(new char[] { '/', '\\', ':' }).Count(c => str2.Contains(c)) > 0

Example of usage:

var invalidPath = @"C:\Temp\hjk&(*&ghj\config\";
var validPath = @"C:\Temp\hjk&(&ghj\config\"; // No asterisk (*)

var invalidPathChars = System.IO.Path.GetInvalidFileNameChars().Except(new char[] { '/', '\\', ':' });

if (invalidPathChars.Count(c => invalidPath.Contains(c)) > 0)
{
    Console.WriteLine("Invalid character found.");
}
else
{
    Console.WriteLine("Free and clear.");
}

if (invalidPathChars.Count(c => validPath.Contains(c)) > 0)
{
    Console.WriteLine("Invalid character found.");
}
else
{
    Console.WriteLine("Free and clear.");
}

*: This is arguable, you may want to manually validate the path if you are certain your validation code will not invalidate valid paths. As MikeT said: "you should always try to validate before getting an exception". Your validation code should be equal or less restrictive than the next level of validation.

Community
  • 1
  • 1
Der Kommissar
  • 5,848
  • 1
  • 29
  • 43
0

I faced the same problem as described above.

Due to the fact that each subdirectory name is virtually a file name, I have connected some solutions that I found here:

string retValue = string.Empty;
var dirParts = path.Split(Path.DirectorySeparatorChar, (char)StringSplitOptions.RemoveEmptyEntries);

for (int i = 0; i < dirParts.Length; i++)
{
    if (i == 0 && Path.IsPathRooted(path))
    {
        retValue = string.Join("_", dirParts[0].Split(Path.GetInvalidPathChars()));
    } 
    else
    {
        retValue = Path.Combine(retValue, string.Join("_", dirParts[i].Split(Path.GetInvalidFileNameChars())));
    }
}

My solution returns "C:\Temp\hjk&(_&ghj\config" for the given path in the question.

mueslifix
  • 1
  • 2