关于.net:有没有办法在c#中使字符串文件路径安全?

Is there a way of making strings file-path safe in c#?

我的程序将从互联网上获取任意字符串并将其用于文件名。 有没有一种简单的方法从这些字符串中删除坏字符或我是否需要为此编写自定义函数?


呃,当人们试图猜测哪些角色有效时,我讨厌它。除了完全不可移植(总是考虑Mono)之外,两个早期的评论都错过了25个无效字符。

1
2
3
4
5
6
7
'Clean just a filename
Dim filename As String ="salmnas dlajhdla kjha;dmas'
lkasn"
For Each c In IO.Path.GetInvalidFileNameChars
    filename = filename.Replace(c,"
")
Next

'See also IO.Path.GetInvalidPathChars


这个问题之前已被多次询问,并且如前所述多次,IO.Path.GetInvalidFileNameChars是不够的。

首先,有许多名称,如PRN和CON,这些名称是保留的,不允许用于文件名。还有其他名称不允许仅在根文件夹中。也不允许以句点结尾的名称。

其次,存在各种长度限制。在这里阅读NTFS的完整列表。

第三,您可以附加到具有其他限制的文件系统。例如,ISO 9660文件名不能以" -"开头,但可以包含它。

第四,如果两个进程"任意"选择相同的名称,你会怎么做?

通常,使用外部生成的文件名名称是个坏主意。我建议生成您自己的私有文件名并在内部存储人类可读的名称。


要删除无效字符:

1
2
3
4
static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars();

// Builds a string out of valid chars
var validFilename = new string(filename.Where(ch => !invalidFileNameChars.Contains(ch)).ToArray());

要替换无效字符:

1
2
3
4
static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars();

// Builds a string out of valid chars and an _ for invalid ones
var validFilename = new string(filename.Select(ch => invalidFileNameChars.Contains(ch) ? '_' : ch).ToArray());

要替换无效字符(并避免潜在的名称冲突,如Hell * vs Hell $):

1
2
3
4
static readonly IList<char> invalidFileNameChars = Path.GetInvalidFileNameChars();

// Builds a string out of valid chars and replaces invalid chars with a unique letter (Moves the Char into the letter range of unicode, starting at"A")
var validFilename = new string(filename.Select(ch => invalidFileNameChars.Contains(ch) ? Convert.ToChar(invalidFileNameChars.IndexOf(ch) + 65) : ch).ToArray());


我同意Grauenwolf并强烈推荐Path.GetInvalidFileNameChars()

这是我的C#贡献:

1
2
3
string file = @"38?/.\}[+=n a882 a.a*/|n^%$ ad#(-))";
Array.ForEach(Path.GetInvalidFileNameChars(),
      c => file = file.Replace(c.ToString(), String.Empty));

附: - 这比应该更加神秘 - 我试图简洁。


这是我的版本:

1
2
3
4
static string GetSafeFileName(string name, char replace = '_') {
  char[] invalids = Path.GetInvalidFileNameChars();
  return new string(name.Select(c => invalids.Contains(c) ? replace : c).ToArray());
}

我不确定如何计算GetInvalidFileNameChars的结果,但"Get"表明它是非平凡的,所以我缓存了结果。此外,这只会遍历输入字符串一次而不是多次,就像上面的迭代遍历无效字符集的解决方案一样,在源字符串中一次替换它们。此外,我喜欢基于位置的解决方案,但我更喜欢替换无效的字符而不是删除它们。最后,我的替换只是一个字符,以避免在迭代字符串时将字符转换为字符串。

我说所有这些都没有进行分析 - 这个对我来说"感觉"很好。 :)


