Có cách nào để tạo đường dẫn tệp chuỗi an toàn trong c # không?


Câu trả lời:


171

Ugh, tôi ghét khi mọi người cố đoán xem ký tự nào hợp lệ. Bên cạnh việc hoàn toàn không di động (luôn nghĩ về Mono), cả hai bình luận trước đó đều bỏ sót thêm 25 ký tự không hợp lệ.

'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
Phiên bản C #: foreach (var c in Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
jcollum

8
Giải pháp này sẽ xử lý xung đột tên như thế nào? Có vẻ như nhiều chuỗi có thể khớp với một tên tệp (ví dụ: "Địa ngục?" Và "Địa ngục *"). Nếu bạn ổn, chỉ xóa các ký tự vi phạm thì tốt thôi; nếu không bạn cần phải cẩn thận để xử lý xung đột tên.
Stefano Ricciardi

2
còn về giới hạn độ dài tên (và đường dẫn) của mục tệp thì sao? những gì về tên tệp dành riêng (PRN CON)? Nếu cần lưu dữ liệu và tên gốc bạn có thể sử dụng 2 tệp có tên Guid là: Guid.txt và Guid.dat
Jack

6
Một lớp lót, cho kết quả thú vị = Path.GetInvalidFileNameChars (). Aggregate (result, (current, c) => current.Replace (c, '-'));
Paul Knopf

1
@PaulKnopf, bạn có chắc JetBrain không có bản quyền để mã mà;)
Marcus

36

Để loại bỏ các ký tự không hợp lệ:

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

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

Để thay thế các ký tự không hợp lệ:

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

Để thay thế các ký tự không hợp lệ (và tránh xung đột tên tiềm ẩn như 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

Câu hỏi này đã được hỏi nhiều lần trước đây và như đã chỉ ra nhiều lần trước đây, IO.Path.GetInvalidFileNameCharslà không đầy đủ.

Đầu tiên, có nhiều tên như PRN và CON được dành riêng và không được phép đặt cho tên tệp. Có những tên khác không được phép chỉ ở thư mục gốc. Tên kết thúc bằng dấu chấm cũng không được phép.

Thứ hai, có nhiều giới hạn về độ dài. Đọc danh sách đầy đủ cho NTFS ở đây .

Thứ ba, bạn có thể đính kèm vào các hệ thống tệp có những hạn chế khác. Ví dụ: tên tệp ISO 9660 không thể bắt đầu bằng "-" nhưng có thể chứa nó.

Thứ tư, bạn sẽ làm gì nếu hai tiến trình "tự ý" chọn cùng một tên?

Nói chung, sử dụng tên được tạo từ bên ngoài cho tên tệp là một ý tưởng tồi. Tôi khuyên bạn nên tạo các tên tệp riêng tư của riêng bạn và lưu trữ các tên có thể đọc được trong nội bộ.


13
Mặc dù bạn chính xác về mặt kỹ thuật, GetInvalidFileNameChars phù hợp với hơn 80% các trường hợp bạn sử dụng, do đó, đó là một câu trả lời tốt. Câu trả lời của bạn sẽ thích hợp hơn như một nhận xét cho câu trả lời được chấp nhận mà tôi nghĩ.
CubanX

4
Tôi đồng ý với DourHighArch. Lưu tệp nội bộ dưới dạng hướng dẫn, tham chiếu đến "tên thân thiện" được lưu trữ trong cơ sở dữ liệu. Đừng để người dùng kiểm soát đường dẫn của bạn trên trang web, nếu không họ sẽ cố đánh cắp web.config của bạn. Nếu bạn kết hợp viết lại url để làm cho nó sạch sẽ, nó sẽ chỉ hoạt động đối với các url thân thiện phù hợp trong cơ sở dữ liệu.
rtpHarry 16/10/12

22

Tôi đồng ý với Grauenwolf và rất muốn giới thiệu Path.GetInvalidFileNameChars()

Đây là đóng góp C # của tôi:

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

ps - điều này khó hiểu hơn nó nên làm - tôi đã cố gắng ngắn gọn.


3
Tại sao trên thế giới bạn sẽ sử dụng Array.ForEachthay vì chỉ foreachở đây
BlueRaja - Danny Pflughoeft

9
Nếu bạn muốn trở thành súc tích hơn / khó hiểu:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito

@ BlueRaja-DannyPflughoeft Vì bạn muốn làm cho nó chậm hơn?
Jonathan Allen

@Johnathan Allen, điều gì khiến bạn nghĩ foreach nhanh hơn Array.ForEach?
Ryan Buddicom

5
@rbuddicom Array.ForEach nhận một đại biểu, có nghĩa là nó cần gọi một hàm không thể nội dòng. Đối với các chuỗi ngắn, bạn có thể dành nhiều thời gian hơn cho chi phí cuộc gọi hàm so với logic thực tế. .NET Core đang xem xét các cách để "khử ảo hóa" các cuộc gọi, giảm chi phí.
Jonathan Allen,

13

Đây là phiên bản của tôi:

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

Tôi không chắc kết quả của GetInvalidFileNameChars được tính như thế nào, nhưng "Get" cho thấy nó không tầm thường, vì vậy tôi lưu kết quả vào bộ nhớ cache. Hơn nữa, điều này chỉ duyệt chuỗi đầu vào một lần thay vì nhiều lần, giống như các giải pháp ở trên lặp lại tập hợp các ký tự không hợp lệ, thay thế chúng trong chuỗi nguồn tại một thời điểm. Ngoài ra, tôi thích các giải pháp Dựa trên vị trí, nhưng tôi thích thay thế các ký tự không hợp lệ thay vì xóa chúng. Cuối cùng, sự thay thế của tôi là chính xác một ký tự để tránh chuyển đổi ký tự thành chuỗi khi tôi lặp qua chuỗi.

Tôi nói tất cả những gì đang làm trong hồ sơ - điều này tôi cảm thấy rất tuyệt. :)


