Apakah ada cara untuk membuat jalur file string aman di c #?

94

Program saya akan mengambil string sewenang-wenang dari internet dan menggunakannya untuk nama file. Apakah ada cara sederhana untuk menghapus karakter jahat dari string ini atau apakah saya perlu menulis fungsi kustom untuk ini?

Martin Doms
sumber

Jawaban:

172

Ugh, aku benci kalau orang mencoba menebak karakter mana yang valid. Selain benar-benar tidak portabel (selalu memikirkan Mono), kedua komentar sebelumnya melewatkan lebih dari 25 karakter tidak valid.

'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
Jonathan Allen
sumber
83
Versi C #: foreach (var c di Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
jcollum
8
Bagaimana solusi ini menangani konflik nama? Tampaknya lebih dari satu string dapat cocok dengan satu nama file (misalnya, "Neraka?" Dan "Neraka *"). Jika Anda baik-baik saja hanya menghapus karakter yang menyinggung maka baik-baik saja; jika tidak, Anda harus berhati-hati menangani konflik nama.
Stefano Ricciardi
2
bagaimana dengan batas panjang nama (dan jalur) filesytem? bagaimana dengan nama file yang dipesan (PRN CON)? Jika Anda perlu menyimpan data dan nama asli Anda dapat menggunakan 2 file dengan nama Guid: guid.txt dan guid.dat
Jack
7
Satu liner, untuk hasil menyenangkan = Path.GetInvalidFileNameChars (). Agregat (hasil, (saat ini, c) => current.Replace (c, '-'));
Paul Knopf
1
@PaulKnopf, apakah Anda yakin JetBrain tidak memiliki hak cipta atas kode itu;)
Marcus
37

Untuk menghapus karakter yang tidak valid:

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

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

Untuk mengganti karakter yang tidak valid:

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

Untuk mengganti karakter yang tidak valid (dan menghindari potensi konflik nama seperti 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());
Tupai
sumber
34

Pertanyaan ini telah berkali- kali diajukan sebelumnya dan, seperti yang telah disebutkan beberapa kali sebelumnya, IO.Path.GetInvalidFileNameCharstidaklah memadai.

Pertama, ada banyak nama seperti PRN dan CON yang dicadangkan dan tidak diperbolehkan untuk nama file. Ada nama lain yang tidak diperbolehkan hanya di folder root. Nama yang diakhiri dengan titik juga tidak diperbolehkan.

Kedua, ada berbagai batasan panjang. Baca daftar lengkap NTFS di sini .

Ketiga, Anda dapat melampirkan ke sistem file yang memiliki batasan lain. Misalnya, nama file ISO 9660 tidak boleh dimulai dengan "-" tetapi dapat memuatnya.

Keempat, apa yang Anda lakukan jika dua proses "sembarangan" memilih nama yang sama?

Secara umum, menggunakan nama yang dibuat secara eksternal untuk nama file adalah ide yang buruk. Saya sarankan membuat nama file pribadi Anda sendiri dan menyimpan nama yang dapat dibaca manusia secara internal.

Arch Tinggi Dour
sumber
13
Meskipun Anda secara teknis akurat, GetInvalidFileNameChars bagus untuk 80% + situasi di mana Anda akan menggunakannya, oleh karena itu ini adalah jawaban yang bagus. Jawaban Anda akan lebih sesuai sebagai komentar untuk jawaban yang diterima saya kira.
CubanX
4
Saya setuju dengan DourHighArch. Simpan file secara internal sebagai pedoman, referensi yang bertentangan dengan "nama akrab" yang disimpan dalam database. Jangan biarkan pengguna mengontrol jalur Anda di situs web atau mereka akan mencoba mencuri web.config Anda. Jika Anda menggabungkan penulisan ulang url untuk membuatnya bersih, itu hanya akan berfungsi untuk url ramah yang cocok dalam database.
rtpHarry
22

Saya setuju dengan Grauenwolf dan akan sangat merekomendasikan Path.GetInvalidFileNameChars()

Ini kontribusi C # saya:

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

ps - ini lebih samar dari yang seharusnya - Saya mencoba untuk ringkas.