这是我现在使用的功能(感谢jcollum用于C#示例):

1
2
3
4
5
6
7
8
public static string MakeSafeFilename(string filename, char replaceChar)
{
    foreach (char c in System.IO.Path.GetInvalidFileNameChars())
    {
        filename = filename.Replace(c, replaceChar);
    }
    return filename;
}

为了方便起见,我把它放在"助手"课程中。


如果你想快速删除所有特殊字符,这些特殊字符有时候对于文件名更具用户可读性,这很有效:

1
2
3
4
5
6
7
string myCrazyName ="q`w^e!r@t#y$u%i^o&p*a(s)d_f-g+h=j{k}l|z:x"c<v>b?n[m]q\\w;e'r,t.y/u";
string safeName = Regex.Replace(
    myCrazyName,
   "\W",  /*Matches any nonword character. Equivalent to '
[^A-Za-z0-9_]'*/
   "",
    RegexOptions.IgnoreCase);
// safeName =="qwertyuiopasd_fghjklzxcvbnmqwertyu"


这是我刚刚添加到ClipFlair(http://github.com/Zoomicon/ClipFlair)StringExtensions静态类(Utils.Silverlight项目)的内容,基于从Dour High Arch上面发布的相关stackoverflow问题的链接收集的信息:

1
2
3
4
5
6
7
8
public static string ReplaceInvalidFileNameChars(this string s, string replacement ="")
{
  return Regex.Replace(s,
   "[" + Regex.Escape(new String(System.IO.Path.GetInvalidPathChars())) +"]",
    replacement, //can even use a replacement string of any length
    RegexOptions.IgnoreCase);
    //not using System.IO.Path.InvalidPathChars (deprecated insecure API)
}

1
2
3
4
5
6
7
8
9
10
11
12
static class Utils
{
    public static string MakeFileSystemSafe(this string s)
    {
        return new string(s.Where(IsFileSystemSafe).ToArray());
    }

    public static bool IsFileSystemSafe(char c)
    {
        return !Path.GetInvalidFileNameChars().Contains(c);
    }
}


为什么不将字符串转换为类似这样的Base64:

1
2
string UnsafeFileName ="salmnas dlajhdla kjha;dmas'lkasn";
string SafeFileName = Convert.ToBase64String(Encoding.UTF8.GetBytes(UnsafeFileName));

如果您想将其转换回来,那么您可以阅读它:

1
UnsafeFileName = Encoding.UTF8.GetString(Convert.FromBase64String(SafeFileName));

我用它来保存PNG文件,其中包含随机描述中的唯一名称。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private void textBoxFileName_KeyPress(object sender, KeyPressEventArgs e)
{
   e.Handled = CheckFileNameSafeCharacters(e);
}

/// <summary>
/// This is a good function for making sure that a user who is naming a file uses proper characters
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
internal static bool CheckFileNameSafeCharacters(System.Windows.Forms.KeyPressEventArgs e)
{
    if (e.KeyChar.Equals(24) ||
        e.KeyChar.Equals(3) ||
        e.KeyChar.Equals(22) ||
        e.KeyChar.Equals(26) ||
        e.KeyChar.Equals(25))//Control-X, C, V, Z and Y
            return false;
    if (e.KeyChar.Equals('\b'))//backspace
        return false;

    char[] charArray = Path.GetInvalidFileNameChars();
    if (charArray.Contains(e.KeyChar))
       return true;//Stop the character from being entered into the control since it is non-numerical
    else
        return false;            
}


我发现使用它很容易理解:

1
2
3
4
<Extension()>
Public Function MakeSafeFileName(FileName As String) As String
    Return FileName.Where(Function(x) Not IO.Path.GetInvalidFileNameChars.Contains(x)).ToArray
End Function

这是因为stringIEnumerable作为char数组,并且有一个string构造函数字符串,它采用char数组。


许多人建议使用Path.GetInvalidFileNameChars(),这对我来说似乎是一个糟糕的解决方案。我鼓励你使用白名单而不是黑名单,因为黑客总会找到最终绕过它的方法。

以下是您可以使用的代码示例:

1
2
3
4
5
6
7
8
    string whitelist ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
    foreach (char c in filename)
    {
        if (!whitelist.Contains(c))
        {
            filename = filename.Replace(c, '-');
        }
    }