1
Bạn có thể làm new HashSet<char>(Path.GetInvalidFileNameChars())để tránh liệt kê O (n) - tối ưu hóa vi mô.
TrueWill

12

Đây là hàm mà tôi đang sử dụng bây giờ (cảm ơn jcollum về ví dụ C #):

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

Tôi chỉ đặt điều này trong một lớp "Người trợ giúp" để thuận tiện.


7

Nếu bạn muốn nhanh chóng loại bỏ tất cả các ký tự đặc biệt mà đôi khi người dùng dễ đọc hơn đối với tên tệp, điều này hoạt động tốt:

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
thực sự \Wkhớp với nhiều hơn các số không phải chữ cái ( [^A-Za-z0-9_]). Tất cả các ký tự 'từ' Unicode (русский 中文 ..., v.v.) cũng sẽ không được thay thế. Nhưng đây là một điều tốt.
Ishmael

Nhược điểm duy nhất là điều này cũng loại bỏ, .vì vậy bạn phải giải nén phần mở rộng trước và thêm lại sau.
awe

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

Tại sao không chuyển đổi chuỗi thành một chuỗi tương đương Base64 như thế này:

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

Nếu bạn muốn chuyển đổi nó trở lại để bạn có thể đọc nó:

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

Tôi đã sử dụng điều này để lưu các tệp PNG với một tên duy nhất từ ​​một mô tả ngẫu nhiên.


5

Đây là những gì tôi vừa thêm vào lớp tĩnh StringExtensions của ClipFlair ( http://github.com/Zoomicon/ClipFlair ) (dự án Utils.Silverlight), dựa trên thông tin thu thập từ các liên kết đến các câu hỏi liên quan đến stackoverflow được đăng bởi Dour High Arch ở trên:

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

Tôi thấy việc sử dụng này sẽ nhanh chóng và dễ hiểu:

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

Điều này hoạt động bởi vì a stringIEnumerablemột charmảng và có một stringchuỗi phương thức khởi tạo nhận một charmảng.


1

Từ các dự án cũ của tôi, tôi đã tìm ra giải pháp này, đã hoạt động hoàn hảo trong hơn 2 năm. Tôi đang thay thế các ký tự bất hợp pháp bằng "!", Và sau đó kiểm tra các ký tự kép !!, sử dụng ký tự của riêng bạn.

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

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

        return res;
    }

0

Nhiều người đề nghị sử dụng Path.GetInvalidFileNameChars() mà có vẻ như là một giải pháp tồi đối với tôi. Tôi khuyến khích bạn sử dụng danh sách trắng thay vì danh sách đen vì cuối cùng tin tặc sẽ luôn tìm ra cách để vượt qua nó.

Đây là một ví dụ về mã bạn có thể sử dụng:

    string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
    foreach (char c in filename)
    {
        if (!whitelist.Contains(c))
        {
            filename = filename.Replace(c, '-');
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.