Есть ли способ сделать строковый путь к файлу безопасным в С #?

94

Моя программа будет брать произвольные строки из Интернета и использовать их для имен файлов. Есть ли простой способ удалить плохие символы из этих строк или мне нужно написать для этого специальную функцию?

Мартин Домс
источник

Ответы:

172

Ух, ненавижу, когда люди пытаются угадать, какие символы действительны. Помимо того, что они полностью непереносимы (всегда думают о 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 и guid.dat
Джек,
7
Один лайнер, для развлечения result = Path.GetInvalidFileNameChars (). Aggregate (result, (current, c) => current.Replace (c, '-'));
Paul Knopf
1
@PaulKnopf, вы уверены, что JetBrain не имеет авторских прав на этот код;)
Маркус
37

Чтобы удалить недопустимые символы:

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());
Белка
источник
34

Этот вопрос задавался много раз раньше, и, как уже неоднократно отмечалось, IO.Path.GetInvalidFileNameCharsон неадекватен.

Во-первых, есть много имен, таких как PRN и CON, которые зарезервированы и не допускаются для имен файлов. Есть другие имена, которые нельзя использовать только в корневой папке. Имена, оканчивающиеся на точку, также не допускаются.

Во-вторых, существует множество ограничений по длине. Полный список NTFS читайте здесь .

В-третьих, вы можете подключаться к файловым системам, которые имеют другие ограничения. Например, имена файлов ISO 9660 не могут начинаться с символа «-», но могут содержать его.

В-четвертых, что делать, если два процесса «произвольно» выбирают одно и то же имя?

В общем, использование имен файлов, сгенерированных извне, - плохая идея. Я предлагаю сгенерировать ваши собственные личные имена файлов и хранить внутри себя удобочитаемые имена.

Мрачная высокая арка
источник
13
Хотя вы технически точны, GetInvalidFileNameChars подходит для 80% + ситуаций, в которых вы бы его использовали, поэтому это хороший ответ. Думаю, ваш ответ был бы более подходящим в качестве комментария к принятому ответу.
CubanX
4
Я согласен с DourHighArch. Сохраните файл внутри как guid, сославшись на «понятное имя», которое хранится в базе данных. Не позволяйте пользователям контролировать ваши пути на веб-сайте, иначе они попытаются украсть ваш web.config. Если вы включите переопределение URL-адресов, чтобы очистить его, это будет работать только для совпадающих дружественных URL-адресов в базе данных.
rtpHarry
22

Я согласен с Грауэнвольфом и очень рекомендую 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 - Дэнни Pflughoeft
9
Если вы хотите быть еще более кратким / загадочным:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Майкл Петито
@ BlueRaja-DannyPflughoeft Потому что вы хотите сделать его медленнее?
Джонатан Аллен,
@ Джонатан Аллен, почему вы думаете, что foreach быстрее, чем Array.ForEach?
Ryan Buddicom
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, но я предпочитаю заменять недопустимые символы вместо их удаления. Наконец, моя замена - это ровно один символ, чтобы избежать преобразования символов в строки, когда я перебираю строку.

Я говорю все это без профилирования - мне просто "показалось" приятным. :)

csells
источник
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
Единственным недостатком является то, что это также удаляет, .поэтому вам нужно сначала извлечь расширение, а затем добавить его снова.
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

Почему бы не преобразовать строку в эквивалент 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

Вот что я только что добавил в статический класс StringExtensions ( http://github.com/Zoomicon/ClipFlair ) ClipFlair (проект Utils.Silverlight) на основе информации, собранной из ссылок на связанные вопросы о стеке, опубликованные 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

Это работает, потому что a stringпредставляет IEnumerableсобой charмассив, и есть stringстрока конструктора, которая принимает charмассив.

Cjbarth
источник
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, '-');
        }
    }
AnonBird
источник