Aaron Wagner
sumber
3
Mengapa di dunia ini Anda akan menggunakan Array.ForEachalih-alih hanya di foreachsini
BlueRaja - Danny Pflughoeft
9
Jika Anda ingin lebih ringkas / samar:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito
@ BlueRaja-DannyPflughoeft Karena Anda ingin membuatnya lebih lambat?
Jonathan Allen
@Johnathan Allen, apa yang membuat Anda berpikir foreach lebih cepat daripada Array.ForEach?
Ryan Buddicom
5
@rbuddicom Array.ForEach membutuhkan delegasi, yang berarti ia perlu menjalankan fungsi yang tidak dapat disebariskan. Untuk string pendek, Anda bisa menghabiskan lebih banyak waktu pada overhead pemanggilan fungsi daripada logika sebenarnya. .NET Core sedang mencari cara untuk "de-virtualisasi" panggilan, mengurangi overhead.
Jonathan Allen
13

Ini versi saya:

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

Saya tidak yakin bagaimana hasil dari GetInvalidFileNameChars dihitung, tetapi "Get" menyarankan itu tidak sepele, jadi saya cache hasilnya. Lebih lanjut, ini hanya melintasi string input sekali, bukan beberapa kali, seperti solusi di atas yang mengulangi rangkaian karakter yang tidak valid, menggantikannya dalam string sumber satu per satu. Juga, saya suka solusi berbasis Di mana, tetapi saya lebih suka mengganti karakter yang tidak valid daripada menghapusnya. Akhirnya, penggantian saya persis satu karakter untuk menghindari konversi karakter menjadi string saat saya mengulang string.

Saya mengatakan semua itu tanpa melakukan pembuatan profil - yang ini hanya "terasa" baik bagi saya. :)

csells
sumber
1
Anda dapat melakukannya new HashSet<char>(Path.GetInvalidFileNameChars())untuk menghindari pencacahan O (n) - optimasi mikro.
TrueWill
12

Inilah fungsi yang saya gunakan sekarang (terima kasih jcollum untuk contoh C #):

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

Saya hanya meletakkan ini di kelas "Pembantu" untuk kenyamanan.

sidewinderguy
sumber
7

Jika Anda ingin segera menghapus semua karakter khusus yang terkadang lebih mudah dibaca pengguna untuk nama file, ini berfungsi dengan baik:

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"
Keith
sumber
1
sebenarnya \Wlebih cocok daripada non-alpha-numerics ( [^A-Za-z0-9_]). Semua karakter 'kata' Unicode (русский 中文 ..., dll.) Juga tidak akan diganti. Tapi ini hal yang bagus.
Ismael
Satu-satunya downside adalah ini juga menghapus .sehingga Anda harus mengekstrak ekstensi terlebih dahulu, dan menambahkannya lagi setelahnya.
kagum
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);
    }
}
Ronnie Overby
sumber
5

Mengapa tidak mengonversi string menjadi setara Base64 seperti ini:

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

Jika Anda ingin mengubahnya kembali sehingga Anda dapat membacanya:

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

Saya menggunakan ini untuk menyimpan file PNG dengan nama unik dari deskripsi acak.

Bart Vanseer
sumber
5

Inilah yang baru saja saya tambahkan ke kelas statis ClipFlair ( http://github.com/Zoomicon/ClipFlair ) StringExtensions (proyek Utils.Silverlight), berdasarkan info yang dikumpulkan dari tautan ke pertanyaan stackoverflow terkait yang diposting oleh Dour High Arch di atas:

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)
}
George Birbilis
sumber
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;            
}
ecklerpa
sumber
1

Saya merasa menggunakan ini cepat dan mudah dimengerti:

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

Ini berfungsi karena a stringadalah IEnumerablesebagai chararray dan ada stringstring konstruktor yang mengambil chararray.

cjbarth.dll
sumber
1

Dari proyek lama saya, saya telah menemukan solusi ini, yang telah bekerja dengan sempurna selama 2 tahun. Saya mengganti karakter ilegal dengan "!", Lalu periksa double !!, gunakan karakter Anda sendiri.

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

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

        return res;
    }
Roni Tovi
sumber
0

Banyak jawaban yang menyarankan untuk menggunakan Path.GetInvalidFileNameChars()yang sepertinya solusi yang buruk bagi saya. Saya mendorong Anda untuk menggunakan daftar putih daripada daftar hitam karena peretas pada akhirnya akan selalu menemukan cara untuk melewatinya.

Berikut adalah contoh kode yang dapat Anda gunakan:

    string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
    foreach (char c in filename)
    {
        if (!whitelist.Contains(c))
        {
            filename = filename.Replace(c, '-');
        }
    }
AnonBird
sumber