Чи є спосіб зробити рядок файловим шляхом безпечним у c #?


92

Моя програма буде брати довільні рядки з Інтернету та використовувати їх для імен файлів. Чи існує простий спосіб видалити з цих рядків погані символи чи мені потрібно написати спеціальну функцію для цього?


Відповіді:


171

Тьфу, я ненавиджу, коли люди намагаються вгадати, які символи дійсні. Окрім того, що вони повністю не портативні (завжди думаючи про 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 (var c у Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
jcollum

8
Як би це рішення обробляло конфлікти назв? Здається, що більше одного рядка може збігатися з однією назвою файлу (наприклад, "Пекло?" Та "Пекло *"). Якщо ви в порядку, лише видаляючи символи, що порушують, тоді штрафуйте; в іншому випадку потрібно бути обережним, вирішуючи конфлікти імен.
Стефано Ріккарді,

2
як щодо обмежень довжини імені (та шляху) у файловому файлі? як щодо зарезервованих імен файлів (PRN CON)? Якщо вам потрібно зберегти дані та оригінальне ім'я, ви можете використовувати 2 файли з іменами Guid: guid.txt та guide.dat
Джек

6
Один лайнер, для задоволення result = Path.GetInvalidFileNameChars (). Aggregate (result, (current, c) => current.Replace (c, '-'));
Пол Нопф,

1
@PaulKnopf, ти впевнений, що JetBrain не має авторських прав на цей код;)
Маркус

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

4
Я згоден з DourHighArch. Збережіть файл внутрішньо як керівництво, посилання на те, що стосується "дружнього імені", яке зберігається в базі даних. Не дозволяйте користувачам контролювати ваші шляхи на веб-сайті, інакше вони намагатимуться викрасти вашу web.config. Якщо ви включите переписування URL-адрес для очищення, це буде працювати лише для відповідних дружніх URL-адрес у базі даних.
rtpHarry

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

9
Якщо ви хочете бути ще більш стислим / загадковим:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Майкл Петіто

@ BlueRaja-DannyPflughoeft Тому що ви хочете зробити це повільнішим?
Джонатан Аллен

@ Джонатан Аллен, що змушує вас думати, що foreach швидший за Array.ForEach?
Райан Буддіком,

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" припускає, що він нетривіальний, тому я кешую результати. Крім того, це лише перетинає вхідний рядок один раз, а не кілька разів, як наведені вище рішення, які перебирають набір недійсних символів, замінюючи їх у вихідному рядку по одному. Крім того, мені подобаються рішення на основі 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' (руський 中文 ... тощо) також не будуть замінені. Але це добре.
Ізмаїл

Єдиним недоліком є ​​те, що це також видаляє, .тому вам доведеться спочатку витягти розширення, а потім додати його знову.
благоговіння

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) на основі інформації, зібраної за посиланнями на відповідні запитання щодо stackoverflow, розміщених Dour High Arch вище:

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

Це працює , тому що stringце IEnumerableяк charмасив , і є stringконструктор , який приймає рядок в charмасив.


1

Зі своїх старих проектів я знайшов це рішення, яке чудово працює вже 2 роки. Я замінюю нелегальні символи на "!", А потім перевіряю наявність подвійних !!, використовую власну символіку

    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, '-');
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.