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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
96
1
68 188
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

Ответ принят как подходящий

Ух, ненавижу, когда люди пытаются угадать, какие символы действительны. Помимо того, что они полностью не переносятся (всегда думают о 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

Вряд ли это будет иметь большое значение в этой ситуации. Ошибка Windows жалуется только на эту горстку символов. Спасибо, что указали на GetInvalidFileNameChars, я бы не сталкивался с этим раньше. Буду иметь в виду.

BenAlabaster 02.12.2008 11:29

Версия C#: foreach (var c в Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }

jcollum 16.02.2010 01:12

Как это решение справится с конфликтами имен? Кажется, что несколько строк могут соответствовать одному имени файла (например, «Ад?» И «Ад *»). Если вы в порядке, удаляйте только оскорбительные символы, тогда хорошо; в противном случае вам нужно быть осторожным при разрешении конфликтов имен.

Stefano Ricciardi 13.06.2011 13:55

как насчет ограничений файловой системы на длину имени (и пути)? как насчет зарезервированных имен файлов (PRN CON)? Если вам нужно сохранить данные и исходное имя, вы можете использовать 2 файла с именами Guid: guid.txt и guid.dat

Jack 26.02.2013 15:26

Если вам нужно, чтобы имена файлов можно было идентифицировать (или сортировать), вы можете префикс Guid с помощью набора безопасных символов.

Jack 26.02.2013 15:28

Я просто хотел упомянуть, что эта функция позволяет использовать пробельные символы.

WLin 19.03.2013 01:29

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

Paul Knopf 22.03.2013 07:44

@PaulKnopf, вы уверены, что JetBrain не имеет авторских прав на этот код;)

Marcus 20.06.2015 10:25

это не C#, пожалуйста, исправьте это, посмотрите ответ sidewinderguy ниже

DarkPh03n1X 14.12.2017 21:04

Я согласен с Грауэнвольфом и очень рекомендую Path.GetInvalidFileNameChars().

Вот мой вклад в C#:

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

p.s. - это более загадочно, чем должно быть - я старался быть кратким.

Зачем вам здесь использовать Array.ForEach вместо foreach?

BlueRaja - Danny Pflughoeft 12.04.2012 03:21

Если вы хотите быть еще более кратким / загадочным: Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))

Michael Petito 11.10.2012 01:09

@ BlueRaja-DannyPflughoeft Потому что вы хотите сделать его медленнее?

Jonathan Allen 22.11.2014 10:07

@ Джонатан Аллен, почему вы думаете, что foreach быстрее, чем Array.ForEach?

Ryan Buddicom 24.11.2014 06:01

Будет ли это также фильтровать любые символы, требующие кодировки URL-адреса? например #

PUG 22.02.2015 02:29

@rbuddicom Array.ForEach принимает делегата, что означает, что ему нужно вызвать функцию, которую нельзя встроить. Для коротких строк вы можете потратить больше времени на служебные вызовы функций, чем на фактическую логику. .NET Core ищет способы «де-виртуализации» вызовов, уменьшая накладные расходы.

Jonathan Allen 06.02.2018 02:47

Если вы хотите быстро удалить все специальные символы, которые иногда более удобочитаемы для имен файлов, это отлично работает:

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"

на самом деле \W соответствует большему количеству не буквенно-цифровых символов ([^A-Za-z0-9_]). Все символы Unicode "word" (русский 中文 ... и т. д.) Также не будут заменены. Но это хорошо.

Ishmael 29.07.2014 01:04

Единственным недостатком является то, что при этом также удаляется ., поэтому вам нужно сначала извлечь расширение, а затем добавить его снова.

awe 23.09.2015 16:06

Вот функция, которую я использую сейчас (спасибо 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;
}

Я просто поместил это в класс «Помощники» для удобства.

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

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

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

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

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

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

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

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

Хотя вы технически точны, GetInvalidFileNameChars подходит для 80% + ситуаций, в которых вы бы его использовали, поэтому это хороший ответ. Думаю, ваш ответ был бы более подходящим в качестве комментария к принятому ответу.

CubanX 15.03.2011 16:24

Я согласен с DourHighArch. Сохраните файл внутри как guid, сославшись на «понятное имя», которое хранится в базе данных. Не позволяйте пользователям контролировать ваши пути на веб-сайте, иначе они попытаются украсть ваш web.config. Если вы включите переопределение URL-адресов, чтобы очистить их, это будет работать только для совпадающих дружественных URL-адресов в базе данных.

rtpHarry 16.10.2012 17:11

Я считаю, что это быстро и легко понять:

<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.

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

Вот что я только что добавил в статический класс ClipFlair (http://github.com/Zoomicon/ClipFlair) StringExtensions (проект 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)
}

Вот моя версия:

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

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

Вы можете сделать new HashSet<char>(Path.GetInvalidFileNameChars()), чтобы избежать перечисления O (n) - микрооптимизация.

TrueWill 01.10.2015 20:36
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;            
}

Почему бы не преобразовать строку в эквивалент Base64 следующим образом:

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

Если вы хотите преобразовать его обратно, чтобы вы могли его прочитать:

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

Я использовал это для сохранения файлов PNG с уникальным именем из случайного описания.

Многие ответчики предлагают использовать Path.GetInvalidFileNameChars(), что мне кажется плохим решением. Я рекомендую вам использовать белые списки вместо черных, потому что хакеры всегда найдут способ обойти их.

Вот пример кода, который вы можете использовать:

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

В своих старых проектах я нашел это решение, которое отлично работает более 2 лет. Я заменяю недопустимые символы на "!", А затем проверяю наличие двойных !!, используйте свой собственный символ.

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

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

        return res;
    }

Другие вопросы по теме