有没有一种方法可以在C#中使字符串文件路径安全?


92

我的程序将从互联网上获取任意字符串,并将其用作文件名。有没有简单的方法可以从这些字符串中删除不良字符,或者我需要为此编写自定义函数?


Answers:


171

gh,当人们试图猜测哪些字符有效时,我讨厌它。除了完全不可携带(总是考虑Mono)之外,两个早期注释都遗漏了25个以上的无效字符。

'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

83
C#版本:foreach(Path.GetInvalidFileNameChars()中的var c){fileName = fileName.Replace(c,'-'); }
jcollum

8
该解决方案将如何处理名称冲突?单个文件名似乎可以匹配多个字符串(例如,“ Hell?”和“ Hell *”)。如果可以的话,只删除有问题的字符就可以了;否则,您需要小心处理名称冲突。
Stefano Ricciardi

2
文件系统的名称(和路径)长度限制如何?保留文件名(PRN CON)呢?如果您需要存储数据和原始名称,则可以使用2个带有Guid名称的文件:guid.txt和guid.dat
Jack

6
一个衬里,有趣的结果= Path.GetInvalidFileNameChars()。Aggregate(result,(current,c)=> current.Replace(c,'-'));
Paul Knopf

1
@PaulKnopf,您确定JetBrain没有该代码的版权吗?)
Marcus

36

要删除无效字符:

static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars();

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

替换无效字符:

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 $):

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());

33

这个问题以前已经被问过很多 次了 而且正如前面多次指出的那样,这个问题还IO.Path.GetInvalidFileNameChars不够。

首先,有许多名称(例如PRN和CON)已保留,并且不允许使用文件名。还有仅在根文件夹中不允许的其他名称。以句点结尾的名称也是不允许的。

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

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

第四,如果两个进程“任意”选择相同的名称,您该怎么办?

通常,将外部生成的名称用作文件名是一个坏主意。我建议生成自己的私有文件名并在内部存储易于阅读的名称。


13
尽管从技术上讲您是准确的,但GetInvalidFileNameChars可以很好地解决80%以上的使用情况,因此这是一个很好的答案。您的答案作为对我认为可接受的答案的评论会更合适。
CubanX 2011年

4
我同意DourHighArch。在内部将文件另存为GUID,并针对存储在数据库中的“友好名称”进行引用。不要让用户控制您在网站上的路径,否则他们将尝试窃取您的web.config。如果合并URL重写以使其整洁,则仅适用于数据库中匹配的友好URL。
rtpHarry 2012年

22

我同意Grauenwolf的观点,强烈建议您 Path.GetInvalidFileNameChars()

这是我的C#贡献:

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

ps-这比它应该的要神秘得多-我试图简明扼要。


3
您为什么要在世界范围内Array.ForEach而不是foreach这里使用
BlueRaja-Danny Pflughoeft 2012年

9
如果您想更加简洁/神秘:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito 2012年

@ BlueRaja-DannyPflughoeft因为您想让它变慢?
乔纳森·艾伦

@Johnathan Allen,是什么让您认为foreach比Array.ForEach快?
Ryan Buddicom 2014年

5
@rbuddicom Array.ForEach需要一个委托,这意味着它需要调用一个无法内联的函数。对于短字符串,您可能最终会花费比实际逻辑更多的时间用于函数调用开销。.NET Core正在研究“虚拟化”调用以减少开销的方法。
乔纳森·艾伦

13

这是我的版本:

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”提示它是不平凡的,因此我缓存了结果。此外,这只会遍历输入字符串一次,而不是多次遍历,就像上面的解决方案遍历一组无效char一样,一次将它们替换到源字符串中。另外,我喜欢基于Where的解决方案,但我更喜欢替换无效字符而不是删除它们。最后,我的替换字符正好是一个字符,以避免在迭代字符串时将字符转换为字符串。

我说了所有不进行概要分析的工作-这对我来说只是“感觉”到的。:)


1
new HashSet<char>(Path.GetInvalidFileNameChars())可以避免O(n)枚举-微观优化。
TrueWill

12

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

public static string MakeSafeFilename(string filename, char replaceChar)
{
    foreach (char c in System.IO.Path.GetInvalidFileNameChars())
    {
        filename = filename.Replace(c, replaceChar);
    }
    return filename;
}

为了方便起见,我只是将其放在“助手”类中。


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"

1
实际上\W比非字母数字([^A-Za-z0-9_])更匹配。所有Unicode'word'字符(русский中文...等)也不会被替换。但这是一件好事。
Ishmael 2014年

唯一的缺点是它也会删除,.因此您必须先提取扩展名,然后再添加。
2015年

5
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);
    }
}

5

为什么不将字符串转换为像这样的Base64等效项:

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

如果您想将其转换回去,则可以阅读:

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

我用它来保存随机描述中具有唯一名称的PNG文件。


5

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

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)
}

2
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

我发现使用它是快速且容易理解的:

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

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


1

从我的较早项目中,我找到了这个解决方案,该解决方案已经运行了两年多了。我用“!”替换了非法字符,然后检查是否有双!!,请使用您自己的字符。

    public string GetSafeFilename(string filename)
    {
        string res = string.Join("!", filename.Split(Path.GetInvalidFileNameChars()));

        while (res.IndexOf("!!") >= 0)
            res = res.Replace("!!", "!");

        return res;
    }

0

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

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

    string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
    foreach (char c in filename)
    {
        if (!whitelist.Contains(c))
        {
            filename = filename.Replace(c, '-');
        }
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.